mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-24 01:33:46 +08:00
Compare commits
786 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 535ae098c6 | |||
| 0b26d1c050 | |||
| 7d52c80bf4 | |||
| 50630f121c | |||
| f26c89d6cb | |||
| e88854319c | |||
| 2e53b419d1 | |||
| f4bc8ee6b7 | |||
| 6daefdc0e1 | |||
| 96e578fa71 | |||
| 768a2f324c | |||
| f50b8dabbb | |||
| d9e0345a2b | |||
| 8a505e6677 | |||
| 40b5468615 | |||
| f7418649f8 | |||
| 5d3b0f4ea9 | |||
| 7e773e5373 | |||
| 6d088863a6 | |||
| 6b515db122 | |||
| bdc1532258 | |||
| afe15a9a80 | |||
| 02060a4b67 | |||
| efd25eca90 | |||
| b4f17f35f9 | |||
| a0669247e3 | |||
| be811cbe61 | |||
| 460b61ebe8 | |||
| 7411a11f83 | |||
| a7ad72489b | |||
| e82a4f034b | |||
| a8ec45ec12 | |||
| 0df2444e95 | |||
| 32cf065988 | |||
| 41c680e60c | |||
| fac7554cb6 | |||
| 92bc8ee934 | |||
| 9c30fc2387 | |||
| 3f5dc27170 | |||
| a5fb9a2cff | |||
| 201d0c0476 | |||
| 54e722ff00 | |||
| fb1c2ea1c4 | |||
| 0f43f042fb | |||
| ee1f0bbe83 | |||
| b811825b38 | |||
| ee71b70f24 | |||
| b75b21681d | |||
| 562c5eb58d | |||
| 2798c12c90 | |||
| 19d545b22e | |||
| 36e935c9dc | |||
| cf89678178 | |||
| 88e4004c9a | |||
| 9f0ae1a495 | |||
| f5a5b9e141 | |||
| 057ca874ce | |||
| 497eb3f771 | |||
| 284bb45c0c | |||
| 836b0278eb | |||
| 4ce9be2752 | |||
| 24fc6fe4e5 | |||
| 297ff1973d | |||
| 727985697f | |||
| 59d1581c0a | |||
| 3a86dffa17 | |||
| 9f7dcb68f9 | |||
| 6793c1a4f5 | |||
| f351dbac09 | |||
| 44255d67c2 | |||
| 1ce49be89b | |||
| bdf7bca569 | |||
| d8e292dc94 | |||
| f0601e27ec | |||
| cec9f090c4 | |||
| d08da1ff22 | |||
| 58b028cfc8 | |||
| d7f3037c88 | |||
| 028ee7b3f5 | |||
| 94aff6faed | |||
| b63039ce84 | |||
| aec5590d57 | |||
| abd19df5f4 | |||
| b1dae43ea8 | |||
| 8c32f6d99d | |||
| f6b51e6742 | |||
| d3881e6a77 | |||
| d83b065c53 | |||
| 16c3d5f48a | |||
| d6dc906ff8 | |||
| ea0ef444de | |||
| ea1da70b22 | |||
| ad9aa72979 | |||
| 1382200cda | |||
| 7e8832d672 | |||
| 9cd9a6201c | |||
| 6ee00d3f60 | |||
| d97100bd14 | |||
| bb36fe4287 | |||
| 066a0f2520 | |||
| c5a6e1da9d | |||
| f8cec6f270 | |||
| 544979d652 | |||
| 353672fb6a | |||
| 4101ab9b28 | |||
| 7fb1d854c6 | |||
| d8587bba31 | |||
| ad3bad8b85 | |||
| c91ac533ef | |||
| 32a6af9ac8 | |||
| e84c0e44ab | |||
| d28cd4df8b | |||
| cfd762f0a2 | |||
| 16d6af490a | |||
| 0b37a23c75 | |||
| bf8204e9a2 | |||
| 34813dee41 | |||
| d84518b97f | |||
| 2356823b62 | |||
| ce98b31cbf | |||
| 97881b5c66 | |||
| fc90fcb879 | |||
| 762c54bb2f | |||
| 9910651a9d | |||
| 42a23508b7 | |||
| fbb6eb651c | |||
| e4809535dc | |||
| 63490fe5e6 | |||
| a7b7148c25 | |||
| e50874baca | |||
| ef4f174c8d | |||
| 9c726d99de | |||
| 25a50a2ec3 | |||
| 5d3d5a884b | |||
| 7a3c1ede9a | |||
| d931d3007b | |||
| 05c2a90e16 | |||
| dc581f6300 | |||
| a6c5b43caf | |||
| 3ec82de5fc | |||
| cd08c87a80 | |||
| d8b715799c | |||
| 0bc009f800 | |||
| ff49f332b0 | |||
| f04b561f88 | |||
| 1f237019e7 | |||
| b47c18426d | |||
| 0e5d40fb1b | |||
| 496d36f805 | |||
| 9217bf289c | |||
| 2c2d8b86ab | |||
| c8128d2cde | |||
| 9c85ebc365 | |||
| 8c510f964c | |||
| 35e623c392 | |||
| 5eb6fe2dd9 | |||
| 64bb95e3cf | |||
| 96818d8efc | |||
| c23434b947 | |||
| 2a0053c046 | |||
| f1a256dac0 | |||
| 967fb96167 | |||
| bb4f3b74d9 | |||
| 7dab3a0ceb | |||
| e6b12d612a | |||
| d17a4bd6cb | |||
| 540becea52 | |||
| a71be7c47b | |||
| 7305822e67 | |||
| 6b0e5aed24 | |||
| 6f907e2060 | |||
| 29b777f12c | |||
| 335d6934fb | |||
| 60305edeae | |||
| da305d9da3 | |||
| 624f51aef4 | |||
| ba76832979 | |||
| 49126f29ae | |||
| 2ed92c88ae | |||
| a9771a0e85 | |||
| 1dcac8cfef | |||
| 18ea1dd170 | |||
| 59336096e7 | |||
| 57d7796edb | |||
| 2dc441d621 | |||
| 93ec2a94e6 | |||
| 56564d55c8 | |||
| 322364c1ca | |||
| 838c7fdc9b | |||
| e02d67c153 | |||
| 407e2ea90a | |||
| 712256eac4 | |||
| 1c2aedcc9a | |||
| 9af2969540 | |||
| 8a7946b26c | |||
| 53e0aded4c | |||
| 3ecd8be1f1 | |||
| 0262b934c4 | |||
| 9aeaf593c3 | |||
| 00c64ba805 | |||
| 7d939e1c76 | |||
| 8a4d2b558b | |||
| 0508653a29 | |||
| 2e75486d16 | |||
| 7bd73401cd | |||
| 4a107868cf | |||
| 53766ff6b7 | |||
| 4ee6758b9f | |||
| 4c1317e30d | |||
| 8a68dec71f | |||
| c7efa7fa20 | |||
| bd7394ea33 | |||
| fa7fa6afaf | |||
| 491ec0c0b8 | |||
| 87f1334ce9 | |||
| 140b047e7f | |||
| 21b1bfd600 | |||
| 5da99bc977 | |||
| a30dc40b8a | |||
| 96893d9c36 | |||
| 9be0defc29 | |||
| afc8c6fb4b | |||
| b559b0f827 | |||
| 79e31ff241 | |||
| 1e604fd80e | |||
| 56989a164e | |||
| eaf5d44037 | |||
| 93cb562321 | |||
| 31d3df673a | |||
| c0aa7a7460 | |||
| 498b7f14a9 | |||
| 95b6ecec86 | |||
| 8e6fc9bed3 | |||
| cf7822180b | |||
| f93c536f7e | |||
| d555d1f588 | |||
| 88e297b183 | |||
| 37f5c8ca58 | |||
| 7d53bd5af7 | |||
| 455a8ec11d | |||
| dac0610a19 | |||
| da5b3f829f | |||
| 02960fc8c2 | |||
| 4585938753 | |||
| f777e71c56 | |||
| 0a698d202b | |||
| 63db39082b | |||
| 4f48c2ec27 | |||
| 6faf9bd88a | |||
| a624cf89bb | |||
| e836ed09c8 | |||
| 8cc9c59330 | |||
| a11d064aeb | |||
| 77c82edb44 | |||
| 9a9ac3af77 | |||
| ac8511b211 | |||
| c3ff2e86db | |||
| 98d6a4eac0 | |||
| 5f413b967e | |||
| bdb0da4fa0 | |||
| cabacda0ad | |||
| d8c79a9712 | |||
| 88c259a666 | |||
| 796d081fce | |||
| 6d61feff11 | |||
| 5d0ab0cfb0 | |||
| 6c687a9f5c | |||
| 7a17c21fab | |||
| 1994a0bd16 | |||
| 8a1bfb925c | |||
| 914411fe06 | |||
| c88bebbc3e | |||
| aba65bf755 | |||
| cf1c99cf78 | |||
| 9c51a6024e | |||
| bec246c6ef | |||
| eeab4b8c85 | |||
| a6758eb9e6 | |||
| cdcc8da555 | |||
| 1f9aa83440 | |||
| 15d218677e | |||
| f8934840da | |||
| a888c5602a | |||
| 4134da76ca | |||
| 24b0f37ae7 | |||
| 559a738fe7 | |||
| f20b1f2a4f | |||
| c76b770807 | |||
| 3c0dea5747 | |||
| e47fbd1cb9 | |||
| efdc48980d | |||
| f84f7d4f17 | |||
| aa82ae0bd0 | |||
| 743ff67927 | |||
| 2880189713 | |||
| 2847d0fd8a | |||
| c23aeb9fcd | |||
| 980c85cfb1 | |||
| 826775bb1f | |||
| e01d61feb9 | |||
| fee42e4d7b | |||
| 038b83ac4a | |||
| 4c27f3cd5e | |||
| 2aaad84ab6 | |||
| a1d338f35c | |||
| 50bd4f121e | |||
| 3d3f6ea888 | |||
| e5cc74603a | |||
| 7da3d84053 | |||
| 47c2cc9990 | |||
| 85e3e11c10 | |||
| 840e4814d0 | |||
| 42bcb717a4 | |||
| b515b285db | |||
| 7fb15f97ab | |||
| b202886c33 | |||
| e3e4542ef2 | |||
| 3027988a4d | |||
| 119fcb15a1 | |||
| 1b4e609b33 | |||
| c80365de78 | |||
| 3e591f311c | |||
| 3118ff5c52 | |||
| d0598334e4 | |||
| 2f13d3846c | |||
| 7558054914 | |||
| 71260b52d1 | |||
| f55046f6e9 | |||
| 3cf48ae3c8 | |||
| e44e612465 | |||
| 78e9b5139c | |||
| d2a8163f32 | |||
| 9f30661f6d | |||
| 5632559e5a | |||
| ae2f502767 | |||
| 669143a178 | |||
| 54cb76ddfd | |||
| fabf122627 | |||
| 3486c695b8 | |||
| 6eb513501d | |||
| 45060a07b7 | |||
| 7f5e78fd8f | |||
| b178e67ae5 | |||
| 026c83fb1b | |||
| c7b3251918 | |||
| 14f94151a5 | |||
| 6dc473713c | |||
| 594f525c0c | |||
| 2132a5ceb0 | |||
| 5d86b7ac5b | |||
| 0e7123d632 | |||
| b96c9290f7 | |||
| 416b5e3cd2 | |||
| 8a3a872a93 | |||
| 6f5690618f | |||
| fad4434104 | |||
| 86359bacab | |||
| a7d8d7a544 | |||
| 86d059bdb4 | |||
| caa5f41b1e | |||
| fcda700625 | |||
| cdca40f018 | |||
| 736c344cdd | |||
| a28250c81b | |||
| b406a1a9d8 | |||
| a44e306343 | |||
| ecbed0f4f8 | |||
| 04273eefad | |||
| bd5436c55f | |||
| 6ca397b840 | |||
| cc2b53a6e9 | |||
| 6c4717cc39 | |||
| 6afdc99dd5 | |||
| 07fcbbdab8 | |||
| d55d1bff69 | |||
| a80d3680d0 | |||
| 9673c72d44 | |||
| 7051b79674 | |||
| e7fa0f51a6 | |||
| e9dc540f64 | |||
| e26a91147d | |||
| 3de95a341a | |||
| dff6f898a8 | |||
| dfd614c31f | |||
| 5020d33748 | |||
| bd687f353f | |||
| ce19d2c5f0 | |||
| 29ade037d3 | |||
| 75b1deaf60 | |||
| a9930207ba | |||
| 742c104964 | |||
| 4183698fc6 | |||
| f06c82b2b8 | |||
| b23b4384f1 | |||
| 93bad13469 | |||
| 31e0a5b8cc | |||
| 732830b095 | |||
| 43d42594ef | |||
| e61039bb67 | |||
| 8e8225b6ae | |||
| 01bd291b78 | |||
| 61e28a0ac9 | |||
| c744f66b2a | |||
| 9b177b25b5 | |||
| b9d89a3e85 | |||
| 903fd2264e | |||
| 0f8142a15c | |||
| b95b5b1d53 | |||
| 3d0c3a8a45 | |||
| 9af23999d1 | |||
| 74a9bb5a4e | |||
| cf506e59b0 | |||
| 68d7fd09e2 | |||
| b70680aa86 | |||
| 823ff696ab | |||
| 9b530d520c | |||
| 3d35284a49 | |||
| f425d86457 | |||
| 98a9373346 | |||
| 35a7e43045 | |||
| b535c0845e | |||
| 0ed821d32d | |||
| 2317057411 | |||
| dc3b39a198 | |||
| ce5611f24c | |||
| f819386399 | |||
| e1812ec3d7 | |||
| e9935624f7 | |||
| 0fec51a5d9 | |||
| 3fa2d370bf | |||
| 0750072ef0 | |||
| fb93198a92 | |||
| a4789c9f61 | |||
| 7386073c71 | |||
| 7d8be7079c | |||
| 148929d2f1 | |||
| 13e4f301b9 | |||
| dc91ac13b1 | |||
| a129d591e7 | |||
| 8aa0cf327a | |||
| b78e44303d | |||
| feba46db30 | |||
| a8d501fcd9 | |||
| fb17126f68 | |||
| 693b1f08a3 | |||
| 6e81630e49 | |||
| 76dbf9412a | |||
| 68123093c3 | |||
| 73cbee1b85 | |||
| 0cb613b6ce | |||
| 7834d15127 | |||
| 71ce49a301 | |||
| fd7f75578d | |||
| 4c8452de78 | |||
| 7bebf0ef3c | |||
| 56a0c4e0d9 | |||
| 666b9d35d6 | |||
| 2ef8f8ebef | |||
| b88c886592 | |||
| e742482175 | |||
| 8c63451137 | |||
| a30a59331d | |||
| 95d074eb92 | |||
| 103c44bfd8 | |||
| bfcd43452e | |||
| 0c5cf54d57 | |||
| 3f4f2a1813 | |||
| a94c3b72d3 | |||
| c848b5f1b4 | |||
| 55f110351e | |||
| 58e7f84627 | |||
| db769226b8 | |||
| bfcab984e2 | |||
| b33a8dea15 | |||
| 5e00550c6a | |||
| 329b41a84b | |||
| 5e74861311 | |||
| 70229486bb | |||
| 813507267c | |||
| f55402e033 | |||
| f3cf4d6969 | |||
| da88d20b1e | |||
| b17bba7e93 | |||
| c99a999f1a | |||
| c5c5253de5 | |||
| 1f8652d809 | |||
| 8846b907d7 | |||
| 3d6c55962b | |||
| 0b0f18399c | |||
| 5856e77f3b | |||
| c80d9ffb99 | |||
| 1f84395539 | |||
| f3a7620188 | |||
| 180b0c6768 | |||
| 55a93013c7 | |||
| 982e742322 | |||
| 3676191fc8 | |||
| 5b19d38d88 | |||
| 32c088ac00 | |||
| 123adc7edc | |||
| dbecce602c | |||
| 2b10dc21b0 | |||
| 51701ecffc | |||
| 601b8855f6 | |||
| f56bfb7d4b | |||
| 3f8d368b87 | |||
| 70ccc64831 | |||
| 2f5aef1fb8 | |||
| 3581a9ecf0 | |||
| ed06aa8920 | |||
| 5bb401772a | |||
| 5c30a69e10 | |||
| be585f72a3 | |||
| 309665f63e | |||
| 9d8a055a04 | |||
| 45a1d3fa8e | |||
| 473e3efae9 | |||
| e2607ea6e8 | |||
| 75dcc0772e | |||
| 38ad334e12 | |||
| 5fcc1bc196 | |||
| 5bd1977c89 | |||
| 3eb5db5b31 | |||
| aa84176c7d | |||
| f30e540690 | |||
| 1729d3cda3 | |||
| c7c10822fb | |||
| 6b39e4103e | |||
| 5a90937c52 | |||
| 69e7ed3eae | |||
| 01bcb40c50 | |||
| 3ecb73dc8a | |||
| 06c00b0895 | |||
| 3f8b47aa91 | |||
| dc82e51ef6 | |||
| 2516d13bc5 | |||
| 2ffb286569 | |||
| af84c8802b | |||
| d7e43b7ecc | |||
| 471a33d64d | |||
| 334481c78d | |||
| c2bb8f9c56 | |||
| c650edd193 | |||
| 3e03917edc | |||
| 4d0f65a6d1 | |||
| 02717ebe73 | |||
| a67c8f98b7 | |||
| 21589861fd | |||
| 0ce8161cbf | |||
| 013ecbed97 | |||
| 4370e9c7a3 | |||
| a5dedeb164 | |||
| 7b557144b3 | |||
| 4747d26cf6 | |||
| 3c3969b09d | |||
| 8bd080b3e0 | |||
| 5a044781fc | |||
| 98480a3018 | |||
| 5c412cea96 | |||
| ee3d33526b | |||
| 25633be976 | |||
| cd20d94c7c | |||
| 90fbb74f93 | |||
| 867159198a | |||
| 6c4ae9749d | |||
| 4254090cf4 | |||
| 006319deff | |||
| 9872c5fb4b | |||
| 2bc5247dc0 | |||
| 2ca5f36590 | |||
| c1e3525929 | |||
| ef44741760 | |||
| eebfd509d7 | |||
| 61ca62df5f | |||
| ba18142718 | |||
| 1714c516ab | |||
| 1b2b13881c | |||
| 7dc26d1a68 | |||
| fbba97f9a6 | |||
| e969a96c17 | |||
| 930ef6e646 | |||
| b0bba1917f | |||
| 1fbd6ecdfa | |||
| 888a674675 | |||
| 510e2c2225 | |||
| ffd8f6404b | |||
| e0259e191c | |||
| 245a5eaaeb | |||
| 1fb31b2e12 | |||
| ea64cc4de3 | |||
| b038b102d8 | |||
| 5e0a1bbec3 | |||
| 5f3d71798d | |||
| c1804b1fd8 | |||
| 86ce0b03ee | |||
| e5ac0c511d | |||
| 8c0e8375f4 | |||
| 357d7e545f | |||
| d2bd12e9a1 | |||
| 300ad3d49c | |||
| be9ab0a7e6 | |||
| c71a9aed6e | |||
| ecf6161f9c | |||
| 78d5f71fed | |||
| c1d3e4540b | |||
| 2e7036ab07 | |||
| cfff7d7cb7 | |||
| 1f68e0deaf | |||
| c79e2b3ad7 | |||
| 49aad9cce3 | |||
| 8ec27d25c5 | |||
| cd9115b0b2 | |||
| 7918dab915 | |||
| 0913a22dfe | |||
| e36a9c6c61 | |||
| a98bf277c8 | |||
| c5b12e0a16 | |||
| e421eb271e | |||
| 9b97124f80 | |||
| 1a39c51617 | |||
| 2f74f93308 | |||
| 4747792c37 | |||
| 5e0447df1e | |||
| d9676ca194 | |||
| 026a2eb528 | |||
| e49cfe44b9 | |||
| 2e113835a7 | |||
| 5f51e6704d | |||
| 806a210467 | |||
| 1ddbc24a14 | |||
| aed0b7fdb9 | |||
| 7837a3c1aa | |||
| 7e4818916b | |||
| 32185c0c2e | |||
| 90b05a1d18 | |||
| 37882e86d5 | |||
| 1fd3f2cb9d | |||
| 3f3a63a14b | |||
| 4abda00404 | |||
| b2ded79155 | |||
| 7ea096686d | |||
| 847333d536 | |||
| f132bd7b82 | |||
| d7ec15bddb | |||
| 41fef69a57 | |||
| 14cde4a64c | |||
| abd2721a15 | |||
| 3deb8fbb84 | |||
| c49b4ec228 | |||
| 8125fd789a | |||
| 6764db8b3b | |||
| 24af3405f2 | |||
| 6626202ff1 | |||
| 261b139bcc | |||
| da5a028057 | |||
| b9ab07d7cd | |||
| a9d57b521e | |||
| 7f03e2880c | |||
| 6923c35e7d | |||
| 48ea42c026 | |||
| 5bc24ff148 | |||
| 8ddf2b4757 | |||
| 863273edbc | |||
| b8a84d6196 | |||
| 997c1be7b6 | |||
| 471981f732 | |||
| 989b69ee92 | |||
| 650b79941f | |||
| f7d79c613e | |||
| 37c82cc2d4 | |||
| 1c38b731bf | |||
| 5312ce3082 | |||
| 3c27a6a273 | |||
| d4f73f56c4 | |||
| 5584535842 | |||
| 5607d3d01a | |||
| 5ace618860 | |||
| fc4a53c02e | |||
| 1a016da5fa | |||
| 0f5ab2f6f6 | |||
| be88d86b21 | |||
| d98d1c3432 | |||
| 873275374d | |||
| 712aa081bc | |||
| 0c39558afb | |||
| 762146babc | |||
| 708af332de | |||
| c612f7a102 | |||
| 943a060db2 | |||
| b77341c804 | |||
| f69599fabb | |||
| 617aa0a5db | |||
| 86f5102158 | |||
| 900bb97459 | |||
| 86f99b9e1b | |||
| e57d75a3d7 | |||
| 375a73891c | |||
| ae4c155c6e | |||
| eedcfe3c56 | |||
| 345747a042 | |||
| 26e25bf9a8 | |||
| 8b12ed459f | |||
| 0041a79823 | |||
| b820a3214f | |||
| f93a4f5adb | |||
| 4503811202 | |||
| 8ec92d12fd | |||
| edc9a9ce60 | |||
| f58b6fc6c7 | |||
| d5b8ab3262 | |||
| 609ba6cf63 | |||
| b9c5a78d16 | |||
| a919998872 | |||
| f7fdd7634e | |||
| feb9d49c93 | |||
| d0c19745c3 | |||
| 3adc8592da | |||
| dcaec4f160 | |||
| b5340a3f3d | |||
| b3a2f141d6 | |||
| 86db8e39c6 | |||
| 749b0d6d79 | |||
| 0c7d259e7f | |||
| f9d6c0d648 | |||
| 3e82e773a9 | |||
| 04dbd8d0fd | |||
| 60d6cc4476 | |||
| 91a6df08b0 | |||
| 40aff87f88 | |||
| e19f7d29b4 | |||
| f27069f2d2 | |||
| aac395ccd2 | |||
| 422d92f0ea | |||
| d547e3632d | |||
| ef57854b81 | |||
| d7a29e6d81 | |||
| a68bc7be2e | |||
| 2ed89e4bb7 | |||
| 145f9d2b19 | |||
| 94c71b8b20 | |||
| 52071db172 | |||
| 66d2a63bcf | |||
| 53116c29c8 | |||
| d8fccdb01c | |||
| be77164d8d | |||
| 0889b3b0c2 | |||
| 67c88675da | |||
| 65a9fcf3ec | |||
| cb56102da7 | |||
| 2e28374fc9 | |||
| 9598e47162 | |||
| 40372c63ee | |||
| e24520254b | |||
| 43a5c09412 | |||
| 426dbd1718 | |||
| 8bae6a1063 | |||
| 8f7b28deed | |||
| 4026b6af9b | |||
| a5dae4669c | |||
| 6b1e11c314 | |||
| e2681b17a4 | |||
| da61e79f2e | |||
| 31fefd8eb8 | |||
| bcf78fec02 | |||
| 71528fd547 | |||
| 962e0d740a | |||
| 1262a2d8f3 | |||
| a8ba10aac6 | |||
| ff67d2eedd | |||
| a2c39aa2ed | |||
| a1a781bcf6 | |||
| 6d0559c6d9 | |||
| 330a811114 | |||
| ed695fab1b | |||
| a2d51807c7 | |||
| df0b6c3e2b | |||
| 636e0d0585 | |||
| 124daf1f51 | |||
| e8d0d7bc3a | |||
| f074dcc3b5 | |||
| 2eb4dfbdc9 | |||
| 2e4c3f43ee | |||
| be87ecec3e | |||
| 317be3b383 | |||
| ca5b641b96 | |||
| 031ad89851 |
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(python3 -c \"import json; json.load\\(open\\(r'c:\\\\Users\\\\user\\\\Documents\\\\GitHub\\\\openpilot\\\\starpilot\\\\system\\\\the_pond\\\\assets\\\\components\\\\tools\\\\device_settings_layout.json'\\)\\)\")",
|
||||
"Bash(python3 -m py_compile \"c:\\\\Users\\\\user\\\\Documents\\\\GitHub\\\\openpilot\\\\starpilot\\\\controls\\\\lib\\\\starpilot_vcruise.py\")",
|
||||
"Bash(python3 -m py_compile \"c:\\\\Users\\\\user\\\\Documents\\\\GitHub\\\\openpilot\\\\selfdrive\\\\ui\\\\layouts\\\\settings\\\\starpilot\\\\longitudinal.py\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
name: "PR review"
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize, edited]
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: review
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
# Label PRs
|
||||
- uses: actions/labeler@v5.0.0
|
||||
with:
|
||||
dot: true
|
||||
configuration-path: .github/labeler.yaml
|
||||
|
||||
# Check PR target branch
|
||||
- name: check branch
|
||||
uses: Vankka/pr-target-branch-action@def32ec9d93514138d6ac0132ee62e120a72aed5
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
target: /^(?!master$).*/
|
||||
exclude: /commaai:.*/
|
||||
change-to: ${{ github.base_ref }}
|
||||
already-exists-action: close_this
|
||||
already-exists-comment: "Your PR should be made against the `master` branch"
|
||||
|
||||
# Welcome comment
|
||||
- name: "First timers PR"
|
||||
uses: actions/first-interaction@v1
|
||||
if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot'
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr-message: |
|
||||
<!-- _(run_id **${{ github.run_id }}**)_ -->
|
||||
Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following:
|
||||
* Convert your PR to a draft unless it's ready to review
|
||||
* Read the [contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md)
|
||||
* Before marking as "ready for review", ensure:
|
||||
* the goal is clearly stated in the description
|
||||
* all the tests are passing
|
||||
* the change is [something we merge](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md#what-gets-merged)
|
||||
* include a route or your device' dongle ID if relevant
|
||||
@@ -1,37 +0,0 @@
|
||||
name: badges
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 * * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BASE_IMAGE: openpilot-base
|
||||
DOCKER_REGISTRY: ghcr.io/commaai
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/bash -c
|
||||
|
||||
jobs:
|
||||
badges:
|
||||
name: create badges
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Push badges
|
||||
run: |
|
||||
${{ env.RUN }} "python3 selfdrive/ui/translations/create_badges.py"
|
||||
|
||||
rm .gitattributes
|
||||
|
||||
git checkout --orphan badges
|
||||
git rm -rf --cached .
|
||||
git config user.email "badge-researcher@comma.ai"
|
||||
git config user.name "Badge Researcher"
|
||||
|
||||
git add translation_badge.svg
|
||||
git commit -m "Add/Update badges"
|
||||
git push -f origin HEAD
|
||||
@@ -1,101 +0,0 @@
|
||||
name: weekly CI test report
|
||||
on:
|
||||
schedule:
|
||||
- cron: '37 9 * * 1' # 9:37AM UTC -> 2:37AM PST every monday
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ci_runs:
|
||||
description: 'The amount of runs to trigger in CI test report'
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CI_RUNS: ${{ github.event.inputs.ci_runs || '50' }}
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
ci_runs: ${{ steps.ci_runs_setup.outputs.matrix }}
|
||||
steps:
|
||||
- id: ci_runs_setup
|
||||
name: CI_RUNS=${{ env.CI_RUNS }}
|
||||
run: |
|
||||
matrix=$(python3 -c "import json; print(json.dumps({ 'run_number' : list(range(${{ env.CI_RUNS }})) }))")
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
|
||||
ci_matrix_run:
|
||||
needs: [ setup ]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{fromJSON(needs.setup.outputs.ci_runs)}}
|
||||
uses: commaai/openpilot/.github/workflows/ci_weekly_run.yaml@master
|
||||
with:
|
||||
run_number: ${{ matrix.run_number }}
|
||||
|
||||
report:
|
||||
needs: [ci_matrix_run]
|
||||
runs-on: ubuntu-latest
|
||||
if: always() && github.repository == 'commaai/openpilot'
|
||||
steps:
|
||||
- name: Get job results
|
||||
uses: actions/github-script@v7
|
||||
id: get-job-results
|
||||
with:
|
||||
script: |
|
||||
const jobs = await github
|
||||
.paginate("GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt}/jobs", {
|
||||
owner: "commaai",
|
||||
repo: "${{ github.event.repository.name }}",
|
||||
run_id: "${{ github.run_id }}",
|
||||
attempt: "${{ github.run_attempt }}",
|
||||
})
|
||||
var report = {}
|
||||
jobs.slice(1, jobs.length-1).forEach(job => {
|
||||
if (job.conclusion === "skipped") return;
|
||||
const jobName = job.name.split(" / ")[2];
|
||||
const runRegex = /\((.*?)\)/;
|
||||
const run = job.name.match(runRegex)[1];
|
||||
report[jobName] = report[jobName] || { successes: [], failures: [], canceled: [] };
|
||||
switch (job.conclusion) {
|
||||
case "success":
|
||||
report[jobName].successes.push({ "run_number": run, "link": job.html_url}); break;
|
||||
case "failure":
|
||||
report[jobName].failures.push({ "run_number": run, "link": job.html_url }); break;
|
||||
case "canceled":
|
||||
report[jobName].canceled.push({ "run_number": run, "link": job.html_url }); break;
|
||||
}
|
||||
});
|
||||
return JSON.stringify({"jobs": report});
|
||||
|
||||
- name: Add job results to summary
|
||||
env:
|
||||
JOB_RESULTS: ${{ fromJSON(steps.get-job-results.outputs.result) }}
|
||||
run: |
|
||||
cat <<EOF >> template.html
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Job</th>
|
||||
<th>✅ Passing</th>
|
||||
<th>❌ Failure Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for key in jobs.keys() %}<tr>
|
||||
<td>{% for i in range(5) %}{% if i+1 <= (5 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }}) %}🟩{% else %}🟥{% endif %}{% endfor%}</td>
|
||||
<td>{{ key }}</td>
|
||||
<td>{{ 100 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }} }}%</td>
|
||||
<td>{% if jobs[key]["failures"]|length > 0 %}<details>{% for failure in jobs[key]["failures"] %}<a href="{{ failure['link'] }}">Log for run #{{ failure['run_number'] }}</a><br>{% endfor %}</details>{% else %}{% endif %}</td>
|
||||
</td>
|
||||
</tr>{% endfor %}
|
||||
</table>
|
||||
EOF
|
||||
|
||||
pip install jinja2-cli
|
||||
echo $JOB_RESULTS | jinja2 template.html > report.html
|
||||
echo "# CI Test Report - ${{ env.CI_RUNS }} Runs" >> $GITHUB_STEP_SUMMARY
|
||||
cat report.html >> $GITHUB_STEP_SUMMARY
|
||||
@@ -1,17 +0,0 @@
|
||||
name: weekly CI test run
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_number:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ci-run-${{ inputs.run_number }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
uses: commaai/openpilot/.github/workflows/tests.yaml@master
|
||||
with:
|
||||
run_number: ${{ inputs.run_number }}
|
||||
@@ -0,0 +1,215 @@
|
||||
name: Compile Bluescreens
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target_ref:
|
||||
description: "Branch to build and publish back to"
|
||||
type: string
|
||||
default: "bluescreensonly2"
|
||||
required: true
|
||||
jobs:
|
||||
description: "Optional parallel job count passed to ./build"
|
||||
type: string
|
||||
default: "12"
|
||||
required: false
|
||||
enable_scons_cache:
|
||||
description: "Enable SCons cache for faster incremental builds"
|
||||
type: boolean
|
||||
default: true
|
||||
required: false
|
||||
build_all:
|
||||
description: "Build everything"
|
||||
type: boolean
|
||||
default: true
|
||||
required: false
|
||||
build_ui:
|
||||
description: "Build UI (selfdrive/ui)"
|
||||
type: boolean
|
||||
default: true
|
||||
required: false
|
||||
build_params:
|
||||
description: "Build params Python extension"
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
build_cereal:
|
||||
description: "Build cereal messaging bridge"
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
build_panda:
|
||||
description: "Build panda firmware outputs"
|
||||
type: boolean
|
||||
default: true
|
||||
required: false
|
||||
build_modeld:
|
||||
description: "Build modeld Python extension"
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
build_controls:
|
||||
description: "Build controls solvers + locationd generated models"
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
|
||||
concurrency:
|
||||
group: compile-bluescreens-${{ inputs.target_ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GIT_EMAIL: "168790843+firestar5683@users.noreply.github.com"
|
||||
GIT_NAME: "StarPilot Build Bot"
|
||||
TARGET_REF: ${{ inputs.target_ref }}
|
||||
|
||||
jobs:
|
||||
build_and_push:
|
||||
runs-on: [self-hosted, truenas, starpilot-build]
|
||||
timeout-minutes: 720
|
||||
steps:
|
||||
- name: Validate target branch
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${TARGET_REF}" ]]; then
|
||||
echo "target_ref is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${TARGET_REF}" == refs/heads/* ]]; then
|
||||
echo "BUILD_BRANCH=${TARGET_REF#refs/heads/}" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "BUILD_BRANCH=${TARGET_REF}" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Checkout target branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.BUILD_BRANCH }}
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Configure git identity
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "$GIT_NAME"
|
||||
git config user.email "$GIT_EMAIL"
|
||||
git config http.postBuffer 104857600
|
||||
|
||||
- name: Ensure branch exists on origin
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git ls-remote --exit-code --heads origin "refs/heads/${BUILD_BRANCH}" >/dev/null
|
||||
|
||||
- name: Link persistent sysroot
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ls -ld /runner /runner/sysroots /runner/sysroots/default || true
|
||||
ls -ld /runner/sysroots/default/usr /runner/sysroots/default/usr/local /runner/sysroots/default/usr/local/lib || true
|
||||
rm -rf .comma_sysroot
|
||||
ln -s /runner/sysroots/default .comma_sysroot
|
||||
ls -ld .comma_sysroot .comma_sysroot/usr .comma_sysroot/usr/local .comma_sysroot/usr/local/lib
|
||||
|
||||
- name: Run Bluescreens build
|
||||
shell: bash
|
||||
env:
|
||||
SP_ENABLE_SCONS_CACHE: ${{ inputs.enable_scons_cache && '1' || '0' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
RUNNER_HOST_ROOT="/mnt/Apparition/My_App_Data/starpilot-runner"
|
||||
if [[ "${GITHUB_WORKSPACE}" != /runner/_work/* ]]; then
|
||||
echo "Unexpected GITHUB_WORKSPACE: ${GITHUB_WORKSPACE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORKSPACE_REL="${GITHUB_WORKSPACE#/runner}"
|
||||
export COMMA_HOST_ROOT_DIR="${RUNNER_HOST_ROOT}${WORKSPACE_REL}"
|
||||
export COMMA_HOST_SYSROOT_DIR="${RUNNER_HOST_ROOT}/sysroots/default"
|
||||
export COMMA_HOST_CACHE_DIR="${RUNNER_HOST_ROOT}/cache/work"
|
||||
export COMMA_HOST_VENV_DIR="${RUNNER_HOST_ROOT}/cache/venv-linux-arm64"
|
||||
export COMMA_SYSROOT_DIR="/runner/sysroots/default"
|
||||
|
||||
echo "GITHUB_WORKSPACE=${GITHUB_WORKSPACE}"
|
||||
echo "COMMA_HOST_ROOT_DIR=${COMMA_HOST_ROOT_DIR}"
|
||||
echo "COMMA_HOST_SYSROOT_DIR=${COMMA_HOST_SYSROOT_DIR}"
|
||||
echo "COMMA_HOST_CACHE_DIR=${COMMA_HOST_CACHE_DIR}"
|
||||
echo "COMMA_HOST_VENV_DIR=${COMMA_HOST_VENV_DIR}"
|
||||
|
||||
JOBS="${{ inputs.jobs }}"
|
||||
|
||||
if [[ "${{ inputs.build_all }}" == "true" ]]; then
|
||||
if [[ -n "${JOBS}" ]]; then
|
||||
./build "${JOBS}"
|
||||
else
|
||||
./build
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TARGETS=()
|
||||
[[ "${{ inputs.build_ui }}" == "true" ]] && TARGETS+=("selfdrive/ui/ui")
|
||||
[[ "${{ inputs.build_params }}" == "true" ]] && TARGETS+=("common/params_pyx.so")
|
||||
[[ "${{ inputs.build_cereal }}" == "true" ]] && TARGETS+=("cereal/messaging/bridge")
|
||||
if [[ "${{ inputs.build_panda }}" == "true" ]]; then
|
||||
TARGETS+=(
|
||||
"panda/board/obj/panda.bin.signed"
|
||||
"panda/board/obj/panda_h7.bin.signed"
|
||||
"panda/board/obj/panda_remote.bin.signed"
|
||||
"panda/board/obj/panda_h7_remote.bin.signed"
|
||||
"panda/board/obj/panda_can_ignition_only.bin.signed"
|
||||
"panda/board/obj/panda_h7_can_ignition_only.bin.signed"
|
||||
"panda/board/obj/panda_remote_can_ignition_only.bin.signed"
|
||||
"panda/board/obj/panda_h7_remote_can_ignition_only.bin.signed"
|
||||
"panda/board/obj/panda_jungle_h7.bin.signed"
|
||||
"panda/board/obj/body_h7.bin.signed"
|
||||
)
|
||||
fi
|
||||
[[ "${{ inputs.build_modeld }}" == "true" ]] && TARGETS+=("selfdrive/modeld/models/commonmodel_pyx.so")
|
||||
if [[ "${{ inputs.build_controls }}" == "true" ]]; then
|
||||
TARGETS+=(
|
||||
"selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so"
|
||||
"selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so"
|
||||
"selfdrive/locationd/models/generated/car.cpp"
|
||||
"selfdrive/locationd/models/generated/pose.cpp"
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ ${#TARGETS[@]} -eq 0 ]]; then
|
||||
echo "No partial targets selected; defaulting to UI + panda"
|
||||
TARGETS=("selfdrive/ui/ui" "panda/board/obj/panda.bin.signed")
|
||||
fi
|
||||
|
||||
echo "Running partial build for targets: ${TARGETS[*]}"
|
||||
if [[ -n "${JOBS}" ]]; then
|
||||
./build "${JOBS}" "${TARGETS[@]}"
|
||||
else
|
||||
./build "$(nproc)" "${TARGETS[@]}"
|
||||
fi
|
||||
|
||||
- name: Commit build output
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "$(git status --porcelain --untracked-files=no)" ]]; then
|
||||
echo "No build output changes detected."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git add -u
|
||||
git commit -m "build"
|
||||
|
||||
- name: Push build commit
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git push origin "HEAD:refs/heads/${BUILD_BRANCH}"
|
||||
@@ -3,237 +3,113 @@ name: Compile StarPilot
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
not_vetted:
|
||||
description: "This branch is not vetted"
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
publish_custom_branch:
|
||||
description: "Push to custom branch:"
|
||||
target_ref:
|
||||
description: "Branch to build and publish back to"
|
||||
type: string
|
||||
default: ""
|
||||
required: false
|
||||
publish_starpilot:
|
||||
description: "Push to StarPilot"
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
publish_staging:
|
||||
description: "Push to StarPilot-Staging"
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
publish_testing:
|
||||
description: "Push to StarPilot-Testing"
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
runner:
|
||||
description: "Select runner"
|
||||
type: choice
|
||||
options:
|
||||
- c3
|
||||
- c3x
|
||||
default: "c3"
|
||||
default: "master"
|
||||
required: true
|
||||
update_translations:
|
||||
description: "Update missing/outdated translations"
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
vet_existing_translations:
|
||||
description: "Vet existing translations"
|
||||
type: boolean
|
||||
default: false
|
||||
jobs:
|
||||
description: "Optional parallel job count passed to ./build"
|
||||
type: string
|
||||
default: "12"
|
||||
required: false
|
||||
|
||||
concurrency:
|
||||
group: compile-starpilot-${{ inputs.target_ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
BASE_DIR: ${{ github.workspace }}
|
||||
BUILD_DIR: "/data/openpilot"
|
||||
CUSTOM_BRANCH: ${{ inputs.publish_custom_branch }}
|
||||
GIT_EMAIL: "91348155+FrogAi@users.noreply.github.com"
|
||||
GIT_NAME: "James"
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
GIT_EMAIL: "168790843+firestar5683@users.noreply.github.com"
|
||||
GIT_NAME: "StarPilot Build Bot"
|
||||
TARGET_REF: ${{ inputs.target_ref }}
|
||||
|
||||
jobs:
|
||||
get_branch:
|
||||
runs-on: [self-hosted, "${{ inputs.runner }}"]
|
||||
outputs:
|
||||
branch: ${{ steps.get_branch.outputs.branch }}
|
||||
python_version: ${{ steps.get_python_version.outputs.python_version }}
|
||||
build_and_push:
|
||||
runs-on: [self-hosted, truenas, starpilot-build]
|
||||
timeout-minutes: 720
|
||||
steps:
|
||||
- name: Get Current Branch
|
||||
id: get_branch
|
||||
- name: Validate target branch
|
||||
shell: bash
|
||||
run: |
|
||||
cd "$BUILD_DIR"
|
||||
echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT"
|
||||
set -euo pipefail
|
||||
|
||||
- name: Get Python Version
|
||||
id: get_python_version
|
||||
run: |
|
||||
echo "python_version=$(tr -d '[:space:]' < "$BUILD_DIR/.python-version")" >> $GITHUB_OUTPUT
|
||||
if [[ -z "${TARGET_REF}" ]]; then
|
||||
echo "target_ref is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
translate:
|
||||
needs: get_branch
|
||||
if: inputs.update_translations
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Configure Git Identity
|
||||
run: |
|
||||
git config --global user.name "$GIT_NAME"
|
||||
git config --global user.email "$GIT_EMAIL"
|
||||
if [[ "${TARGET_REF}" == refs/heads/* ]]; then
|
||||
echo "BUILD_BRANCH=${TARGET_REF#refs/heads/}" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "BUILD_BRANCH=${TARGET_REF}" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Checkout Required Files
|
||||
- name: Checkout target branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.get_branch.outputs.branch }}
|
||||
sparse-checkout: |
|
||||
starpilot/ui/
|
||||
selfdrive/controls/lib/alerts_offroad.json
|
||||
selfdrive/ui/
|
||||
selfdrive/ui/translations/
|
||||
selfdrive/ui/translations/auto_translate.py
|
||||
selfdrive/ui/update_translations.py
|
||||
ref: ${{ env.BUILD_BRANCH }}
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Set Up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
cache: "pip"
|
||||
python-version: ${{ needs.get_branch.outputs.python_version }}
|
||||
|
||||
- name: Install Dependencies
|
||||
- name: Configure git identity
|
||||
shell: bash
|
||||
run: |
|
||||
pip install requests
|
||||
sudo apt-get update && sudo apt-get install -y --no-install-recommends qttools5-dev-tools
|
||||
set -euo pipefail
|
||||
git config user.name "$GIT_NAME"
|
||||
git config user.email "$GIT_EMAIL"
|
||||
git config http.postBuffer 104857600
|
||||
|
||||
- name: Update Translations
|
||||
- name: Ensure branch exists on origin
|
||||
shell: bash
|
||||
run: |
|
||||
python selfdrive/ui/update_translations.py --vanish
|
||||
set -euo pipefail
|
||||
git ls-remote --exit-code --heads origin "refs/heads/${BUILD_BRANCH}" >/dev/null
|
||||
|
||||
- name: Update Missing Translations
|
||||
continue-on-error: true
|
||||
timeout-minutes: 300
|
||||
- name: Link persistent sysroot
|
||||
shell: bash
|
||||
run: |
|
||||
python selfdrive/ui/translations/auto_translate.py --all-files
|
||||
set -euo pipefail
|
||||
ls -ld /runner /runner/sysroots /runner/sysroots/default || true
|
||||
ls -ld /runner/sysroots/default/usr /runner/sysroots/default/usr/local /runner/sysroots/default/usr/local/lib || true
|
||||
rm -rf .comma_sysroot
|
||||
ln -s /runner/sysroots/default .comma_sysroot
|
||||
ls -ld .comma_sysroot .comma_sysroot/usr .comma_sysroot/usr/local .comma_sysroot/usr/local/lib
|
||||
|
||||
- name: Vet Existing Translations
|
||||
if: inputs.vet_existing_translations
|
||||
continue-on-error: true
|
||||
timeout-minutes: 300
|
||||
- name: Run StarPilot device build
|
||||
shell: bash
|
||||
run: |
|
||||
python selfdrive/ui/translations/auto_translate.py --all-files --vet-translations
|
||||
set -euo pipefail
|
||||
|
||||
- name: Commit and Push Translations
|
||||
export COMMA_HOST_ROOT_DIR="/mnt/Apparition/My_App_Data/starpilot-runner/_work/${GITHUB_REPOSITORY#*/}/${GITHUB_REPOSITORY#*/}"
|
||||
export COMMA_HOST_SYSROOT_DIR="/mnt/Apparition/My_App_Data/starpilot-runner/sysroots/default"
|
||||
export COMMA_HOST_CACHE_DIR="/mnt/Apparition/My_App_Data/starpilot-runner/cache/work"
|
||||
export COMMA_HOST_VENV_DIR="/mnt/Apparition/My_App_Data/starpilot-runner/cache/venv-linux-arm64"
|
||||
export COMMA_SYSROOT_DIR="/runner/sysroots/default"
|
||||
|
||||
if [[ -n "${{ inputs.jobs }}" ]]; then
|
||||
./build "${{ inputs.jobs }}"
|
||||
else
|
||||
./build
|
||||
fi
|
||||
|
||||
- name: Commit build output
|
||||
shell: bash
|
||||
run: |
|
||||
if git diff --quiet selfdrive/ui/translations/*.ts; then
|
||||
echo "No translation updates detected."
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "$(git status --porcelain --untracked-files=no)" ]]; then
|
||||
echo "No build output changes detected."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git fetch --unshallow origin "${{ needs.get_branch.outputs.branch }}"
|
||||
git checkout "${{ needs.get_branch.outputs.branch }}"
|
||||
git add selfdrive/ui/translations/*.ts
|
||||
git commit --amend --no-edit
|
||||
git push --force origin "${{ needs.get_branch.outputs.branch }}"
|
||||
git add -u
|
||||
git commit -m "build"
|
||||
|
||||
build_and_push:
|
||||
needs: [get_branch, translate]
|
||||
if: ${{ !failure() && !cancelled() && needs.get_branch.result == 'success' }}
|
||||
runs-on: [self-hosted, "${{ inputs.runner }}"]
|
||||
permissions:
|
||||
contents: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
steps:
|
||||
- name: Configure Git
|
||||
- name: Push build commit
|
||||
shell: bash
|
||||
run: |
|
||||
git config http.postBuffer 104857600
|
||||
git config user.name "$GIT_NAME"
|
||||
git config user.email "$GIT_EMAIL"
|
||||
git remote set-url origin "https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/FrogAi/StarPilot.git"
|
||||
|
||||
- name: Sync Translation Updates
|
||||
if: inputs.update_translations
|
||||
run: |
|
||||
git fetch origin "${{ needs.get_branch.outputs.branch }}"
|
||||
git reset --hard FETCH_HEAD
|
||||
|
||||
- name: Take Ownership of Build Directory
|
||||
run: |
|
||||
sudo chown -R $(whoami):$(whoami) .
|
||||
|
||||
- name: Clean Build Artifacts
|
||||
run: |
|
||||
find . -name "matlab.*.md" -delete
|
||||
|
||||
find . -type d \( -iname "debug" -o -iname "test" -o -iname "tests" -o -name '__pycache__' \) -exec rm -rf {} +
|
||||
|
||||
find . -type f \( \
|
||||
-name '*.a' -o \
|
||||
-name '*.o' -o \
|
||||
-name '*.onnx' -o \
|
||||
-name '*.os' -o \
|
||||
-name '*.pyc' -o \
|
||||
-name 'moc_*' \
|
||||
\) -delete
|
||||
|
||||
find .github -mindepth 1 -maxdepth 1 ! -name 'workflows' -exec rm -rf {} +
|
||||
find .github/workflows -mindepth 1 ! \( \
|
||||
-type f \( \
|
||||
-name 'compile_starpilot.yaml' -o \
|
||||
-name 'review_pull_request.yaml' -o \
|
||||
-name 'schedule_update.yaml' -o \
|
||||
-name 'update_pr_branch.yaml' -o \
|
||||
-name 'update_release_branch.yaml' -o \
|
||||
-name 'update_tinygrad.yaml' \
|
||||
\) \
|
||||
\) -exec rm -rf {} +
|
||||
|
||||
find panda/board -type f \
|
||||
! -name '__init__.py' \
|
||||
! -name 'bootstub.panda.bin' \
|
||||
! -name 'bootstub.panda_h7.bin' \
|
||||
! -name 'panda.bin.signed' \
|
||||
! -name 'panda_h7.bin.signed' \
|
||||
-delete
|
||||
|
||||
find third_party/ -name '*Darwin*' -exec rm -rf {} +
|
||||
find third_party/ -name '*x86*' -exec rm -rf {} +
|
||||
|
||||
rm -f .gitignore .gitmodules .gitattributes .lfsconfig .overlay_init
|
||||
|
||||
rm -rf .sconsign.dblite .vscode/ Jenkinsfile release/ scripts/ site_scons/ teleoprtc_repo/
|
||||
|
||||
find . -type d -empty ! -path "./.git*" -delete
|
||||
|
||||
touch prebuilt
|
||||
[ "${{ inputs.not_vetted }}" = "true" ] && touch not_vetted || true
|
||||
|
||||
- name: Add Update Date File
|
||||
if: inputs.publish_staging
|
||||
continue-on-error: true
|
||||
run: |
|
||||
curl -fLsS https://raw.githubusercontent.com/FrogAi/StarPilot/StarPilot-Staging/.github/update_date -o .github/update_date || echo "No update_date found, skipping..."
|
||||
|
||||
- name: Commit and Push Build
|
||||
run: |
|
||||
git add -f .
|
||||
git commit -m "Compile StarPilot"
|
||||
git push --force origin HEAD
|
||||
|
||||
if [ "${{ inputs.publish_starpilot }}" = "true" ]; then
|
||||
git push --force origin HEAD:StarPilot
|
||||
fi
|
||||
|
||||
if [ "${{ inputs.publish_staging }}" = "true" ]; then
|
||||
git push --force origin HEAD:StarPilot-Staging
|
||||
fi
|
||||
|
||||
if [ "${{ inputs.publish_testing }}" = "true" ]; then
|
||||
git push --force origin HEAD:StarPilot-Testing
|
||||
fi
|
||||
|
||||
if [ -n "$CUSTOM_BRANCH" ]; then
|
||||
git push --force origin HEAD:"$CUSTOM_BRANCH"
|
||||
fi
|
||||
set -euo pipefail
|
||||
git push origin "HEAD:refs/heads/${BUILD_BRANCH}"
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
name: docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_number:
|
||||
default: '1'
|
||||
required: true
|
||||
type: string
|
||||
concurrency:
|
||||
group: docs-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: build docs
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: commaai/timeout@v1
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
# Build
|
||||
- name: Build docs
|
||||
run: |
|
||||
# TODO: can we install just the "docs" dependency group without the normal deps?
|
||||
pip install mkdocs
|
||||
mkdocs build
|
||||
|
||||
# Push to docs.comma.ai
|
||||
- uses: actions/checkout@v4
|
||||
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
|
||||
with:
|
||||
path: openpilot-docs
|
||||
ssh-key: ${{ secrets.OPENPILOT_DOCS_KEY }}
|
||||
repository: commaai/openpilot-docs
|
||||
- name: Push
|
||||
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
|
||||
run: |
|
||||
set -x
|
||||
|
||||
source release/identity.sh
|
||||
|
||||
cd openpilot-docs
|
||||
git checkout --orphan tmp
|
||||
git rm -rf .
|
||||
|
||||
# copy over docs
|
||||
cp -r ../docs_site/ docs/
|
||||
|
||||
# GitHub pages config
|
||||
touch docs/.nojekyll
|
||||
echo -n docs.comma.ai > docs/CNAME
|
||||
|
||||
git add -f .
|
||||
git commit -m "build docs"
|
||||
|
||||
# docs live in different repo to not bloat openpilot's full clone size
|
||||
git push -f origin tmp:gh-pages
|
||||
@@ -1,59 +0,0 @@
|
||||
name: jenkins scan
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
|
||||
jobs:
|
||||
# TODO: gc old branches in a separate job in this workflow
|
||||
scan-comments:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.issue.pull_request }}
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
steps:
|
||||
- name: Check for trigger phrase
|
||||
id: check_comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const triggerPhrase = "trigger-jenkins";
|
||||
const comment = context.payload.comment.body;
|
||||
const commenter = context.payload.comment.user.login;
|
||||
|
||||
const { data: permissions } = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
username: commenter
|
||||
});
|
||||
|
||||
const hasWriteAccess = permissions.permission === 'write' || permissions.permission === 'admin';
|
||||
|
||||
return (hasWriteAccess && comment.includes(triggerPhrase));
|
||||
result-encoding: json
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.check_comment.outputs.result == 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: refs/pull/${{ github.event.issue.number }}/head
|
||||
|
||||
- name: Push to tmp-jenkins branch
|
||||
if: steps.check_comment.outputs.result == 'true'
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git checkout -b tmp-jenkins-${{ github.event.issue.number }}
|
||||
GIT_LFS_SKIP_PUSH=1 git push -f origin tmp-jenkins-${{ github.event.issue.number }}
|
||||
|
||||
- name: Delete trigger comment
|
||||
if: steps.check_comment.outputs.result == 'true' && always()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.deleteComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: context.payload.comment.id,
|
||||
});
|
||||
@@ -1,151 +0,0 @@
|
||||
name: "mici raylib ui preview"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
types: [assigned, opened, synchronize, reopened, edited]
|
||||
branches:
|
||||
- 'master'
|
||||
paths:
|
||||
- 'selfdrive/assets/**'
|
||||
- 'selfdrive/ui/**'
|
||||
- 'system/ui/**'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
UI_JOB_NAME: "Create mici raylib UI Report"
|
||||
REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||
SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }}
|
||||
BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-mici-raylib-ui"
|
||||
MASTER_BRANCH_NAME: "openpilot_master_ui_mici_raylib"
|
||||
# All report files are pushed here
|
||||
REPORT_FILES_BRANCH_NAME: "mici-raylib-ui-reports"
|
||||
|
||||
jobs:
|
||||
preview:
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
name: preview
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
actions: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Waiting for ui generation to end
|
||||
uses: lewagon/wait-on-check-action@v1.3.4
|
||||
with:
|
||||
ref: ${{ env.SHA }}
|
||||
check-name: ${{ env.UI_JOB_NAME }}
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
allowed-conclusions: success
|
||||
wait-interval: 20
|
||||
|
||||
- name: Getting workflow run ID
|
||||
id: get_run_id
|
||||
run: |
|
||||
echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?<number>[0-9]+)") | .number')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Getting proposed ui # filename: pr_ui/mici_ui_replay.mp4
|
||||
id: download-artifact
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run_id: ${{ steps.get_run_id.outputs.run_id }}
|
||||
search_artifacts: true
|
||||
name: mici-raylib-report-1-${{ env.REPORT_NAME }}
|
||||
path: ${{ github.workspace }}/pr_ui
|
||||
|
||||
- name: Getting master ui # filename: master_ui_raylib/mici_ui_replay.mp4
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: commaai/ci-artifacts
|
||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||
path: ${{ github.workspace }}/master_ui_raylib
|
||||
ref: ${{ env.MASTER_BRANCH_NAME }}
|
||||
|
||||
- name: Saving new master ui
|
||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
working-directory: ${{ github.workspace }}/master_ui_raylib
|
||||
run: |
|
||||
git checkout --orphan=new_master_ui_mici_raylib
|
||||
git rm -rf *
|
||||
git branch -D ${{ env.MASTER_BRANCH_NAME }}
|
||||
git branch -m ${{ env.MASTER_BRANCH_NAME }}
|
||||
git config user.name "GitHub Actions Bot"
|
||||
git config user.email "<>"
|
||||
mv ${{ github.workspace }}/pr_ui/* .
|
||||
git add .
|
||||
git commit -m "mici raylib video for commit ${{ env.SHA }}"
|
||||
git push origin ${{ env.MASTER_BRANCH_NAME }} --force
|
||||
|
||||
- name: Setup FFmpeg
|
||||
uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae
|
||||
|
||||
- name: Finding diff
|
||||
if: github.event_name == 'pull_request_target'
|
||||
id: find_diff
|
||||
run: |
|
||||
# Find the video file from PR
|
||||
pr_video="${{ github.workspace }}/pr_ui/mici_ui_replay_proposed.mp4"
|
||||
mv "${{ github.workspace }}/pr_ui/mici_ui_replay.mp4" "$pr_video"
|
||||
|
||||
master_video="${{ github.workspace }}/pr_ui/mici_ui_replay_master.mp4"
|
||||
mv "${{ github.workspace }}/master_ui_raylib/mici_ui_replay.mp4" "$master_video"
|
||||
|
||||
# Run report
|
||||
export PYTHONPATH=${{ github.workspace }}
|
||||
baseurl="https://github.com/commaai/ci-artifacts/raw/refs/heads/${{ env.BRANCH_NAME }}"
|
||||
diff_exit_code=0
|
||||
python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py "${{ github.workspace }}/pr_ui/mici_ui_replay_master.mp4" "${{ github.workspace }}/pr_ui/mici_ui_replay_proposed.mp4" "diff.html" --basedir "$baseurl" --no-open || diff_exit_code=$?
|
||||
|
||||
# Copy diff report files
|
||||
cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.html ${{ github.workspace }}/pr_ui/
|
||||
cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.mp4 ${{ github.workspace }}/pr_ui/
|
||||
|
||||
REPORT_URL="https://commaai.github.io/ci-artifacts/diff_pr_${{ github.event.number }}.html"
|
||||
if [ $diff_exit_code -eq 0 ]; then
|
||||
DIFF="✅ Videos are identical! [View Diff Report]($REPORT_URL)"
|
||||
else
|
||||
DIFF="❌ <strong>Videos differ!</strong> [View Diff Report]($REPORT_URL)"
|
||||
fi
|
||||
echo "DIFF=$DIFF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Saving proposed ui
|
||||
if: github.event_name == 'pull_request_target'
|
||||
working-directory: ${{ github.workspace }}/master_ui_raylib
|
||||
run: |
|
||||
# Overwrite PR branch w/ proposed ui, and master ui at this point in time for future reference
|
||||
git config user.name "GitHub Actions Bot"
|
||||
git config user.email "<>"
|
||||
git checkout --orphan=${{ env.BRANCH_NAME }}
|
||||
git rm -rf *
|
||||
mv ${{ github.workspace }}/pr_ui/* .
|
||||
git add .
|
||||
git commit -m "mici raylib video for PR #${{ github.event.number }}"
|
||||
git push origin ${{ env.BRANCH_NAME }} --force
|
||||
|
||||
# Append diff report to report files branch
|
||||
git fetch origin ${{ env.REPORT_FILES_BRANCH_NAME }}
|
||||
git checkout ${{ env.REPORT_FILES_BRANCH_NAME }}
|
||||
cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.html diff_pr_${{ github.event.number }}.html
|
||||
git add diff_pr_${{ github.event.number }}.html
|
||||
git commit -m "mici raylib ui diff report for PR #${{ github.event.number }}" || echo "No changes to commit"
|
||||
git push origin ${{ env.REPORT_FILES_BRANCH_NAME }}
|
||||
|
||||
- name: Comment Video on PR
|
||||
if: github.event_name == 'pull_request_target'
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
with:
|
||||
message: |
|
||||
<!-- _(run_id_video_mici_raylib **${{ github.run_id }}**)_ -->
|
||||
## mici raylib UI Preview
|
||||
${{ steps.find_diff.outputs.DIFF }}
|
||||
comment_tag: run_id_video_mici_raylib
|
||||
pr_number: ${{ github.event.number }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,42 +0,0 @@
|
||||
name: "model review"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
paths:
|
||||
- 'selfdrive/modeld/models/*.onnx'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
path: base
|
||||
- run: git lfs pull
|
||||
- run: cd base && git lfs pull
|
||||
|
||||
- run: pip install onnx
|
||||
|
||||
- name: scripts/reporter.py
|
||||
id: report
|
||||
run: |
|
||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "## Model Review" >> $GITHUB_OUTPUT
|
||||
MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Post model report comment
|
||||
uses: marocchino/sticky-pull-request-comment@baa7203ed60924babbe5dcd0ac8eae3b66ec5e16
|
||||
with:
|
||||
header: model-review
|
||||
message: ${{ steps.report.outputs.content }}
|
||||
@@ -1,39 +0,0 @@
|
||||
name: prebuilt
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 * * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD: selfdrive/test/docker_build.sh prebuilt
|
||||
|
||||
jobs:
|
||||
build_prebuilt:
|
||||
name: build prebuilt
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
env:
|
||||
PUSH_IMAGE: true
|
||||
permissions:
|
||||
checks: read
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Wait for green check mark
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
|
||||
with:
|
||||
ref: master
|
||||
wait-interval: 30
|
||||
running-workflow-name: 'build prebuilt'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
check-regexp: ^((?!.*(build master-ci).*).)*$
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: git lfs pull
|
||||
- name: Build and Push docker image
|
||||
run: |
|
||||
$DOCKER_LOGIN
|
||||
eval "$BUILD"
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Publish TrueNAS Runner Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
image_tag:
|
||||
description: "Container tag to publish to GHCR"
|
||||
type: string
|
||||
default: "latest"
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push runner image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: tools/truenas_github_runner/Dockerfile
|
||||
push: true
|
||||
tags: ghcr.io/${{ github.repository_owner }}/starpilot-truenas-runner:${{ inputs.image_tag }}
|
||||
@@ -1,175 +0,0 @@
|
||||
name: "raylib ui preview"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
types: [assigned, opened, synchronize, reopened, edited]
|
||||
branches:
|
||||
- 'master'
|
||||
paths:
|
||||
- 'selfdrive/assets/**'
|
||||
- 'selfdrive/ui/**'
|
||||
- 'system/ui/**'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
UI_JOB_NAME: "Create raylib UI Report"
|
||||
REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||
SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }}
|
||||
BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-raylib-ui"
|
||||
|
||||
jobs:
|
||||
preview:
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
name: preview
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
actions: read
|
||||
steps:
|
||||
- name: Waiting for ui generation to start
|
||||
run: sleep 30
|
||||
|
||||
- name: Waiting for ui generation to end
|
||||
uses: lewagon/wait-on-check-action@v1.3.4
|
||||
with:
|
||||
ref: ${{ env.SHA }}
|
||||
check-name: ${{ env.UI_JOB_NAME }}
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
allowed-conclusions: success
|
||||
wait-interval: 20
|
||||
|
||||
- name: Getting workflow run ID
|
||||
id: get_run_id
|
||||
run: |
|
||||
echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?<number>[0-9]+)") | .number')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Getting proposed ui
|
||||
id: download-artifact
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run_id: ${{ steps.get_run_id.outputs.run_id }}
|
||||
search_artifacts: true
|
||||
name: raylib-report-1-${{ env.REPORT_NAME }}
|
||||
path: ${{ github.workspace }}/pr_ui
|
||||
|
||||
- name: Getting master ui
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: commaai/ci-artifacts
|
||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||
path: ${{ github.workspace }}/master_ui_raylib
|
||||
ref: openpilot_master_ui_raylib
|
||||
|
||||
- name: Saving new master ui
|
||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
working-directory: ${{ github.workspace }}/master_ui_raylib
|
||||
run: |
|
||||
git checkout --orphan=new_master_ui_raylib
|
||||
git rm -rf *
|
||||
git branch -D openpilot_master_ui_raylib
|
||||
git branch -m openpilot_master_ui_raylib
|
||||
git config user.name "GitHub Actions Bot"
|
||||
git config user.email "<>"
|
||||
mv ${{ github.workspace }}/pr_ui/*.png .
|
||||
git add .
|
||||
git commit -m "raylib screenshots for commit ${{ env.SHA }}"
|
||||
git push origin openpilot_master_ui_raylib --force
|
||||
|
||||
- name: Finding diff
|
||||
if: github.event_name == 'pull_request_target'
|
||||
id: find_diff
|
||||
run: >-
|
||||
sudo apt-get update && sudo apt-get install -y imagemagick
|
||||
|
||||
scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device')
|
||||
A=($scenes)
|
||||
|
||||
DIFF=""
|
||||
TABLE="<details><summary>All Screenshots</summary>"
|
||||
TABLE="${TABLE}<table>"
|
||||
|
||||
for ((i=0; i<${#A[*]}; i=i+1));
|
||||
do
|
||||
# Check if the master file exists
|
||||
if [ ! -f "${{ github.workspace }}/master_ui_raylib/${A[$i]}.png" ]; then
|
||||
# This is a new file in PR UI that doesn't exist in master
|
||||
DIFF="${DIFF}<details open>"
|
||||
DIFF="${DIFF}<summary>${A[$i]} : \$\${\\color{cyan}\\text{NEW}}\$\$</summary>"
|
||||
DIFF="${DIFF}<table>"
|
||||
|
||||
DIFF="${DIFF}<tr>"
|
||||
DIFF="${DIFF} <td> <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
DIFF="${DIFF}</tr>"
|
||||
|
||||
DIFF="${DIFF}</table>"
|
||||
DIFF="${DIFF}</details>"
|
||||
elif ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then
|
||||
convert ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png -transparent black mask.png
|
||||
composite mask.png ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png composite_diff.png
|
||||
convert -delay 100 ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif
|
||||
|
||||
mv ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_master_ref.png
|
||||
|
||||
DIFF="${DIFF}<details open>"
|
||||
DIFF="${DIFF}<summary>${A[$i]} : \$\${\\color{red}\\text{DIFFERENT}}\$\$</summary>"
|
||||
DIFF="${DIFF}<table>"
|
||||
|
||||
DIFF="${DIFF}<tr>"
|
||||
DIFF="${DIFF} <td> master <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_master_ref.png\"> </td>"
|
||||
DIFF="${DIFF} <td> proposed <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
DIFF="${DIFF}</tr>"
|
||||
|
||||
DIFF="${DIFF}<tr>"
|
||||
DIFF="${DIFF} <td> diff <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.png\"> </td>"
|
||||
DIFF="${DIFF} <td> composite diff <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.gif\"> </td>"
|
||||
DIFF="${DIFF}</tr>"
|
||||
|
||||
DIFF="${DIFF}</table>"
|
||||
DIFF="${DIFF}</details>"
|
||||
else
|
||||
rm -f ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png
|
||||
fi
|
||||
|
||||
INDEX=$(($i % 2))
|
||||
if [[ $INDEX -eq 0 ]]; then
|
||||
TABLE="${TABLE}<tr>"
|
||||
fi
|
||||
TABLE="${TABLE} <td> <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then
|
||||
TABLE="${TABLE}</tr>"
|
||||
fi
|
||||
done
|
||||
|
||||
TABLE="${TABLE}</table></details>"
|
||||
|
||||
echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Saving proposed ui
|
||||
if: github.event_name == 'pull_request_target'
|
||||
working-directory: ${{ github.workspace }}/master_ui_raylib
|
||||
run: |
|
||||
git config user.name "GitHub Actions Bot"
|
||||
git config user.email "<>"
|
||||
git checkout --orphan=${{ env.BRANCH_NAME }}
|
||||
git rm -rf *
|
||||
mv ${{ github.workspace }}/pr_ui/* .
|
||||
git add .
|
||||
git commit -m "raylib screenshots for PR #${{ github.event.number }}"
|
||||
git push origin ${{ env.BRANCH_NAME }} --force
|
||||
|
||||
- name: Comment Screenshots on PR
|
||||
if: github.event_name == 'pull_request_target'
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
with:
|
||||
message: |
|
||||
<!-- _(run_id_screenshots_raylib **${{ github.run_id }}**)_ -->
|
||||
## raylib UI Preview
|
||||
${{ steps.find_diff.outputs.DIFF }}
|
||||
comment_tag: run_id_screenshots_raylib
|
||||
pr_number: ${{ github.event.number }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,42 +0,0 @@
|
||||
name: release
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 9 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_masterci:
|
||||
name: build master-ci
|
||||
env:
|
||||
ImageOS: ubuntu24
|
||||
container:
|
||||
image: ghcr.io/commaai/openpilot-base:latest
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
permissions:
|
||||
checks: read
|
||||
contents: write
|
||||
steps:
|
||||
- name: Install wait-on-check-action dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libyaml-dev
|
||||
- name: Wait for green check mark
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
|
||||
with:
|
||||
ref: master
|
||||
wait-interval: 30
|
||||
running-workflow-name: 'build master-ci'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
check-regexp: ^((?!.*(build prebuilt).*).)*$
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
- name: Pull LFS
|
||||
run: |
|
||||
git config --global --add safe.directory '*'
|
||||
git lfs pull
|
||||
- name: Push master-ci
|
||||
run: BRANCH=__nightly release/build_stripped.sh
|
||||
@@ -1,72 +0,0 @@
|
||||
name: repo maintenance
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 14 * * 1" # every Monday at 2am UTC (6am PST)
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BASE_IMAGE: openpilot-base
|
||||
BUILD: selfdrive/test/docker_build.sh base
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||
|
||||
jobs:
|
||||
update_translations:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Update translations
|
||||
run: |
|
||||
${{ env.RUN }} "python3 selfdrive/ui/update_translations.py --vanish"
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
|
||||
with:
|
||||
author: Vehicle Researcher <user@comma.ai>
|
||||
commit-message: "Update translations"
|
||||
title: "[bot] Update translations"
|
||||
body: "Automatic PR from repo-maintenance -> update_translations"
|
||||
branch: "update-translations"
|
||||
base: "master"
|
||||
delete-branch: true
|
||||
labels: bot
|
||||
|
||||
package_updates:
|
||||
name: package_updates
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/commaai/openpilot-base:latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: uv lock
|
||||
run: |
|
||||
python3 -m ensurepip --upgrade
|
||||
pip3 install uv
|
||||
uv lock --upgrade
|
||||
- name: bump submodules
|
||||
run: |
|
||||
git config --global --add safe.directory '*'
|
||||
git submodule update --remote
|
||||
git add .
|
||||
- name: update car docs
|
||||
run: |
|
||||
export PYTHONPATH="$PWD"
|
||||
scons -j$(nproc) --minimal opendbc_repo
|
||||
python selfdrive/car/docs.py
|
||||
git add docs/CARS.md
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
|
||||
with:
|
||||
author: Vehicle Researcher <user@comma.ai>
|
||||
token: ${{ secrets.ACTIONS_CREATE_PR_PAT }}
|
||||
commit-message: Update Python packages
|
||||
title: '[bot] Update Python packages'
|
||||
branch: auto-package-updates
|
||||
base: master
|
||||
delete-branch: true
|
||||
body: 'Automatic PR from repo-maintenance -> package_updates'
|
||||
labels: bot
|
||||
@@ -1,71 +0,0 @@
|
||||
name: Schedule StarPilot Update
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
scheduled_date:
|
||||
description: "Enter the date to update the \"StarPilot\" branch (YYYY-MM-DD)"
|
||||
required: true
|
||||
|
||||
env:
|
||||
GIT_EMAIL: "91348155+FrogAi@users.noreply.github.com"
|
||||
GIT_NAME: "James"
|
||||
TARGET_BRANCH: "StarPilot-Staging"
|
||||
UPDATE_FILE_PATH: ".github/update_date"
|
||||
|
||||
jobs:
|
||||
schedule_update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Target Branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.TARGET_BRANCH }}
|
||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||
fetch-depth: 3
|
||||
|
||||
- name: Configure Git Identity
|
||||
run: |
|
||||
git config --global user.name "$GIT_NAME"
|
||||
git config --global user.email "$GIT_EMAIL"
|
||||
|
||||
- name: Write Schedule Date
|
||||
env:
|
||||
SCHEDULED_DATE: ${{ github.event.inputs.scheduled_date }}
|
||||
run: |
|
||||
echo "$SCHEDULED_DATE" > "$UPDATE_FILE_PATH"
|
||||
git add "$UPDATE_FILE_PATH"
|
||||
|
||||
- name: Get Target Commit Data
|
||||
id: get_target
|
||||
run: |
|
||||
TARGET_COMMIT=$(git rev-parse HEAD~1)
|
||||
AUTHOR_DATE=$(git show -s --format=%aD "$TARGET_COMMIT")
|
||||
COMMITTER_DATE=$(git show -s --format=%cD "$TARGET_COMMIT")
|
||||
|
||||
echo "AUTHOR_DATE=$AUTHOR_DATE" >> "$GITHUB_ENV"
|
||||
echo "COMMITTER_DATE=$COMMITTER_DATE" >> "$GITHUB_ENV"
|
||||
echo "TARGET_COMMIT=$TARGET_COMMIT" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Create Fixup Commit
|
||||
id: fixup_commit
|
||||
run: |
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes detected."
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Changes detected. Creating fixup commit."
|
||||
git commit --fixup="$TARGET_COMMIT"
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Autosquash and Restore Timestamps
|
||||
if: steps.fixup_commit.outputs.has_changes == 'true'
|
||||
run: |
|
||||
GIT_SEQUENCE_EDITOR=: git rebase --autosquash -i HEAD~3
|
||||
git rebase --exec "GIT_COMMITTER_DATE='$COMMITTER_DATE' git commit --amend --no-edit --date='$AUTHOR_DATE'" HEAD~2
|
||||
|
||||
- name: Push Changes
|
||||
if: steps.fixup_commit.outputs.has_changes == 'true'
|
||||
run: |
|
||||
git push origin "$TARGET_BRANCH" --force-with-lease
|
||||
@@ -1,52 +0,0 @@
|
||||
name: stale
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DAYS_BEFORE_PR_CLOSE: 7
|
||||
DAYS_BEFORE_PR_STALE: 24
|
||||
DAYS_BEFORE_PR_STALE_DRAFT: 30
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
exempt-all-milestones: true
|
||||
|
||||
# pull request config
|
||||
stale-pr-message: 'This PR has had no activity for ${{ env.DAYS_BEFORE_PR_STALE }} days. It will be automatically closed in ${{ env.DAYS_BEFORE_PR_CLOSE }} days if there is no activity.'
|
||||
close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.'
|
||||
stale-pr-label: stale
|
||||
delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo
|
||||
exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale
|
||||
days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }}
|
||||
days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }}
|
||||
exempt-draft-pr: false
|
||||
|
||||
# issue config
|
||||
days-before-issue-stale: -1 # ignore issues for now
|
||||
|
||||
# same as above, but give draft PRs more time
|
||||
stale_drafts:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
exempt-all-milestones: true
|
||||
|
||||
# pull request config
|
||||
stale-pr-message: 'This PR has had no activity for ${{ env.DAYS_BEFORE_PR_STALE_DRAFT }} days. It will be automatically closed in ${{ env.DAYS_BEFORE_PR_CLOSE }} days if there is no activity.'
|
||||
close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.'
|
||||
stale-pr-label: stale
|
||||
delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo
|
||||
exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale
|
||||
days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE_DRAFT }}
|
||||
days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }}
|
||||
exempt-draft-pr: true
|
||||
|
||||
# issue config
|
||||
days-before-issue-stale: -1 # ignore issues for now
|
||||
@@ -1,294 +0,0 @@
|
||||
name: tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_number:
|
||||
default: '1'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PYTHONWARNINGS: error
|
||||
BASE_IMAGE: openpilot-base
|
||||
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
|
||||
|
||||
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD: selfdrive/test/docker_build.sh base
|
||||
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||
|
||||
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical
|
||||
|
||||
jobs:
|
||||
build_release:
|
||||
name: build release
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
env:
|
||||
STRIPPED_DIR: /tmp/releasepilot
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Getting LFS files
|
||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: git lfs pull
|
||||
- name: Build devel
|
||||
timeout-minutes: 1
|
||||
run: TARGET_DIR=$STRIPPED_DIR release/build_stripped.sh
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot and run checks
|
||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache
|
||||
run: |
|
||||
cd $STRIPPED_DIR
|
||||
${{ env.RUN }} "python3 system/manager/build.py"
|
||||
- name: Run tests
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
cd $STRIPPED_DIR
|
||||
${{ env.RUN }} "release/check-dirty.sh"
|
||||
- name: Check submodules
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
timeout-minutes: 3
|
||||
run: release/check-submodules.sh
|
||||
|
||||
build:
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup docker push
|
||||
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
|
||||
run: |
|
||||
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
|
||||
$DOCKER_LOGIN
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- uses: ./.github/workflows/compile-openpilot
|
||||
timeout-minutes: 30
|
||||
|
||||
build_mac:
|
||||
name: build macOS
|
||||
if: false # tmp disable due to brew install not working
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
||||
- name: Homebrew cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
path: ~/Library/Caches/Homebrew
|
||||
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
brew-macos-${{ env.CACHE_COMMIT_DATE }}
|
||||
brew-macos
|
||||
- name: Install dependencies
|
||||
run: ./tools/mac_setup.sh
|
||||
env:
|
||||
PYTHONWARNINGS: default # package install has DeprecationWarnings
|
||||
HOMEBREW_DISPLAY_INSTALL_TIMES: 1
|
||||
- run: git lfs pull
|
||||
- name: Getting scons cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
path: /tmp/scons_cache
|
||||
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}
|
||||
scons-${{ runner.arch }}-macos
|
||||
- name: Building openpilot
|
||||
run: . .venv/bin/activate && scons -j$(nproc)
|
||||
|
||||
static_analysis:
|
||||
name: static analysis
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
env:
|
||||
PYTHONWARNINGS: default
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Static analysis
|
||||
timeout-minutes: 1
|
||||
run: ${{ env.RUN }} "scripts/lint/lint.sh"
|
||||
|
||||
unit_tests:
|
||||
name: unit tests
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
id: setup-step
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run unit tests
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }}
|
||||
run: |
|
||||
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
|
||||
# Pre-compile Python bytecode so each pytest worker doesn't need to
|
||||
$PYTEST --collect-only -m 'not slow' -qq && \
|
||||
MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
|
||||
process_replay:
|
||||
name: process replay
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
id: setup-step
|
||||
- name: Cache test routes
|
||||
id: dependency-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .ci_cache/comma_download_cache
|
||||
key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_processes.py') }}
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run replay
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.dependency-cache.outputs.cache-hit == 'true') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }}
|
||||
run: |
|
||||
${{ env.RUN }} "selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
- name: Print diff
|
||||
id: print-diff
|
||||
if: always()
|
||||
run: cat selfdrive/test/process_replay/diff.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: process_replay_diff.txt
|
||||
path: selfdrive/test/process_replay/diff.txt
|
||||
- name: Upload reference logs
|
||||
if: false # TODO: move this to github instead of azure
|
||||
run: |
|
||||
${{ env.RUN }} "unset PYTHONWARNINGS && AZURE_TOKEN='$AZURE_TOKEN' python3 selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
|
||||
- name: Run regen
|
||||
if: false
|
||||
timeout-minutes: 4
|
||||
run: |
|
||||
${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
|
||||
simulator_driving:
|
||||
name: simulator driving
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
if: false # FIXME: Started to timeout recently
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
id: setup-step
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Driving test
|
||||
timeout-minutes: ${{ (steps.setup-step.outputs.duration < 18) && 1 || 2 }}
|
||||
run: |
|
||||
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
|
||||
source selfdrive/test/setup_vsound.sh && \
|
||||
CI=1 pytest -s tools/sim/tests/test_metadrive_bridge.py"
|
||||
|
||||
create_raylib_ui_report:
|
||||
name: Create raylib UI Report
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Create raylib UI Report
|
||||
run: >
|
||||
${{ env.RUN }} "PYTHONWARNINGS=ignore &&
|
||||
source selfdrive/test/setup_xvfb.sh &&
|
||||
python3 selfdrive/ui/tests/test_ui/raylib_screenshots.py"
|
||||
- name: Upload Raylib UI Report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||
path: selfdrive/ui/tests/test_ui/raylib_report/screenshots
|
||||
|
||||
create_mici_raylib_ui_report:
|
||||
name: Create mici raylib UI Report
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Create mici raylib UI Report
|
||||
run: >
|
||||
${{ env.RUN }} "PYTHONWARNINGS=ignore &&
|
||||
source selfdrive/test/setup_xvfb.sh &&
|
||||
WINDOWED=1 python3 selfdrive/ui/tests/diff/replay.py"
|
||||
- name: Upload Raylib UI Report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mici-raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||
path: selfdrive/ui/tests/diff/report
|
||||
@@ -1,107 +0,0 @@
|
||||
name: Update StarPilot Branch
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 18 * * 6"
|
||||
|
||||
env:
|
||||
BRANCH_STARPILOT: StarPilot
|
||||
BRANCH_PREVIOUS: StarPilot-Previous
|
||||
BRANCH_STAGING: StarPilot-Staging
|
||||
GIT_EMAIL: "91348155+FrogAi@users.noreply.github.com"
|
||||
GIT_NAME: "James"
|
||||
GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||
TZ: America/Phoenix
|
||||
UPDATE_FILE: .github/update_date
|
||||
|
||||
jobs:
|
||||
check_update:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
update_due: ${{ steps.check_update.outputs.update_due }}
|
||||
scheduled_date: ${{ steps.check_update.outputs.scheduled_date }}
|
||||
steps:
|
||||
- name: Check Update Status
|
||||
id: check_update
|
||||
env:
|
||||
REPO_NAME: ${{ github.repository }}
|
||||
run: |
|
||||
URL="https://raw.githubusercontent.com/$REPO_NAME/$BRANCH_STAGING/$UPDATE_FILE"
|
||||
STATUS=$(curl -o /dev/null -s -w "%{http_code}\n" "$URL")
|
||||
|
||||
if [ "$STATUS" != "200" ]; then
|
||||
echo "update_due=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SCHEDULED_DATE=$(curl -s "$URL")
|
||||
CURRENT_DATE=$(TZ="$TZ" date +%F)
|
||||
|
||||
if [ "$SCHEDULED_DATE" == "$CURRENT_DATE" ]; then
|
||||
echo "update_due=true" >> "$GITHUB_OUTPUT"
|
||||
echo "scheduled_date=$SCHEDULED_DATE" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "update_due=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
update_branch:
|
||||
needs: check_update
|
||||
if: ${{ needs.check_update.outputs.update_due == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Staging
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.BRANCH_STAGING }}
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||
|
||||
- name: Configure Git Identity
|
||||
run: |
|
||||
git config --global user.name "$GIT_NAME"
|
||||
git config --global user.email "$GIT_EMAIL"
|
||||
|
||||
- name: Update README and Cleanup
|
||||
env:
|
||||
SCHEDULED_DATE: ${{ needs.check_update.outputs.scheduled_date }}
|
||||
run: |
|
||||
DAY=$(TZ="$TZ" date +'%d' | sed 's/^0//')
|
||||
case "$DAY" in
|
||||
1|21|31) SUFFIX="st" ;;
|
||||
2|22) SUFFIX="nd" ;;
|
||||
3|23) SUFFIX="rd" ;;
|
||||
*) SUFFIX="th" ;;
|
||||
esac
|
||||
|
||||
MONTH=$(TZ="$TZ" date +'%B')
|
||||
YEAR=$(TZ="$TZ" date +'%Y')
|
||||
DATE_FMT="${MONTH} ${DAY}${SUFFIX}, ${YEAR}"
|
||||
DATE_ESCAPED=$(printf '%s' "$DATE_FMT" | sed -E 's/ /%20/g; s/,/%2C/g')
|
||||
|
||||
sed -i -E "s|(Last%20Updated-)[^-)]*|\1${DATE_ESCAPED}|g" README.md
|
||||
|
||||
git rm -f "$UPDATE_FILE"
|
||||
git add README.md
|
||||
git commit -m "Updated README date to ${DATE_FMT}"
|
||||
|
||||
git reset --soft HEAD~2
|
||||
ORIGINAL_MSG=$(git log -1 --pretty=%B HEAD)
|
||||
|
||||
COMMIT_PHX=$(TZ="$TZ" date -d "$SCHEDULED_DATE 12:00" +"%Y-%m-%dT%H:%M:%S %z")
|
||||
GIT_COMMITTER_DATE="$COMMIT_PHX" GIT_AUTHOR_DATE="$COMMIT_PHX" git commit -m "$ORIGINAL_MSG"
|
||||
|
||||
- name: Wait Until Noon ${{ env.TZ }}
|
||||
run: |
|
||||
NOW=$(TZ="$TZ" date +%s)
|
||||
TARGET=$(TZ="$TZ" date -d "12:00" +%s)
|
||||
|
||||
if [ "$NOW" -lt "$TARGET" ]; then
|
||||
sleep $((TARGET - NOW))
|
||||
fi
|
||||
|
||||
- name: Push and Sync Branches
|
||||
run: |
|
||||
git push origin "$BRANCH_STAGING" --force
|
||||
git fetch origin "$BRANCH_STARPILOT:$BRANCH_STARPILOT"
|
||||
git push origin "$BRANCH_STARPILOT:$BRANCH_PREVIOUS" --force
|
||||
git push origin "$BRANCH_STAGING:$BRANCH_STARPILOT" --force
|
||||
@@ -1,95 +0,0 @@
|
||||
name: Update Tinygrad
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
runner:
|
||||
description: "Select runner"
|
||||
type: choice
|
||||
options:
|
||||
- c3
|
||||
- c3x
|
||||
default: "c3"
|
||||
required: true
|
||||
|
||||
env:
|
||||
GIT_EMAIL: "91348155+FrogAi@users.noreply.github.com"
|
||||
GIT_NAME: "James"
|
||||
GITLAB_REPO_DIR: "StarPilot-Resources"
|
||||
GITLAB_URL: "gitlab.com/FrogAi/StarPilot-Resources.git"
|
||||
OPENPILOT_DIR: "/data/openpilot"
|
||||
|
||||
jobs:
|
||||
update_tinygrad:
|
||||
runs-on: [self-hosted, "${{ inputs.runner }}"]
|
||||
steps:
|
||||
- name: Get Version
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep -oP '^VERSION\s*=\s*"\K[^"]+' "$OPENPILOT_DIR/starpilot/assets/model_manager.py")
|
||||
echo "VERSION=$VERSION"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Workspace and Clone GitLab
|
||||
id: setup
|
||||
env:
|
||||
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
|
||||
run: |
|
||||
WORK_DIR="$RUNNER_TEMP/starpilot_tinygrad"
|
||||
rm -rf "$WORK_DIR" && mkdir -p "$WORK_DIR"
|
||||
echo "work_dir=$WORK_DIR" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cd "$WORK_DIR"
|
||||
git clone --depth 1 --branch Tinygrad "https://oauth2:${GITLAB_TOKEN}@$GITLAB_URL"
|
||||
|
||||
- name: Create Tinygrad Archive
|
||||
working-directory: ${{ env.OPENPILOT_DIR }}
|
||||
env:
|
||||
WORK_DIR: ${{ steps.setup.outputs.work_dir }}
|
||||
VERSION: ${{ steps.get_version.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
ARCHIVE_DEST="$WORK_DIR/$GITLAB_REPO_DIR"
|
||||
ARCHIVE_NAME="Tinygrad_$VERSION.tar.gz"
|
||||
|
||||
DUMMY_DIR=$(mktemp -d)
|
||||
touch "$DUMMY_DIR/SConscript"
|
||||
|
||||
tar -czf "$ARCHIVE_DEST/$ARCHIVE_NAME" \
|
||||
--exclude="*.a" \
|
||||
--exclude="*.o" \
|
||||
--exclude="*.onnx" \
|
||||
--exclude="*__pycache__*" \
|
||||
--exclude="*tests*" \
|
||||
--exclude="selfdrive/modeld/SConscript" \
|
||||
selfdrive/modeld tinygrad_repo \
|
||||
-C "$DUMMY_DIR" \
|
||||
--transform 's|^SConscript$|selfdrive/modeld/SConscript|' \
|
||||
SConscript
|
||||
|
||||
rm -rf "$DUMMY_DIR"
|
||||
|
||||
- name: Push Updated Tinygrad
|
||||
working-directory: ${{ steps.setup.outputs.work_dir }}/${{ env.GITLAB_REPO_DIR }}
|
||||
env:
|
||||
VERSION: ${{ steps.get_version.outputs.version }}
|
||||
run: |
|
||||
git config user.name "$GIT_NAME"
|
||||
git config user.email "$GIT_EMAIL"
|
||||
|
||||
git add Tinygrad_*.tar.gz
|
||||
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes to commit."
|
||||
else
|
||||
git commit -m "Updated Tinygrad: $VERSION"
|
||||
git push origin Tinygrad
|
||||
fi
|
||||
|
||||
- name: Cleanup Temporary Files
|
||||
if: always()
|
||||
env:
|
||||
WORK_DIR: ${{ steps.setup.outputs.work_dir }}
|
||||
run: |
|
||||
rm -rf "$WORK_DIR"
|
||||
@@ -68,6 +68,7 @@ cppcheck_report.txt
|
||||
comma*.sh
|
||||
|
||||
selfdrive/modeld/models/*.pkl
|
||||
!selfdrive/modeld/models/driving_tinygrad.pkl
|
||||
!selfdrive/modeld/models/driving_vision_tinygrad.pkl
|
||||
!selfdrive/modeld/models/driving_policy_tinygrad.pkl
|
||||
!selfdrive/modeld/models/driving_vision_metadata.pkl
|
||||
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,14 +1,13 @@
|
||||
# StarPilot
|
||||
|
||||
[](https://deepwiki.com/firestar5683/StarPilot)
|
||||
[](https://firestar.link/discord)
|
||||
[](https://firestar.link/discord)
|
||||
[](https://github.com/firestar5683/StarPilot)
|
||||
[](https://wiki.firestar.link)
|
||||
|
||||
**StarPilot** is a custom fork of [comma.ai's openpilot](https://comma.ai/openpilot),
|
||||
an open source driver assistance system.
|
||||
|
||||
whoisdomi
|
||||
|
||||
Openpilot provides
|
||||
* Automated Lane Centering
|
||||
@@ -16,11 +15,11 @@ Openpilot provides
|
||||
* Lane Change Assist
|
||||
* Driver Monitoring *without wheel nags*
|
||||
|
||||
StarPilot adds support for many GM vehicles along with improved tuning,
|
||||
especially for radar-less (camera only) vehicles.
|
||||
StarPilot was formerly a GM targeted fork,
|
||||
but [has expanded to offer Quality-Of-Life improvements for all](#features)!
|
||||
|
||||
StarPilot is built off of [StarPilot](https://github.com/FrogAi/StarPilot)
|
||||
and supports the major features StarPilot offers.
|
||||
StarPilot is built off of [FrogPilot](https://github.com/FrogAi/FrogPilot)
|
||||
and supports the major features FrogPilot offers.
|
||||
|
||||
StarPilot has a vibrant, welcoming community [discord](https://firestar.link/discord).
|
||||
Stop by to chat or ask questions!
|
||||
@@ -33,9 +32,9 @@ installation guides, and software configuration.
|
||||
## Features
|
||||
|
||||
* Full support for Comma C3, C3X, and C4
|
||||
* C4 is currently in release testing. Join our fleet of C4 testers!
|
||||
* Model switcher with all of comma's tinygrad driving models
|
||||
* Special longitudinal planner tuning for VoACC (visual only, radar-less) vehicles
|
||||
* Custom-tuned torque controllers for an expanding list of cars.
|
||||
* Galaxy: StarPilot's portal to configure your comma device using your phone from anywhere.
|
||||
Download models, change settings, update software, visualize live model outputs for tuning.
|
||||
* Always On Lateral (full time steering assist)*
|
||||
@@ -50,8 +49,9 @@ Download models, change settings, update software, visualize live model outputs
|
||||
* ZSS support*
|
||||
* High quality dashcam recordings*
|
||||
* Enhanced tuning for CEM (dynamic experimental mode switching)
|
||||
* And more!
|
||||
|
||||
\* [Inherited from StarPilot](https://github.com/FrogAi/StarPilot#openpilot-vs-starpilot)
|
||||
\* [Inherited from FrogPilot](https://github.com/FrogAi/FrogPilot#openpilot-vs-frogpilot)
|
||||
|
||||
## GM-only Features
|
||||
|
||||
@@ -75,3 +75,6 @@ Download models, change settings, update software, visualize live model outputs
|
||||
Uses your comma's sysroot/toolchain
|
||||
* Toggle: "Use Precompiled Binaries" to allow switching between fast boot / editable builds
|
||||
* Custom long maneuver tests, specifically designed for regen-only vehicles
|
||||
|
||||
## Third-Party Notices
|
||||
* Portions of this software include modified versions of the Material Design Icons provided by Google under the Apache License 2.0. A copy of the license is included in the `LICENSE-MDI` file.
|
||||
|
||||
+17
-9
@@ -153,7 +153,7 @@ lenv = {
|
||||
}
|
||||
|
||||
# Allow callers to override cache/temp dirs used by subprocesses (e.g. tinygrad model compilation).
|
||||
for key in ("HOME", "TMPDIR", "XDG_CACHE_HOME", "CACHEDB"):
|
||||
for key in ("HOME", "TMPDIR", "XDG_CACHE_HOME", "CACHEDB", "PARAMS_ROOT"):
|
||||
if key in os.environ:
|
||||
lenv[key] = os.environ[key]
|
||||
|
||||
@@ -404,12 +404,22 @@ if arch == "larch64" and os.environ.get("SP_TICI_SYSROOT"):
|
||||
qt_arm_moc = os.path.join(qt_tool_bin, "moc")
|
||||
qt_arm_uic = os.path.join(qt_tool_bin, "uic")
|
||||
qt_arm_rcc = os.path.join(qt_tool_bin, "rcc")
|
||||
qt_host_bin = os.environ.get("SP_QT_HOST_BIN", "/usr/lib/qt5/bin")
|
||||
qt_host_moc = os.environ.get("SP_QT_HOST_MOC", os.path.join(qt_host_bin, "moc"))
|
||||
qt_host_uic = os.environ.get("SP_QT_HOST_UIC", os.path.join(qt_host_bin, "uic"))
|
||||
qt_host_rcc = os.environ.get("SP_QT_HOST_RCC", "rcc")
|
||||
if platform.machine() in ("aarch64", "arm64"):
|
||||
if os.path.isfile(qt_arm_moc):
|
||||
if "SP_QT_HOST_MOC" in os.environ:
|
||||
qt_env['QT3_MOC'] = qt_host_moc
|
||||
elif os.path.isfile(qt_arm_moc):
|
||||
qt_env['QT3_MOC'] = qt_arm_moc
|
||||
if os.path.isfile(qt_arm_uic):
|
||||
if "SP_QT_HOST_UIC" in os.environ:
|
||||
qt_env['QT3_UIC'] = qt_host_uic
|
||||
elif os.path.isfile(qt_arm_uic):
|
||||
qt_env['QT3_UIC'] = qt_arm_uic
|
||||
if os.path.isfile(qt_arm_rcc):
|
||||
if "SP_QT_HOST_RCC" in os.environ:
|
||||
qt_env['SP_QT_RCC'] = qt_host_rcc
|
||||
elif os.path.isfile(qt_arm_rcc):
|
||||
qt_env['SP_QT_RCC'] = qt_arm_rcc
|
||||
else:
|
||||
qt_qemu = shutil.which("qemu-aarch64-static") or shutil.which("qemu-aarch64")
|
||||
@@ -417,19 +427,17 @@ if arch == "larch64" and os.environ.get("SP_TICI_SYSROOT"):
|
||||
if qt_qemu and os.path.isfile(qt_arm_moc):
|
||||
qt_env['QT3_MOC'] = f"{qt_qemu} -L {qt_tool_root} {qt_arm_moc}"
|
||||
else:
|
||||
qt_host_bin = os.environ.get("SP_QT_HOST_BIN", "/usr/lib/qt5/bin")
|
||||
qt_env['QT3_MOC'] = os.environ.get("SP_QT_HOST_MOC", os.path.join(qt_host_bin, "moc"))
|
||||
qt_env['QT3_MOC'] = qt_host_moc
|
||||
|
||||
if qt_qemu and os.path.isfile(qt_arm_uic):
|
||||
qt_env['QT3_UIC'] = f"{qt_qemu} -L {qt_tool_root} {qt_arm_uic}"
|
||||
else:
|
||||
qt_host_bin = os.environ.get("SP_QT_HOST_BIN", "/usr/lib/qt5/bin")
|
||||
qt_env['QT3_UIC'] = os.environ.get("SP_QT_HOST_UIC", os.path.join(qt_host_bin, "uic"))
|
||||
qt_env['QT3_UIC'] = qt_host_uic
|
||||
|
||||
if qt_qemu and os.path.isfile(qt_arm_rcc):
|
||||
qt_env['SP_QT_RCC'] = f"{qt_qemu} -L {qt_tool_root} {qt_arm_rcc}"
|
||||
else:
|
||||
qt_env['SP_QT_RCC'] = os.environ.get("SP_QT_HOST_RCC", "rcc")
|
||||
qt_env['SP_QT_RCC'] = qt_host_rcc
|
||||
|
||||
qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"]
|
||||
qt_flags = [
|
||||
|
||||
+29
-2
@@ -59,6 +59,8 @@ struct StarPilotCarParams @0xaedffd8f31e7b55d {
|
||||
isHDA2 @4 :Bool;
|
||||
openpilotLongitudinalControlDisabled @5 :Bool;
|
||||
safetyConfigs @6 :List(SafetyConfig);
|
||||
pcmCruiseSpeed @7 :Bool = true;
|
||||
redneckCruiseAvailable @8 :Bool;
|
||||
|
||||
struct SafetyConfig {
|
||||
safetyParam @0 :UInt16;
|
||||
@@ -85,6 +87,17 @@ struct StarPilotCarState @0xf35cc4560bbf6ec2 {
|
||||
modePressed @16 :Bool;
|
||||
customPressed @17 :Bool;
|
||||
alwaysOnLateralAllowed @18 :Bool;
|
||||
dashboardStopSign @19 :UInt8; # 0 = no signal / platform doesn't publish
|
||||
cancelPressed @20 :Bool;
|
||||
cancelLongPressed @21 :Bool;
|
||||
cancelVeryLongPressed @22 :Bool;
|
||||
pedalMaxRegen @23 :Bool; # pedal at max regen, driver should use brake for more decel
|
||||
pedalLongActive @24 :Bool; # Pre-AP pedal longitudinal mode is active (enableLongControl)
|
||||
teslaCCEngaged @25 :Bool; # rising edge of stock Tesla CC engaging (no-pedal mode)
|
||||
teslaCCDisengaged @26 :Bool; # falling edge of stock Tesla CC
|
||||
teslaCCNotArmed @27 :Bool; # lateral engaged but DI_cruiseState != STANDBY/ENABLED
|
||||
accelHardCruise @28 :Bool; # current/releasing accel cruise button came from GM hard-press signal
|
||||
decelHardCruise @29 :Bool; # current/releasing decel cruise button came from GM hard-press signal
|
||||
}
|
||||
|
||||
struct StarPilotDeviceState @0xda96579883444c35 {
|
||||
@@ -102,7 +115,11 @@ struct StarPilotModelDataV2 @0x80ae746ee2596b11 {
|
||||
}
|
||||
}
|
||||
|
||||
struct StarPilotOnroadEvent @0xa5cd762cd951a455 {
|
||||
struct StarPilotOnroadEvents @0xa5cd762cd951a455 {
|
||||
events @0 :List(StarPilotOnroadEvent);
|
||||
}
|
||||
|
||||
struct StarPilotOnroadEvent @0xe344718567f9ce71 {
|
||||
name @0 :EventName;
|
||||
|
||||
enable @1 :Bool;
|
||||
@@ -152,6 +169,14 @@ struct StarPilotOnroadEvent @0xa5cd762cd951a455 {
|
||||
switchbackModeInactive @30;
|
||||
lkasEnable @31;
|
||||
lkasDisable @32;
|
||||
lateralManeuver @33;
|
||||
pedalCruiseEnabled @34;
|
||||
pedalCruiseDisabled @35;
|
||||
pedalMaxRegen @36;
|
||||
teslaCCEngaged @37;
|
||||
teslaCCDisengaged @38;
|
||||
teslaCCNotArmed @39;
|
||||
pedalNotCalibrated @40;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +218,7 @@ struct StarPilotPlan @0xf98d843bfd7004a3 {
|
||||
weatherId @34 :Int16;
|
||||
disableThrottle @35 :Bool;
|
||||
trackingLead @36 :Bool;
|
||||
stopSignConfirmed @37 :Bool;
|
||||
}
|
||||
|
||||
struct StarPilotRadarState @0xb86e6369214c01c8 {
|
||||
@@ -252,7 +278,8 @@ struct CustomReserved9 @0xa1680744031fdb2d {
|
||||
wallTimeNanos @5 :UInt64;
|
||||
}
|
||||
|
||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
||||
struct StarPilotLateralManeuverPlanDEPRECATED @0xcb9fd56c7057593a {
|
||||
desiredCurvature @0 :Float32; # 1/m
|
||||
}
|
||||
|
||||
struct CustomReserved11 @0xc2243c65e0340384 {
|
||||
|
||||
Binary file not shown.
+123
-32
@@ -68,12 +68,12 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
||||
longitudinalManeuver @30;
|
||||
steerTempUnavailableSilent @31;
|
||||
resumeRequired @32;
|
||||
preDriverDistracted @33;
|
||||
promptDriverDistracted @34;
|
||||
driverDistracted @35;
|
||||
preDriverUnresponsive @36;
|
||||
promptDriverUnresponsive @37;
|
||||
driverUnresponsive @38;
|
||||
driverDistracted1 @33;
|
||||
driverDistracted2 @34;
|
||||
driverDistracted3 @35;
|
||||
driverUnresponsive1 @36;
|
||||
driverUnresponsive2 @37;
|
||||
driverUnresponsive3 @38;
|
||||
belowSteerSpeed @39;
|
||||
lowBattery @40;
|
||||
accFaulted @41;
|
||||
@@ -130,7 +130,6 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
||||
userBookmark @95;
|
||||
excessiveActuation @96;
|
||||
audioFeedback @97;
|
||||
lateralManeuver @98;
|
||||
|
||||
soundsUnavailableDEPRECATED @47;
|
||||
}
|
||||
@@ -826,13 +825,30 @@ struct SelfdriveState {
|
||||
alertStatus @5 :AlertStatus;
|
||||
alertSize @6 :AlertSize;
|
||||
alertType @7 :Text;
|
||||
alertSound @8 :Car.CarControl.HUDControl.AudibleAlert;
|
||||
alertSound @13 :AudibleAlert;
|
||||
alertHudVisual @12 :Car.CarControl.HUDControl.VisualAlert;
|
||||
|
||||
# configurable driving settings
|
||||
experimentalMode @10 :Bool;
|
||||
personality @11 :LongitudinalPersonality;
|
||||
|
||||
enum AudibleAlert {
|
||||
none @0;
|
||||
|
||||
engage @1;
|
||||
disengage @2;
|
||||
refuse @3;
|
||||
|
||||
warningSoft @4;
|
||||
warningImmediate @5;
|
||||
|
||||
prompt @6;
|
||||
promptRepeat @7;
|
||||
promptDistracted @8;
|
||||
|
||||
preAlert @9;
|
||||
}
|
||||
|
||||
enum OpenpilotState @0xdbe58b96d2d1ac61 {
|
||||
disabled @0;
|
||||
preEnabled @1;
|
||||
@@ -853,6 +869,10 @@ struct SelfdriveState {
|
||||
mid @2;
|
||||
full @3;
|
||||
}
|
||||
|
||||
deprecated :group {
|
||||
alertSound @8 :Car.CarControl.HUDControl.AudibleAlert;
|
||||
}
|
||||
}
|
||||
|
||||
struct ControlsState @0x97ff69c53601abf1 {
|
||||
@@ -1088,7 +1108,7 @@ struct ModelDataV2 {
|
||||
confidence @23: ConfidenceClass;
|
||||
|
||||
# Model perceived motion
|
||||
temporalPose @21 :Pose;
|
||||
temporalPoseDEPRECATED @21 :Pose;
|
||||
|
||||
# e2e lateral planner
|
||||
action @26: Action;
|
||||
@@ -1262,12 +1282,6 @@ struct LongitudinalPlan @0xe00b5b3eba12876c {
|
||||
|
||||
solverExecutionTime @35 :Float32;
|
||||
|
||||
# lead trajectories the MPC solved against (13 points at T_IDXS)
|
||||
leadTrajectoryX0 @40 :List(Float32);
|
||||
leadTrajectoryV0 @41 :List(Float32);
|
||||
leadTrajectoryX1 @42 :List(Float32);
|
||||
leadTrajectoryV1 @43 :List(Float32);
|
||||
|
||||
enum LongitudinalPlanSource {
|
||||
cruise @0;
|
||||
lead0 @1;
|
||||
@@ -2172,14 +2186,12 @@ struct DriverStateV2 {
|
||||
facePosition @2 :List(Float32);
|
||||
facePositionStd @3 :List(Float32);
|
||||
faceProb @4 :Float32;
|
||||
eyesVisibleProb @14 :Float32;
|
||||
eyesClosedProb @15 :Float32;
|
||||
leftEyeProb @5 :Float32;
|
||||
rightEyeProb @6 :Float32;
|
||||
leftBlinkProb @7 :Float32;
|
||||
rightBlinkProb @8 :Float32;
|
||||
sunglassesProb @9 :Float32;
|
||||
phoneProb @13 :Float32;
|
||||
leftEyeProbDEPRECATED @5 :Float32;
|
||||
rightEyeProbDEPRECATED @6 :Float32;
|
||||
leftBlinkProbDEPRECATED @7 :Float32;
|
||||
rightBlinkProbDEPRECATED @8 :Float32;
|
||||
sunglassesProbDEPRECATED @9 :Float32;
|
||||
notReadyProbDEPRECATED @12 :List(Float32);
|
||||
occludedProbDEPRECATED @10 :Float32;
|
||||
readyProbDEPRECATED @11 :List(Float32);
|
||||
@@ -2221,7 +2233,7 @@ struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 {
|
||||
stdDEPRECATED @2 :Float32;
|
||||
}
|
||||
|
||||
struct DriverMonitoringState @0xb83cda094a1da284 {
|
||||
struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 {
|
||||
events @18 :List(OnroadEvent);
|
||||
faceDetected @1 :Bool;
|
||||
isDistracted @2 :Bool;
|
||||
@@ -2239,12 +2251,90 @@ struct DriverMonitoringState @0xb83cda094a1da284 {
|
||||
isActiveMode @16 :Bool;
|
||||
isRHD @4 :Bool;
|
||||
uncertainCount @19 :UInt32;
|
||||
phoneProbOffset @20 :Float32;
|
||||
phoneProbValidCount @21 :UInt32;
|
||||
|
||||
isPreviewDEPRECATED @15 :Bool;
|
||||
rhdCheckedDEPRECATED @5 :Bool;
|
||||
eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED);
|
||||
deprecated :group {
|
||||
phoneProbOffset @20 :Float32;
|
||||
phoneProbValidCount @21 :UInt32;
|
||||
isPreview @15 :Bool;
|
||||
rhdChecked @5 :Bool;
|
||||
events @0 :List(Car.OnroadEventDEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
struct DriverMonitoringState {
|
||||
lockout @0 :Bool;
|
||||
lockoutRecoveryPercent @11 :Int8;
|
||||
alert3Count @12 :Int8;
|
||||
noResponseCount @13 :Int8;
|
||||
noResponseForceDecel @14 :Bool;
|
||||
|
||||
alwaysOn @3 :Bool;
|
||||
alwaysOnLockout @4 :Bool;
|
||||
|
||||
alertLevel @5 :AlertLevel;
|
||||
activePolicy @6 :MonitoringPolicy;
|
||||
isRHD @7 :Bool;
|
||||
rhdCalibration @8 :CalibrationState;
|
||||
|
||||
visionPolicyState @9 :VisionPolicyState;
|
||||
wheeltouchPolicyState @10 :WheeltouchPolicyState;
|
||||
|
||||
enum AlertLevel {
|
||||
# ordinal must match the name to prevent bugs
|
||||
# comparing against the raw ordinal value
|
||||
none @0;
|
||||
one @1;
|
||||
two @2;
|
||||
three @3;
|
||||
}
|
||||
|
||||
enum MonitoringPolicy {
|
||||
wheeltouch @0;
|
||||
vision @1;
|
||||
}
|
||||
|
||||
struct VisionPolicyState {
|
||||
awarenessPercent @0 :Int8;
|
||||
awarenessStep @1 :Float32;
|
||||
isDistracted @2 :Bool;
|
||||
distractedTypes @3 :DistractedTypes;
|
||||
|
||||
faceDetected @4 :Bool;
|
||||
pose @5 :Pose;
|
||||
wheeltouchFallbackPercent @6 :Int8;
|
||||
uncertainOffroadAlertPercent @7 :Int8;
|
||||
|
||||
struct DistractedTypes {
|
||||
pose @0: Bool;
|
||||
eye @1: Bool;
|
||||
phone @2: Bool;
|
||||
}
|
||||
|
||||
struct Pose {
|
||||
pitch @0 :Float32;
|
||||
yaw @1 :Float32;
|
||||
pitchCalib @2 :CalibrationState;
|
||||
yawCalib @3 :CalibrationState;
|
||||
calibrated @4 :Bool;
|
||||
uncertainty @5 :Float32;
|
||||
}
|
||||
}
|
||||
|
||||
struct WheeltouchPolicyState {
|
||||
awarenessPercent @0 :Int8;
|
||||
awarenessStep @1 :Float32;
|
||||
driverInteracting @2 :Bool;
|
||||
}
|
||||
|
||||
struct CalibrationState {
|
||||
calibratedPercent @0 :Int8;
|
||||
offset @1 :Float32;
|
||||
}
|
||||
|
||||
deprecated :group {
|
||||
alertCountLockoutPercent @1 :Int8;
|
||||
alertTimeLockoutPercent @2 :Int8;
|
||||
}
|
||||
}
|
||||
|
||||
struct Boot {
|
||||
@@ -2565,7 +2655,7 @@ struct Event {
|
||||
thumbnail @66: Thumbnail;
|
||||
onroadEvents @134: List(OnroadEvent);
|
||||
carParams @69: Car.CarParams;
|
||||
driverMonitoringState @71: DriverMonitoringState;
|
||||
driverMonitoringState @151 :DriverMonitoringState;
|
||||
livePose @129 :LivePose;
|
||||
modelV2 @75 :ModelDataV2;
|
||||
drivingModelData @128 :DrivingModelData;
|
||||
@@ -2614,8 +2704,8 @@ struct Event {
|
||||
userBookmark @93 :UserBookmark;
|
||||
bookmarkButton @148 :UserBookmark;
|
||||
audioFeedback @149 :AudioFeedback;
|
||||
lateralManeuverPlan @150 :LateralManeuverPlan;
|
||||
|
||||
lateralManeuverPlan @150 :LateralManeuverPlan;
|
||||
# *********** debug ***********
|
||||
testJoystick @52 :Joystick;
|
||||
roadEncodeData @86 :EncodeData;
|
||||
@@ -2644,12 +2734,12 @@ struct Event {
|
||||
starpilotCarState @109 :Custom.StarPilotCarState;
|
||||
starpilotDeviceState @110 :Custom.StarPilotDeviceState;
|
||||
starpilotModelV2 @111 :Custom.StarPilotModelDataV2;
|
||||
starpilotOnroadEvents @112 :List(Custom.StarPilotOnroadEvent);
|
||||
starpilotOnroadEvents @112 :Custom.StarPilotOnroadEvents;
|
||||
starpilotPlan @113 :Custom.StarPilotPlan;
|
||||
starpilotRadarState @114 :Custom.StarPilotRadarState;
|
||||
starpilotSelfdriveState @115 :Custom.StarPilotSelfdriveState;
|
||||
customReserved9 @116 :Custom.CustomReserved9;
|
||||
customReserved10 @136 :Custom.CustomReserved10;
|
||||
starpilotLateralManeuverPlanDEPRECATED @136 :Custom.StarPilotLateralManeuverPlanDEPRECATED;
|
||||
customReserved11 @137 :Custom.CustomReserved11;
|
||||
customReserved12 @138 :Custom.CustomReserved12;
|
||||
customReserved13 @139 :Custom.CustomReserved13;
|
||||
@@ -2707,5 +2797,6 @@ struct Event {
|
||||
gyroscope2DEPRECATED @100 :SensorEventData;
|
||||
accelerometer2DEPRECATED @101 :SensorEventData;
|
||||
temperatureSensor2DEPRECATED @123 :SensorEventData;
|
||||
driverMonitoringStateDEPRECATED @71 :DriverMonitoringStateDEPRECATED;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
+243
-58
@@ -1,70 +1,255 @@
|
||||
from openpilot.common.params_pyx import Params as _Params, ParamKeyFlag, ParamKeyType, UnknownKeyName
|
||||
assert _Params
|
||||
assert ParamKeyFlag
|
||||
assert ParamKeyType
|
||||
assert UnknownKeyName
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import IntEnum, IntFlag
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
class Params(_Params):
|
||||
def get(self, key, block=False, return_default=False, encoding=None, default=None):
|
||||
try:
|
||||
value = super().get(key, block=block, return_default=return_default)
|
||||
except UnknownKeyName:
|
||||
return default
|
||||
if value is None:
|
||||
return default
|
||||
if encoding is not None and isinstance(value, bytes):
|
||||
try:
|
||||
from openpilot.common.params_pyx import Params as _Params, ParamKeyFlag, ParamKeyType, UnknownKeyName
|
||||
except Exception:
|
||||
class UnknownKeyName(KeyError):
|
||||
pass
|
||||
|
||||
class ParamKeyFlag(IntFlag):
|
||||
PERSISTENT = 0x02
|
||||
CLEAR_ON_MANAGER_START = 0x04
|
||||
CLEAR_ON_ONROAD_TRANSITION = 0x08
|
||||
CLEAR_ON_OFFROAD_TRANSITION = 0x10
|
||||
DONT_LOG = 0x20
|
||||
DEVELOPMENT_ONLY = 0x40
|
||||
CLEAR_ON_IGNITION_ON = 0x80
|
||||
ALL = 0xFFFFFFFF
|
||||
|
||||
class ParamKeyType(IntEnum):
|
||||
STRING = 0
|
||||
BOOL = 1
|
||||
INT = 2
|
||||
FLOAT = 3
|
||||
TIME = 4
|
||||
JSON = 5
|
||||
BYTES = 6
|
||||
|
||||
def _load_key_types() -> dict[str, ParamKeyType]:
|
||||
key_types: dict[str, ParamKeyType] = {}
|
||||
params_keys = Path(__file__).with_name("params_keys.h")
|
||||
if not params_keys.exists():
|
||||
return key_types
|
||||
|
||||
for line in params_keys.read_text(encoding="utf-8", errors="ignore").splitlines():
|
||||
if not line.lstrip().startswith('{"'):
|
||||
continue
|
||||
|
||||
parts = line.split('"')
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
|
||||
key = parts[1]
|
||||
for type_name in ParamKeyType.__members__:
|
||||
if f", {type_name}" in line:
|
||||
key_types[key] = ParamKeyType[type_name]
|
||||
break
|
||||
else:
|
||||
key_types[key] = ParamKeyType.STRING
|
||||
|
||||
return key_types
|
||||
|
||||
_KEY_TYPES = _load_key_types()
|
||||
_PERSISTENT_STORE: dict[str, object] = {}
|
||||
_MEMORY_STORE: dict[str, object] = {}
|
||||
|
||||
class Params:
|
||||
def __init__(self, d: str | None = None, memory: bool = False, return_defaults: bool = False):
|
||||
self.d = d if d is not None else ""
|
||||
self.m = memory
|
||||
self.return_defaults = return_defaults
|
||||
self._store = _MEMORY_STORE if memory else _PERSISTENT_STORE
|
||||
|
||||
def __reduce__(self):
|
||||
return type(self), (self.d, self.m, self.return_defaults)
|
||||
|
||||
def clear_all(self, tx_flag=ParamKeyFlag.ALL):
|
||||
self._store.clear()
|
||||
|
||||
def check_key(self, key):
|
||||
if isinstance(key, bytes):
|
||||
key = key.decode("utf-8")
|
||||
return str(key)
|
||||
|
||||
def _coerce_bool(self, value) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, (int, float)):
|
||||
return bool(value)
|
||||
if isinstance(value, bytes):
|
||||
return value == b"1"
|
||||
if isinstance(value, str):
|
||||
return value.strip().lower() not in ("", "0", "false", "none", "null")
|
||||
return bool(value)
|
||||
|
||||
def _coerce_value(self, key: str, value):
|
||||
value_type = self.get_type(key)
|
||||
if value_type == ParamKeyType.BOOL:
|
||||
return self._coerce_bool(value)
|
||||
if value_type == ParamKeyType.INT:
|
||||
return int(float(value))
|
||||
if value_type == ParamKeyType.FLOAT:
|
||||
return float(value)
|
||||
if value_type == ParamKeyType.BYTES and isinstance(value, str):
|
||||
return value.encode("utf-8")
|
||||
return value
|
||||
|
||||
def get(self, key, block: bool = False, return_default: bool = False, encoding=None, default=None):
|
||||
key = self.check_key(key)
|
||||
value = self._store.get(key, default)
|
||||
if value is None:
|
||||
return default
|
||||
if encoding is not None and isinstance(value, bytes):
|
||||
try:
|
||||
return value.decode(encoding)
|
||||
except Exception:
|
||||
return value.decode("utf-8", errors="replace")
|
||||
return value
|
||||
|
||||
def get_bool(self, key, block: bool = False, default: bool = False):
|
||||
value = self.get(key, block=block, return_default=True, default=default)
|
||||
return self._coerce_bool(value)
|
||||
|
||||
def get_int(self, key, block: bool = False, return_default: bool = False, default: int = 0):
|
||||
value = self.get(key, block=block, return_default=return_default, encoding="utf-8", default=default)
|
||||
if value is None or value == "":
|
||||
return default
|
||||
try:
|
||||
return value.decode(encoding)
|
||||
except Exception:
|
||||
return value.decode("utf-8", errors="replace")
|
||||
return value
|
||||
return int(float(value))
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
def get_bool(self, key, block=False, default=False):
|
||||
try:
|
||||
return super().get_bool(key, block=block)
|
||||
except UnknownKeyName:
|
||||
return bool(default)
|
||||
def get_float(self, key, block: bool = False, return_default: bool = False, default: float = 0.0):
|
||||
value = self.get(key, block=block, return_default=return_default, encoding="utf-8", default=default)
|
||||
if value is None or value == "":
|
||||
return default
|
||||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
def get_int(self, key, block=False, return_default=False, default=0):
|
||||
val = self.get(key, block=block, return_default=return_default, encoding="utf-8")
|
||||
if val is None or val == "":
|
||||
return default
|
||||
try:
|
||||
return int(float(val))
|
||||
except ValueError:
|
||||
return default
|
||||
def put(self, key, dat):
|
||||
key = self.check_key(key)
|
||||
self._store[key] = self._coerce_value(key, dat)
|
||||
|
||||
def get_float(self, key, block=False, return_default=False, default=0.0):
|
||||
val = self.get(key, block=block, return_default=return_default, encoding="utf-8")
|
||||
if val is None or val == "":
|
||||
return default
|
||||
try:
|
||||
return float(val)
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
def put_int(self, key, val):
|
||||
t = self.get_type(key)
|
||||
if t == ParamKeyType.FLOAT:
|
||||
self.put(key, float(val))
|
||||
elif t == ParamKeyType.INT:
|
||||
self.put(key, int(val))
|
||||
elif t == ParamKeyType.BOOL:
|
||||
def put_bool(self, key, val: bool):
|
||||
self.put(key, bool(val))
|
||||
else:
|
||||
self.put(key, str(int(val)))
|
||||
|
||||
def put_float(self, key, val):
|
||||
t = self.get_type(key)
|
||||
if t == ParamKeyType.FLOAT:
|
||||
self.put(key, float(val))
|
||||
elif t == ParamKeyType.INT:
|
||||
def put_nonblocking(self, key, dat):
|
||||
self.put(key, dat)
|
||||
|
||||
def put_bool_nonblocking(self, key, val: bool):
|
||||
self.put_bool(key, val)
|
||||
|
||||
def put_int(self, key, val):
|
||||
self.put(key, int(val))
|
||||
elif t == ParamKeyType.BOOL:
|
||||
self.put(key, bool(val))
|
||||
else:
|
||||
self.put(key, str(float(val)))
|
||||
|
||||
def put_float(self, key, val):
|
||||
self.put(key, float(val))
|
||||
|
||||
def remove(self, key):
|
||||
self._store.pop(self.check_key(key), None)
|
||||
|
||||
def get_param_path(self, key: str = ""):
|
||||
base = Path(tempfile.gettempdir()) / ("params_memory" if self.m else "params")
|
||||
base.mkdir(parents=True, exist_ok=True)
|
||||
return str(base / key) if key else str(base)
|
||||
|
||||
def get_type(self, key):
|
||||
return _KEY_TYPES.get(self.check_key(key), ParamKeyType.STRING)
|
||||
|
||||
def all_keys(self):
|
||||
return list(_KEY_TYPES)
|
||||
|
||||
def get_default_value(self, key):
|
||||
return None
|
||||
|
||||
def cpp2python(self, key, value):
|
||||
return self._coerce_value(self.check_key(key), value)
|
||||
|
||||
def get_key_flag(self, key):
|
||||
return ParamKeyFlag.PERSISTENT
|
||||
|
||||
def get_stock_value(self, key):
|
||||
return None
|
||||
|
||||
def get_tuning_level(self, key):
|
||||
return 0
|
||||
|
||||
else:
|
||||
assert _Params
|
||||
assert ParamKeyFlag
|
||||
assert ParamKeyType
|
||||
assert UnknownKeyName
|
||||
|
||||
class Params(_Params):
|
||||
def get(self, key, block=False, return_default=False, encoding=None, default=None):
|
||||
try:
|
||||
value = super().get(key, block=block, return_default=return_default)
|
||||
except UnknownKeyName:
|
||||
return default
|
||||
if value is None:
|
||||
return default
|
||||
if encoding is not None and isinstance(value, bytes):
|
||||
try:
|
||||
return value.decode(encoding)
|
||||
except Exception:
|
||||
return value.decode("utf-8", errors="replace")
|
||||
return value
|
||||
|
||||
def get_bool(self, key, block=False, default=False):
|
||||
try:
|
||||
result = super().get(key, block=block, return_default=True)
|
||||
if result is None:
|
||||
return bool(default)
|
||||
return bool(result)
|
||||
except UnknownKeyName:
|
||||
return bool(default)
|
||||
|
||||
def get_int(self, key, block=False, return_default=False, default=0):
|
||||
val = self.get(key, block=block, return_default=return_default, encoding="utf-8")
|
||||
if val is None or val == "":
|
||||
return default
|
||||
try:
|
||||
return int(float(val))
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
def get_float(self, key, block=False, return_default=False, default=0.0):
|
||||
val = self.get(key, block=block, return_default=return_default, encoding="utf-8")
|
||||
if val is None or val == "":
|
||||
return default
|
||||
try:
|
||||
return float(val)
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
def put_int(self, key, val):
|
||||
t = self.get_type(key)
|
||||
if t == ParamKeyType.FLOAT:
|
||||
self.put(key, float(val))
|
||||
elif t == ParamKeyType.INT:
|
||||
self.put(key, int(val))
|
||||
elif t == ParamKeyType.BOOL:
|
||||
self.put(key, bool(val))
|
||||
else:
|
||||
self.put(key, str(int(val)))
|
||||
|
||||
def put_float(self, key, val):
|
||||
t = self.get_type(key)
|
||||
if t == ParamKeyType.FLOAT:
|
||||
self.put(key, float(val))
|
||||
elif t == ParamKeyType.INT:
|
||||
self.put(key, int(val))
|
||||
elif t == ParamKeyType.BOOL:
|
||||
self.put(key, bool(val))
|
||||
else:
|
||||
self.put(key, str(float(val)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
+102
-29
@@ -11,7 +11,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"AlwaysAllowUploads", {PERSISTENT, BOOL, "0"}},
|
||||
{"AlwaysOnDM", {PERSISTENT, BOOL}},
|
||||
{"ApiCache_Device", {PERSISTENT, STRING}},
|
||||
{"ApiCache_FirehoseStats", {PERSISTENT, JSON}},
|
||||
{"AssistNowToken", {PERSISTENT, STRING}},
|
||||
{"AthenadPid", {PERSISTENT, INT}},
|
||||
{"AthenadUploadQueue", {PERSISTENT, JSON}},
|
||||
@@ -43,7 +42,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ExperimentalLongitudinalEnabled", {PERSISTENT, BOOL}},
|
||||
{"ExperimentalMode", {PERSISTENT, BOOL}},
|
||||
{"ExperimentalModeConfirmed", {PERSISTENT, BOOL}},
|
||||
{"PersistChillState", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PersistExperimentalState", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PersistedCCStatus", {PERSISTENT, INT, "0", "0"}},
|
||||
{"PersistedCEStatus", {PERSISTENT, INT, "0", "0"}},
|
||||
{"FirmwareQueryDone", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||
{"ForcePowerDown", {PERSISTENT, BOOL}},
|
||||
@@ -59,6 +60,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"GsmRoaming", {PERSISTENT, BOOL}},
|
||||
{"HardwareSerial", {PERSISTENT, STRING}},
|
||||
{"HasAcceptedTerms", {PERSISTENT, STRING, "0"}},
|
||||
{"HondaGasFactorParams", {PERSISTENT, FLOAT}},
|
||||
{"HondaWindFactorParams", {PERSISTENT, FLOAT}},
|
||||
{"InstallDate", {PERSISTENT, TIME}},
|
||||
{"IsDriverViewEnabled", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"IsEngaged", {PERSISTENT, BOOL}},
|
||||
@@ -66,7 +69,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"IsMetric", {PERSISTENT, BOOL}},
|
||||
{"IsOffroad", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"IsOnroad", {PERSISTENT, BOOL}},
|
||||
{"IsRHD", {PERSISTENT, BOOL}},
|
||||
{"IsRhdDetected", {PERSISTENT, BOOL}},
|
||||
{"IsRHDOverride", {PERSISTENT, BOOL}},
|
||||
{"IsReleaseBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
@@ -111,6 +116,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"PandaSomResetTriggered", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||
{"PandaSignatures", {CLEAR_ON_MANAGER_START, BYTES}},
|
||||
{"PrimeType", {PERSISTENT, INT}},
|
||||
{"PriusClusterOffsetMigrated", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"RecordAudio", {PERSISTENT, BOOL}},
|
||||
{"RecordAudioFeedback", {PERSISTENT, BOOL, "0"}},
|
||||
{"RecordFront", {PERSISTENT, BOOL}},
|
||||
@@ -142,7 +148,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
|
||||
// StarPilot variables
|
||||
{"AccelerationPath", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"AccelerationProfile", {PERSISTENT, INT, "2", "0", 0}},
|
||||
{"AccelerationProfile", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"AdjacentLeadsUI", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"AdjacentPath", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"AdjacentPathMetrics", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
@@ -150,13 +156,14 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"AdvancedLateralTune", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"AdvancedLongitudinalTune", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"AggressiveFollow", {PERSISTENT, FLOAT, "1.25", "1.25", 2}},
|
||||
{"AggressiveFollowHigh", {PERSISTENT, FLOAT, "1.25", "1.25", 2}},
|
||||
{"AggressiveFollowHigh", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
|
||||
{"AggressiveJerkAcceleration", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"AggressiveJerkDanger", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"AggressiveJerkDeceleration", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"AggressiveJerkSpeed", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"AggressiveJerkSpeedDecrease", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"AlertVolumeControl", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"AllowImpossibleAcceleration", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"AlwaysOnLateral", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"AlwaysOnLateralLKAS", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ApiCache_DriveStats", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
@@ -165,6 +172,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"AvailableModelNames", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"AvailableModelSeries", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"AvailableModels", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"AvailableModelArtifactFormats", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"BlacklistedModels", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"BootLogo", {PERSISTENT, STRING, "starpilot", "stock", 0}},
|
||||
{"BuildMetadata", {PERSISTENT, STRING, "", "", 0}},
|
||||
@@ -175,8 +183,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"BorderWidth", {PERSISTENT, FLOAT, "100.0", "100.0", 2}},
|
||||
{"CalibratedLateralAcceleration", {PERSISTENT, FLOAT, "2.0", "2.0", 2}},
|
||||
{"CalibrationProgress", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"CameraOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"CameraView", {PERSISTENT, INT, "3", "0", 2}},
|
||||
{"CancelDownloadMaps", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"DisableWideRoad", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"CancelModelDownload", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"CancelThemeDownload", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"CarMake", {PERSISTENT, STRING, "mock", "mock", 0}},
|
||||
@@ -191,15 +201,22 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"CESlowerLead", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"CESpeed", {PERSISTENT, FLOAT, "0.0", "0.0", 1}},
|
||||
{"CESpeedLead", {PERSISTENT, FLOAT, "0.0", "0.0", 1}},
|
||||
{"CCMLead", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"CCMLaunchAssist", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"CCMSetSpeedMargin", {PERSISTENT, FLOAT, "3.0", "0.0", 1}},
|
||||
{"CCMSpeed", {PERSISTENT, FLOAT, "45.0", "0.0", 1}},
|
||||
{"CCMSpeedLead", {PERSISTENT, FLOAT, "35.0", "0.0", 1}},
|
||||
{"CCStatus", {CLEAR_ON_OFFROAD_TRANSITION, INT, "0", "0"}},
|
||||
{"CEStatus", {CLEAR_ON_OFFROAD_TRANSITION, INT, "0", "0"}},
|
||||
{"CEStopLights", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"CEStoppedLead", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"CEStoppedLead", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"ClusterOffset", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
|
||||
{"ColorScheme", {PERSISTENT, STRING, "frog", "stock", 0}},
|
||||
{"ColorScheme", {PERSISTENT, STRING, "stock", "stock", 0}},
|
||||
{"ColorToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"BootLogoToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"Compass", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"CommunityFavorites", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"ConditionalChill", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"ConditionalExperimental", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"CurvatureData", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"CurveSpeedController", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
@@ -216,11 +233,14 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"CustomCruise", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
|
||||
{"CustomCruiseLong", {PERSISTENT, FLOAT, "5.0", "5.0", 2}},
|
||||
{"CustomPersonalities", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"CancelButtonControl", {PERSISTENT, INT, "1", "0", 2}},
|
||||
{"CancelButtonControlsMigrated", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"AOLLKASMigratedToButtonControl", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"TrafficPersonalityProfile", {PERSISTENT, BOOL, "1", "1", 2}},
|
||||
{"AggressivePersonalityProfile", {PERSISTENT, BOOL, "1", "1", 2}},
|
||||
{"StandardPersonalityProfile", {PERSISTENT, BOOL, "1", "1", 2}},
|
||||
{"RelaxedPersonalityProfile", {PERSISTENT, BOOL, "1", "1", 2}},
|
||||
{"CustomThemes", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"CustomThemes", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"CustomUI", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"DebugMode", {CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0", "0"}},
|
||||
{"DecelerationProfile", {PERSISTENT, INT, "1", "0", 2}},
|
||||
@@ -267,22 +287,40 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"FlashPanda", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"GMDashSpoofOffsets", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"GMPedalLongitudinal", {PERSISTENT, BOOL, "1", "1", 2}},
|
||||
{"IgnoreIgnitionLine", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"LongPitch", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"RemoteStartBootsComma", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"RemapCancelToDistance", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"NAPAdaptiveAccel", {PERSISTENT, BOOL, "1", "1"}},
|
||||
{"NAPFollowDistance", {PERSISTENT, INT, "4", "4"}},
|
||||
{"NAPForcePreAP", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"NAPPedalEnabled", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"NAPPedalCanBus", {PERSISTENT, INT, "2", "2"}},
|
||||
{"NAPPedalCalibDone", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"NAPPedalCalibMin", {PERSISTENT, FLOAT, "-3.0", "-3.0"}},
|
||||
{"NAPPedalCalibMax", {PERSISTENT, FLOAT, "99.6", "99.6"}},
|
||||
{"NAPPedalCalibFactor", {PERSISTENT, FLOAT, "1.0", "1.0"}},
|
||||
{"NAPPedalCalibZero", {PERSISTENT, FLOAT, "0.0", "0.0"}},
|
||||
{"NAPPedalProfile", {PERSISTENT, INT, "4", "4"}},
|
||||
{"NAPRadarBehindNosecone", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"NAPRadarEnabled", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"NAPRadarOffset", {PERSISTENT, FLOAT, "0.0", "0.0"}},
|
||||
{"ForceAutoTune", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"ForceAutoTuneOff", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ForceFingerprint", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ForceOffroad", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"ForceOnroad", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"ForceStops", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ForceStops", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ForceStopDistanceOffset", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"ForceStandstill", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"FPSCounter", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"GalaxyDashboardStats", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"StarPilotApiToken", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"StarPilotCarParams", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES, "", ""}},
|
||||
{"StarPilotCarParamsPersistent", {PERSISTENT, BYTES, "", ""}},
|
||||
{"StarPilotDongleId", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"StarPilotFavoriteSlots", {PERSISTENT, JSON, "[]", "[]", 1}},
|
||||
{"StarPilotStats", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"StarPilotTogglesUpdated", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"FrogsGoMoosTweak", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
@@ -290,23 +328,29 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"GoatScreamCriticalAlerts", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"GreenLightAlert", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"HideAlerts", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideChangingLanesBanner", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideDistanceProfileBanner", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideTurningBanner", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideDMIcon", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideLeadMarker", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideMaxSpeed", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideSpeed", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideSpeedLimit", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideSteeringWheel", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HigherBitrate", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HolidayThemes", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"HumanAcceleration", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HumanFollowing", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"CoastUpToLeads", {PERSISTENT, BOOL, "1", "1", 2}},
|
||||
{"PrioritizeSmoothFollowing", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HumanLaneChanges", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"IconPack", {PERSISTENT, STRING, "frog-animated", "stock", 0}},
|
||||
{"IconPack", {PERSISTENT, STRING, "stock", "stock", 0}},
|
||||
{"IconToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"IncreasedStoppedDistance", {PERSISTENT, FLOAT, "0.0", "0.0", 1}},
|
||||
{"IncreasedStoppedDistanceLowVisibility", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"IncreasedStoppedDistanceRain", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"IncreasedStoppedDistanceRainStorm", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"IncreasedStoppedDistanceSnow", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"RedneckCruise", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"IncreaseFollowingLowVisibility", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"IncreaseFollowingRain", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"IncreaseFollowingRainStorm", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
@@ -330,6 +374,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"LKASButtonControl", {PERSISTENT, INT, "5", "0", 2}},
|
||||
{"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"LockDoorsTimer", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"LongCancelButtonControl", {PERSISTENT, INT, "5", "0", 2}},
|
||||
{"LongDistanceButtonControl", {PERSISTENT, INT, "5", "0", 2}},
|
||||
{"LongModeButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"LongStarButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
@@ -340,8 +385,14 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"LongitudinalManeuverStatus", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON, "{}", "{}"}},
|
||||
{"LongitudinalTune", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"LoudBlindspotAlert", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"LoudBlindspotAlertWhenDisengaged", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"LowVoltageShutdown", {PERSISTENT, FLOAT, "11.8", "11.8", 3}},
|
||||
{"MainCruiseButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"ManualUpdateInitiated", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"AMapKey1", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"AMapKey2", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"ApiCache_NavDestinations", {PERSISTENT, JSON, "[]", "[]"}},
|
||||
{"FavoriteDestinations", {PERSISTENT, JSON, "[]", "[]"}},
|
||||
{"MapAcceleration", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"MapboxPublicKey", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"MapBoxRequests", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
@@ -351,6 +402,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"MapGears", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"MapsSelected", {PERSISTENT, STRING, "", "", 0}},
|
||||
{"MapSpeedLimit", {CLEAR_ON_MANAGER_START, FLOAT, "0.0", "0.0"}},
|
||||
{"NavDesiresAllowed", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"NavLongitudinalAllowed", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ClearNavOnOffroad", {PERSISTENT, BOOL, "1", "1", 2}},
|
||||
{"NavDestination", {PERSISTENT | CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"NavInstructionCollapsed", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0", "0"}},
|
||||
{"NavInstructionState", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON, "{}", "{}"}},
|
||||
{"NextMapSpeedLimit", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
|
||||
{"VisionSpeedLimit", {CLEAR_ON_MANAGER_START, FLOAT, "0.0", "0.0"}},
|
||||
{"VisionSpeedLimitConfidence", {CLEAR_ON_MANAGER_START, FLOAT, "0.0", "0.0"}},
|
||||
@@ -371,6 +428,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ModelToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"ModelUI", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ModelVersions", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"ModelManifestVersion", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"NavigationUI", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"NNFF", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"NNFFLite", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
@@ -378,7 +436,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"NNFFModelName", {CLEAR_ON_MANAGER_START, STRING, "", "", 0}},
|
||||
{"NoLogging", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"NoUploads", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"NudgelessLaneChange", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"NudgelessLaneChange", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"NudgelessLaneChangeOnlyWhenEngaged", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"NumericalTemp", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"Offset1", {PERSISTENT, FLOAT, "5.0", "0.0", 0}},
|
||||
{"Offset2", {PERSISTENT, FLOAT, "5.0", "0.0", 0}},
|
||||
@@ -400,9 +459,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"PauseAOLOnBrake", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PauseLateralOnSignal", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PauseLateralSpeed", {PERSISTENT, FLOAT, "0.0", "0.0", 1}},
|
||||
{"LateralResumeDelay", {PERSISTENT, FLOAT, "0.0", "0.0", 1}},
|
||||
{"PedalsOnUI", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PondPaired", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"PondUploadPending", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"GalaxyPaired", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"GalaxyUploadPending", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"PreferredSchedule", {PERSISTENT, INT, "2", "0", 0}},
|
||||
{"PreviousSpeedLimit", {PERSISTENT, FLOAT, "0.0", "0.0"}},
|
||||
{"PromptDistractedVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
@@ -410,6 +470,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"QOLLateral", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"QOLLongitudinal", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"QOLVisuals", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"RadarTakeoffs", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"RadarTracksUI", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"RainbowPath", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"RandomEvents", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
@@ -424,13 +485,13 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ReduceLateralAccelerationRainStorm", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"ReduceLateralAccelerationSnow", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"RefuseVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"RelaxedFollow", {PERSISTENT, FLOAT, "1.75", "1.75", 2}},
|
||||
{"RelaxedFollowHigh", {PERSISTENT, FLOAT, "1.75", "1.75", 2}},
|
||||
{"RelaxedJerkAcceleration", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"RelaxedFollow", {PERSISTENT, FLOAT, "1.6", "1.6", 2}},
|
||||
{"RelaxedFollowHigh", {PERSISTENT, FLOAT, "1.4", "1.4", 2}},
|
||||
{"RelaxedJerkAcceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"RelaxedJerkDanger", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"ReverseCruise", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"RecoveryPower", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
|
||||
{"RoadEdgesWidth", {PERSISTENT, FLOAT, "2.0", "2.0", 2}},
|
||||
@@ -448,11 +509,13 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SetSpeedLimit", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"SetSpeedOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"ShowCEMStatus", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ShowCCMStatus", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ShowCPU", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"ShowCSCStatus", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ShowGPU", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"ShowIP", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"ShowMemoryUsage", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"ShowModeStatusBanner", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ShownToggleDescriptions", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"ShowSLCOffset", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"ShowSpeedLimits", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
@@ -463,10 +526,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ShowStorageUsed", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"SidebarMetrics", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"SidebarOpen", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"SignalAnimation", {PERSISTENT, STRING, "frog", "stock", 0}},
|
||||
{"SignalAnimation", {PERSISTENT, STRING, "stock", "stock", 0}},
|
||||
{"SignalMetrics", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"SignalToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"SimpleMode", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"SLCAbbreviatedSources", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"SLCActiveSourcesOnly", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"SLCConfirmation", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"SLCConfirmationHigher", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"SLCConfirmationLower", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
@@ -479,8 +544,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SLCPriority1", {PERSISTENT, STRING, "Map Data", "Map Data", 2}},
|
||||
{"SLCPriority2", {PERSISTENT, STRING, "Dashboard", "Dashboard", 2}},
|
||||
{"SNGHack", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"SoundPack", {PERSISTENT, STRING, "frog", "stock", 0}},
|
||||
{"SoundPack", {PERSISTENT, STRING, "stock", "stock", 0}},
|
||||
{"SoundToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"SLCAdoptSpeedLimit", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"SLCForceCruiseSpeed", {CLEAR_ON_MANAGER_START, FLOAT, "0.0", "0.0"}},
|
||||
{"SpeedLimitAccepted", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"SpeedLimitChangedAlert", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"SpeedLimitController", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
@@ -493,17 +560,17 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"VisionSpeedLimitDetection", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"VisionSpeedLimitTrainingCollector", {PERSISTENT, BOOL, "1", "1", 0}},
|
||||
{"StandardFollow", {PERSISTENT, FLOAT, "1.45", "1.45", 2}},
|
||||
{"StandardFollowHigh", {PERSISTENT, FLOAT, "1.45", "1.45", 2}},
|
||||
{"StandardJerkAcceleration", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"StandardFollowHigh", {PERSISTENT, FLOAT, "1.2", "1.2", 2}},
|
||||
{"StandardJerkAcceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"StandardJerkDanger", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"StandardJerkDeceleration", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"StandardJerkSpeed", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"StandardJerkSpeedDecrease", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"StandardJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"StandardJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"StandardJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"StandbyMode", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"StartAccel", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StartAccelStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StartupMessageBottom", {PERSISTENT, STRING, "Human-tested, frog-approved 🐸", "Always keep hands on wheel and eyes on road", 0}},
|
||||
{"StartupMessageTop", {PERSISTENT, STRING, "Hop in and buckle up!", "Be ready to take over at any time", 0}},
|
||||
{"StartupMessageBottom", {PERSISTENT, STRING, "Always keep hands on wheel and eyes on road", "Always keep hands on wheel and eyes on road", 0}},
|
||||
{"StartupMessageTop", {PERSISTENT, STRING, "Be ready to take over at any time", "Be ready to take over at any time", 0}},
|
||||
{"StaticPedalsOnUI", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"SteerDelay", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"SteerDelayStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
@@ -517,6 +584,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SteerOffsetStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"SteerRatio", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"SteerRatioStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StockConfidenceBallWidget", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"StockDongleId", {PERSISTENT, STRING, "", ""}},
|
||||
{"StopAccel", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StopAccelStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
@@ -528,8 +596,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SwitchbackModeCooldown", {PERSISTENT, INT, "5", "0", 2}},
|
||||
{"SwitchbackModeEnabled", {CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0", "0"}},
|
||||
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"TacoTuneHacks", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"TetheringEnabled", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"ThemeDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
@@ -537,6 +605,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"Timezone", {PERSISTENT, STRING, "", ""}},
|
||||
{"TinygradUpdateAvailable", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"ToyotaDoors", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"TrailerLoad", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"TrafficFollow", {PERSISTENT, FLOAT, "0.5", "0.5", 2}},
|
||||
{"TrafficJerkAcceleration", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"TrafficJerkDanger", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
@@ -562,16 +631,20 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"VEgoStartingStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"VEgoStopping", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"VEgoStoppingStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"VeryLongCancelButtonControl", {PERSISTENT, INT, "6", "0", 2}},
|
||||
{"VeryLongDistanceButtonControl", {PERSISTENT, INT, "6", "0", 2}},
|
||||
{"VeryLongModeButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"VeryLongStarButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"VoltSNG", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"GMAutoHold", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"VoltOnePedalMode", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ToyotaAutoHold", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"WarningImmediateVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"WarningSoftVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"WeatherPresets", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"WeatherToken", {PERSISTENT | DONT_LOG, STRING, "", "", 2}},
|
||||
{"WheelControls", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"WheelIcon", {PERSISTENT, STRING, "frog", "stock", 0}},
|
||||
{"WheelIcon", {PERSISTENT, STRING, "stock", "stock", 0}},
|
||||
{"WheelSpeed", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"WheelToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
};
|
||||
|
||||
Binary file not shown.
@@ -133,8 +133,8 @@ class TestParams:
|
||||
|
||||
def test_params_get_type(self):
|
||||
# json
|
||||
self.params.put("ApiCache_FirehoseStats", {"a": 0})
|
||||
assert self.params.get("ApiCache_FirehoseStats") == {"a": 0}
|
||||
self.params.put("ApiCache_DriveStats", {"a": 0})
|
||||
assert self.params.get("ApiCache_DriveStats") == {"a": 0}
|
||||
|
||||
# int
|
||||
self.params.put("BootCount", 1441)
|
||||
|
||||
+32
@@ -1,8 +1,40 @@
|
||||
import contextlib
|
||||
import gc
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _prepend_host_pytest_runtime() -> None:
|
||||
if platform.system() != "Darwin" or os.getenv("SP_DISABLE_HOST_PYTEST_REDIRECT") == "1":
|
||||
return
|
||||
|
||||
root_dir = Path(__file__).resolve().parent
|
||||
work_dir = root_dir / ".host_runtime" / "darwin" / "worktree"
|
||||
required_extension = work_dir / "msgq_repo" / "msgq" / "ipc_pyx.so"
|
||||
if not required_extension.exists():
|
||||
return
|
||||
|
||||
extra_paths = [work_dir, work_dir / "starpilot" / "third_party"]
|
||||
extra_paths.extend(sorted(work_dir.glob("*_repo")))
|
||||
acados_dir = work_dir / "third_party" / "acados"
|
||||
if acados_dir.is_dir():
|
||||
extra_paths.append(acados_dir)
|
||||
|
||||
existing = set(sys.path)
|
||||
insert_at = 0
|
||||
for path in [str(p) for p in extra_paths if p.exists()]:
|
||||
if path in existing:
|
||||
continue
|
||||
sys.path.insert(insert_at, path)
|
||||
insert_at += 1
|
||||
|
||||
|
||||
_prepend_host_pytest_runtime()
|
||||
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.system.manager import manager
|
||||
from openpilot.system.hardware import TICI, HARDWARE
|
||||
|
||||
+2
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
|
||||
|
||||
# 325 Supported Cars
|
||||
# 326 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|Setup Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -110,6 +110,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera 2022">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid 2019">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Azera Hybrid 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid 2020">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Azera Hybrid (with HDA II & LFA2) 2025|Highway Driving Assist II & Lane Follow Assist 2|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai S connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid (with HDA II & LFA2) 2025">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Custin 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Custin 2023">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra 2017-18">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra 2019">Buy Here</a></sub></details>|||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
# StarPilot Unified Model Rebuild
|
||||
|
||||
This workflow rebuilds StarPilot driving and driver-monitoring artifacts for the vendored tinygrad revision. Driving-model behavior versions remain manifest metadata; every runtime driving artifact uses the `tinygrad_single_v1` layout.
|
||||
|
||||
## Safety
|
||||
|
||||
- The supported build device is `comma@192.168.3.110`.
|
||||
- Never run these commands against `192.168.3.109`.
|
||||
- Do not compile normal and big-GPU artifacts together. This workflow builds normal QCOM artifacts only.
|
||||
- Keep source ONNX files and compiled PKLs on the T5 workspace, not the comma.
|
||||
|
||||
## Workspace
|
||||
|
||||
The default workspace is:
|
||||
|
||||
```text
|
||||
/Volumes/T5/StarPilot-Model-Rebuild-2026-06-22/
|
||||
```
|
||||
|
||||
Important directories:
|
||||
|
||||
- `onnx/<model-id>/`: ID-prefixed source ONNX files.
|
||||
- `compiled/`: completed unified driving PKLs.
|
||||
- `driver-monitoring/`: DM ONNX, model PKL, metadata, and camera warps.
|
||||
- `ready-for-resources/`: flat repository-upload handoff.
|
||||
- Oversized models are represented by repository-safe `.p00`, `.p01`, and `.sha256` files in `ready-for-resources/`.
|
||||
- `logs/`: one remote compilation log per model.
|
||||
- `results/`: source and artifact checksum records.
|
||||
- `manifests/`: generated `model_names_v22.json`.
|
||||
|
||||
## Initialize And Extract
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py init
|
||||
python3 scripts/model_rebuild_pipeline.py extract \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
Extraction streams Git blobs directly to disk. LFS pointers are resolved from the local object cache or fetched by object ID, then checked against the pointer SHA-256 and size. Binary ONNX data is never stored in a shell variable.
|
||||
|
||||
To retry one source:
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py extract \
|
||||
--model pop22 \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
Source commits are defined in `scripts/model_source_map_v22.json`.
|
||||
|
||||
## Compile
|
||||
|
||||
Compile one model:
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py compile \
|
||||
--model pop22 \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
Compile or resume the full catalog:
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py compile \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
Existing artifacts are skipped unless `--force` is passed. Each model is staged in its own remote input directory, compiled on `.110`, copied back to the T5, hashed, and copied into `ready-for-resources/`. Failures are written to `results/<id>_failure.json`; rerunning the same command resumes incomplete models.
|
||||
|
||||
Validate one or all completed artifacts with synthetic camera inputs on QCOM:
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py validate \
|
||||
--model pop22 \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
The lower-level device compiler also supports direct use:
|
||||
|
||||
```bash
|
||||
./models --model pop22 --input-format split --version v11
|
||||
./models --model deeprl3v2 --input-format supercombo --version v15
|
||||
```
|
||||
|
||||
`--version` records behavioral semantics only. It does not change artifact layout.
|
||||
|
||||
If the compiled PKL exceeds 100 MiB, `./models` automatically keeps the full
|
||||
local PKL and creates 95 MiB upload parts beside it:
|
||||
|
||||
```text
|
||||
deeprl3v2_driving_tinygrad.pkl
|
||||
deeprl3v2_driving_tinygrad.pkl.p00
|
||||
deeprl3v2_driving_tinygrad.pkl.p01
|
||||
deeprl3v2_driving_tinygrad.pkl.sha256
|
||||
```
|
||||
|
||||
To split an already compiled artifact:
|
||||
|
||||
```bash
|
||||
./models --split-artifact /path/to/deeprl3v2_driving_tinygrad.pkl \
|
||||
--output-dir /path/to/upload-ready
|
||||
```
|
||||
|
||||
Upload only the numbered parts and checksum when the full PKL exceeds the
|
||||
repository limit. The downloader reassembles into a temporary file, verifies
|
||||
the companion SHA-256, and atomically installs the final PKL. No manifest field
|
||||
is required for multipart artifacts.
|
||||
|
||||
## Driver Monitoring
|
||||
|
||||
Stage the current DM ONNX in `uncompiledmodels`, then run:
|
||||
|
||||
```bash
|
||||
./models --dm \
|
||||
--input-dir /data/openpilot/uncompiledmodels \
|
||||
--output-dir /tmp/dm_artifacts
|
||||
```
|
||||
|
||||
This builds:
|
||||
|
||||
- `dmonitoring_model_tinygrad.pkl`
|
||||
- `dmonitoring_model_metadata.pkl`
|
||||
- `dm_warp_1928x1208_tinygrad.pkl`
|
||||
- `dm_warp_1344x760_tinygrad.pkl`
|
||||
|
||||
All four files must be updated together.
|
||||
|
||||
## Manifest
|
||||
|
||||
Generate v22 after compilation:
|
||||
|
||||
```bash
|
||||
python3 scripts/model_rebuild_pipeline.py manifest \
|
||||
--base-manifest /path/to/model_names_v21.json
|
||||
```
|
||||
|
||||
The generator preserves existing IDs and behavioral metadata and adds
|
||||
`deeprl3v2`. Manifest v22 implies the unified single-PKL runtime layout.
|
||||
Repository-hosted multipart files are discovered by naming convention, so no
|
||||
size, hash, format, or part-count metadata is required.
|
||||
|
||||
## Runtime Verification
|
||||
|
||||
Compilation validates JIT capture/replay, pickle round-trip, finite outputs, metadata slices, and both camera warps. Before release:
|
||||
|
||||
1. Select representative v8, v11, v12, v15, and supercombo models.
|
||||
2. Confirm `modeld` stays running.
|
||||
3. Confirm finite `modelV2` path, lane-line, lead, pose, and action data.
|
||||
4. Confirm `driverStateV2` on both supported camera resolutions.
|
||||
5. Test download, selection, deletion, randomization, migration, and fallback in QT, raylib/mici, and Galaxy.
|
||||
|
||||
The built-in South Carolina artifact is `selfdrive/modeld/models/driving_tinygrad.pkl`. If migration cannot download the selected v22 artifact, StarPilot switches to that built-in model.
|
||||
@@ -0,0 +1,113 @@
|
||||
# TrueNAS SCALE GitHub Build Runner
|
||||
|
||||
This setup runs a single GitHub self-hosted runner as a TrueNAS SCALE custom app and routes the `Compile StarPilot` workflow onto that runner.
|
||||
|
||||
The app container:
|
||||
|
||||
- registers one repository-scoped GitHub Actions runner
|
||||
- stays visible in the TrueNAS Apps UI
|
||||
- mounts the host Docker socket so `./build` can launch the existing larch64 build container flow
|
||||
- persists runner state and build caches in one dataset mounted at `/runner`
|
||||
|
||||
## What this workflow does
|
||||
|
||||
`.github/workflows/compile_starpilot.yaml` now:
|
||||
|
||||
- lets an admin choose a `target_ref` branch from the Actions UI
|
||||
- checks out that branch on the NAS runner
|
||||
- runs `./build`
|
||||
- prunes the tree with `scripts/ci_package_prebuilt_tree.sh`
|
||||
- commits the result as `build`
|
||||
- pushes that commit back to the selected branch without force-pushing
|
||||
|
||||
Use a branch intended for built output. The prune step removes developer-facing files and is not meant for normal day-to-day development branches.
|
||||
|
||||
## 1. Publish the runner image
|
||||
|
||||
You need a registry image for TrueNAS to deploy. The repo includes `.github/workflows/publish_truenas_runner.yaml` for this.
|
||||
|
||||
1. Open **Actions** on GitHub.
|
||||
2. Run **Publish TrueNAS Runner Image**.
|
||||
3. Leave `image_tag` as `latest` unless you want a versioned tag.
|
||||
4. After it finishes, the image is available at:
|
||||
|
||||
```text
|
||||
ghcr.io/<repo-owner>/starpilot-truenas-runner:latest
|
||||
```
|
||||
|
||||
If you prefer to build it locally, run:
|
||||
|
||||
```bash
|
||||
docker build -f tools/truenas_github_runner/Dockerfile -t ghcr.io/<repo-owner>/starpilot-truenas-runner:latest .
|
||||
docker push ghcr.io/<repo-owner>/starpilot-truenas-runner:latest
|
||||
```
|
||||
|
||||
## 2. Create a runner token
|
||||
|
||||
Generate a repository-scoped self-hosted runner token in GitHub:
|
||||
|
||||
1. Open the repository.
|
||||
2. Go to **Settings > Actions > Runners**.
|
||||
3. Click **New self-hosted runner**.
|
||||
4. Choose **Linux** and **x64**.
|
||||
5. Copy the one-time registration token from the generated setup commands.
|
||||
|
||||
That token expires quickly. Paste it into TrueNAS during deployment and restart the app if you need to re-register.
|
||||
|
||||
## 3. Deploy on TrueNAS SCALE
|
||||
|
||||
TrueNAS custom apps support Docker Compose YAML through **Apps > Discover Apps > Custom App > Install via YAML**.
|
||||
|
||||
Create a dataset for persistent runner state first, for example:
|
||||
|
||||
```text
|
||||
/mnt/<pool>/apps/starpilot-runner
|
||||
```
|
||||
|
||||
Then paste this compose YAML into TrueNAS after replacing the placeholders:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
starpilot-runner:
|
||||
image: ghcr.io/<repo-owner>/starpilot-truenas-runner:latest
|
||||
container_name: starpilot-runner
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
GITHUB_REPOSITORY_URL: https://github.com/<repo-owner>/<repo-name>
|
||||
RUNNER_NAME: starpilot-truenas-runner
|
||||
RUNNER_LABELS: self-hosted,truenas,starpilot-build
|
||||
RUNNER_WORKDIR: /runner/_work
|
||||
RUNNER_TOKEN: <paste-one-time-runner-token>
|
||||
DOCKER_GID: ""
|
||||
REMOVE_RUNNER_ON_EXIT: "0"
|
||||
volumes:
|
||||
- /mnt/<pool>/apps/starpilot-runner:/runner
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
If the container starts but cannot talk to Docker, inspect the Docker socket group on TrueNAS and set `DOCKER_GID` to that numeric group id.
|
||||
|
||||
## 4. Verify runner registration
|
||||
|
||||
Confirm all of the following:
|
||||
|
||||
- the app shows as `Running` in TrueNAS
|
||||
- the container passes its healthcheck
|
||||
- GitHub shows the runner as online with labels `self-hosted`, `truenas`, and `starpilot-build`
|
||||
|
||||
## 5. Run a build
|
||||
|
||||
1. Open **Actions > Compile StarPilot**.
|
||||
2. Click **Run workflow**.
|
||||
3. Enter the branch name in `target_ref`.
|
||||
4. Optionally set `jobs` to the parallelism you want for `./build`.
|
||||
5. Start the run.
|
||||
|
||||
On success, the workflow pushes a new commit named `build` back to that branch.
|
||||
|
||||
## Operational notes
|
||||
|
||||
- `RUNNER_TOKEN` is only required on first registration or when you want to replace a stale runner with the same name.
|
||||
- Runner state is persisted in `/runner`, so normal restarts do not need a new token.
|
||||
- `REMOVE_RUNNER_ON_EXIT=1` enables best-effort deregistration on shutdown, but that only works when the provided token is still valid.
|
||||
- The build cache, GitHub workspace, and `.comma_sysroot` data all persist under the runner dataset as long as the workflow runs in the same mounted work area.
|
||||
+4
-63
@@ -33,64 +33,7 @@ function agnos_init {
|
||||
# StarPilot variables
|
||||
sudo chmod 0777 /cache
|
||||
|
||||
# Weston loads display color correction from /data/misc/display/color_cal/color_cal.
|
||||
# Prefer a factory /persist/comma/color_cal blob when present. Otherwise, derive a
|
||||
# Weston-compatible calibration blob from the device's legacy dwo gamma tables.
|
||||
COLOR_CAL_SRC="/persist/comma/color_cal"
|
||||
DWO_GAMMA_SRC="/persist/comma/dwo_gamma_curves"
|
||||
COLOR_CAL_DST_DIR="/data/misc/display/color_cal"
|
||||
COLOR_CAL_DST="${COLOR_CAL_DST_DIR}/color_cal"
|
||||
COLOR_CAL_HASH_PATH="${COLOR_CAL_DST_DIR}/source.sha256"
|
||||
DWO_REFERENCE="$DIR/tools/reference_dwo_gamma_curves.txt"
|
||||
DWO_GENERATOR="$DIR/tools/generate_color_cal_from_dwo.py"
|
||||
COLOR_CAL_UPDATED=0
|
||||
COLOR_CAL_TMP=""
|
||||
DWO_SOURCE_HASH=""
|
||||
|
||||
if [ -f "$COLOR_CAL_SRC" ]; then
|
||||
sudo mkdir -p "$COLOR_CAL_DST_DIR"
|
||||
if [ ! -f "$COLOR_CAL_DST" ] || ! cmp -s "$COLOR_CAL_SRC" "$COLOR_CAL_DST"; then
|
||||
sudo cp "$COLOR_CAL_SRC" "$COLOR_CAL_DST"
|
||||
sudo chown -R comma:comma "$COLOR_CAL_DST_DIR"
|
||||
sudo chmod 664 "$COLOR_CAL_DST"
|
||||
sudo rm -f "$COLOR_CAL_HASH_PATH"
|
||||
COLOR_CAL_UPDATED=1
|
||||
fi
|
||||
elif [ -f "$DWO_GAMMA_SRC" ] && [ -f "$DWO_REFERENCE" ] && [ -f "$DWO_GENERATOR" ]; then
|
||||
DWO_SOURCE_HASH="$(cat "$DWO_GAMMA_SRC" "$DWO_REFERENCE" "$DWO_GENERATOR" | sha256sum | awk '{print $1}')"
|
||||
if [ ! -f "$COLOR_CAL_DST" ] || [ ! -f "$COLOR_CAL_HASH_PATH" ] || [ "$(cat "$COLOR_CAL_HASH_PATH" 2>/dev/null)" != "$DWO_SOURCE_HASH" ]; then
|
||||
COLOR_CAL_TMP="$(mktemp)"
|
||||
if python3 "$DWO_GENERATOR" --reference "$DWO_REFERENCE" --input "$DWO_GAMMA_SRC" --output "$COLOR_CAL_TMP"; then
|
||||
sudo mkdir -p "$COLOR_CAL_DST_DIR"
|
||||
if [ ! -f "$COLOR_CAL_DST" ] || ! cmp -s "$COLOR_CAL_TMP" "$COLOR_CAL_DST"; then
|
||||
sudo cp "$COLOR_CAL_TMP" "$COLOR_CAL_DST"
|
||||
COLOR_CAL_UPDATED=1
|
||||
fi
|
||||
printf '%s' "$DWO_SOURCE_HASH" | sudo tee "$COLOR_CAL_HASH_PATH" >/dev/null
|
||||
sudo chown -R comma:comma "$COLOR_CAL_DST_DIR"
|
||||
sudo chmod 664 "$COLOR_CAL_DST" "$COLOR_CAL_HASH_PATH"
|
||||
fi
|
||||
rm -f "$COLOR_CAL_TMP"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$COLOR_CAL_UPDATED" = "1" ] && systemctl is-active --quiet weston.service; then
|
||||
sudo systemctl restart weston.service
|
||||
|
||||
# Weston can recreate wayland-0 as root on service restart before weston-ready
|
||||
# fixes ownership. Repair it here so the Qt UI can always reconnect.
|
||||
if [ -d /var/tmp/weston ]; then
|
||||
for _ in $(seq 1 50); do
|
||||
if [ -S /var/tmp/weston/wayland-0 ]; then
|
||||
sudo chown -R comma:comma /var/tmp/weston 2>/dev/null || true
|
||||
sudo chmod -R 700 /var/tmp/weston 2>/dev/null || true
|
||||
SOCKET_OWNER="$(stat -c '%U:%G' /var/tmp/weston/wayland-0 2>/dev/null || true)"
|
||||
[ "$SOCKET_OWNER" = "comma:comma" ] && break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
fi
|
||||
fi
|
||||
sudo rm -f /data/misc/display/color_cal/color_cal /data/misc/display/color_cal/source.sha256
|
||||
|
||||
# Check if AGNOS update is required
|
||||
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
|
||||
@@ -179,7 +122,6 @@ mods = [
|
||||
"msgq.ipc_pyx",
|
||||
"msgq.visionipc.visionipc_pyx",
|
||||
"openpilot.common.transformations.transformations",
|
||||
"openpilot.selfdrive.modeld.models.commonmodel_pyx",
|
||||
"openpilot.selfdrive.pandad.pandad_api_impl",
|
||||
"openpilot.selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx",
|
||||
"openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx",
|
||||
@@ -194,12 +136,11 @@ for mod in mods:
|
||||
|
||||
repo_root = Path.cwd().parents[1]
|
||||
required_files = [
|
||||
repo_root / "selfdrive/modeld/models/driving_vision_metadata.pkl",
|
||||
repo_root / "selfdrive/modeld/models/driving_policy_metadata.pkl",
|
||||
repo_root / "selfdrive/modeld/models/driving_vision_tinygrad.pkl",
|
||||
repo_root / "selfdrive/modeld/models/driving_policy_tinygrad.pkl",
|
||||
repo_root / "selfdrive/modeld/models/driving_tinygrad.pkl",
|
||||
repo_root / "selfdrive/modeld/models/dmonitoring_model_metadata.pkl",
|
||||
repo_root / "selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl",
|
||||
repo_root / "selfdrive/modeld/models/dm_warp_1928x1208_tinygrad.pkl",
|
||||
repo_root / "selfdrive/modeld/models/dm_warp_1344x760_tinygrad.pkl",
|
||||
repo_root / "selfdrive/pandad/pandad_api_impl.so",
|
||||
repo_root / "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so",
|
||||
repo_root / "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so",
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ fi
|
||||
export QCOM_PRIORITY=12
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="12.8.16"
|
||||
export AGNOS_VERSION="12.8.17"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
@@ -0,0 +1,600 @@
|
||||
#!/bin/sh
|
||||
""":"
|
||||
REPO_ROOT="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
|
||||
PYTHON_BIN="$REPO_ROOT/.venv/bin/python"
|
||||
|
||||
if [ ! -x "$PYTHON_BIN" ]; then
|
||||
echo "Missing $PYTHON_BIN. Run 'uv sync' from $REPO_ROOT first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$PYTHON_BIN" "$0" "$@"
|
||||
":"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import bz2
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from email.utils import parsedate_to_datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import quote, urlparse
|
||||
|
||||
import requests
|
||||
import zstandard
|
||||
|
||||
from cereal import log
|
||||
from openpilot.tools.lib.auth_config import DEFAULT_API_HOST, KONIK_API_HOST, get_token, normalize_api_host
|
||||
|
||||
|
||||
API_HOST = normalize_api_host(os.getenv("COMMA_API_HOST") or os.getenv("API_HOST") or DEFAULT_API_HOST)
|
||||
API_HOSTS = [API_HOST] if os.getenv("COMMA_API_HOST") or os.getenv("API_HOST") else [DEFAULT_API_HOST, KONIK_API_HOST]
|
||||
ROUTE_ID_RE = re.compile(r"([0-9a-f]{16})/([^/]+)")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StreamSpec:
|
||||
api_key: str
|
||||
display_name: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RouteId:
|
||||
dongle_id: str
|
||||
log_id: str
|
||||
|
||||
@property
|
||||
def canonical_name(self) -> str:
|
||||
return f"{self.dongle_id}|{self.log_id}"
|
||||
|
||||
@property
|
||||
def cli_name(self) -> str:
|
||||
return f"{self.dongle_id}/{self.log_id}"
|
||||
|
||||
@property
|
||||
def safe_name(self) -> str:
|
||||
return f"{self.dongle_id}_{self.log_id}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DownloadedFile:
|
||||
segment: int
|
||||
stream: str
|
||||
filename: str
|
||||
relative_path: str
|
||||
mtime_epoch: int | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class MapTileSummary:
|
||||
qlogs_scanned: int = 0
|
||||
mapd_messages: int = 0
|
||||
tile_loaded_messages: int = 0
|
||||
first_tile_segment: int | None = None
|
||||
|
||||
@property
|
||||
def tile_loaded_any(self) -> bool:
|
||||
return self.tile_loaded_messages > 0
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
def __init__(self, route: RouteId, reasons: list[str]):
|
||||
self.route = route
|
||||
self.reasons = reasons
|
||||
super().__init__(self._render())
|
||||
|
||||
def _render(self) -> str:
|
||||
lines = [f"Validation failed for {self.route.cli_name}:"]
|
||||
lines.extend(f"- {reason}" for reason in self.reasons)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
REQUIRED_STREAMS = (
|
||||
StreamSpec("qlogs", "qlog"),
|
||||
StreamSpec("logs", "rlog"),
|
||||
StreamSpec("cameras", "fcamera.hevc"),
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
argv = list(sys.argv[1:])
|
||||
if argv and ROUTE_ID_RE.fullmatch(argv[0].lstrip("-")):
|
||||
if argv[0].startswith("--"):
|
||||
argv[0] = argv[0][2:]
|
||||
argv = ["--routeid", argv[0], *argv[1:]]
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Validate and bundle a public comma route for speed-limit vision review."
|
||||
)
|
||||
parser.add_argument("routeid_positional", nargs="?", help="Route id like 'dongle/logid' or 'dongle/logid--7'.")
|
||||
parser.add_argument("--routeid", help="Route id like 'dongle/logid' or 'dongle/logid--7'.")
|
||||
parser.add_argument("--desktop-root", type=Path, default=Path.home() / "Desktop", help="Destination root for the bundle folder and zip.")
|
||||
parser.add_argument("--timeout", type=float, default=60.0, help="HTTP timeout in seconds.")
|
||||
parser.add_argument("--overwrite", action="store_true", help="Replace an existing Desktop bundle for this route.")
|
||||
parser.add_argument("--validate-only", action="store_true", help="Run validation only and skip downloads/zipping.")
|
||||
args = parser.parse_args(argv)
|
||||
args.routeid = args.routeid or args.routeid_positional
|
||||
if not args.routeid:
|
||||
parser.error("the following arguments are required: --routeid")
|
||||
return args
|
||||
|
||||
|
||||
def parse_route_id(raw: str) -> RouteId:
|
||||
text = raw.strip().strip("'\"").replace("|", "/")
|
||||
match = ROUTE_ID_RE.fullmatch(text)
|
||||
if match is None:
|
||||
raise ValueError(f"Unrecognized route id: {raw}")
|
||||
|
||||
dongle_id = match.group(1)
|
||||
tail = match.group(2)
|
||||
parts = tail.split("--")
|
||||
if len(parts) == 3 and parts[-1].isdigit():
|
||||
log_id = "--".join(parts[:2])
|
||||
else:
|
||||
log_id = tail
|
||||
|
||||
if len(log_id) != 20:
|
||||
raise ValueError(f"Invalid log id in route: {raw}")
|
||||
return RouteId(dongle_id=dongle_id, log_id=log_id)
|
||||
|
||||
|
||||
def route_url(route: RouteId, api_host: str) -> str:
|
||||
return f"{api_host}/v1/route/{quote(route.canonical_name, safe='')}/"
|
||||
|
||||
|
||||
def route_files_url(route: RouteId, api_host: str) -> str:
|
||||
return f"{api_host}/v1/route/{quote(route.canonical_name, safe='')}/files"
|
||||
|
||||
|
||||
def format_segments(segments: list[int]) -> str:
|
||||
if not segments:
|
||||
return "none"
|
||||
|
||||
ranges: list[str] = []
|
||||
start = prev = segments[0]
|
||||
for segment in segments[1:]:
|
||||
if segment == prev + 1:
|
||||
prev = segment
|
||||
continue
|
||||
ranges.append(f"{start}-{prev}" if start != prev else str(start))
|
||||
start = prev = segment
|
||||
ranges.append(f"{start}-{prev}" if start != prev else str(start))
|
||||
return ", ".join(ranges)
|
||||
|
||||
|
||||
def as_int(value: Any) -> int | None:
|
||||
if isinstance(value, bool):
|
||||
return int(value)
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
if isinstance(value, str) and value.isdigit():
|
||||
return int(value)
|
||||
return None
|
||||
|
||||
|
||||
def relative_posix(path: Path, root: Path) -> str:
|
||||
return path.relative_to(root).as_posix()
|
||||
|
||||
|
||||
def api_headers(api_host: str) -> dict[str, str] | None:
|
||||
token = get_token(api_host)
|
||||
return {"Authorization": f"JWT {token}"} if token else None
|
||||
|
||||
|
||||
def fetch_json(session: requests.Session, url: str, timeout: float, api_host: str | None = None) -> Any:
|
||||
response = session.get(url, timeout=timeout, allow_redirects=True, headers=api_headers(api_host) if api_host else None)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def fetch_bytes(session: requests.Session, url: str, timeout: float) -> bytes:
|
||||
response = session.get(url, timeout=timeout, allow_redirects=True)
|
||||
response.raise_for_status()
|
||||
return response.content
|
||||
|
||||
|
||||
def fetch_stream_to_path(session: requests.Session, url: str, dest_path: Path, timeout: float) -> int | None:
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
response = session.get(url, stream=True, timeout=timeout, allow_redirects=True)
|
||||
response.raise_for_status()
|
||||
|
||||
temp_path = dest_path.with_suffix(dest_path.suffix + ".part")
|
||||
with temp_path.open("wb") as handle:
|
||||
for chunk in response.iter_content(chunk_size=1 << 20):
|
||||
if chunk:
|
||||
handle.write(chunk)
|
||||
temp_path.replace(dest_path)
|
||||
|
||||
last_modified = response.headers.get("Last-Modified")
|
||||
if not last_modified:
|
||||
return None
|
||||
|
||||
epoch = int(parsedate_to_datetime(last_modified).timestamp())
|
||||
os.utime(dest_path, (epoch, epoch))
|
||||
return epoch
|
||||
|
||||
|
||||
def decompress_log_bytes(payload: bytes) -> bytes:
|
||||
if payload[:3] == b"BZh":
|
||||
return bz2.decompress(payload)
|
||||
if payload[:4] == b"\x28\xb5\x2f\xfd":
|
||||
reader = zstandard.ZstdDecompressor().stream_reader(io.BytesIO(payload))
|
||||
try:
|
||||
return reader.read()
|
||||
finally:
|
||||
reader.close()
|
||||
return payload
|
||||
|
||||
|
||||
def load_init_params(qlog_bytes: bytes) -> dict[str, str]:
|
||||
payload = decompress_log_bytes(qlog_bytes)
|
||||
for msg in log.Event.read_multiple_bytes(payload):
|
||||
if msg.which() != "initData":
|
||||
continue
|
||||
|
||||
params: dict[str, str] = {}
|
||||
entries = msg.initData.params.to_dict().get("entries", [])
|
||||
for entry in entries:
|
||||
key = entry.get("key")
|
||||
if not key:
|
||||
continue
|
||||
value = entry.get("value", b"")
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode("utf-8", "replace")
|
||||
elif value is None:
|
||||
value = ""
|
||||
else:
|
||||
value = str(value)
|
||||
params[str(key)] = value
|
||||
return params
|
||||
|
||||
raise RuntimeError("No initData params found in qlog.")
|
||||
|
||||
|
||||
def scan_qlogs_for_map_tiles(session: requests.Session, qlog_urls: list[str | None], timeout: float) -> MapTileSummary:
|
||||
summary = MapTileSummary()
|
||||
for segment, url in enumerate(qlog_urls):
|
||||
if not isinstance(url, str) or not url:
|
||||
continue
|
||||
|
||||
summary.qlogs_scanned += 1
|
||||
payload = decompress_log_bytes(fetch_bytes(session, url, timeout))
|
||||
for msg in log.Event.read_multiple_bytes(payload):
|
||||
if msg.which() != "mapdOut":
|
||||
continue
|
||||
summary.mapd_messages += 1
|
||||
if msg.mapdOut.tileLoaded:
|
||||
summary.tile_loaded_messages += 1
|
||||
if summary.first_tile_segment is None:
|
||||
summary.first_tile_segment = segment
|
||||
if summary.tile_loaded_any:
|
||||
break
|
||||
return summary
|
||||
|
||||
|
||||
def expected_segment_count(route_meta: dict[str, Any], files_payload: dict[str, Any]) -> int:
|
||||
candidates: list[int] = []
|
||||
for key in ("maxqlog", "maxlog"):
|
||||
value = as_int(route_meta.get(key))
|
||||
if value is not None:
|
||||
candidates.append(value + 1)
|
||||
|
||||
for key in ("qlogs", "logs", "cameras"):
|
||||
value = files_payload.get(key)
|
||||
if isinstance(value, list):
|
||||
candidates.append(len(value))
|
||||
|
||||
return max(candidates, default=0)
|
||||
|
||||
|
||||
def collect_missing_segments(urls: list[Any], expected_segments: int) -> list[int]:
|
||||
missing: list[int] = []
|
||||
for segment in range(expected_segments):
|
||||
url = urls[segment] if segment < len(urls) else None
|
||||
if not isinstance(url, str) or not url:
|
||||
missing.append(segment)
|
||||
return missing
|
||||
|
||||
|
||||
def first_available_url(urls: list[Any]) -> str | None:
|
||||
for url in urls:
|
||||
if isinstance(url, str) and url:
|
||||
return url
|
||||
return None
|
||||
|
||||
|
||||
def filename_from_url(url: str) -> str:
|
||||
return Path(urlparse(url).path).name
|
||||
|
||||
|
||||
def validate_route(route: RouteId, session: requests.Session, timeout: float) -> dict[str, Any]:
|
||||
route_meta = None
|
||||
files_payload = None
|
||||
api_host = None
|
||||
not_found_hosts = []
|
||||
|
||||
for candidate_host in API_HOSTS:
|
||||
try:
|
||||
route_meta = fetch_json(session, route_url(route, candidate_host), timeout, candidate_host)
|
||||
except requests.HTTPError as exc:
|
||||
status_code = exc.response.status_code if exc.response is not None else "unknown"
|
||||
if status_code == 404 and len(API_HOSTS) > 1:
|
||||
not_found_hosts.append(candidate_host)
|
||||
continue
|
||||
raise ValidationError(route, [f"route is not publicly accessible from {candidate_host} (HTTP {status_code})."]) from exc
|
||||
|
||||
try:
|
||||
files_payload = fetch_json(session, route_files_url(route, candidate_host), timeout, candidate_host)
|
||||
except requests.HTTPError as exc:
|
||||
status_code = exc.response.status_code if exc.response is not None else "unknown"
|
||||
if status_code == 404 and len(API_HOSTS) > 1:
|
||||
not_found_hosts.append(candidate_host)
|
||||
continue
|
||||
raise ValidationError(route, [f"public route files could not be fetched from {candidate_host} (HTTP {status_code})."]) from exc
|
||||
|
||||
api_host = candidate_host
|
||||
break
|
||||
|
||||
if route_meta is None or files_payload is None or api_host is None:
|
||||
raise ValidationError(route, [f"route was not found on: {', '.join(not_found_hosts) or ', '.join(API_HOSTS)}."])
|
||||
|
||||
if not route_meta.get("is_public", False):
|
||||
raise ValidationError(route, ["route metadata loaded, but `is_public` was false."])
|
||||
|
||||
expected_segments = expected_segment_count(route_meta, files_payload)
|
||||
if expected_segments <= 0:
|
||||
raise ValidationError(route, ["could not determine any route segments from the route API."])
|
||||
|
||||
failures: list[str] = []
|
||||
stream_urls: dict[str, list[Any]] = {}
|
||||
for spec in REQUIRED_STREAMS:
|
||||
urls = files_payload.get(spec.api_key)
|
||||
if not isinstance(urls, list):
|
||||
urls = []
|
||||
stream_urls[spec.api_key] = urls
|
||||
missing_segments = collect_missing_segments(urls, expected_segments)
|
||||
if missing_segments:
|
||||
failures.append(f"missing uploaded {spec.display_name} files for segment(s): {format_segments(missing_segments)}.")
|
||||
|
||||
first_qlog_url = first_available_url(stream_urls["qlogs"])
|
||||
params: dict[str, str] = {}
|
||||
map_tiles = MapTileSummary()
|
||||
if first_qlog_url is None:
|
||||
failures.append("no qlog was available to inspect route params.")
|
||||
else:
|
||||
try:
|
||||
params = load_init_params(fetch_bytes(session, first_qlog_url, timeout))
|
||||
except Exception as exc:
|
||||
failures.append(f"could not read `initData.params` from the first qlog: {exc}.")
|
||||
|
||||
if params:
|
||||
auto_bookmark = params.get("VisionSpeedLimitAutoBookmark", "")
|
||||
if auto_bookmark != "1":
|
||||
failures.append(f"`VisionSpeedLimitAutoBookmark` was {auto_bookmark!r}; it must be '1'.")
|
||||
|
||||
vision_detection = params.get("VisionSpeedLimitDetection", "")
|
||||
if vision_detection != "1":
|
||||
failures.append(f"`VisionSpeedLimitDetection` was {vision_detection!r}; it must be '1' for useful vision-model collection.")
|
||||
|
||||
try:
|
||||
qlog_urls = [url if isinstance(url, str) and url else None for url in stream_urls["qlogs"]]
|
||||
map_tiles = scan_qlogs_for_map_tiles(session, qlog_urls, timeout)
|
||||
if not map_tiles.tile_loaded_any:
|
||||
maps_selected = params.get("MapsSelected", "")
|
||||
if maps_selected:
|
||||
failures.append(
|
||||
"offline map tiles never loaded during this route even though "
|
||||
f"`MapsSelected` was {maps_selected!r}; tell the user to download local maps for the driven area so collection trains faster."
|
||||
)
|
||||
else:
|
||||
failures.append(
|
||||
"offline map tiles never loaded during this route and `MapsSelected` was empty; "
|
||||
"tell the user to download local maps for the driven area so collection trains faster."
|
||||
)
|
||||
except Exception as exc:
|
||||
failures.append(f"could not inspect qlogs for offline map usage: {exc}.")
|
||||
|
||||
if failures:
|
||||
raise ValidationError(route, failures)
|
||||
|
||||
return {
|
||||
"api_host": api_host,
|
||||
"route_meta": route_meta,
|
||||
"files_payload": files_payload,
|
||||
"expected_segments": expected_segments,
|
||||
"params": params,
|
||||
"map_tiles": map_tiles,
|
||||
}
|
||||
|
||||
|
||||
def prepare_output_paths(route: RouteId, desktop_root: Path, overwrite: bool) -> tuple[Path, Path]:
|
||||
desktop_root = desktop_root.expanduser().resolve()
|
||||
desktop_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
bundle_root = desktop_root / f"speed_limit_route_bundle_{route.safe_name}"
|
||||
zip_path = desktop_root / f"{bundle_root.name}.zip"
|
||||
if overwrite:
|
||||
if bundle_root.exists():
|
||||
shutil.rmtree(bundle_root)
|
||||
if zip_path.exists():
|
||||
zip_path.unlink()
|
||||
else:
|
||||
if bundle_root.exists():
|
||||
raise FileExistsError(f"Bundle folder already exists: {bundle_root}")
|
||||
if zip_path.exists():
|
||||
raise FileExistsError(f"Bundle zip already exists: {zip_path}")
|
||||
return bundle_root, zip_path
|
||||
|
||||
|
||||
def write_bundle_manifests(
|
||||
bundle_root: Path,
|
||||
route: RouteId,
|
||||
validation: dict[str, Any],
|
||||
downloaded_files: list[DownloadedFile],
|
||||
) -> None:
|
||||
live_routes_meta = bundle_root / "live_routes_meta"
|
||||
live_routes_meta.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
files_txt_lines: list[str] = []
|
||||
qlog_mtimes_lines: list[str] = []
|
||||
for record in sorted(downloaded_files, key=lambda item: (item.segment, item.stream, item.filename)):
|
||||
segment_name = f"{route.log_id}--{record.segment}"
|
||||
files_txt_lines.append(f"{segment_name} {record.relative_path}")
|
||||
if record.filename.startswith("qlog") and record.mtime_epoch is not None:
|
||||
qlog_mtimes_lines.append(f"{record.relative_path} {record.mtime_epoch}")
|
||||
|
||||
(live_routes_meta / "files.txt").write_text("\n".join(files_txt_lines) + ("\n" if files_txt_lines else ""), encoding="utf-8")
|
||||
(live_routes_meta / "qlog_mtimes.txt").write_text("\n".join(qlog_mtimes_lines) + ("\n" if qlog_mtimes_lines else ""), encoding="utf-8")
|
||||
|
||||
params = validation["params"]
|
||||
route_meta = validation["route_meta"]
|
||||
map_tiles: MapTileSummary = validation["map_tiles"]
|
||||
manifest = {
|
||||
"bundleCreatedAt": datetime.now(UTC).isoformat(),
|
||||
"routeId": route.cli_name,
|
||||
"routeFullname": route.canonical_name,
|
||||
"expectedSegments": validation["expected_segments"],
|
||||
"downloadedStreams": [spec.display_name for spec in REQUIRED_STREAMS],
|
||||
"validation": {
|
||||
"isPublic": bool(route_meta.get("is_public")),
|
||||
"visionSpeedLimitAutoBookmark": params.get("VisionSpeedLimitAutoBookmark", ""),
|
||||
"visionSpeedLimitDetection": params.get("VisionSpeedLimitDetection", ""),
|
||||
"mapsSelected": params.get("MapsSelected", ""),
|
||||
"lastMapsUpdate": params.get("LastMapsUpdate", ""),
|
||||
"mapdMessagesScanned": map_tiles.mapd_messages,
|
||||
"offlineTileLoaded": map_tiles.tile_loaded_any,
|
||||
"firstTileSegment": map_tiles.first_tile_segment,
|
||||
},
|
||||
"routeMeta": {
|
||||
"startTime": route_meta.get("start_time"),
|
||||
"endTime": route_meta.get("end_time"),
|
||||
"distance": route_meta.get("distance"),
|
||||
"startLat": route_meta.get("start_lat"),
|
||||
"startLng": route_meta.get("start_lng"),
|
||||
"endLat": route_meta.get("end_lat"),
|
||||
"endLng": route_meta.get("end_lng"),
|
||||
"gitBranch": route_meta.get("git_branch"),
|
||||
"gitCommit": route_meta.get("git_commit"),
|
||||
"platform": route_meta.get("platform"),
|
||||
"version": route_meta.get("version"),
|
||||
"make": route_meta.get("make"),
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"segment": record.segment,
|
||||
"stream": record.stream,
|
||||
"filename": record.filename,
|
||||
"relativePath": record.relative_path,
|
||||
"mtimeEpoch": record.mtime_epoch,
|
||||
}
|
||||
for record in sorted(downloaded_files, key=lambda item: (item.segment, item.stream, item.filename))
|
||||
],
|
||||
}
|
||||
(bundle_root / "bundle_manifest.json").write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def zip_bundle(bundle_root: Path, zip_path: Path) -> None:
|
||||
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_STORED) as archive:
|
||||
for path in sorted(bundle_root.rglob("*")):
|
||||
if path.is_file():
|
||||
archive.write(path, arcname=Path(bundle_root.name) / path.relative_to(bundle_root))
|
||||
|
||||
|
||||
def download_bundle(
|
||||
route: RouteId,
|
||||
validation: dict[str, Any],
|
||||
session: requests.Session,
|
||||
desktop_root: Path,
|
||||
timeout: float,
|
||||
overwrite: bool,
|
||||
) -> tuple[Path, Path]:
|
||||
bundle_root, zip_path = prepare_output_paths(route, desktop_root, overwrite)
|
||||
clip_root = bundle_root / "data" / "media" / "0" / "realdata"
|
||||
|
||||
expected_segments = validation["expected_segments"]
|
||||
files_payload = validation["files_payload"]
|
||||
downloaded_files: list[DownloadedFile] = []
|
||||
|
||||
for spec in REQUIRED_STREAMS:
|
||||
urls = files_payload.get(spec.api_key, [])
|
||||
for segment in range(expected_segments):
|
||||
url = urls[segment]
|
||||
if not isinstance(url, str) or not url:
|
||||
raise RuntimeError(f"Unexpected missing {spec.display_name} URL for segment {segment} after validation.")
|
||||
|
||||
filename = filename_from_url(url)
|
||||
segment_name = f"{route.log_id}--{segment}"
|
||||
dest_path = clip_root / segment_name / filename
|
||||
print(f"Downloading {segment_name}/{filename}")
|
||||
mtime_epoch = fetch_stream_to_path(session, url, dest_path, timeout)
|
||||
downloaded_files.append(
|
||||
DownloadedFile(
|
||||
segment=segment,
|
||||
stream=spec.display_name,
|
||||
filename=filename,
|
||||
relative_path=relative_posix(dest_path, bundle_root),
|
||||
mtime_epoch=mtime_epoch,
|
||||
)
|
||||
)
|
||||
|
||||
write_bundle_manifests(bundle_root, route, validation, downloaded_files)
|
||||
print(f"Creating {zip_path.name}")
|
||||
zip_bundle(bundle_root, zip_path)
|
||||
return bundle_root, zip_path
|
||||
|
||||
|
||||
def print_validation_summary(route: RouteId, validation: dict[str, Any]) -> None:
|
||||
params = validation["params"]
|
||||
map_tiles: MapTileSummary = validation["map_tiles"]
|
||||
print(f"Validated {route.cli_name}")
|
||||
print(f" route API: {validation['api_host']}")
|
||||
print(f" public route: yes")
|
||||
print(f" segments: {validation['expected_segments']}")
|
||||
print(f" VisionSpeedLimitDetection: {params.get('VisionSpeedLimitDetection', '')}")
|
||||
print(f" VisionSpeedLimitAutoBookmark: {params.get('VisionSpeedLimitAutoBookmark', '')}")
|
||||
print(f" MapsSelected: {params.get('MapsSelected', '') or '(empty)'}")
|
||||
print(f" offline tile loaded: {'yes' if map_tiles.tile_loaded_any else 'no'}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
route = parse_route_id(args.routeid)
|
||||
|
||||
session = requests.Session()
|
||||
session.headers.update({"User-Agent": "starpilot-speed-limit-route-bundler/1.0"})
|
||||
|
||||
try:
|
||||
validation = validate_route(route, session, args.timeout)
|
||||
print_validation_summary(route, validation)
|
||||
|
||||
if args.validate_only:
|
||||
return 0
|
||||
|
||||
bundle_root, zip_path = download_bundle(route, validation, session, args.desktop_root, args.timeout, args.overwrite)
|
||||
print(f"Bundle folder: {bundle_root}")
|
||||
print(f"Bundle zip: {zip_path}")
|
||||
return 0
|
||||
except ValidationError as exc:
|
||||
print(str(exc), file=sys.stderr)
|
||||
return 2
|
||||
except FileExistsError as exc:
|
||||
print(f"{exc}\nUse --overwrite to replace the existing Desktop bundle, or --validate-only to skip packaging.", file=sys.stderr)
|
||||
return 3
|
||||
except Exception as exc:
|
||||
print(f"Error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -106,7 +106,7 @@ def make_tester_present_msg(addr, bus, subaddr=None, suppress_response=False):
|
||||
return CanData(addr, bytes(dat), bus)
|
||||
|
||||
|
||||
def get_safety_config(safety_model: structs.CarParams.SafetyModel, safety_param: int = None) -> structs.CarParams.SafetyConfig:
|
||||
def get_safety_config(safety_model, safety_param: int = None) -> structs.CarParams.SafetyConfig:
|
||||
ret = structs.CarParams.SafetyConfig()
|
||||
ret.safetyModel = safety_model
|
||||
if safety_param is not None:
|
||||
@@ -122,7 +122,7 @@ class CanBusBase:
|
||||
assert fingerprint is not None
|
||||
num = max([k for k, v in fingerprint.items() if len(v)], default=0) // 4 + 1
|
||||
else:
|
||||
num = len(CP.safetyConfigs)
|
||||
num = max(1, len(CP.safetyConfigs))
|
||||
self.offset = 4 * (num - 1)
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from opendbc.car.structs import CarParams, CarParamsT
|
||||
from opendbc.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
|
||||
from opendbc.car.fw_versions import ObdCallback, get_fw_versions_ordered, get_present_ecus, match_fw_to_car
|
||||
from opendbc.car.mock.values import CAR as MOCK
|
||||
from opendbc.car.toyota.values import ToyotaSafetyFlags
|
||||
from opendbc.car.values import BRANDS
|
||||
from opendbc.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN
|
||||
from openpilot.common.params import Params
|
||||
@@ -108,6 +109,20 @@ def _normalize_gm_bolt_candidate(candidate: str | None, fingerprints: dict[int,
|
||||
return candidate
|
||||
|
||||
|
||||
def _apply_disable_openpilot_long(CP: CarParams, FPCP: StarPilotCarParams) -> None:
|
||||
CP.openpilotLongitudinalControl = False
|
||||
CP.pcmCruise = True
|
||||
FPCP.openpilotLongitudinalControlDisabled = True
|
||||
|
||||
if CP.brand == "toyota":
|
||||
# Toyota stock longitudinal safety changes the forwarding rules so the
|
||||
# camera's ACC_CONTROL message can reach the PT bus again.
|
||||
for cfg in CP.safetyConfigs:
|
||||
cfg.safetyParam |= ToyotaSafetyFlags.STOCK_LONGITUDINAL.value
|
||||
for cfg in FPCP.safetyConfigs:
|
||||
cfg.safetyParam |= ToyotaSafetyFlags.STOCK_LONGITUDINAL.value
|
||||
|
||||
|
||||
def _normalize_gm_volt_candidate(candidate: str | None, fingerprints: dict[int, dict]) -> str | None:
|
||||
cam = fingerprints.get(GM_CAMERA_BUS, {})
|
||||
has_live_camera_msg = GM_VOLT_CAMERA_MSG in cam
|
||||
@@ -196,6 +211,11 @@ def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_mu
|
||||
disable_fw_cache = os.environ.get('DISABLE_FW_CACHE', False)
|
||||
ecu_rx_addrs = set()
|
||||
|
||||
if not fixed_fingerprint and Params().get_bool("NAPForcePreAP"):
|
||||
fixed_fingerprint = "TESLA_MODEL_S_PREAP"
|
||||
skip_fw_query = True
|
||||
carlog.warning("NAPForcePreAP enabled; forcing TESLA_MODEL_S_PREAP fingerprint")
|
||||
|
||||
start_time = time.monotonic()
|
||||
if not skip_fw_query:
|
||||
if cached_params is not None and cached_params.brand != "mock" and len(cached_params.carFw) > 0 and \
|
||||
@@ -314,12 +334,14 @@ def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multip
|
||||
CP.carFw = car_fw
|
||||
CP.fingerprintSource = source
|
||||
CP.fuzzyFingerprint = not exact_match
|
||||
post_fingerprint_params = getattr(CarInterface, "apply_post_fingerprint_params", None)
|
||||
if post_fingerprint_params is not None:
|
||||
post_fingerprint_params(CP, candidate, fingerprints, car_fw)
|
||||
|
||||
FPCP: StarPilotCarParams = CarInterface.get_starpilot_params(candidate, fingerprints, car_fw, CP, starpilot_toggles)
|
||||
|
||||
if not CP.alphaLongitudinalAvailable and starpilot_toggles.disable_openpilot_long:
|
||||
CP.openpilotLongitudinalControl = False
|
||||
FPCP.openpilotLongitudinalControlDisabled = True
|
||||
_apply_disable_openpilot_long(CP, FPCP)
|
||||
|
||||
return interfaces[CP.carFingerprint](CP, FPCP)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus, DT_CTRL
|
||||
from opendbc.car.lateral import apply_meas_steer_torque_limits
|
||||
from opendbc.car.chrysler import chryslercan
|
||||
from opendbc.car.chrysler.values import RAM_CARS, RAM_DT, CarControllerParams, ChryslerFlags
|
||||
from opendbc.car.chrysler.values import RAM_CARS, RAM_DT, CarControllerParams, ChryslerFlags, ChryslerStarPilotFlags
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
|
||||
|
||||
@@ -50,7 +50,9 @@ class CarController(CarControllerBase):
|
||||
|
||||
# TODO: can we make this more sane? why is it different for all the cars?
|
||||
lkas_control_bit = self.lkas_control_bit_prev
|
||||
if self.CP.carFingerprint in RAM_DT:
|
||||
if self.FPCP is not None and self.FPCP.flags & ChryslerStarPilotFlags.NO_MIN_STEERING_SPEED:
|
||||
lkas_control_bit = CC.latActive
|
||||
elif self.CP.carFingerprint in RAM_DT:
|
||||
if self.CP.minEnableSpeed <= CS.out.vEgo <= self.CP.minEnableSpeed + 0.5:
|
||||
lkas_control_bit = True
|
||||
if (self.CP.minEnableSpeed >= 14.5) and (CS.out.gearShifter != 2):
|
||||
|
||||
@@ -21,6 +21,7 @@ class ChryslerFlags(IntFlag):
|
||||
|
||||
class ChryslerStarPilotFlags(IntFlag):
|
||||
RAM_HD_ALT_BUTTONS = 1
|
||||
NO_MIN_STEERING_SPEED = 2
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -97,6 +98,25 @@ class CAR(Platforms):
|
||||
)
|
||||
|
||||
|
||||
PACIFICA_HYBRID_AOL_CARS = frozenset({
|
||||
CAR.CHRYSLER_PACIFICA_2019_HYBRID,
|
||||
})
|
||||
|
||||
|
||||
def pacifica_hybrid_aol_requires_set_press(car_fingerprint, pcm_cruise: bool) -> bool:
|
||||
return car_fingerprint in PACIFICA_HYBRID_AOL_CARS and pcm_cruise
|
||||
|
||||
|
||||
def pacifica_hybrid_aol_stock_acc_mode(car_fingerprint, pcm_cruise: bool,
|
||||
controls_enabled: bool, always_on_lateral_enabled: bool) -> bool:
|
||||
# Keep this narrow until we have logs proving other Chrysler platforms need the same exemption.
|
||||
return (
|
||||
pacifica_hybrid_aol_requires_set_press(car_fingerprint, pcm_cruise) and
|
||||
always_on_lateral_enabled and
|
||||
not controls_enabled
|
||||
)
|
||||
|
||||
|
||||
class CarControllerParams:
|
||||
def __init__(self, CP):
|
||||
self.STEER_STEP = 2 # 50 Hz
|
||||
|
||||
@@ -5,6 +5,8 @@ from opendbc.car.isotp_parallel_query import IsoTpParallelQuery
|
||||
|
||||
EXT_DIAG_REQUEST = b'\x10\x03'
|
||||
EXT_DIAG_RESPONSE = b'\x50\x03'
|
||||
RESET_REQUEST = b'\x11\x01'
|
||||
RESET_RESPONSE = b''
|
||||
|
||||
# File-based logging for debugging
|
||||
ECU_LOG_FILE = "/data/ecu_disable.log"
|
||||
@@ -21,7 +23,7 @@ def ecu_log(msg):
|
||||
pass
|
||||
|
||||
|
||||
def disable_ecu(can_recv, can_send, bus=0, addr=0x7d0, sub_addr=None, com_cont_req=b'\x28\x83\x01', timeout=0.1, retry=10):
|
||||
def disable_ecu(can_recv, can_send, bus=0, addr=0x7d0, sub_addr=None, com_cont_req=b'\x28\x83\x01', timeout=0.1, retry=10, reset=False):
|
||||
"""Silence an ECU by disabling sending and receiving messages using UDS 0x28.
|
||||
The ECU will stay silent as long as openpilot keeps sending Tester Present.
|
||||
|
||||
@@ -29,6 +31,15 @@ def disable_ecu(can_recv, can_send, bus=0, addr=0x7d0, sub_addr=None, com_cont_r
|
||||
WARNING: THIS DISABLES AEB!"""
|
||||
ecu_log(f"=== ECU DISABLE START === addr={hex(addr)}, bus={bus}")
|
||||
|
||||
if reset:
|
||||
try:
|
||||
ecu_log("sending ECU reset before communication control...")
|
||||
reset_query = IsoTpParallelQuery(can_send, can_recv, bus, [(addr, sub_addr)], [RESET_REQUEST], [RESET_RESPONSE])
|
||||
reset_query.get_data(timeout=timeout)
|
||||
time.sleep(0.2)
|
||||
except Exception as e:
|
||||
ecu_log(f"reset exception: {e}")
|
||||
|
||||
# Try multiple times with different approaches
|
||||
for i in range(retry):
|
||||
try:
|
||||
@@ -50,6 +61,7 @@ def disable_ecu(can_recv, can_send, bus=0, addr=0x7d0, sub_addr=None, com_cont_r
|
||||
# Log what we got back
|
||||
cc_success = False
|
||||
cc_rejected = False
|
||||
cc_nrc = None
|
||||
for (rx_addr, _), data in cc_response.items():
|
||||
ecu_log(f"CC response: {data.hex() if data else 'empty'}")
|
||||
# Check for positive response (0x68 = 0x28 + 0x40)
|
||||
@@ -59,6 +71,7 @@ def disable_ecu(can_recv, can_send, bus=0, addr=0x7d0, sub_addr=None, com_cont_r
|
||||
# Check for negative response
|
||||
elif len(data) >= 3 and data[0] == 0x7F:
|
||||
nrc = data[2]
|
||||
cc_nrc = nrc
|
||||
nrc_meanings = {
|
||||
0x12: "subFunctionNotSupported",
|
||||
0x13: "incorrectMessageLengthOrInvalidFormat",
|
||||
@@ -73,6 +86,10 @@ def disable_ecu(can_recv, can_send, bus=0, addr=0x7d0, sub_addr=None, com_cont_r
|
||||
if cc_success:
|
||||
return True
|
||||
elif cc_rejected:
|
||||
if reset and cc_nrc == 0x22 and i < retry - 1:
|
||||
ecu_log("CC rejected with NRC 0x22 after reset; retrying...")
|
||||
time.sleep(0.2)
|
||||
continue
|
||||
# ECU explicitly rejected - don't retry, it won't work
|
||||
ecu_log("=== ECU DISABLE REJECTED ===")
|
||||
return False
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
from types import SimpleNamespace
|
||||
from typing import get_args
|
||||
|
||||
from collections import defaultdict
|
||||
@@ -32,7 +33,8 @@ def get_params_for_docs(platform) -> CarParams:
|
||||
cp_platform = platform if platform in interfaces else MOCK.MOCK
|
||||
CP: CarParams = interfaces[cp_platform].get_params(cp_platform, fingerprint=gen_empty_fingerprint(),
|
||||
car_fw=[CarParams.CarFw(ecu=CarParams.Ecu.unknown)],
|
||||
alpha_long=True, is_release=False, docs=True)
|
||||
alpha_long=True, is_release=False, docs=True,
|
||||
starpilot_toggles=SimpleNamespace())
|
||||
return CP
|
||||
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ class CarHarness(EnumBase):
|
||||
hyundai_p = BaseCarHarness("Hyundai P connector")
|
||||
hyundai_q = BaseCarHarness("Hyundai Q connector")
|
||||
hyundai_r = BaseCarHarness("Hyundai R connector")
|
||||
hyundai_s = BaseCarHarness("Hyundai S connector")
|
||||
custom = BaseCarHarness("Developer connector")
|
||||
obd_ii = BaseCarHarness("OBD-II connector", parts=[Cable.long_obdc_cable, Cable.usbc_coupler], has_connector=False)
|
||||
gm = BaseCarHarness("GM connector", parts=[Accessory.harness_box])
|
||||
|
||||
@@ -72,6 +72,7 @@ MIGRATION = {
|
||||
"HONDA CR-V 2016 TOURING": HONDA.HONDA_CRV,
|
||||
"HONDA CR-V 2017 EX": HONDA.HONDA_CRV_5G,
|
||||
"HONDA CR-V 2019 HYBRID": HONDA.HONDA_CRV_HYBRID,
|
||||
"HONDA CLARITY 2018": HONDA.HONDA_CLARITY,
|
||||
"HONDA FIT 2018 EX": HONDA.HONDA_FIT,
|
||||
"HONDA HRV 2019 TOURING": HONDA.HONDA_HRV,
|
||||
"HONDA INSIGHT 2019 TOURING": HONDA.HONDA_INSIGHT,
|
||||
@@ -192,28 +193,43 @@ MIGRATION = {
|
||||
"HYUNDAI IONIQ PLUG-IN HYBRID 2019": HYUNDAI.HYUNDAI_IONIQ_PHEV_2019,
|
||||
"HYUNDAI IONIQ PHEV 2020": HYUNDAI.HYUNDAI_IONIQ_PHEV,
|
||||
"HYUNDAI KONA 2020": HYUNDAI.HYUNDAI_KONA,
|
||||
"HYUNDAI KONA 2ND GEN": HYUNDAI.HYUNDAI_KONA_2ND_GEN,
|
||||
"HYUNDAI KONA ELECTRIC 2019": HYUNDAI.HYUNDAI_KONA_EV,
|
||||
"HYUNDAI KONA ELECTRIC 2022": HYUNDAI.HYUNDAI_KONA_EV_2022,
|
||||
"HYUNDAI KONA ELECTRIC 2ND GEN": HYUNDAI.HYUNDAI_KONA_EV_2ND_GEN,
|
||||
"HYUNDAI KONA HYBRID 2020": HYUNDAI.HYUNDAI_KONA_HEV,
|
||||
"HYUNDAI KONA HYBRID 2ND GEN": HYUNDAI.HYUNDAI_KONA_HEV_2ND_GEN,
|
||||
"HYUNDAI SANTA FE 2019": HYUNDAI.HYUNDAI_SANTA_FE,
|
||||
"HYUNDAI SANTA FE 2022": HYUNDAI.HYUNDAI_SANTA_FE_2022,
|
||||
"HYUNDAI SANTA FE HYBRID 2022": HYUNDAI.HYUNDAI_SANTA_FE_HEV_2022,
|
||||
"HYUNDAI SANTA FE HYBRID 5TH GEN": HYUNDAI.HYUNDAI_SANTA_FE_HEV_5TH_GEN,
|
||||
"HYUNDAI SANTA FE PlUG-IN HYBRID 2022": HYUNDAI.HYUNDAI_SANTA_FE_PHEV_2022,
|
||||
"HYUNDAI SONATA 2020": HYUNDAI.HYUNDAI_SONATA,
|
||||
"HYUNDAI SONATA 2024": HYUNDAI.HYUNDAI_SONATA_2024,
|
||||
"HYUNDAI SONATA 2019": HYUNDAI.HYUNDAI_SONATA_LF,
|
||||
"HYUNDAI STARIA 4TH GEN": HYUNDAI.HYUNDAI_STARIA_4TH_GEN,
|
||||
"HYUNDAI TUCSON 2019": HYUNDAI.HYUNDAI_TUCSON,
|
||||
"HYUNDAI TUCSON 2025": HYUNDAI.HYUNDAI_TUCSON_2025,
|
||||
"HYUNDAI TUCSON HYBRID 2025": HYUNDAI.HYUNDAI_TUCSON_HEV_2025,
|
||||
"HYUNDAI TUCSON PLUG-IN HYBRID 2025": HYUNDAI.HYUNDAI_TUCSON_PHEV_2025,
|
||||
"HYUNDAI PALISADE 2020": HYUNDAI.HYUNDAI_PALISADE,
|
||||
"HYUNDAI PALISADE 2023": HYUNDAI.HYUNDAI_PALISADE_2023,
|
||||
"HYUNDAI VELOSTER 2019": HYUNDAI.HYUNDAI_VELOSTER,
|
||||
"HYUNDAI SONATA HYBRID 2021": HYUNDAI.HYUNDAI_SONATA_HYBRID,
|
||||
"HYUNDAI SONATA HYBRID 2024": HYUNDAI.HYUNDAI_SONATA_HEV_2024,
|
||||
"HYUNDAI IONIQ 5 2022": HYUNDAI.HYUNDAI_IONIQ_5,
|
||||
"HYUNDAI IONIQ 5 2025": HYUNDAI.HYUNDAI_IONIQ_5_PE,
|
||||
"HYUNDAI IONIQ 5 N 2024": HYUNDAI.HYUNDAI_IONIQ_5_N,
|
||||
"HYUNDAI IONIQ 6 2023": HYUNDAI.HYUNDAI_IONIQ_6,
|
||||
"HYUNDAI IONIQ 9 2025": HYUNDAI.HYUNDAI_IONIQ_9,
|
||||
"HYUNDAI TUCSON 4TH GEN": HYUNDAI.HYUNDAI_TUCSON_4TH_GEN,
|
||||
"HYUNDAI SANTA CRUZ 1ST GEN": HYUNDAI.HYUNDAI_SANTA_CRUZ_1ST_GEN,
|
||||
"HYUNDAI SANTA CRUZ 2025": HYUNDAI.HYUNDAI_SANTA_CRUZ_2025,
|
||||
"HYUNDAI CUSTIN 1ST GEN": HYUNDAI.HYUNDAI_CUSTIN_1ST_GEN,
|
||||
"KIA FORTE E 2018 & GT 2021": HYUNDAI.KIA_FORTE,
|
||||
"KIA K4 2025": HYUNDAI.KIA_K4_2025,
|
||||
"KIA K5 2021": HYUNDAI.KIA_K5_2021,
|
||||
"KIA K5 2025": HYUNDAI.KIA_K5_2025,
|
||||
"KIA K5 HYBRID 2020": HYUNDAI.KIA_K5_HEV_2020,
|
||||
"KIA K8 HYBRID 1ST GEN": HYUNDAI.KIA_K8_HEV_1ST_GEN,
|
||||
"KIA NIRO EV 2020": HYUNDAI.KIA_NIRO_EV,
|
||||
@@ -228,22 +244,29 @@ MIGRATION = {
|
||||
"KIA OPTIMA HYBRID 4TH GEN FACELIFT": HYUNDAI.KIA_OPTIMA_H_G4_FL,
|
||||
"KIA SELTOS 2021": HYUNDAI.KIA_SELTOS,
|
||||
"KIA SPORTAGE 5TH GEN": HYUNDAI.KIA_SPORTAGE_5TH_GEN,
|
||||
"KIA SPORTAGE 2026": HYUNDAI.KIA_SPORTAGE_2026,
|
||||
"KIA SPORTAGE HYBRID 2026": HYUNDAI.KIA_SPORTAGE_HEV_2026,
|
||||
"KIA SORENTO GT LINE 2018": HYUNDAI.KIA_SORENTO,
|
||||
"KIA SORENTO 4TH GEN": HYUNDAI.KIA_SORENTO_4TH_GEN,
|
||||
"KIA SORENTO 2024": HYUNDAI.KIA_SORENTO_2024,
|
||||
"KIA SORENTO HYBRID 4TH GEN": HYUNDAI.KIA_SORENTO_HEV_4TH_GEN,
|
||||
"KIA SORENTO HYBRID 4TH GEN LFA2": HYUNDAI.KIA_SORENTO_HEV_4TH_GEN_LFA2,
|
||||
"KIA STINGER GT2 2018": HYUNDAI.KIA_STINGER,
|
||||
"KIA STINGER 2022": HYUNDAI.KIA_STINGER_2022,
|
||||
"KIA CEED INTRO ED 2019": HYUNDAI.KIA_CEED,
|
||||
"KIA EV6 2022": HYUNDAI.KIA_EV6,
|
||||
"KIA EV6 2025": HYUNDAI.KIA_EV6_2025,
|
||||
"KIA EV9 2025": HYUNDAI.KIA_EV9,
|
||||
"KIA CARNIVAL 4TH GEN": HYUNDAI.KIA_CARNIVAL_4TH_GEN,
|
||||
"GENESIS GV60 ELECTRIC 1ST GEN": HYUNDAI.GENESIS_GV60_EV_1ST_GEN,
|
||||
"GENESIS G70 2018": HYUNDAI.GENESIS_G70,
|
||||
"GENESIS G70 2020": HYUNDAI.GENESIS_G70_2020,
|
||||
"GENESIS GV70 1ST GEN": HYUNDAI.GENESIS_GV70_1ST_GEN,
|
||||
"GENESIS GV70 ELECTRIFIED 2026": HYUNDAI.GENESIS_GV70_ELECTRIFIED_2ND_GEN,
|
||||
"GENESIS G80 2017": HYUNDAI.GENESIS_G80,
|
||||
"GENESIS G90 2017": HYUNDAI.GENESIS_G90,
|
||||
"GENESIS GV80 2023": HYUNDAI.GENESIS_GV80,
|
||||
"GENESIS GV80 2025": HYUNDAI.GENESIS_GV80_2025,
|
||||
"MAZDA CX-5": MAZDA.MAZDA_CX5,
|
||||
"MAZDA CX-9": MAZDA.MAZDA_CX9,
|
||||
"MAZDA 3": MAZDA.MAZDA_3,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from opendbc.car.gm.values import AccState, CAR
|
||||
|
||||
|
||||
def get_stock_cc_active_for_cancel(CP, CS):
|
||||
stock_cc_active = CS.out.cruiseState.enabled or CS.pcm_acc_status != AccState.OFF
|
||||
if CP.carFingerprint == CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL:
|
||||
return CS.out.cruiseState.enabled
|
||||
return stock_cc_active
|
||||
@@ -7,20 +7,65 @@ from opendbc.car.gm import gmcan
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.gm.values import (
|
||||
ASCM_INT, CAR, CC_ONLY_CAR, CC_REGEN_PADDLE_CAR, DBC, EV_CAR, SDGM_CAR, AccState, CanBus, CarControllerParams,
|
||||
CruiseButtons, GMFlags,
|
||||
CruiseButtons, GMFlags, GMSafetyFlags,
|
||||
)
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from openpilot.common.pid import PIDController
|
||||
from openpilot.common.params import Params, UnknownKeyName
|
||||
from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
NetworkLocation = structs.CarParams.NetworkLocation
|
||||
TransmissionType = structs.CarParams.TransmissionType
|
||||
LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
GearShifter = structs.CarState.GearShifter
|
||||
|
||||
# Camera cancels up to 0.1s after brake is pressed, ECM allows 0.5s
|
||||
CAMERA_CANCEL_DELAY_FRAMES = 10
|
||||
# Enforce a minimum interval between steering messages to avoid a fault
|
||||
MIN_STEER_MSG_INTERVAL_MS = 15
|
||||
AUTO_HOLD_VOLT_CARS = {
|
||||
CAR.CHEVROLET_VOLT,
|
||||
CAR.CHEVROLET_VOLT_2019,
|
||||
CAR.CHEVROLET_VOLT_ASCM,
|
||||
CAR.CHEVROLET_VOLT_CAMERA,
|
||||
}
|
||||
AUTO_HOLD_DRIVE_GEARS = (
|
||||
GearShifter.drive,
|
||||
GearShifter.low,
|
||||
GearShifter.manumatic,
|
||||
)
|
||||
AUTO_HOLD_MIN_BRAKE = 80
|
||||
AUTO_HOLD_MAX_BRAKE = 240
|
||||
AUTO_HOLD_MIN_DRIVE_TIME_S = 3.0
|
||||
AUTO_HOLD_STOPPED_SPEED = 0.02
|
||||
AUTO_HOLD_2019_MIN_BRAKE = 100
|
||||
BOLT_ACC_PEDAL_FRICTION_RELEASE_FRAMES = 5
|
||||
BOLT_PEDAL_LONG_ACCEL_LIMIT_BP = [0.0, 1.5, 4.0, 8.0, 15.0, 30.0]
|
||||
BOLT_PEDAL_LONG_ACCEL_LIMIT_V = [-0.93, -1.28, -1.98, -2.58, -2.86, -2.95]
|
||||
VOLT_ONE_PEDAL_DECEL_BP = [0.5 * CV.MPH_TO_MS, 6.0 * CV.MPH_TO_MS]
|
||||
VOLT_ONE_PEDAL_DECEL_V = [-1.0, -1.1]
|
||||
VOLT_ONE_PEDAL_REGEN_PADDLE_DECEL_V = [-1.5, -1.6]
|
||||
VOLT_ONE_PEDAL_MAX_DECEL = min((*VOLT_ONE_PEDAL_DECEL_V, *VOLT_ONE_PEDAL_REGEN_PADDLE_DECEL_V)) - 0.5
|
||||
VOLT_ONE_PEDAL_PID_NEG_LIMIT = -3.5
|
||||
VOLT_ONE_PEDAL_SPEED_ERROR_FACTOR_BP = [1.5, 20.0]
|
||||
VOLT_ONE_PEDAL_SPEED_ERROR_FACTOR_V = [0.4, 0.2]
|
||||
VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_SPEED_FACTOR_BP = [0.0, 10.0 * CV.MPH_TO_MS]
|
||||
VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_SPEED_FACTOR_V = [0.2, 1.0]
|
||||
VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_STEER_FACTOR_BP = [20.0, 120.0]
|
||||
VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_STEER_FACTOR_V = [1.0, 0.2]
|
||||
VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_UP = 0.8 * DT_CTRL * 4
|
||||
VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_DOWN = 0.8 * DT_CTRL * 4
|
||||
VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_BP = [4.0, 8.0]
|
||||
VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_V = [0.4, 1.0]
|
||||
VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_INCLINE_V = [0.2, 1.0]
|
||||
VOLT_ONE_PEDAL_LIFT_BRAKE_BP = [0.0, CarControllerParams.NEAR_STOP_BRAKE_PHASE, 2.0 * CV.MPH_TO_MS]
|
||||
VOLT_ONE_PEDAL_LIFT_BRAKE_V = [AUTO_HOLD_MIN_BRAKE, AUTO_HOLD_MIN_BRAKE, 20.0]
|
||||
VOLT_ONE_PEDAL_LIFT_BRAKE_FRAMES = 8
|
||||
TRUCK_LONG_SMOOTH_CARS = {
|
||||
CAR.CHEVROLET_SILVERADO,
|
||||
CAR.CHEVROLET_SILVERADO_CC,
|
||||
}
|
||||
|
||||
|
||||
def get_stock_cc_active_for_cancel(CP, CS):
|
||||
@@ -36,7 +81,13 @@ def use_interceptor_sng_launch(CP, CS, maneuver_mode=False):
|
||||
launch_speed = max(CP.vEgoStarting, 0.3)
|
||||
if maneuver_mode:
|
||||
launch_speed = max(launch_speed, 2.0)
|
||||
return CS.out.cruiseState.standstill and (CS.out.standstill or CS.out.vEgo < launch_speed)
|
||||
near_stop = CS.out.standstill or CS.out.vEgo < launch_speed
|
||||
if (
|
||||
getattr(CP, "carFingerprint", None) == CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL and
|
||||
getattr(CP, "enableGasInterceptorDEPRECATED", False)
|
||||
):
|
||||
return near_stop
|
||||
return CS.out.cruiseState.standstill and near_stop
|
||||
|
||||
|
||||
def should_spoof_dash_speed(CP, starpilot_toggles):
|
||||
@@ -62,6 +113,28 @@ def should_send_acc_dashboard_status(CP, dash_speed_spoof_active):
|
||||
return status_car and (dash_speed_spoof_active or volt_camera_no_camera)
|
||||
|
||||
|
||||
def get_acc_dashboard_status_active(CP, CC):
|
||||
if CC.enabled:
|
||||
return True
|
||||
|
||||
return CP.carFingerprint == CAR.BUICK_LACROSSE_ASCM and CC.latActive
|
||||
|
||||
|
||||
def get_acc_dashboard_fcw_alert(hud_alert, CS):
|
||||
if hud_alert == VisualAlert.fcw:
|
||||
return 0x3
|
||||
|
||||
stock_fcw_alert = int(getattr(CS, "stock_fcw_alert", 0)) & 0x3
|
||||
if stock_fcw_alert != 0:
|
||||
return stock_fcw_alert
|
||||
|
||||
cs_out = getattr(CS, "out", None)
|
||||
if cs_out is not None and (getattr(cs_out, "stockAeb", False) or getattr(cs_out, "stockFcw", False)):
|
||||
return 0x3
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
ECM_CRUISE_SPOOF_CARS = {
|
||||
CAR.CHEVROLET_BOLT_CC_2017,
|
||||
CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
@@ -97,10 +170,245 @@ def get_adas_keepalive_step(CP, is_kaofui_car):
|
||||
return None
|
||||
|
||||
|
||||
def should_send_adas_status(CP, is_kaofui_car):
|
||||
if CP.radarUnavailable:
|
||||
return False
|
||||
|
||||
if not is_kaofui_car:
|
||||
return True
|
||||
|
||||
if CP.carFingerprint in ASCM_INT:
|
||||
return False
|
||||
|
||||
return CP.networkLocation != NetworkLocation.fwdCamera and CP.carFingerprint not in SDGM_CAR
|
||||
|
||||
|
||||
def get_testing_ground_1_brake_switch_bias(v_ego: float) -> int:
|
||||
return int(round(np.interp(v_ego, [0.0, 6.0, 15.0, 30.0], [40.0, 85.0, 130.0, 170.0])))
|
||||
|
||||
|
||||
def shape_truck_positive_accel(accel: float, v_ego: float, enabled: bool) -> float:
|
||||
if not enabled or accel <= 0.0 or v_ego < 12.0:
|
||||
return accel
|
||||
|
||||
low_scale = float(np.interp(v_ego, [12.0, 18.0, 25.0, 35.0], [0.95, 0.88, 0.82, 0.76]))
|
||||
mid_scale = float(np.interp(v_ego, [12.0, 18.0, 25.0, 35.0], [0.98, 0.94, 0.89, 0.84]))
|
||||
|
||||
if accel <= 0.12:
|
||||
return accel * low_scale
|
||||
if accel <= 0.35:
|
||||
return float(np.interp(accel, [0.12, 0.35], [0.12 * low_scale, 0.35 * mid_scale]))
|
||||
if accel <= 0.65:
|
||||
return float(np.interp(accel, [0.35, 0.65], [0.35 * mid_scale, 0.65]))
|
||||
return accel
|
||||
|
||||
|
||||
def get_lka_steering_cmd_counter(next_counter: int, CS) -> int:
|
||||
if getattr(CS, "loopback_lka_steering_cmd_updated", False):
|
||||
return (getattr(CS, "loopback_lka_steering_cmd_counter", next_counter) + 1) % 4
|
||||
if next_counter < 0 and getattr(CS, "loopback_lka_steering_cmd_ts_nanos", 0) == 0:
|
||||
return (getattr(CS, "pt_lka_steering_cmd_counter", next_counter) + 1) % 4
|
||||
return next_counter
|
||||
|
||||
|
||||
def should_send_stock_long_cancel(cancel_counter: int, CS) -> bool:
|
||||
cs_out = getattr(CS, "out", None)
|
||||
return cancel_counter > CAMERA_CANCEL_DELAY_FRAMES and not bool(getattr(cs_out, "accFaulted", False))
|
||||
|
||||
|
||||
def supports_volt_auto_hold(CP, auto_hold_enabled: bool):
|
||||
safety_cfg = getattr(CP, "safetyConfigs", ())
|
||||
safety_param = safety_cfg[0].safetyParam if safety_cfg else 0
|
||||
stock_hold_safety_ready = bool(safety_param & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value)
|
||||
return (
|
||||
auto_hold_enabled and
|
||||
stock_hold_safety_ready and
|
||||
CP.carFingerprint in AUTO_HOLD_VOLT_CARS
|
||||
)
|
||||
|
||||
|
||||
def supports_volt_one_pedal(CP, one_pedal_enabled: bool):
|
||||
safety_cfg = getattr(CP, "safetyConfigs", ())
|
||||
safety_param = safety_cfg[0].safetyParam if safety_cfg else 0
|
||||
stock_hold_safety_ready = bool(safety_param & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value)
|
||||
return (
|
||||
one_pedal_enabled and
|
||||
stock_hold_safety_ready and
|
||||
getattr(CP, "transmissionType", None) == TransmissionType.direct and
|
||||
CP.carFingerprint in AUTO_HOLD_VOLT_CARS
|
||||
)
|
||||
|
||||
|
||||
def estimate_auto_hold_brake(driver_brake: float, op_brake: float, CP=None) -> int:
|
||||
driver_hold = np.interp(float(driver_brake), [8.0, 20.0, 40.0, 80.0], [80.0, 110.0, 150.0, 220.0])
|
||||
hold_brake = max(float(op_brake), float(driver_hold))
|
||||
min_brake = AUTO_HOLD_2019_MIN_BRAKE if getattr(CP, "carFingerprint", None) == CAR.CHEVROLET_VOLT_2019 else AUTO_HOLD_MIN_BRAKE
|
||||
return int(round(np.clip(hold_brake, min_brake, AUTO_HOLD_MAX_BRAKE)))
|
||||
|
||||
|
||||
def get_auto_hold_stop_threshold(CP, auto_hold_engaged: bool) -> float:
|
||||
if auto_hold_engaged and getattr(CP, "carFingerprint", None) == CAR.CHEVROLET_VOLT_2019:
|
||||
return CarControllerParams.NEAR_STOP_BRAKE_PHASE
|
||||
return AUTO_HOLD_STOPPED_SPEED
|
||||
|
||||
|
||||
def get_volt_one_pedal_target_decel(v_ego: float) -> float:
|
||||
return float(np.interp(v_ego, VOLT_ONE_PEDAL_DECEL_BP, VOLT_ONE_PEDAL_DECEL_V))
|
||||
|
||||
|
||||
def get_volt_one_pedal_lift_brake(v_ego: float) -> int:
|
||||
if v_ego > VOLT_ONE_PEDAL_LIFT_BRAKE_BP[-1]:
|
||||
return 0
|
||||
|
||||
return int(round(np.interp(v_ego, VOLT_ONE_PEDAL_LIFT_BRAKE_BP, VOLT_ONE_PEDAL_LIFT_BRAKE_V)))
|
||||
|
||||
|
||||
def should_activate_volt_one_pedal(one_pedal_ready: bool, cruise_main: bool, long_active: bool,
|
||||
gas_pressed: bool, brake_pressed: bool, regen_braking: bool,
|
||||
single_pedal_mode: bool, gear_shifter, drive_time_s: float) -> bool:
|
||||
# Volt rear wheel direction bits can falsely report reverse while stopping in L.
|
||||
return (
|
||||
one_pedal_ready and
|
||||
cruise_main and
|
||||
single_pedal_mode and
|
||||
gear_shifter in AUTO_HOLD_DRIVE_GEARS and
|
||||
drive_time_s >= AUTO_HOLD_MIN_DRIVE_TIME_S and
|
||||
not long_active and
|
||||
not gas_pressed and
|
||||
not brake_pressed and
|
||||
not regen_braking
|
||||
)
|
||||
|
||||
|
||||
def should_activate_auto_hold(hold_ready: bool, auto_hold_armed: bool, auto_hold_engaged: bool,
|
||||
brake_pressed: bool, gas_pressed: bool, standstill: bool, long_active: bool,
|
||||
regen_braking: bool, v_ego: float, stop_speed_threshold: float=AUTO_HOLD_STOPPED_SPEED) -> bool:
|
||||
stopped = standstill or v_ego < stop_speed_threshold
|
||||
return (
|
||||
hold_ready and
|
||||
(auto_hold_armed or auto_hold_engaged or brake_pressed) and
|
||||
not gas_pressed and
|
||||
stopped and
|
||||
not long_active and
|
||||
not regen_braking
|
||||
)
|
||||
|
||||
|
||||
def get_friction_brake_bus(CP):
|
||||
volt_gateway_alt_brake = (
|
||||
CP.carFingerprint == CAR.CHEVROLET_VOLT and
|
||||
CP.networkLocation == NetworkLocation.gateway and
|
||||
bool(CP.flags & GMFlags.NO_ACCELERATOR_POS_MSG.value)
|
||||
)
|
||||
if volt_gateway_alt_brake:
|
||||
return CanBus.POWERTRAIN
|
||||
|
||||
if CP.networkLocation == NetworkLocation.fwdCamera:
|
||||
if CP.carFingerprint in SDGM_CAR:
|
||||
return CanBus.CAMERA
|
||||
return CanBus.POWERTRAIN
|
||||
|
||||
return CanBus.CHASSIS
|
||||
|
||||
|
||||
def supports_bolt_acc_pedal_friction_experiment(CP) -> bool:
|
||||
return (
|
||||
CP.carFingerprint == CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL and
|
||||
CP.openpilotLongitudinalControl and
|
||||
CP.enableGasInterceptorDEPRECATED and
|
||||
bool(CP.flags & GMFlags.PEDAL_LONG.value)
|
||||
)
|
||||
|
||||
|
||||
def get_bolt_acc_pedal_friction_brake(apply_brake, full_brake_accel, v_ego, params) -> int:
|
||||
if apply_brake <= 0:
|
||||
return 0
|
||||
|
||||
full_brake_accel = min(full_brake_accel, -0.1)
|
||||
legacy_full_scale = max(-params.ACCEL_MIN, 0.1)
|
||||
corrected_scale = legacy_full_scale / max(-full_brake_accel, 0.1)
|
||||
speed_gain = float(np.interp(v_ego, [0.0, 8.0, 15.0, 25.0], [1.0, 1.08, 1.2, 1.35]))
|
||||
onset_gain = float(np.interp(
|
||||
apply_brake,
|
||||
[0.0, 5.0, 20.0, 60.0, 120.0, 240.0, params.MAX_BRAKE],
|
||||
[0.0, 1.8, 1.65, 1.4, 1.22, 1.08, 1.0],
|
||||
))
|
||||
|
||||
shaped_brake = apply_brake * corrected_scale * speed_gain * onset_gain
|
||||
minimum_brake = float(np.interp(v_ego, [0.0, 6.0, 8.0, 12.0, 18.0, 25.0], [0.0, 0.0, 4.0, 10.0, 20.0, 28.0]))
|
||||
shaped_brake = max(shaped_brake, minimum_brake)
|
||||
|
||||
return int(round(np.clip(shaped_brake, 0, params.MAX_BRAKE)))
|
||||
|
||||
|
||||
def shape_bolt_acc_pedal_low_speed_friction(apply_brake: int, v_ego: float, stopping: bool, active: bool):
|
||||
if apply_brake <= 0:
|
||||
return 0, False
|
||||
|
||||
engage_threshold = float(np.interp(v_ego, [0.0, 1.5, 3.0, 5.0, 8.0], [40.0, 20.0, 12.0, 10.0, 0.0]))
|
||||
release_threshold = float(np.interp(v_ego, [0.0, 1.5, 3.0, 5.0, 8.0], [0.0, 8.0, 6.0, 4.0, 0.0]))
|
||||
|
||||
if not active:
|
||||
if apply_brake < engage_threshold:
|
||||
return 0, False
|
||||
active = True
|
||||
elif apply_brake < release_threshold:
|
||||
return 0, False
|
||||
|
||||
if stopping:
|
||||
stop_fade = float(np.interp(v_ego, [0.0, 0.6, 0.9, 1.2, 1.8, 2.8], [0.0, 0.0, 0.05, 0.12, 0.32, 0.78]))
|
||||
apply_brake = int(round(apply_brake * stop_fade))
|
||||
if apply_brake <= 0 or apply_brake < release_threshold:
|
||||
return 0, False
|
||||
|
||||
return apply_brake, active
|
||||
|
||||
|
||||
def get_bolt_pedal_long_accel_limit(v_ego: float) -> float:
|
||||
return float(np.interp(v_ego, BOLT_PEDAL_LONG_ACCEL_LIMIT_BP, BOLT_PEDAL_LONG_ACCEL_LIMIT_V))
|
||||
|
||||
|
||||
def get_bolt_acc_pedal_planner_brake_switch(v_ego: float, params, tire_radius: float, mass: float,
|
||||
coeff_drag: float, frontal_area: float, air_density: float) -> int:
|
||||
planner_accel_limit = get_bolt_pedal_long_accel_limit(v_ego)
|
||||
aero_drag_force = 0.5 * coeff_drag * frontal_area * air_density * v_ego ** 2
|
||||
planner_torque = tire_radius * ((mass * planner_accel_limit) + aero_drag_force)
|
||||
return int(round(planner_torque + params.ZERO_GAS))
|
||||
|
||||
|
||||
def get_bolt_acc_pedal_effective_brake_switch(stock_switch: int, planner_switch: int) -> int:
|
||||
return max(stock_switch, planner_switch)
|
||||
|
||||
|
||||
def get_bolt_acc_pedal_friction_command_state(apply_brake: int, cruise_main_on: bool, release_frames: int):
|
||||
command_brake = apply_brake if cruise_main_on else 0
|
||||
|
||||
if command_brake > 0:
|
||||
release_frames = BOLT_ACC_PEDAL_FRICTION_RELEASE_FRAMES
|
||||
elif release_frames > 0:
|
||||
release_frames -= 1
|
||||
|
||||
should_send = cruise_main_on or release_frames > 0
|
||||
return command_brake, release_frames, should_send
|
||||
|
||||
|
||||
def get_interceptor_sng_gas_cmd(CP, interceptor_gas_cmd: float, accel: float, params, maneuver_mode: bool) -> float:
|
||||
if maneuver_mode:
|
||||
return max(interceptor_gas_cmd, float(np.interp(accel, [0.0, 1.0, 2.0], [params.SNG_INTERCEPTOR_GAS, 0.11, 0.16])))
|
||||
|
||||
if supports_bolt_acc_pedal_friction_experiment(CP):
|
||||
return max(interceptor_gas_cmd, params.SNG_INTERCEPTOR_GAS)
|
||||
|
||||
return params.SNG_INTERCEPTOR_GAS
|
||||
|
||||
|
||||
def should_use_fixed_stopping_brake(CP, near_stop: bool, stopping: bool, resume: bool) -> bool:
|
||||
if not (near_stop and stopping and not resume):
|
||||
return False
|
||||
|
||||
return not supports_bolt_acc_pedal_friction_experiment(CP)
|
||||
|
||||
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_names, CP):
|
||||
super().__init__(dbc_names, CP)
|
||||
@@ -153,6 +461,60 @@ class CarController(CarControllerBase):
|
||||
self.malibu_cancel_phase = 0
|
||||
self.malibu_button_phase = 0
|
||||
self.malibu_last_button_ts_nanos = 0
|
||||
self.auto_hold_brake = 0
|
||||
self.volt_one_pedal_pid = PIDController(
|
||||
(CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV),
|
||||
(CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV),
|
||||
rate=1 / (DT_CTRL * 4),
|
||||
pos_limit=0.0,
|
||||
neg_limit=VOLT_ONE_PEDAL_PID_NEG_LIMIT,
|
||||
)
|
||||
self.volt_one_pedal_decel = 0.0
|
||||
self.volt_one_pedal_brake = 0
|
||||
self.volt_one_pedal_lift_frames = 0
|
||||
self.volt_one_pedal_gas_pressed_last = False
|
||||
try:
|
||||
self.gm_auto_hold_enabled = self.params_.get_bool("GMAutoHold")
|
||||
except UnknownKeyName:
|
||||
self.gm_auto_hold_enabled = False
|
||||
self.bolt_acc_pedal_friction_release_frames = 0
|
||||
self.bolt_acc_pedal_friction_low_speed_active = False
|
||||
|
||||
def _reset_volt_one_pedal(self):
|
||||
self.volt_one_pedal_pid.reset()
|
||||
self.volt_one_pedal_decel = min(0.0, float(self.aego))
|
||||
self.volt_one_pedal_brake = 0
|
||||
self.volt_one_pedal_lift_frames = 0
|
||||
|
||||
def _update_volt_one_pedal_brake(self, CC, CS):
|
||||
pitch_accel = 0.0
|
||||
if len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping:
|
||||
pitch_accel = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY
|
||||
pitch_factor_values = VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_V if pitch_accel <= 0.0 else VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_INCLINE_V
|
||||
pitch_accel *= float(np.interp(CS.out.vEgo, VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_BP, pitch_factor_values))
|
||||
|
||||
target_decel = get_volt_one_pedal_target_decel(CS.out.vEgo)
|
||||
measured_decel = min(0.0, CS.out.aEgo + pitch_accel)
|
||||
error_factor = float(np.interp(CS.out.vEgo, VOLT_ONE_PEDAL_SPEED_ERROR_FACTOR_BP, VOLT_ONE_PEDAL_SPEED_ERROR_FACTOR_V))
|
||||
error = (target_decel - measured_decel) * error_factor
|
||||
|
||||
raw_decel = float(self.volt_one_pedal_pid.update(error, speed=CS.out.vEgo, feedforward=target_decel))
|
||||
rate_limit_factor = min(
|
||||
float(np.interp(CS.out.vEgo, VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_SPEED_FACTOR_BP, VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_SPEED_FACTOR_V)),
|
||||
float(np.interp(abs(CS.out.steeringAngleDeg), VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_STEER_FACTOR_BP, VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_STEER_FACTOR_V)),
|
||||
)
|
||||
lower = min(self.volt_one_pedal_decel, measured_decel) - VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_UP * rate_limit_factor
|
||||
upper = max(self.volt_one_pedal_decel, measured_decel) + VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_DOWN + rate_limit_factor
|
||||
self.volt_one_pedal_decel = float(np.clip(raw_decel, lower, upper))
|
||||
self.volt_one_pedal_decel = max(self.volt_one_pedal_decel, VOLT_ONE_PEDAL_MAX_DECEL)
|
||||
self.volt_one_pedal_brake = int(round(np.clip(
|
||||
np.interp(self.volt_one_pedal_decel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V),
|
||||
0,
|
||||
self.params.MAX_BRAKE,
|
||||
)))
|
||||
if self.volt_one_pedal_lift_frames > 0:
|
||||
self.volt_one_pedal_brake = max(self.volt_one_pedal_brake, get_volt_one_pedal_lift_brake(CS.out.vEgo))
|
||||
self.volt_one_pedal_lift_frames -= 1
|
||||
|
||||
def calc_pedal_command(self, accel: float, long_active: bool, v_ego: float):
|
||||
if not long_active:
|
||||
@@ -275,6 +637,9 @@ class CarController(CarControllerBase):
|
||||
else:
|
||||
urgency = float(np.clip(abs(accel) / 2.0, 0.0, 1.0))
|
||||
rate_up = np.interp(v_ego, [0.0, 3.0, 8.0, 20.0], [0.007, 0.012, 0.022, 0.036]) + 0.011 * urgency
|
||||
if accel > 0.0 and v_ego > 6.0:
|
||||
comfort_factor = np.interp(abs(accel), [0.0, 0.12, 0.25, 0.45, 0.8], [0.55, 0.58, 0.68, 0.82, 1.0])
|
||||
rate_up *= comfort_factor
|
||||
if accel > 1.2:
|
||||
rate_up += np.interp(v_ego, [0.0, 4.0, 12.0, 25.0], [0.006, 0.005, 0.003, 0.002])
|
||||
rate_down = np.interp(v_ego, [0.0, 3.0, 8.0, 20.0], [0.008, 0.014, 0.026, 0.045]) + 0.015 * urgency
|
||||
@@ -302,8 +667,64 @@ class CarController(CarControllerBase):
|
||||
self.aego = CS.out.aEgo
|
||||
accel = actuators.accel
|
||||
press_regen_paddle = False
|
||||
auto_hold_enabled = supports_volt_auto_hold(self.CP, self.gm_auto_hold_enabled)
|
||||
volt_one_pedal_supported = supports_volt_one_pedal(
|
||||
self.CP, bool(getattr(starpilot_toggles, "volt_one_pedal_mode", False))
|
||||
)
|
||||
volt_one_pedal_active = should_activate_volt_one_pedal(
|
||||
volt_one_pedal_supported,
|
||||
CS.out.cruiseState.available,
|
||||
CC.longActive,
|
||||
CS.out.gasPressed,
|
||||
CS.out.brakePressed,
|
||||
CS.out.regenBraking,
|
||||
bool(getattr(CS, "single_pedal_mode", False)),
|
||||
CS.out.gearShifter,
|
||||
float(getattr(CS, "one_pedal_drive_time", 0.0)),
|
||||
)
|
||||
if volt_one_pedal_active and self.volt_one_pedal_gas_pressed_last and not CS.out.gasPressed:
|
||||
if CS.out.vEgo < VOLT_ONE_PEDAL_LIFT_BRAKE_BP[-1]:
|
||||
self.volt_one_pedal_lift_frames = VOLT_ONE_PEDAL_LIFT_BRAKE_FRAMES
|
||||
elif CS.out.gasPressed or not volt_one_pedal_active:
|
||||
self.volt_one_pedal_lift_frames = 0
|
||||
|
||||
if self.frame % 4 == 0:
|
||||
if volt_one_pedal_active:
|
||||
self._update_volt_one_pedal_brake(CC, CS)
|
||||
else:
|
||||
self._reset_volt_one_pedal()
|
||||
if not self.CP.openpilotLongitudinalControl:
|
||||
self.apply_gas = 0
|
||||
self.apply_brake = self.volt_one_pedal_brake if volt_one_pedal_active else 0
|
||||
self.volt_one_pedal_gas_pressed_last = CS.out.gasPressed
|
||||
|
||||
stock_hold_apply_brake = max(self.apply_brake if self.CP.openpilotLongitudinalControl else 0, self.volt_one_pedal_brake)
|
||||
|
||||
hold_ready = (
|
||||
auto_hold_enabled and
|
||||
CS.out.cruiseState.available and
|
||||
CS.out.gearShifter in AUTO_HOLD_DRIVE_GEARS and
|
||||
CS.auto_hold_drive_time >= AUTO_HOLD_MIN_DRIVE_TIME_S
|
||||
)
|
||||
if not hold_ready or CS.out.gasPressed:
|
||||
CS.auto_hold_armed = False
|
||||
if CS.out.gasPressed:
|
||||
CS.auto_hold_engaged = False
|
||||
elif CS.regen_release_timer > 0.0:
|
||||
CS.auto_hold_armed = False
|
||||
elif not CS.auto_hold_armed and (CS.out.vEgo > 0.03 or ((CS.out.standstill or CS.out.vEgo < 0.02) and CS.out.brakePressed)):
|
||||
CS.auto_hold_armed = True
|
||||
|
||||
if CS.out.vEgo > 0.1 or CS.out.gasPressed or CS.out.gearShifter not in AUTO_HOLD_DRIVE_GEARS:
|
||||
self.auto_hold_brake = 0
|
||||
elif CS.out.brakePressed or stock_hold_apply_brake > 0:
|
||||
self.auto_hold_brake = estimate_auto_hold_brake(CS.out.brake, stock_hold_apply_brake, self.CP)
|
||||
|
||||
if self.frame % 25 == 0:
|
||||
try:
|
||||
self.gm_auto_hold_enabled = self.params_.get_bool("GMAutoHold")
|
||||
except UnknownKeyName:
|
||||
self.gm_auto_hold_enabled = False
|
||||
try:
|
||||
mode = self.params_.get("LongitudinalManeuverPaddleMode")
|
||||
except UnknownKeyName:
|
||||
@@ -383,6 +804,29 @@ class CarController(CarControllerBase):
|
||||
paddle_sched_feed_active = False
|
||||
|
||||
paddle_spoof_pressed = raw_regen_active and (CS.out.vEgo > 2.68)
|
||||
auto_hold_active = should_activate_auto_hold(
|
||||
hold_ready,
|
||||
CS.auto_hold_armed,
|
||||
CS.auto_hold_engaged,
|
||||
CS.out.brakePressed,
|
||||
CS.out.gasPressed,
|
||||
CS.out.standstill,
|
||||
CC.longActive,
|
||||
CS.out.regenBraking,
|
||||
CS.out.vEgo,
|
||||
get_auto_hold_stop_threshold(self.CP, CS.auto_hold_engaged),
|
||||
)
|
||||
bolt_acc_pedal_friction_experiment = supports_bolt_acc_pedal_friction_experiment(self.CP)
|
||||
bolt_acc_pedal_friction_main_on = bolt_acc_pedal_friction_experiment and CS.out.cruiseState.available
|
||||
if not bolt_acc_pedal_friction_main_on:
|
||||
self.bolt_acc_pedal_friction_low_speed_active = False
|
||||
volt_one_pedal_braking = volt_one_pedal_active and self.volt_one_pedal_brake > 0
|
||||
volt_one_pedal_hold_active = (
|
||||
volt_one_pedal_braking and
|
||||
not auto_hold_active and
|
||||
CS.one_pedal_drive_time >= AUTO_HOLD_MIN_DRIVE_TIME_S and
|
||||
(CS.out.standstill or CS.out.vEgo < 0.02)
|
||||
)
|
||||
|
||||
# Steering (Active: 50Hz, inactive: 10Hz)
|
||||
steer_step = self.params.STEER_STEP if CC.latActive else self.params.INACTIVE_STEER_STEP
|
||||
@@ -436,6 +880,7 @@ class CarController(CarControllerBase):
|
||||
# ASCM sends max regen when not enabled
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
self.apply_brake = 0
|
||||
self.bolt_acc_pedal_friction_low_speed_active = False
|
||||
self.planner_regen_hold = False
|
||||
self.regen_paddle_pressed = False
|
||||
self.regen_paddle_timer = 0
|
||||
@@ -443,7 +888,7 @@ class CarController(CarControllerBase):
|
||||
self.regen_release_counter = 0
|
||||
self.regen_min_on_frames = 0
|
||||
self.regen_min_off_frames = 0
|
||||
elif near_stop and stopping and not CC.cruiseControl.resume:
|
||||
elif should_use_fixed_stopping_brake(self.CP, near_stop, stopping, CC.cruiseControl.resume):
|
||||
stop_accel = getattr(starpilot_toggles, "stopAccel", self.CP.stopAccel)
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
self.apply_brake = int(min(-100 * stop_accel, self.params.MAX_BRAKE))
|
||||
@@ -487,7 +932,16 @@ class CarController(CarControllerBase):
|
||||
if testing_ground.use_1:
|
||||
accel_max = min(accel_max, np.interp(CS.out.vEgo, [0.0, 4.0, 12.0], [1.25, 1.6, self.params.ACCEL_MAX]))
|
||||
|
||||
accel_cmd = float(np.clip(actuators.accel + accel_due_to_pitch, self.params.ACCEL_MIN, accel_max))
|
||||
accel_input = actuators.accel + accel_due_to_pitch
|
||||
if (
|
||||
getattr(starpilot_toggles, "truck_tuning", False) and
|
||||
self.CP.carFingerprint in TRUCK_LONG_SMOOTH_CARS and
|
||||
getattr(self.CP, "transmissionType", None) == TransmissionType.automatic and
|
||||
not self.CP.enableGasInterceptorDEPRECATED
|
||||
):
|
||||
accel_input = shape_truck_positive_accel(accel_input, CS.out.vEgo, True)
|
||||
|
||||
accel_cmd = float(np.clip(accel_input, self.params.ACCEL_MIN, accel_max))
|
||||
torque = self.tireRadius * ((self.mass * accel_cmd) + (0.5 * self.coeffDrag * self.frontalArea * self.airDensity * CS.out.vEgo ** 2))
|
||||
scaled_torque = torque + self.params.ZERO_GAS
|
||||
apply_gas_torque = np.clip(scaled_torque, self.params.MAX_ACC_REGEN, gas_max)
|
||||
@@ -495,9 +949,27 @@ class CarController(CarControllerBase):
|
||||
if testing_ground.use_1:
|
||||
brake_switch_bias = get_testing_ground_1_brake_switch_bias(CS.out.vEgo)
|
||||
brake_switch = min(self.params.ZERO_GAS, brake_switch + brake_switch_bias)
|
||||
if bolt_acc_pedal_friction_main_on:
|
||||
planner_brake_switch = get_bolt_acc_pedal_planner_brake_switch(
|
||||
CS.out.vEgo, self.params, self.tireRadius, self.mass, self.coeffDrag, self.frontalArea, self.airDensity,
|
||||
)
|
||||
brake_switch = get_bolt_acc_pedal_effective_brake_switch(brake_switch, planner_brake_switch)
|
||||
brake_accel = min((scaled_torque - brake_switch) / (self.tireRadius * self.mass), 0)
|
||||
self.apply_gas = int(round(apply_gas_torque))
|
||||
self.apply_brake = int(round(np.interp(brake_accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))
|
||||
if bolt_acc_pedal_friction_main_on:
|
||||
if self.apply_brake > 0:
|
||||
full_brake_accel = min(
|
||||
self.params.ACCEL_MIN + (0.5 * self.coeffDrag * self.frontalArea * self.airDensity * CS.out.vEgo ** 2) / self.mass +
|
||||
(self.params.ZERO_GAS - brake_switch) / (self.tireRadius * self.mass),
|
||||
-0.1,
|
||||
)
|
||||
self.apply_brake = get_bolt_acc_pedal_friction_brake(
|
||||
self.apply_brake, full_brake_accel, CS.out.vEgo, self.params,
|
||||
)
|
||||
self.apply_brake, self.bolt_acc_pedal_friction_low_speed_active = shape_bolt_acc_pedal_low_speed_friction(
|
||||
self.apply_brake, CS.out.vEgo, stopping, self.bolt_acc_pedal_friction_low_speed_active,
|
||||
)
|
||||
if self.apply_brake > 0:
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
|
||||
@@ -508,15 +980,19 @@ class CarController(CarControllerBase):
|
||||
# gas interceptor only used for full long control on cars without ACC
|
||||
interceptor_gas_cmd, press_regen_paddle = self.calc_pedal_command(actuators.accel, CC.longActive, CS.out.vEgo)
|
||||
|
||||
if volt_one_pedal_braking:
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
self.apply_brake = max(self.apply_brake, self.volt_one_pedal_brake)
|
||||
|
||||
maneuver_sng_launch = self.longitudinal_maneuver_mode and self.is_volt
|
||||
if (
|
||||
self.CP.enableGasInterceptorDEPRECATED and
|
||||
self.apply_gas > self.params.INACTIVE_REGEN and
|
||||
use_interceptor_sng_launch(self.CP, CS, maneuver_sng_launch)
|
||||
):
|
||||
interceptor_gas_cmd = self.params.SNG_INTERCEPTOR_GAS
|
||||
if maneuver_sng_launch:
|
||||
interceptor_gas_cmd = max(interceptor_gas_cmd, float(np.interp(actuators.accel, [0.0, 1.0, 2.0], [self.params.SNG_INTERCEPTOR_GAS, 0.11, 0.16])))
|
||||
interceptor_gas_cmd = get_interceptor_sng_gas_cmd(
|
||||
self.CP, interceptor_gas_cmd, actuators.accel, self.params, maneuver_sng_launch,
|
||||
)
|
||||
self.apply_brake = 0
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
|
||||
@@ -550,15 +1026,33 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.POWERTRAIN, (CS.buttons_counter + 1) % 4, CruiseButtons.DECEL_SET))
|
||||
if self.CP.enableGasInterceptorDEPRECATED:
|
||||
can_sends.append(create_gas_interceptor_command(self.packer_pt, interceptor_gas_cmd, idx))
|
||||
if bolt_acc_pedal_friction_experiment:
|
||||
friction_brake_bus = get_friction_brake_bus(self.CP)
|
||||
if self.CP.networkLocation == NetworkLocation.fwdCamera:
|
||||
at_full_stop = at_full_stop and stopping
|
||||
|
||||
experiment_brake, self.bolt_acc_pedal_friction_release_frames, should_send_bolt_acc_pedal_friction = \
|
||||
get_bolt_acc_pedal_friction_command_state(
|
||||
self.apply_brake,
|
||||
bolt_acc_pedal_friction_main_on,
|
||||
self.bolt_acc_pedal_friction_release_frames,
|
||||
)
|
||||
|
||||
# This fingerprint is routed through the CC-only pedal path, so it
|
||||
# does not fall through to the normal friction-brake sender below.
|
||||
# Never apply stock friction with cruise main off, but do send a short
|
||||
# explicit zero-brake unwind so the last nonzero stock-EBCM command
|
||||
# cannot linger after a disengage or main-off event.
|
||||
if should_send_bolt_acc_pedal_friction:
|
||||
can_sends.append(gmcan.create_friction_brake_command(
|
||||
self.packer_ch, friction_brake_bus, experiment_brake, idx, bolt_acc_pedal_friction_main_on,
|
||||
near_stop, at_full_stop, self.CP))
|
||||
if self.CP.carFingerprint not in CC_ONLY_CAR:
|
||||
friction_brake_bus = CanBus.CHASSIS
|
||||
friction_brake_bus = get_friction_brake_bus(self.CP)
|
||||
# GM Camera exceptions
|
||||
# TODO: can we always check the longControlState?
|
||||
if self.CP.networkLocation == NetworkLocation.fwdCamera:
|
||||
at_full_stop = at_full_stop and stopping
|
||||
friction_brake_bus = CanBus.POWERTRAIN
|
||||
if self.CP.carFingerprint in SDGM_CAR:
|
||||
friction_brake_bus = CanBus.CAMERA
|
||||
|
||||
if self.CP.autoResumeSng:
|
||||
resume = actuators.longControlState != LongCtrlState.starting or CC.cruiseControl.resume
|
||||
@@ -569,47 +1063,66 @@ class CarController(CarControllerBase):
|
||||
else:
|
||||
acc_engaged = CC.enabled
|
||||
|
||||
# GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation
|
||||
can_sends.append(gmcan.create_gas_regen_command(
|
||||
self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, acc_engaged, at_full_stop,
|
||||
include_always_one3=self.CP.carFingerprint in kaofui_cars, use_volt_layout=self.is_volt))
|
||||
can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake,
|
||||
idx, CC.enabled, near_stop, at_full_stop, self.CP))
|
||||
if auto_hold_active:
|
||||
hold_brake = max(self.volt_one_pedal_brake, self.auto_hold_brake or estimate_auto_hold_brake(CS.out.brake, self.apply_brake, self.CP))
|
||||
hold_standstill = CS.pcm_acc_status == AccState.STANDSTILL
|
||||
hold_near_stop = CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE
|
||||
can_sends.append(gmcan.create_friction_brake_command(
|
||||
self.packer_ch, friction_brake_bus, hold_brake, idx, False, hold_near_stop, hold_standstill,
|
||||
self.CP, allow_near_stop_mode=True))
|
||||
CS.auto_hold_engaged = True
|
||||
CS.auto_hold_fault_suppression_timer = 1.0
|
||||
elif volt_one_pedal_hold_active:
|
||||
hold_brake = max(self.volt_one_pedal_brake, self.auto_hold_brake or estimate_auto_hold_brake(0.0, self.volt_one_pedal_brake, self.CP))
|
||||
hold_standstill = CS.pcm_acc_status == AccState.STANDSTILL
|
||||
hold_near_stop = CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE
|
||||
can_sends.append(gmcan.create_friction_brake_command(
|
||||
self.packer_ch, friction_brake_bus, hold_brake, idx, False, hold_near_stop, hold_standstill,
|
||||
self.CP, allow_near_stop_mode=True))
|
||||
CS.auto_hold_engaged = True
|
||||
CS.auto_hold_fault_suppression_timer = 1.0
|
||||
else:
|
||||
if volt_one_pedal_braking:
|
||||
at_full_stop = at_full_stop or CS.pcm_acc_status == AccState.STANDSTILL
|
||||
near_stop = near_stop or (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE)
|
||||
# GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation
|
||||
can_sends.append(gmcan.create_gas_regen_command(
|
||||
self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, acc_engaged, at_full_stop,
|
||||
include_always_one3=self.CP.carFingerprint in kaofui_cars, use_volt_layout=self.is_volt))
|
||||
can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake,
|
||||
idx, CC.enabled, near_stop, at_full_stop, self.CP,
|
||||
allow_near_stop_mode=volt_one_pedal_braking))
|
||||
CS.auto_hold_engaged = False
|
||||
|
||||
if should_send_acc_dashboard_status(self.CP, dash_speed_spoof_active):
|
||||
send_fcw = hud_alert == VisualAlert.fcw
|
||||
can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, CC.enabled,
|
||||
hud_v_cruise * CV.MS_TO_KPH, hud_control, send_fcw))
|
||||
fcw_alert = get_acc_dashboard_fcw_alert(hud_alert, CS)
|
||||
acc_dashboard_status_active = get_acc_dashboard_status_active(self.CP, CC)
|
||||
can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, acc_dashboard_status_active,
|
||||
hud_v_cruise * CV.MS_TO_KPH, hud_control, fcw_alert))
|
||||
|
||||
# Radar needs to know current speed and yaw rate (50hz),
|
||||
# and that ADAS is alive (10hz)
|
||||
if not self.CP.radarUnavailable:
|
||||
send_adas = True
|
||||
if should_send_adas_status(self.CP, self.CP.carFingerprint in kaofui_cars):
|
||||
tt = self.frame * DT_CTRL
|
||||
if self.CP.carFingerprint in kaofui_cars:
|
||||
if self.CP.carFingerprint not in ASCM_INT:
|
||||
send_adas = (self.CP.networkLocation != NetworkLocation.fwdCamera) and (self.CP.carFingerprint not in SDGM_CAR)
|
||||
|
||||
if send_adas:
|
||||
tt = self.frame * DT_CTRL
|
||||
if self.CP.carFingerprint in kaofui_cars:
|
||||
time_and_headlights_step = 10
|
||||
speed_and_accelerometer_step = 2
|
||||
if self.frame % time_and_headlights_step == 0:
|
||||
idx = (self.frame // time_and_headlights_step) % 4
|
||||
can_sends.append(gmcan.create_adas_time_status(CanBus.OBSTACLE, int((tt - self.start_time) * 60), idx))
|
||||
can_sends.append(gmcan.create_adas_headlights_status(self.packer_obj, CanBus.OBSTACLE))
|
||||
if self.frame % speed_and_accelerometer_step == 0:
|
||||
idx = (self.frame // speed_and_accelerometer_step) % 4
|
||||
can_sends.append(gmcan.create_adas_steering_status(CanBus.OBSTACLE, idx))
|
||||
can_sends.append(gmcan.create_adas_accelerometer_speed_status(CanBus.OBSTACLE, CS.out.vEgo, idx))
|
||||
else:
|
||||
time_and_headlights_step = 20
|
||||
if self.frame % time_and_headlights_step == 0:
|
||||
idx = (self.frame // time_and_headlights_step) % 4
|
||||
can_sends.append(gmcan.create_adas_time_status(CanBus.OBSTACLE, int((tt - self.start_time) * 60), idx))
|
||||
can_sends.append(gmcan.create_adas_headlights_status(self.packer_obj, CanBus.OBSTACLE))
|
||||
can_sends.append(gmcan.create_adas_steering_status(CanBus.OBSTACLE, idx))
|
||||
can_sends.append(gmcan.create_adas_accelerometer_speed_status(CanBus.OBSTACLE, CS.out.vEgo, idx))
|
||||
time_and_headlights_step = 10
|
||||
speed_and_accelerometer_step = 2
|
||||
if self.frame % time_and_headlights_step == 0:
|
||||
idx = (self.frame // time_and_headlights_step) % 4
|
||||
can_sends.append(gmcan.create_adas_time_status(CanBus.OBSTACLE, int((tt - self.start_time) * 60), idx))
|
||||
can_sends.append(gmcan.create_adas_headlights_status(self.packer_obj, CanBus.OBSTACLE))
|
||||
if self.frame % speed_and_accelerometer_step == 0:
|
||||
idx = (self.frame // speed_and_accelerometer_step) % 4
|
||||
can_sends.append(gmcan.create_adas_steering_status(CanBus.OBSTACLE, idx))
|
||||
can_sends.append(gmcan.create_adas_accelerometer_speed_status(CanBus.OBSTACLE, CS.out.vEgo, idx))
|
||||
else:
|
||||
time_and_headlights_step = 20
|
||||
if self.frame % time_and_headlights_step == 0:
|
||||
idx = (self.frame // time_and_headlights_step) % 4
|
||||
can_sends.append(gmcan.create_adas_time_status(CanBus.OBSTACLE, int((tt - self.start_time) * 60), idx))
|
||||
can_sends.append(gmcan.create_adas_headlights_status(self.packer_obj, CanBus.OBSTACLE))
|
||||
can_sends.append(gmcan.create_adas_steering_status(CanBus.OBSTACLE, idx))
|
||||
can_sends.append(gmcan.create_adas_accelerometer_speed_status(CanBus.OBSTACLE, CS.out.vEgo, idx))
|
||||
|
||||
keepalive_step = get_adas_keepalive_step(self.CP, self.CP.carFingerprint in kaofui_cars)
|
||||
if keepalive_step is not None and self.frame % keepalive_step == 0:
|
||||
@@ -633,6 +1146,37 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(gmcan.create_buttons(self.packer_pt, cancel_bus, (CS.buttons_counter + 1) % 4, CruiseButtons.CANCEL))
|
||||
|
||||
else:
|
||||
if self.frame % 4 == 0 and auto_hold_active:
|
||||
idx = (self.frame // 4) % 4
|
||||
hold_brake = max(self.volt_one_pedal_brake, self.auto_hold_brake or estimate_auto_hold_brake(CS.out.brake, stock_hold_apply_brake, self.CP))
|
||||
hold_standstill = CS.pcm_acc_status == AccState.STANDSTILL
|
||||
hold_near_stop = CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE
|
||||
can_sends.append(gmcan.create_friction_brake_command(
|
||||
self.packer_ch, get_friction_brake_bus(self.CP), hold_brake, idx, False, hold_near_stop, hold_standstill,
|
||||
self.CP, allow_near_stop_mode=True))
|
||||
CS.auto_hold_engaged = True
|
||||
CS.auto_hold_fault_suppression_timer = 1.0
|
||||
elif self.frame % 4 == 0 and volt_one_pedal_hold_active:
|
||||
idx = (self.frame // 4) % 4
|
||||
hold_brake = max(self.volt_one_pedal_brake, self.auto_hold_brake or estimate_auto_hold_brake(0.0, self.volt_one_pedal_brake, self.CP))
|
||||
hold_standstill = CS.pcm_acc_status == AccState.STANDSTILL
|
||||
hold_near_stop = CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE
|
||||
can_sends.append(gmcan.create_friction_brake_command(
|
||||
self.packer_ch, get_friction_brake_bus(self.CP), hold_brake, idx, False, hold_near_stop, hold_standstill,
|
||||
self.CP, allow_near_stop_mode=True))
|
||||
CS.auto_hold_engaged = True
|
||||
CS.auto_hold_fault_suppression_timer = 1.0
|
||||
elif self.frame % 4 == 0 and volt_one_pedal_braking:
|
||||
idx = (self.frame // 4) % 4
|
||||
near_stop = CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE
|
||||
can_sends.append(gmcan.create_friction_brake_command(
|
||||
self.packer_ch, get_friction_brake_bus(self.CP), self.volt_one_pedal_brake, idx, False, near_stop, False,
|
||||
self.CP, allow_near_stop_mode=True))
|
||||
CS.auto_hold_engaged = False
|
||||
elif self.frame % 4 == 0:
|
||||
self.apply_brake = 0
|
||||
CS.auto_hold_engaged = False
|
||||
|
||||
# While car is braking, cancel button causes ECM to enter a soft disable state with a fault status.
|
||||
# A delayed cancellation allows camera to cancel and avoids a fault when user depresses brake quickly
|
||||
self.cancel_counter = self.cancel_counter + 1 if CC.cruiseControl.cancel else 0
|
||||
|
||||
@@ -2,6 +2,7 @@ import copy
|
||||
from cereal import custom
|
||||
from opendbc.can import CANDefine, CANParser
|
||||
from opendbc.car import Bus, create_button_events, structs
|
||||
from opendbc.car import DT_CTRL
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
from opendbc.car.gm.values import (
|
||||
@@ -26,15 +27,42 @@ TransmissionType = structs.CarParams.TransmissionType
|
||||
NetworkLocation = structs.CarParams.NetworkLocation
|
||||
|
||||
STANDSTILL_THRESHOLD = 10 * 0.0311
|
||||
VOLT_EBCM_BRAKE_PRESSED_THRESHOLD = 6 / 0xd0
|
||||
AUTO_HOLD_MIN_DRIVE_TIME_S = 3.0
|
||||
AUTO_HOLD_REGEN_RELEASE_COOLDOWN_S = 1.0
|
||||
|
||||
BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise,
|
||||
CruiseButtons.MAIN: ButtonType.mainCruise, CruiseButtons.CANCEL: ButtonType.cancel}
|
||||
HARD_BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise}
|
||||
NORMAL_CRUISE_BUTTONS = (CruiseButtons.RES_ACCEL, CruiseButtons.DECEL_SET)
|
||||
|
||||
|
||||
def get_hard_cruise_buttons(steering_button_msg: dict) -> int:
|
||||
return steering_button_msg.get("ACCButtonsHard", CruiseButtons.INIT)
|
||||
|
||||
|
||||
GearShifter = structs.CarState.GearShifter
|
||||
BOLT_GEN1_CANCEL_PERSONALITY_CARS = {
|
||||
CAR.CHEVROLET_BOLT_CC_2017,
|
||||
CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
}
|
||||
BOLT_CANCEL_BUTTON_CARS = BOLT_GEN1_CANCEL_PERSONALITY_CARS | {
|
||||
CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
CAR.CHEVROLET_BOLT_CC_2022_2023,
|
||||
}
|
||||
|
||||
|
||||
def update_auto_hold_drive_timers(in_drive_for_hold: bool, moving_for_hold: bool,
|
||||
auto_hold_drive_time: float, one_pedal_drive_time: float) -> tuple[float, float]:
|
||||
if in_drive_for_hold:
|
||||
if moving_for_hold:
|
||||
auto_hold_drive_time = min(auto_hold_drive_time + DT_CTRL, AUTO_HOLD_MIN_DRIVE_TIME_S)
|
||||
one_pedal_drive_time = min(one_pedal_drive_time + DT_CTRL, AUTO_HOLD_MIN_DRIVE_TIME_S)
|
||||
else:
|
||||
auto_hold_drive_time = 0.0
|
||||
one_pedal_drive_time = 0.0
|
||||
|
||||
return auto_hold_drive_time, one_pedal_drive_time
|
||||
|
||||
|
||||
class CarState(CarStateBase):
|
||||
@@ -56,8 +84,17 @@ class CarState(CarStateBase):
|
||||
|
||||
self.prev_distance_button = 0
|
||||
self.distance_button = 0
|
||||
self.hard_cruise_buttons = CruiseButtons.INIT
|
||||
self.force_reset_cruise_buttons = False
|
||||
|
||||
self.single_pedal_mode = False
|
||||
self.auto_hold_armed = False
|
||||
self.auto_hold_engaged = False
|
||||
self.auto_hold_drive_time = 0.0
|
||||
self.one_pedal_drive_time = 0.0
|
||||
self.auto_hold_fault_suppression_timer = 0.0
|
||||
self.regen_release_timer = 0.0
|
||||
self.user_regen_paddle_pressed = False
|
||||
|
||||
self.pedal_steady = 0
|
||||
self.ecm_cruise_control_ts_nanos = 0
|
||||
@@ -66,6 +103,7 @@ class CarState(CarStateBase):
|
||||
self.lkas_previously_enabled = 0
|
||||
self.lkas_enabled = 0
|
||||
self.pcm_acc_status = AccState.OFF
|
||||
self.stock_fcw_alert = 0
|
||||
|
||||
def update_button_enable(self, buttonEvents: list[structs.CarState.ButtonEvent]):
|
||||
if not self.CP.pcmCruise:
|
||||
@@ -98,21 +136,36 @@ class CarState(CarStateBase):
|
||||
sdgm_non_volt = self.CP.carFingerprint in SDGM_CAR and self.CP.carFingerprint not in kaofui_state_cars
|
||||
|
||||
prev_cruise_buttons = self.cruise_buttons
|
||||
prev_hard_cruise_buttons = self.hard_cruise_buttons
|
||||
prev_distance_button = self.distance_button
|
||||
if not sdgm_non_volt:
|
||||
self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"]
|
||||
self.distance_button = pt_cp.vl["ASCMSteeringButton"]["DistanceButton"]
|
||||
self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"]
|
||||
self.steering_button_checksum = pt_cp.vl["ASCMSteeringButton"]["SteeringButtonChecksum"]
|
||||
steering_button_msg = pt_cp.vl["ASCMSteeringButton"]
|
||||
self.cruise_buttons = steering_button_msg["ACCButtons"]
|
||||
self.hard_cruise_buttons = get_hard_cruise_buttons(steering_button_msg)
|
||||
self.distance_button = steering_button_msg["DistanceButton"]
|
||||
self.buttons_counter = steering_button_msg["RollingCounter"]
|
||||
self.steering_button_checksum = steering_button_msg["SteeringButtonChecksum"]
|
||||
self.steering_button_ts_nanos = pt_cp.ts_nanos["ASCMSteeringButton"]["ACCButtons"]
|
||||
acc_always_one = pt_cp.vl["ASCMSteeringButton"]["ACCAlwaysOne"]
|
||||
acc_hidden_bit = pt_cp.vl["ASCMSteeringButton"].get("ACCHiddenBit", 0)
|
||||
acc_always_one = steering_button_msg["ACCAlwaysOne"]
|
||||
acc_hidden_bit = steering_button_msg.get("ACCHiddenBit", 0)
|
||||
self.steering_button_prefix = (int(acc_always_one) & 1) | ((int(acc_hidden_bit) & 1) << 6)
|
||||
else:
|
||||
self.cruise_buttons = cam_cp.vl["ASCMSteeringButton"]["ACCButtons"]
|
||||
self.distance_button = cam_cp.vl["ASCMSteeringButton"]["DistanceButton"]
|
||||
self.buttons_counter = cam_cp.vl["ASCMSteeringButton"]["RollingCounter"]
|
||||
steering_button_msg = cam_cp.vl["ASCMSteeringButton"]
|
||||
self.cruise_buttons = steering_button_msg["ACCButtons"]
|
||||
self.hard_cruise_buttons = get_hard_cruise_buttons(steering_button_msg)
|
||||
self.distance_button = steering_button_msg["DistanceButton"]
|
||||
self.buttons_counter = steering_button_msg["RollingCounter"]
|
||||
self.steering_button_ts_nanos = cam_cp.ts_nanos["ASCMSteeringButton"]["ACCButtons"]
|
||||
|
||||
# A GM hard press keeps the normal cruise button signal active too. Suppress
|
||||
# the normal button until the wheel reports a different normal state.
|
||||
if self.hard_cruise_buttons != CruiseButtons.INIT and self.cruise_buttons in NORMAL_CRUISE_BUTTONS:
|
||||
self.force_reset_cruise_buttons = True
|
||||
if self.force_reset_cruise_buttons and self.cruise_buttons in NORMAL_CRUISE_BUTTONS:
|
||||
self.cruise_buttons = CruiseButtons.UNPRESS
|
||||
elif self.force_reset_cruise_buttons and self.cruise_buttons not in NORMAL_CRUISE_BUTTONS:
|
||||
self.force_reset_cruise_buttons = False
|
||||
|
||||
self.pscm_status = copy.copy(pt_cp.vl["PSCMStatus"])
|
||||
self.moving_backward = (pt_cp.vl["EBCMWheelSpdRear"]["RLWheelDir"] == 2) or (pt_cp.vl["EBCMWheelSpdRear"]["RRWheelDir"] == 2)
|
||||
|
||||
@@ -153,9 +206,14 @@ class CarState(CarStateBase):
|
||||
else:
|
||||
ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"]
|
||||
|
||||
if self.CP.carFingerprint in {CAR.CHEVROLET_MALIBU_CC} or (self.CP.carFingerprint == CAR.CHEVROLET_BLAZER and not no_accel_pos):
|
||||
if self.CP.carFingerprint == CAR.CHEVROLET_VOLT and no_accel_pos:
|
||||
ret.brakePressed = ret.brake >= VOLT_EBCM_BRAKE_PRESSED_THRESHOLD
|
||||
elif self.CP.carFingerprint in {CAR.CHEVROLET_MALIBU_CC} or (self.CP.carFingerprint == CAR.CHEVROLET_BLAZER and not no_accel_pos):
|
||||
ret.brakePressed = ret.brake >= 8
|
||||
elif (self.CP.flags & GMFlags.FORCE_BRAKE_C9.value) or ((self.CP.networkLocation == NetworkLocation.fwdCamera) and (self.CP.carFingerprint != CAR.CHEVROLET_BLAZER)):
|
||||
elif (self.CP.flags & GMFlags.FORCE_BRAKE_C9.value) or (
|
||||
self.CP.networkLocation == NetworkLocation.fwdCamera and
|
||||
self.CP.carFingerprint not in (SDGM_CAR | ASCM_INT | {CAR.CHEVROLET_BLAZER})
|
||||
):
|
||||
ret.brakePressed = pt_cp.vl["ECMEngineStatus"]["BrakePressed"] != 0
|
||||
else:
|
||||
# Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe
|
||||
@@ -165,9 +223,19 @@ class CarState(CarStateBase):
|
||||
analog_thresh = 0.10 if no_accel_pos else 8
|
||||
ret.brakePressed = ret.brake >= analog_thresh
|
||||
|
||||
in_drive_for_hold = ret.gearShifter in (GearShifter.drive, GearShifter.low, GearShifter.manumatic)
|
||||
self.auto_hold_drive_time, self.one_pedal_drive_time = update_auto_hold_drive_timers(
|
||||
in_drive_for_hold, ret.vEgo > 0.1, self.auto_hold_drive_time, self.one_pedal_drive_time
|
||||
)
|
||||
if not in_drive_for_hold:
|
||||
self.auto_hold_armed = False
|
||||
self.auto_hold_engaged = False
|
||||
|
||||
# Regen braking is braking
|
||||
if self.CP.transmissionType == TransmissionType.direct:
|
||||
ret.regenBraking = pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0
|
||||
if not ret.regenBraking and self.user_regen_paddle_pressed:
|
||||
self.regen_release_timer = AUTO_HOLD_REGEN_RELEASE_COOLDOWN_S
|
||||
self.single_pedal_mode = (ret.gearShifter == GearShifter.low or
|
||||
pt_cp.vl["EVDriveMode"]["SinglePedalModeActive"] == 1 or
|
||||
(ret.regenBraking and ret.gearShifter == GearShifter.manumatic) or
|
||||
@@ -176,6 +244,10 @@ class CarState(CarStateBase):
|
||||
CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
CAR.CHEVROLET_BOLT_CC_2022_2023,
|
||||
} and self.CP.enableGasInterceptorDEPRECATED))
|
||||
self.user_regen_paddle_pressed = ret.regenBraking
|
||||
|
||||
if self.regen_release_timer > 0.0:
|
||||
self.regen_release_timer = max(self.regen_release_timer - DT_CTRL, 0.0)
|
||||
|
||||
if self.CP.enableGasInterceptorDEPRECATED:
|
||||
gas = (pt_cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + pt_cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) / 2.
|
||||
@@ -232,11 +304,20 @@ class CarState(CarStateBase):
|
||||
|
||||
ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF
|
||||
ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
|
||||
self.stock_fcw_alert = 0
|
||||
ret.stockFcw = False
|
||||
if self.CP.networkLocation == NetworkLocation.fwdCamera and not self.CP.flags & GMFlags.NO_CAMERA.value:
|
||||
if self.CP.carFingerprint not in CC_ONLY_CAR:
|
||||
ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS
|
||||
has_acc_dashboard_status = self.CP.carFingerprint not in CC_ONLY_CAR or self.CP.carFingerprint == CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL
|
||||
if has_acc_dashboard_status:
|
||||
acc_dashboard_status = cam_cp.vl["ASCMActiveCruiseControlStatus"]
|
||||
if self.CP.carFingerprint not in CC_ONLY_CAR:
|
||||
ret.cruiseState.speed = acc_dashboard_status["ACCSpeedSetpoint"] * CV.KPH_TO_MS
|
||||
# Preserve the stock camera FCW level from 0x370 so the controller can
|
||||
# replay it when that message is blocked and spoofed by openpilot long.
|
||||
self.stock_fcw_alert = int(acc_dashboard_status["FCWAlert"])
|
||||
ret.stockFcw = self.stock_fcw_alert != 0
|
||||
|
||||
if self.CP.carFingerprint not in (SDGM_CAR | ASCM_INT):
|
||||
if self.CP.carFingerprint not in SDGM_CAR:
|
||||
ret.stockAeb = cam_cp.vl["AEBCmd"]["AEBCmdActive"] != 0
|
||||
else:
|
||||
ret.stockAeb = False
|
||||
@@ -264,6 +345,10 @@ class CarState(CarStateBase):
|
||||
self.ecm_cruise_control_ts_nanos = 0
|
||||
self.accelerator_pedal2_ts_nanos = 0
|
||||
|
||||
if self.auto_hold_fault_suppression_timer > 0.0:
|
||||
self.auto_hold_fault_suppression_timer = max(self.auto_hold_fault_suppression_timer - DT_CTRL, 0.0)
|
||||
ret.accFaulted = False
|
||||
|
||||
if self.CP.enableBsm and not sdgm_non_volt:
|
||||
ret.leftBlindspot = pt_cp.vl["BCMBlindSpotMonitor"]["LeftBSM"] == 1
|
||||
ret.rightBlindspot = pt_cp.vl["BCMBlindSpotMonitor"]["RightBSM"] == 1
|
||||
@@ -278,46 +363,34 @@ class CarState(CarStateBase):
|
||||
self.lkas_enabled = pt_cp.vl["ASCMSteeringButton"]["LKAButton"]
|
||||
self.pcm_acc_status = pt_cp.vl["AcceleratorPedal2"]["CruiseState"]
|
||||
|
||||
# Only activate cancel remap when panda safety was configured for it at startup.
|
||||
remap_cancel_to_distance = bool(self.CP.alternativeExperience & ALTERNATIVE_EXPERIENCE.GM_REMAP_CANCEL_TO_DISTANCE)
|
||||
if not remap_cancel_to_distance:
|
||||
remap_cancel_to_distance = (
|
||||
getattr(starpilot_toggles, "remap_cancel_to_distance", False) and
|
||||
self.CP.openpilotLongitudinalControl and
|
||||
bool(self.CP.flags & GMFlags.PEDAL_LONG.value) and
|
||||
self.CP.carFingerprint in (BOLT_GEN1_CANCEL_PERSONALITY_CARS | {CAR.CHEVROLET_MALIBU_HYBRID_CC})
|
||||
)
|
||||
malibu_cancel_passthrough = (
|
||||
remap_cancel_to_distance and
|
||||
self.CP.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC and
|
||||
self.CP.openpilotLongitudinalControl and
|
||||
bool(self.CP.flags & GMFlags.PEDAL_LONG.value)
|
||||
)
|
||||
bolt_cancel_personality = (
|
||||
bolt_cancel_button = (
|
||||
remap_cancel_to_distance and
|
||||
self.CP.carFingerprint in BOLT_GEN1_CANCEL_PERSONALITY_CARS and
|
||||
self.CP.carFingerprint in BOLT_CANCEL_BUTTON_CARS and
|
||||
self.CP.openpilotLongitudinalControl and
|
||||
bool(self.CP.flags & GMFlags.PEDAL_LONG.value)
|
||||
)
|
||||
bolt_cancel_lkas_conflict = bolt_cancel_button and self.CP.carFingerprint in BOLT_GEN1_CANCEL_PERSONALITY_CARS
|
||||
|
||||
cruise_button_map = BUTTONS_DICT
|
||||
if malibu_cancel_passthrough or bolt_cancel_personality:
|
||||
if malibu_cancel_passthrough or bolt_cancel_button:
|
||||
cruise_button_map = {k: v for k, v in BUTTONS_DICT.items() if k != CruiseButtons.CANCEL}
|
||||
|
||||
cruise_events = create_button_events(
|
||||
self.cruise_buttons, prev_cruise_buttons, cruise_button_map, unpressed_btn=CruiseButtons.UNPRESS
|
||||
)
|
||||
cancel_gap_events = []
|
||||
if bolt_cancel_personality and self.cruise_buttons != prev_cruise_buttons:
|
||||
if prev_cruise_buttons == CruiseButtons.CANCEL:
|
||||
cancel_gap_events.append(structs.CarState.ButtonEvent(pressed=False, type=ButtonType.gapAdjustCruise))
|
||||
if self.cruise_buttons == CruiseButtons.CANCEL:
|
||||
cancel_gap_events.append(structs.CarState.ButtonEvent(pressed=True, type=ButtonType.gapAdjustCruise))
|
||||
|
||||
suppress_malibu_side_buttons = malibu_cancel_passthrough and (
|
||||
self.cruise_buttons in (CruiseButtons.CANCEL, CruiseButtons.MAIN) or
|
||||
prev_cruise_buttons in (CruiseButtons.CANCEL, CruiseButtons.MAIN)
|
||||
)
|
||||
suppress_bolt_cancel_lkas = bolt_cancel_personality and (
|
||||
suppress_bolt_cancel_lkas = bolt_cancel_lkas_conflict and (
|
||||
self.cruise_buttons == CruiseButtons.CANCEL or
|
||||
prev_cruise_buttons == CruiseButtons.CANCEL
|
||||
)
|
||||
@@ -327,23 +400,28 @@ class CarState(CarStateBase):
|
||||
lkas_events = [] if (suppress_malibu_side_buttons or suppress_bolt_cancel_lkas) else create_button_events(
|
||||
self.lkas_enabled, self.lkas_previously_enabled, {1: ButtonType.lkas}
|
||||
)
|
||||
hard_cruise_events = create_button_events(
|
||||
self.hard_cruise_buttons, prev_hard_cruise_buttons, HARD_BUTTONS_DICT, unpressed_btn=CruiseButtons.INIT
|
||||
)
|
||||
|
||||
# Don't add events if transitioning from INIT, unless it's to an actual button.
|
||||
if self.cruise_buttons != CruiseButtons.UNPRESS or prev_cruise_buttons != CruiseButtons.INIT:
|
||||
if (self.cruise_buttons != CruiseButtons.UNPRESS or prev_cruise_buttons != CruiseButtons.INIT or
|
||||
self.hard_cruise_buttons != CruiseButtons.INIT or prev_hard_cruise_buttons != CruiseButtons.INIT):
|
||||
ret.buttonEvents = [
|
||||
*cruise_events,
|
||||
*cancel_gap_events,
|
||||
*distance_events,
|
||||
*lkas_events,
|
||||
*hard_cruise_events,
|
||||
]
|
||||
|
||||
if ret.vEgo < self.CP.minSteerSpeed:
|
||||
ret.lowSpeedAlert = True
|
||||
|
||||
fp_ret = custom.StarPilotCarState.new_message()
|
||||
if bolt_cancel_personality and self.cruise_buttons == CruiseButtons.CANCEL:
|
||||
# Feed long-press personality logic as if distance is held while CANCEL is held.
|
||||
fp_ret.distancePressed = True
|
||||
fp_ret.accelHardCruise = self.hard_cruise_buttons == CruiseButtons.RES_ACCEL or prev_hard_cruise_buttons == CruiseButtons.RES_ACCEL
|
||||
fp_ret.decelHardCruise = self.hard_cruise_buttons == CruiseButtons.DECEL_SET or prev_hard_cruise_buttons == CruiseButtons.DECEL_SET
|
||||
if bolt_cancel_button and self.cruise_buttons == CruiseButtons.CANCEL:
|
||||
fp_ret.cancelPressed = True
|
||||
fp_ret.sportGear = pt_cp.vl["SportMode"]["SportMode"] == 1
|
||||
|
||||
return ret, fp_ret
|
||||
@@ -393,7 +471,7 @@ class CarState(CarStateBase):
|
||||
("ASCMSteeringButton", 33),
|
||||
]
|
||||
if CP.enableBsm:
|
||||
pt_messages.append(("BCMBlindSpotMonitor", 10))
|
||||
pt_messages.append(("BCMBlindSpotMonitor", 0))
|
||||
|
||||
if CP.flags & GMFlags.NO_ACCELERATOR_POS_MSG.value:
|
||||
if ("ECMAcceleratorPos", 80) in pt_messages:
|
||||
@@ -435,8 +513,14 @@ class CarState(CarStateBase):
|
||||
("ASCMSteeringButton", 33),
|
||||
]
|
||||
if CP.enableBsm:
|
||||
cam_messages.append(("BCMBlindSpotMonitor", 10))
|
||||
elif CP.carFingerprint not in (SDGM_CAR | ASCM_INT):
|
||||
cam_messages.append(("BCMBlindSpotMonitor", 0))
|
||||
elif CP.carFingerprint in ASCM_INT:
|
||||
# Volt/ASCM-int variants don't reliably have AEBCmd present at startup,
|
||||
# but when it appears we still want to surface OEM AEB state.
|
||||
cam_messages += [
|
||||
("AEBCmd", 0),
|
||||
]
|
||||
elif CP.carFingerprint not in SDGM_CAR:
|
||||
cam_messages += [
|
||||
("AEBCmd", 10),
|
||||
]
|
||||
|
||||
@@ -7,6 +7,7 @@ from opendbc.car.gm.values import CAR
|
||||
|
||||
CAMERA_DIAGNOSTIC_ADDRESS = 0x24b
|
||||
CAMERA_DIAGNOSTIC_RX_ADDRESS = 0x64b
|
||||
SASCM_ADDRESS = 0x2FF
|
||||
|
||||
|
||||
FINGERPRINTS = {
|
||||
@@ -210,12 +211,16 @@ FINGERPRINTS.update({
|
||||
CAR.GMC_ACADIA_ASCM: FINGERPRINTS[CAR.GMC_ACADIA],
|
||||
CAR.CHEVROLET_MALIBU_ASCM: FINGERPRINTS[CAR.CHEVROLET_MALIBU],
|
||||
CAR.CADILLAC_ESCALADE_ASCM: FINGERPRINTS[CAR.CADILLAC_ESCALADE],
|
||||
CAR.CADILLAC_ESCALADE_ESV_2019_ASCM: [{**fp, SASCM_ADDRESS: 8} for fp in FINGERPRINTS[CAR.CADILLAC_ESCALADE_ESV_2019]],
|
||||
CAR.CHEVROLET_SUBURBAN: FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC],
|
||||
CAR.GMC_YUKON_CC: FINGERPRINTS[CAR.GMC_YUKON],
|
||||
CAR.CADILLAC_XT6: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
|
||||
CAR.CADILLAC_XT5: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
|
||||
CAR.CHEVROLET_BLAZER: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
|
||||
CAR.CHEVROLET_MALIBU_SDGM: FINGERPRINTS[CAR.CHEVROLET_MALIBU_CC],
|
||||
CAR.BUICK_BABYENCLAVE: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
|
||||
CAR.CHEVROLET_SILVERADO_CC: FINGERPRINTS[CAR.CHEVROLET_SILVERADO],
|
||||
CAR.BUICK_LACROSSE_ASCM: FINGERPRINTS[CAR.BUICK_LACROSSE],
|
||||
})
|
||||
|
||||
FW_VERSIONS: dict[str, dict[tuple, list[bytes]]] = {
|
||||
|
||||
@@ -179,22 +179,28 @@ def create_ecm_cruise_control_command(packer, bus, enabled, target_speed_kph):
|
||||
return CanData(0x3D1, bytes(dat), bus)
|
||||
|
||||
|
||||
def create_friction_brake_command(packer, bus, apply_brake, idx, enabled, near_stop, at_full_stop, CP):
|
||||
def get_friction_brake_mode(apply_brake, enabled, near_stop, at_full_stop, CP, allow_near_stop_mode=False):
|
||||
mode = 0x1
|
||||
|
||||
# TODO: Understand this better. Volts and ICE Camera ACC cars are 0x1 when enabled with no brake
|
||||
if enabled and CP.carFingerprint in (CAR.CHEVROLET_BOLT_ACC_2022_2023,):
|
||||
if enabled and CP.carFingerprint in (CAR.CHEVROLET_BOLT_ACC_2022_2023, CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL):
|
||||
mode = 0x9
|
||||
|
||||
if apply_brake > 0:
|
||||
mode = 0xa
|
||||
if at_full_stop:
|
||||
mode = 0xd
|
||||
elif allow_near_stop_mode and near_stop:
|
||||
# Stock Volt auto hold can run with cruise main on but ACC inactive, so
|
||||
# there is no stock STANDSTILL state to promote 0xa -> 0xd. Restore the
|
||||
# older near-stop hold mode only for that path.
|
||||
mode = 0xb
|
||||
|
||||
# TODO: this is to have GM bringing the car to complete stop,
|
||||
# but currently it conflicts with OP controls, so turned off. Not set by all cars
|
||||
#elif near_stop:
|
||||
# mode = 0xb
|
||||
return mode
|
||||
|
||||
|
||||
def create_friction_brake_command(packer, bus, apply_brake, idx, enabled, near_stop, at_full_stop, CP, allow_near_stop_mode=False):
|
||||
mode = get_friction_brake_mode(apply_brake, enabled, near_stop, at_full_stop, CP, allow_near_stop_mode)
|
||||
|
||||
brake = (0x1000 - apply_brake) & 0xfff
|
||||
checksum = (0x10000 - (mode << 12) - brake - idx) & 0xffff
|
||||
@@ -209,7 +215,7 @@ def create_friction_brake_command(packer, bus, apply_brake, idx, enabled, near_s
|
||||
return packer.make_can_msg("EBCMFrictionBrakeCmd", bus, values)
|
||||
|
||||
|
||||
def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, hud_control, fcw):
|
||||
def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, hud_control, fcw_alert):
|
||||
target_speed = min(target_speed_kph, 255)
|
||||
|
||||
values = {
|
||||
@@ -220,7 +226,7 @@ def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, hud_con
|
||||
"ACCCmdActive": enabled,
|
||||
"ACCAlwaysOne2": 1,
|
||||
"ACCLeadCar": hud_control.leadVisible,
|
||||
"FCWAlert": 0x3 if fcw else 0
|
||||
"FCWAlert": int(fcw_alert) & 0x3,
|
||||
}
|
||||
|
||||
return packer.make_can_msg("ASCMActiveCruiseControlStatus", bus, values)
|
||||
|
||||
@@ -58,6 +58,18 @@ NON_LINEAR_TORQUE_PARAMS = {
|
||||
"left": [3.8, 0.81, 0.24, 0.0465122],
|
||||
"right": [3.8, 0.81, 0.24, 0.0465122],
|
||||
},
|
||||
CAR.CHEVROLET_SILVERADO_CC: {
|
||||
"left": [3.8, 0.81, 0.24, 0.0465122],
|
||||
"right": [3.8, 0.81, 0.24, 0.0465122],
|
||||
},
|
||||
CAR.CADILLAC_XT4: {
|
||||
"left": [2.4, 0.95, 0.28, 0.0],
|
||||
"right": [2.4, 0.95, 0.28, 0.0],
|
||||
},
|
||||
CAR.CHEVROLET_VOLT: {
|
||||
"left": [1.525, 1.05, 0.155, 0.0],
|
||||
"right": [1.525, 0.95, 0.150, 0.0],
|
||||
},
|
||||
}
|
||||
|
||||
PEDAL_MSG = 0x201
|
||||
@@ -85,6 +97,14 @@ VOLT_LONG_TEST_TUNE_CARS = {
|
||||
CAR.CHEVROLET_VOLT_CC,
|
||||
}
|
||||
|
||||
VOLT_BSM_CARS = {
|
||||
CAR.CHEVROLET_VOLT,
|
||||
CAR.CHEVROLET_VOLT_2019,
|
||||
CAR.CHEVROLET_VOLT_ASCM,
|
||||
CAR.CHEVROLET_VOLT_CAMERA,
|
||||
CAR.CHEVROLET_VOLT_CC,
|
||||
}
|
||||
|
||||
BOLT_PEDAL_LONG_CARS = {
|
||||
CAR.CHEVROLET_BOLT_CC_2017,
|
||||
CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
@@ -93,12 +113,16 @@ BOLT_PEDAL_LONG_CARS = {
|
||||
CAR.CHEVROLET_MALIBU_HYBRID_CC,
|
||||
}
|
||||
|
||||
# Cancel-to-personality mapping target: gen1 Bolt pedal-long paths only.
|
||||
# Cancel-button remap support uses the same safety path on all pedal-long Bolts,
|
||||
# but only gen1 needs extra LKAS suppression in CarState.
|
||||
BOLT_GEN1_CANCEL_PERSONALITY_CARS = {
|
||||
CAR.CHEVROLET_BOLT_CC_2017,
|
||||
CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
}
|
||||
CANCEL_REMAP_DISTANCE_CARS = BOLT_GEN1_CANCEL_PERSONALITY_CARS
|
||||
CANCEL_REMAP_DISTANCE_CARS = BOLT_GEN1_CANCEL_PERSONALITY_CARS | {
|
||||
CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
CAR.CHEVROLET_BOLT_CC_2022_2023,
|
||||
}
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@@ -109,7 +133,11 @@ class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def get_pid_accel_limits(CP, current_speed, cruise_speed):
|
||||
if CP.enableGasInterceptorDEPRECATED and bool(CP.flags & GMFlags.PEDAL_LONG.value):
|
||||
if CP.carFingerprint in BOLT_PEDAL_LONG_CARS:
|
||||
if CP.carFingerprint == CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL:
|
||||
accel_min = CarControllerParams.ACCEL_MIN
|
||||
accel_max = np.interp(current_speed, [0.0, 1.5, 4.0, 8.0, 15.0],
|
||||
[0.54, 0.74, 1.03, 1.46, CarControllerParams.ACCEL_MAX])
|
||||
elif CP.carFingerprint in BOLT_PEDAL_LONG_CARS:
|
||||
accel_min = np.interp(current_speed, [0.0, 1.5, 4.0, 8.0, 15.0, 30.0],
|
||||
[-0.93, -1.28, -1.98, -2.58, -2.86, -2.95])
|
||||
accel_max = np.interp(current_speed, [0.0, 1.5, 4.0, 8.0, 15.0],
|
||||
@@ -189,11 +217,20 @@ class CarInterface(CarInterfaceBase):
|
||||
disable_openpilot_long = params.get_bool("DisableOpenpilotLongitudinal")
|
||||
except UnknownKeyName:
|
||||
disable_openpilot_long = False
|
||||
try:
|
||||
gm_auto_hold = params.get_bool("GMAutoHold")
|
||||
except UnknownKeyName:
|
||||
gm_auto_hold = False
|
||||
try:
|
||||
volt_one_pedal_mode = params.get_bool("VoltOnePedalMode")
|
||||
except UnknownKeyName:
|
||||
volt_one_pedal_mode = False
|
||||
|
||||
ret.brand = "gm"
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.gm)]
|
||||
ret.autoResumeSng = False
|
||||
ret.enableBsm = 0x142 in fingerprint[CanBus.POWERTRAIN]
|
||||
# Some Volt installs don't expose the BSM frame during startup fingerprinting.
|
||||
ret.enableBsm = 0x142 in fingerprint[CanBus.POWERTRAIN] or candidate in VOLT_BSM_CARS
|
||||
has_sascm = 0x2FF in fingerprint[CanBus.POWERTRAIN]
|
||||
if has_sascm:
|
||||
ret.flags |= GMFlags.SASCM.value
|
||||
@@ -385,8 +422,8 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.BUICK_LACROSSE:
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
elif candidate in (CAR.BUICK_LACROSSE, CAR.BUICK_LACROSSE_ASCM):
|
||||
CarInterfaceBase.configure_torque_tune(CAR.BUICK_LACROSSE, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.CADILLAC_ESCALADE:
|
||||
ret.minEnableSpeed = -1. # engage speed is decided by pcm
|
||||
@@ -395,7 +432,7 @@ class CarInterface(CarInterfaceBase):
|
||||
elif candidate == CAR.CADILLAC_ESCALADE_ASCM:
|
||||
CarInterfaceBase.configure_torque_tune(CAR.CADILLAC_ESCALADE, ret.lateralTuning)
|
||||
|
||||
elif candidate in (CAR.CADILLAC_ESCALADE_ESV, CAR.CADILLAC_ESCALADE_ESV_2019):
|
||||
elif candidate in (CAR.CADILLAC_ESCALADE_ESV, CAR.CADILLAC_ESCALADE_ESV_2019, CAR.CADILLAC_ESCALADE_ESV_2019_ASCM):
|
||||
ret.minEnableSpeed = -1. # engage speed is decided by pcm
|
||||
|
||||
if candidate == CAR.CADILLAC_ESCALADE_ESV:
|
||||
@@ -404,7 +441,8 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.lateralTuning.pid.kf = 0.000045
|
||||
else:
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
torque_candidate = CAR.CADILLAC_ESCALADE_ESV_2019 if candidate == CAR.CADILLAC_ESCALADE_ESV_2019_ASCM else candidate
|
||||
CarInterfaceBase.configure_torque_tune(torque_candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate in (
|
||||
CAR.CHEVROLET_BOLT_ACC_2022_2023,
|
||||
@@ -444,7 +482,7 @@ class CarInterface(CarInterfaceBase):
|
||||
# ACC Bolts use pedal for full longitudinal control, not just SNG.
|
||||
ret.flags |= GMFlags.PEDAL_LONG.value
|
||||
|
||||
elif candidate == CAR.CHEVROLET_SILVERADO:
|
||||
elif candidate in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC):
|
||||
# On the Bolt, the ECM and camera independently check that you are either above 5 kph or at a stop
|
||||
# with foot on brake to allow engagement, but this platform only has that check in the camera.
|
||||
# TODO: check if this is split by EV/ICE with more platforms in the future
|
||||
@@ -481,7 +519,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.minSteerSpeed = 7 * CV.MPH_TO_MS
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.CADILLAC_XT5_CC:
|
||||
elif candidate in (CAR.CADILLAC_XT5, CAR.CADILLAC_XT5_CC):
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
@@ -490,7 +528,19 @@ class CarInterface(CarInterfaceBase):
|
||||
if not ret.openpilotLongitudinalControl:
|
||||
ret.minEnableSpeed = -1.
|
||||
if candidate == CAR.CHEVROLET_BLAZER:
|
||||
# The Blazer builds brake torque noticeably later than the rest of the GM set.
|
||||
# A slightly larger planner delay estimate starts the request earlier and keeps
|
||||
# stopped-lead approaches from turning into a late, harsh max-brake catch-up.
|
||||
ret.longitudinalActuatorDelay = 0.7
|
||||
ret.longitudinalTuning.kpBP = [0.0, 4.0, 12.0, 35.0]
|
||||
ret.longitudinalTuning.kpV = [0.09, 0.075, 0.055, 0.040]
|
||||
ret.longitudinalTuning.kiBP = [0.0, 4.0, 12.0, 35.0]
|
||||
ret.longitudinalTuning.kiV = [0.03, 0.04, 0.055, 0.07]
|
||||
ret.minEnableSpeed = 5 * CV.KPH_TO_MS
|
||||
ret.stoppingDecelRate = 1.0
|
||||
ret.vEgoStopping = 0.35
|
||||
ret.vEgoStarting = 0.35
|
||||
ret.stopAccel = -0.30
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.BUICK_BABYENCLAVE:
|
||||
@@ -584,6 +634,12 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.startAccel = 1.15
|
||||
ret.vEgoStarting = max(ret.vEgoStarting, 0.35)
|
||||
|
||||
if ret.openpilotLongitudinalControl and candidate in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC) and not ret.enableGasInterceptorDEPRECATED:
|
||||
ret.longitudinalTuning.kpBP = [0.0, 5.0, 15.0, 35.0]
|
||||
ret.longitudinalTuning.kpV = [0.02, 0.03, 0.028, 0.022]
|
||||
ret.longitudinalTuning.kiBP = [0.0, 5.0, 15.0, 35.0]
|
||||
ret.longitudinalTuning.kiV = [0.28, 0.26, 0.20, 0.16]
|
||||
|
||||
elif candidate in CC_ONLY_CAR and not ret.enableGasInterceptorDEPRECATED:
|
||||
ret.flags |= GMFlags.CC_LONG.value
|
||||
ret.alphaLongitudinalAvailable = False
|
||||
@@ -614,12 +670,15 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
# Exception for flashed cars, or cars whose camera was removed.
|
||||
missing_camera_msg = CAM_MSG not in fingerprint.get(CanBus.CAMERA, {})
|
||||
if (ret.networkLocation == NetworkLocation.fwdCamera or candidate in CC_ONLY_CAR) and missing_camera_msg and candidate not in SDGM_CAR:
|
||||
if (ret.networkLocation == NetworkLocation.fwdCamera or candidate in CC_ONLY_CAR) and missing_camera_msg and candidate not in (ASCM_INT | SDGM_CAR):
|
||||
ret.flags |= GMFlags.NO_CAMERA.value
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
if ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]:
|
||||
ret.flags |= GMFlags.NO_ACCELERATOR_POS_MSG.value
|
||||
if candidate == CAR.CHEVROLET_VOLT and ret.networkLocation == NetworkLocation.gateway:
|
||||
# Reuse the no-camera safety bit as an ASCM Volt selector for the alternate EBCM brake path.
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
try:
|
||||
remote_start_boots_comma = params.get_bool("RemoteStartBootsComma")
|
||||
@@ -629,6 +688,37 @@ class CarInterface(CarInterfaceBase):
|
||||
if remote_start_boots_comma:
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_REMOTE_START_BOOTS_COMMA.value
|
||||
|
||||
volt_stock_friction_brake_safety = (
|
||||
(gm_auto_hold or volt_one_pedal_mode) and
|
||||
candidate in {
|
||||
CAR.CHEVROLET_VOLT,
|
||||
CAR.CHEVROLET_VOLT_2019,
|
||||
CAR.CHEVROLET_VOLT_ASCM,
|
||||
CAR.CHEVROLET_VOLT_CAMERA,
|
||||
}
|
||||
)
|
||||
if volt_stock_friction_brake_safety:
|
||||
# Reuse the paddle-scheduler safety bit as a Volt stock friction-brake
|
||||
# marker on non-pedal paths. Both auto hold and one-pedal can run while
|
||||
# OP longitudinal is configured but not currently active, so the bit must
|
||||
# be present regardless of the current long-control mode.
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
|
||||
|
||||
volt_stock_one_pedal_safety = (
|
||||
volt_one_pedal_mode and
|
||||
candidate in {
|
||||
CAR.CHEVROLET_VOLT,
|
||||
CAR.CHEVROLET_VOLT_2019,
|
||||
CAR.CHEVROLET_VOLT_ASCM,
|
||||
CAR.CHEVROLET_VOLT_CAMERA,
|
||||
}
|
||||
)
|
||||
if volt_stock_one_pedal_safety:
|
||||
# Reuse the 3D1 scheduler bit as a Volt one-pedal marker on non-pedal
|
||||
# ACC paths. The bit is ignored by the actual 3D1 scheduler unless the
|
||||
# car is on a pedal-long CC-only path, so this stays isolated from Bolt.
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_PANDA_3D1_SCHED.value
|
||||
|
||||
use_panda_3d1_sched = (
|
||||
ret.openpilotLongitudinalControl and
|
||||
ret.enableGasInterceptorDEPRECATED and
|
||||
|
||||
@@ -0,0 +1,923 @@
|
||||
import sys
|
||||
import types
|
||||
from types import SimpleNamespace
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from opendbc.car import structs
|
||||
|
||||
fake_interfaces = types.ModuleType("opendbc.car.interfaces")
|
||||
|
||||
|
||||
class _FakeCarControllerBase:
|
||||
def __init__(self, dbc_names=None, CP=None):
|
||||
self.CP = CP
|
||||
|
||||
|
||||
fake_interfaces.CarControllerBase = _FakeCarControllerBase
|
||||
sys.modules.setdefault("opendbc.car.interfaces", fake_interfaces)
|
||||
|
||||
fake_params = types.ModuleType("openpilot.common.params")
|
||||
|
||||
|
||||
class _FakeParams:
|
||||
pass
|
||||
|
||||
|
||||
class _FakeUnknownKeyName(Exception):
|
||||
pass
|
||||
|
||||
|
||||
fake_params.Params = _FakeParams
|
||||
fake_params.UnknownKeyName = _FakeUnknownKeyName
|
||||
sys.modules.setdefault("openpilot.common.params", fake_params)
|
||||
|
||||
fake_testing_grounds = types.ModuleType("openpilot.starpilot.common.testing_grounds")
|
||||
fake_testing_grounds.testing_ground = SimpleNamespace(use_1=False)
|
||||
sys.modules.setdefault("openpilot.starpilot.common.testing_grounds", fake_testing_grounds)
|
||||
|
||||
from opendbc.car.gm.carcontroller import (
|
||||
AUTO_HOLD_DRIVE_GEARS,
|
||||
CarController,
|
||||
estimate_auto_hold_brake,
|
||||
get_adas_keepalive_step,
|
||||
get_auto_hold_stop_threshold,
|
||||
get_bolt_acc_pedal_friction_brake,
|
||||
get_bolt_acc_pedal_friction_command_state,
|
||||
get_bolt_acc_pedal_effective_brake_switch,
|
||||
get_bolt_acc_pedal_planner_brake_switch,
|
||||
get_bolt_pedal_long_accel_limit,
|
||||
get_interceptor_sng_gas_cmd,
|
||||
get_lka_steering_cmd_counter,
|
||||
get_volt_one_pedal_target_decel,
|
||||
get_testing_ground_1_brake_switch_bias,
|
||||
get_acc_dashboard_status_active,
|
||||
get_stock_cc_active_for_cancel,
|
||||
shape_bolt_acc_pedal_low_speed_friction,
|
||||
shape_truck_positive_accel,
|
||||
should_use_fixed_stopping_brake,
|
||||
should_activate_auto_hold,
|
||||
should_activate_volt_one_pedal,
|
||||
should_send_adas_status,
|
||||
should_send_stock_long_cancel,
|
||||
should_spoof_dash_speed,
|
||||
should_spoof_ecm_cruise_status,
|
||||
supports_bolt_acc_pedal_friction_experiment,
|
||||
supports_volt_auto_hold,
|
||||
supports_volt_one_pedal,
|
||||
use_interceptor_sng_launch,
|
||||
)
|
||||
from opendbc.car.gm.gmcan import get_friction_brake_mode
|
||||
from opendbc.car.gm.values import AccState, CAR, CarControllerParams, GMFlags
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
|
||||
|
||||
def _cs(enabled, pcm_acc_status):
|
||||
return SimpleNamespace(
|
||||
out=SimpleNamespace(cruiseState=SimpleNamespace(enabled=enabled), accFaulted=False),
|
||||
pcm_acc_status=pcm_acc_status,
|
||||
)
|
||||
|
||||
|
||||
def _sng_cs(v_ego, standstill, cruise_standstill):
|
||||
return SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
vEgo=v_ego,
|
||||
standstill=standstill,
|
||||
cruiseState=SimpleNamespace(standstill=cruise_standstill),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _controller(car_fingerprint=CAR.CHEVROLET_BOLT_CC_2018_2021):
|
||||
controller = CarController.__new__(CarController)
|
||||
controller.CP = SimpleNamespace(carFingerprint=car_fingerprint)
|
||||
controller.planner_regen_hold = False
|
||||
controller.regen_paddle_pressed = False
|
||||
controller.regen_paddle_timer = 0
|
||||
controller.regen_press_counter = 0
|
||||
controller.regen_release_counter = 0
|
||||
controller.regen_min_on_frames = 0
|
||||
controller.regen_min_off_frames = 0
|
||||
controller.pedal_active_last = False
|
||||
controller.pedal_steady = 0.0
|
||||
controller.aego = 0.0
|
||||
controller.maneuver_paddle_mode = "auto"
|
||||
controller.bolt_acc_pedal_friction_low_speed_active = False
|
||||
return controller
|
||||
|
||||
|
||||
def test_gen1_bolt_pedal_cancel_uses_pcm_acc_status():
|
||||
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_BOLT_CC_2018_2021)
|
||||
|
||||
assert get_stock_cc_active_for_cancel(CP, _cs(False, AccState.ACTIVE))
|
||||
|
||||
|
||||
def test_gen2_bolt_acc_pedal_cancel_uses_enabled_only():
|
||||
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL)
|
||||
|
||||
assert not get_stock_cc_active_for_cancel(CP, _cs(False, AccState.ACTIVE))
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_friction_experiment_is_single_fingerprint_only():
|
||||
assert supports_bolt_acc_pedal_friction_experiment(SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
openpilotLongitudinalControl=True,
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
))
|
||||
assert not supports_bolt_acc_pedal_friction_experiment(SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_MALIBU_HYBRID_CC,
|
||||
openpilotLongitudinalControl=True,
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
))
|
||||
assert not supports_bolt_acc_pedal_friction_experiment(SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
openpilotLongitudinalControl=False,
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
))
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_friction_blend_preserves_zero_before_crossover():
|
||||
params = SimpleNamespace(ACCEL_MIN=-4.0, MAX_BRAKE=400)
|
||||
|
||||
assert get_bolt_acc_pedal_friction_brake(0, -2.8, 20.0, params) == 0
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_friction_blend_uses_full_brake_range_after_regen():
|
||||
params = SimpleNamespace(ACCEL_MIN=-4.0, MAX_BRAKE=400)
|
||||
|
||||
# Legacy mapping tops out early once regen has already consumed part of the
|
||||
# decel request. The experiment remaps that reduced span back to full scale.
|
||||
assert get_bolt_acc_pedal_friction_brake(286, -2.86, 20.0, params) == 400
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_friction_blend_biases_small_commands_upward_at_speed():
|
||||
params = SimpleNamespace(ACCEL_MIN=-4.0, MAX_BRAKE=400)
|
||||
|
||||
low_speed = get_bolt_acc_pedal_friction_brake(31, -2.86, 0.0, params)
|
||||
high_speed = get_bolt_acc_pedal_friction_brake(31, -2.86, 20.0, params)
|
||||
|
||||
assert low_speed > 31
|
||||
assert high_speed > low_speed
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_friction_blend_applies_a_minimum_pre_stop_command_at_speed():
|
||||
params = SimpleNamespace(ACCEL_MIN=-4.0, MAX_BRAKE=400)
|
||||
|
||||
assert get_bolt_acc_pedal_friction_brake(2, -2.86, 17.0, params) >= 18
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_friction_blend_boosts_midrange_commands_before_stopping_phase():
|
||||
params = SimpleNamespace(ACCEL_MIN=-4.0, MAX_BRAKE=400)
|
||||
|
||||
assert get_bolt_acc_pedal_friction_brake(40, -2.86, 15.0, params) >= 80
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_low_speed_friction_ignores_tiny_inactive_brake_requests():
|
||||
apply_brake, active = shape_bolt_acc_pedal_low_speed_friction(9, 5.0, False, False)
|
||||
|
||||
assert apply_brake == 0
|
||||
assert not active
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_low_speed_friction_uses_hysteresis_once_active():
|
||||
apply_brake, active = shape_bolt_acc_pedal_low_speed_friction(24, 3.0, False, False)
|
||||
assert apply_brake == 24
|
||||
assert active
|
||||
|
||||
apply_brake, active = shape_bolt_acc_pedal_low_speed_friction(5, 3.0, False, active)
|
||||
assert apply_brake == 0
|
||||
assert not active
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_low_speed_friction_fades_out_at_standstill():
|
||||
apply_brake, active = shape_bolt_acc_pedal_low_speed_friction(80, 0.2, True, True)
|
||||
|
||||
assert apply_brake == 0
|
||||
assert not active
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_low_speed_friction_preserves_rolling_stop_authority():
|
||||
apply_brake, active = shape_bolt_acc_pedal_low_speed_friction(80, 1.2, True, True)
|
||||
|
||||
assert 0 < apply_brake < 80
|
||||
assert active
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_low_speed_friction_drops_out_before_two_clamp():
|
||||
apply_brake, active = shape_bolt_acc_pedal_low_speed_friction(80, 0.9, True, True)
|
||||
|
||||
assert apply_brake == 0
|
||||
assert not active
|
||||
|
||||
|
||||
def test_bolt_pedal_long_accel_limit_matches_planner_regen_envelope():
|
||||
assert get_bolt_pedal_long_accel_limit(6.66) == pytest.approx(-2.379, abs=1e-3)
|
||||
assert get_bolt_pedal_long_accel_limit(3.0) == pytest.approx(-1.70, abs=1e-3)
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_planner_brake_switch_is_lower_than_stock_switch():
|
||||
params = SimpleNamespace(ZERO_GAS=6150, BRAKE_SWITCH_LOOKUP_BP=[0.5, 10.0], BRAKE_SWITCH_LOOKUP_V=[6150, 5500])
|
||||
v_ego = 6.66
|
||||
|
||||
stock_switch = int(round(np.interp(v_ego, params.BRAKE_SWITCH_LOOKUP_BP, params.BRAKE_SWITCH_LOOKUP_V)))
|
||||
planner_switch = get_bolt_acc_pedal_planner_brake_switch(
|
||||
v_ego, params, tire_radius=0.336, mass=1832.0, coeff_drag=0.30, frontal_area=2.35, air_density=1.225,
|
||||
)
|
||||
|
||||
assert planner_switch < stock_switch
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_effective_brake_switch_never_suppresses_stock_friction():
|
||||
params = SimpleNamespace(ZERO_GAS=6150, BRAKE_SWITCH_LOOKUP_BP=[0.5, 10.0], BRAKE_SWITCH_LOOKUP_V=[6150, 5500])
|
||||
v_ego = 5.434
|
||||
mass = 1805.0
|
||||
tire_radius = 0.075 * 2.63779 + 0.1453
|
||||
frontal_area = 1.05 * 2.63779 + 0.0679
|
||||
coeff_drag = 0.30
|
||||
air_density = 1.225
|
||||
accel_cmd = -1.399
|
||||
|
||||
aero_drag_force = 0.5 * coeff_drag * frontal_area * air_density * v_ego ** 2
|
||||
torque = tire_radius * ((mass * accel_cmd) + aero_drag_force)
|
||||
scaled_torque = torque + params.ZERO_GAS
|
||||
|
||||
stock_switch = int(round(np.interp(v_ego, params.BRAKE_SWITCH_LOOKUP_BP, params.BRAKE_SWITCH_LOOKUP_V)))
|
||||
planner_switch = get_bolt_acc_pedal_planner_brake_switch(
|
||||
v_ego, params, tire_radius=tire_radius, mass=mass,
|
||||
coeff_drag=coeff_drag, frontal_area=frontal_area, air_density=air_density,
|
||||
)
|
||||
effective_switch = get_bolt_acc_pedal_effective_brake_switch(stock_switch, planner_switch)
|
||||
|
||||
stock_brake_accel = min((scaled_torque - stock_switch) / (tire_radius * mass), 0)
|
||||
effective_brake_accel = min((scaled_torque - effective_switch) / (tire_radius * mass), 0)
|
||||
|
||||
assert planner_switch < stock_switch
|
||||
assert effective_switch == stock_switch
|
||||
assert stock_brake_accel < 0
|
||||
assert effective_brake_accel == stock_brake_accel
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_friction_command_state_requires_cruise_main_for_positive_brake():
|
||||
command_brake, release_frames, should_send = get_bolt_acc_pedal_friction_command_state(120, False, 0)
|
||||
|
||||
assert command_brake == 0
|
||||
assert release_frames == 0
|
||||
assert not should_send
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_friction_command_state_sends_zero_unwind_after_main_off():
|
||||
command_brake, release_frames, should_send = get_bolt_acc_pedal_friction_command_state(120, True, 0)
|
||||
|
||||
assert command_brake == 120
|
||||
assert release_frames > 0
|
||||
assert should_send
|
||||
|
||||
command_brake, release_frames, should_send = get_bolt_acc_pedal_friction_command_state(0, False, release_frames)
|
||||
|
||||
assert command_brake == 0
|
||||
assert release_frames >= 0
|
||||
assert should_send
|
||||
|
||||
|
||||
def test_fixed_stopping_brake_is_disabled_for_bolt_acc_pedal_experiment():
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
openpilotLongitudinalControl=True,
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
)
|
||||
|
||||
assert not should_use_fixed_stopping_brake(CP, True, True, False)
|
||||
|
||||
|
||||
def test_fixed_stopping_brake_stays_enabled_for_normal_acc_path():
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023,
|
||||
openpilotLongitudinalControl=True,
|
||||
enableGasInterceptorDEPRECATED=False,
|
||||
flags=0,
|
||||
)
|
||||
|
||||
assert should_use_fixed_stopping_brake(CP, True, True, False)
|
||||
assert not should_use_fixed_stopping_brake(CP, False, True, False)
|
||||
assert not should_use_fixed_stopping_brake(CP, True, False, False)
|
||||
assert not should_use_fixed_stopping_brake(CP, True, True, True)
|
||||
|
||||
|
||||
def test_stock_cancel_is_suppressed_when_acc_is_faulted():
|
||||
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_CAMERA)
|
||||
cs = _cs(True, AccState.FAULTED)
|
||||
cs.out.accFaulted = True
|
||||
|
||||
assert get_stock_cc_active_for_cancel(CP, cs)
|
||||
assert not should_send_stock_long_cancel(11, cs)
|
||||
|
||||
|
||||
def test_stock_cancel_requires_delay_and_no_acc_fault():
|
||||
cs = _cs(True, AccState.ACTIVE)
|
||||
|
||||
assert not should_send_stock_long_cancel(10, cs)
|
||||
assert should_send_stock_long_cancel(11, cs)
|
||||
|
||||
|
||||
def test_gen1_bolt_pedal_ecm_cruise_spoof_is_not_gated_by_dash_speed_toggle():
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
openpilotLongitudinalControl=True,
|
||||
)
|
||||
|
||||
assert not should_spoof_dash_speed(CP, SimpleNamespace(disable_openpilot_long=True, gm_pedal_longitudinal=True))
|
||||
assert should_spoof_ecm_cruise_status(CP)
|
||||
|
||||
|
||||
def test_gateway_keepalive_uses_gateway_cadence():
|
||||
cp = SimpleNamespace(networkLocation=CarParams.NetworkLocation.gateway, flags=0)
|
||||
|
||||
assert get_adas_keepalive_step(cp, is_kaofui_car=True) == 100
|
||||
assert get_adas_keepalive_step(cp, is_kaofui_car=False) == 200
|
||||
|
||||
|
||||
def test_removed_camera_keepalive_uses_camera_cadence():
|
||||
cp = SimpleNamespace(networkLocation=CarParams.NetworkLocation.fwdCamera, flags=GMFlags.NO_CAMERA.value)
|
||||
|
||||
assert get_adas_keepalive_step(cp, is_kaofui_car=True) == 100
|
||||
|
||||
|
||||
def test_live_camera_path_does_not_send_pt_keepalive():
|
||||
cp = SimpleNamespace(networkLocation=CarParams.NetworkLocation.fwdCamera, flags=0)
|
||||
|
||||
assert get_adas_keepalive_step(cp, is_kaofui_car=True) is None
|
||||
|
||||
|
||||
def test_ascm_int_cars_do_not_send_radar_status():
|
||||
common = {
|
||||
"networkLocation": CarParams.NetworkLocation.fwdCamera,
|
||||
"radarUnavailable": False,
|
||||
}
|
||||
|
||||
assert not should_send_adas_status(SimpleNamespace(carFingerprint=CAR.BUICK_LACROSSE_ASCM, **common), is_kaofui_car=True)
|
||||
assert not should_send_adas_status(SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_ASCM, **common), is_kaofui_car=True)
|
||||
|
||||
|
||||
def test_lacrosse_ascm_marks_acc_dashboard_active_for_aol_only():
|
||||
cc = SimpleNamespace(enabled=False, latActive=True)
|
||||
|
||||
assert get_acc_dashboard_status_active(SimpleNamespace(carFingerprint=CAR.BUICK_LACROSSE_ASCM), cc)
|
||||
assert not get_acc_dashboard_status_active(SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_ASCM), cc)
|
||||
assert not get_acc_dashboard_status_active(SimpleNamespace(carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023), cc)
|
||||
|
||||
|
||||
def test_acc_dashboard_status_active_for_normal_enabled_cars():
|
||||
cc = SimpleNamespace(enabled=True, latActive=False)
|
||||
|
||||
assert get_acc_dashboard_status_active(SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_ASCM), cc)
|
||||
|
||||
|
||||
def test_volt_auto_hold_requires_toggle_supported_non_cc_only_volt_and_stock_safety():
|
||||
stock_safety = [SimpleNamespace(safetyParam=0x8000)]
|
||||
no_safety = [SimpleNamespace(safetyParam=0)]
|
||||
|
||||
assert not supports_volt_auto_hold(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
openpilotLongitudinalControl=True,
|
||||
networkLocation=CarParams.NetworkLocation.fwdCamera,
|
||||
safetyConfigs=no_safety,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_auto_hold(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
openpilotLongitudinalControl=True,
|
||||
networkLocation=CarParams.NetworkLocation.fwdCamera,
|
||||
safetyConfigs=no_safety,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert supports_volt_auto_hold(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
openpilotLongitudinalControl=True,
|
||||
networkLocation=CarParams.NetworkLocation.fwdCamera,
|
||||
safetyConfigs=stock_safety,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert supports_volt_auto_hold(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT,
|
||||
openpilotLongitudinalControl=False,
|
||||
networkLocation=CarParams.NetworkLocation.gateway,
|
||||
safetyConfigs=stock_safety,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert supports_volt_auto_hold(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_2019,
|
||||
openpilotLongitudinalControl=False,
|
||||
networkLocation=CarParams.NetworkLocation.fwdCamera,
|
||||
safetyConfigs=stock_safety,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_auto_hold(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_2019,
|
||||
openpilotLongitudinalControl=False,
|
||||
networkLocation=CarParams.NetworkLocation.fwdCamera,
|
||||
safetyConfigs=no_safety,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_auto_hold(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
openpilotLongitudinalControl=True,
|
||||
networkLocation=CarParams.NetworkLocation.fwdCamera,
|
||||
safetyConfigs=no_safety,
|
||||
),
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
def test_auto_hold_brake_estimate_uses_driver_or_op_brake_and_clamps():
|
||||
assert estimate_auto_hold_brake(0.0, 20.0) == 80
|
||||
assert estimate_auto_hold_brake(20.0, 40.0) == 110
|
||||
assert estimate_auto_hold_brake(20.0, 160.0) == 160
|
||||
assert estimate_auto_hold_brake(100.0, 400.0) == 240
|
||||
assert estimate_auto_hold_brake(7.0, 0.0, SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_2019)) == 100
|
||||
|
||||
|
||||
def test_volt_one_pedal_requires_toggle_supported_volt_stock_safety_and_ev_transmission():
|
||||
stock_safety = [SimpleNamespace(safetyParam=0x8000)]
|
||||
no_safety = [SimpleNamespace(safetyParam=0)]
|
||||
|
||||
assert supports_volt_one_pedal(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
safetyConfigs=stock_safety,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_one_pedal(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
safetyConfigs=no_safety,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_one_pedal(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
safetyConfigs=stock_safety,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_one_pedal(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
safetyConfigs=stock_safety,
|
||||
transmissionType=structs.CarParams.TransmissionType.automatic,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_one_pedal(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
safetyConfigs=stock_safety,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
),
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
def test_auto_hold_drive_gears_accept_capnp_dynamic_enum_membership():
|
||||
msg = structs.CarState.new_message()
|
||||
msg.gearShifter = structs.CarState.GearShifter.drive
|
||||
|
||||
assert msg.gearShifter in AUTO_HOLD_DRIVE_GEARS
|
||||
|
||||
|
||||
def test_auto_hold_activation_allows_direct_entry_from_stopped_brake_press():
|
||||
assert should_activate_auto_hold(
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
0.01,
|
||||
)
|
||||
|
||||
|
||||
def test_auto_hold_activation_stays_latched_after_brake_release():
|
||||
assert should_activate_auto_hold(
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
0.0,
|
||||
)
|
||||
|
||||
|
||||
def test_volt_2019_auto_hold_engaged_uses_near_stop_creep_hysteresis():
|
||||
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_2019)
|
||||
|
||||
assert get_auto_hold_stop_threshold(CP, True) == CarControllerParams.NEAR_STOP_BRAKE_PHASE
|
||||
assert should_activate_auto_hold(
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
0.05,
|
||||
get_auto_hold_stop_threshold(CP, True),
|
||||
)
|
||||
assert not should_activate_auto_hold(
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
0.05,
|
||||
)
|
||||
|
||||
|
||||
def test_auto_hold_activation_blocks_when_long_is_active_or_motion_is_above_threshold():
|
||||
assert not should_activate_auto_hold(
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
0.0,
|
||||
)
|
||||
assert not should_activate_auto_hold(
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
0.03,
|
||||
)
|
||||
|
||||
|
||||
def test_auto_hold_activation_allows_standstill_even_if_speed_filter_is_slightly_above_threshold():
|
||||
assert should_activate_auto_hold(
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
0.05,
|
||||
)
|
||||
|
||||
|
||||
def test_auto_hold_activation_releases_immediately_on_gas_press():
|
||||
assert not should_activate_auto_hold(
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
0.0,
|
||||
)
|
||||
|
||||
|
||||
def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input():
|
||||
assert should_activate_volt_one_pedal(
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
structs.CarState.GearShifter.low,
|
||||
3.0,
|
||||
)
|
||||
assert not should_activate_volt_one_pedal(
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
structs.CarState.GearShifter.low,
|
||||
3.0,
|
||||
)
|
||||
assert not should_activate_volt_one_pedal(
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
structs.CarState.GearShifter.low,
|
||||
3.0,
|
||||
)
|
||||
assert not should_activate_volt_one_pedal(
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
structs.CarState.GearShifter.low,
|
||||
3.0,
|
||||
)
|
||||
assert not should_activate_volt_one_pedal(
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
True,
|
||||
structs.CarState.GearShifter.low,
|
||||
3.0,
|
||||
)
|
||||
assert not should_activate_volt_one_pedal(
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
structs.CarState.GearShifter.drive,
|
||||
3.0,
|
||||
)
|
||||
|
||||
|
||||
def test_volt_one_pedal_target_decel_stays_active_above_low_speed_band():
|
||||
assert get_volt_one_pedal_target_decel(0.5 * CV.MPH_TO_MS) == -1.0
|
||||
assert get_volt_one_pedal_target_decel(6.0 * CV.MPH_TO_MS) == -1.1
|
||||
assert get_volt_one_pedal_target_decel(20.0 * CV.MPH_TO_MS) == -1.1
|
||||
|
||||
|
||||
def test_volt_one_pedal_regression_ignores_noisy_wheel_direction_bits():
|
||||
assert should_activate_volt_one_pedal(
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
structs.CarState.GearShifter.low,
|
||||
3.0,
|
||||
)
|
||||
|
||||
|
||||
def test_volt_one_pedal_requires_time_in_drive_before_arming():
|
||||
assert not should_activate_volt_one_pedal(
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
structs.CarState.GearShifter.low,
|
||||
2.5,
|
||||
)
|
||||
|
||||
|
||||
def test_friction_brake_mode_keeps_near_stop_disabled_for_regular_long_braking():
|
||||
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_ASCM)
|
||||
|
||||
assert get_friction_brake_mode(120, False, True, False, CP) == 0xa
|
||||
|
||||
|
||||
def test_friction_brake_mode_uses_near_stop_hold_mode_for_volt_auto_hold():
|
||||
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_ASCM)
|
||||
|
||||
assert get_friction_brake_mode(120, False, True, False, CP, allow_near_stop_mode=True) == 0xb
|
||||
assert get_friction_brake_mode(120, False, True, True, CP, allow_near_stop_mode=True) == 0xd
|
||||
|
||||
|
||||
def test_friction_brake_mode_uses_stock_bolt_unwind_for_pedal_print_when_enabled():
|
||||
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL)
|
||||
|
||||
assert get_friction_brake_mode(0, False, True, False, CP) == 0x1
|
||||
assert get_friction_brake_mode(0, True, True, False, CP) == 0x9
|
||||
|
||||
|
||||
def test_friction_brake_mode_keeps_bolt_pedal_braking_mode_unchanged():
|
||||
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL)
|
||||
|
||||
assert get_friction_brake_mode(120, True, False, False, CP) == 0xa
|
||||
assert get_friction_brake_mode(120, True, True, True, CP) == 0xd
|
||||
|
||||
|
||||
def test_calc_pedal_command_small_accel_deadband_keeps_creep_target_stable():
|
||||
pos_controller = _controller()
|
||||
neg_controller = _controller()
|
||||
|
||||
pos_pedal, pos_regen = pos_controller.calc_pedal_command(0.02, True, 0.3)
|
||||
neg_pedal, neg_regen = neg_controller.calc_pedal_command(-0.02, True, 0.3)
|
||||
|
||||
assert not pos_regen
|
||||
assert not neg_regen
|
||||
assert pos_pedal == neg_pedal
|
||||
|
||||
|
||||
def test_calc_pedal_command_creep_switch_does_not_snap_to_target():
|
||||
controller = _controller()
|
||||
controller.pedal_active_last = True
|
||||
controller.pedal_steady = 0.18
|
||||
controller.regen_press_counter = 20
|
||||
|
||||
pedal_gas, press_regen = controller.calc_pedal_command(-1.0, True, 0.5)
|
||||
|
||||
assert press_regen
|
||||
assert pedal_gas > 0.15
|
||||
|
||||
|
||||
def test_calc_pedal_command_softens_small_positive_follow_ramp_at_road_speed():
|
||||
controller = _controller()
|
||||
controller.pedal_active_last = True
|
||||
controller.pedal_steady = 0.18
|
||||
|
||||
pedal_gas, press_regen = controller.calc_pedal_command(0.2, True, 18.0)
|
||||
|
||||
assert not press_regen
|
||||
assert pedal_gas - 0.18 < 0.026
|
||||
|
||||
|
||||
def test_calc_pedal_command_keeps_strong_positive_requests_responsive():
|
||||
controller = _controller()
|
||||
controller.pedal_active_last = True
|
||||
controller.pedal_steady = 0.18
|
||||
|
||||
pedal_gas, press_regen = controller.calc_pedal_command(1.4, True, 18.0)
|
||||
|
||||
assert not press_regen
|
||||
assert pedal_gas - 0.18 > 0.04
|
||||
|
||||
|
||||
def test_shape_truck_positive_accel_softens_small_highway_requests():
|
||||
shaped = shape_truck_positive_accel(0.12, 26.0, True)
|
||||
|
||||
assert 0.09 < shaped < 0.10
|
||||
|
||||
|
||||
def test_shape_truck_positive_accel_keeps_mid_follow_requests_available():
|
||||
shaped = shape_truck_positive_accel(0.45, 13.5, True)
|
||||
|
||||
assert 0.43 < shaped < 0.45
|
||||
|
||||
|
||||
def test_shape_truck_positive_accel_leaves_large_requests_alone():
|
||||
assert shape_truck_positive_accel(1.0, 26.0, True) == 1.0
|
||||
|
||||
|
||||
def test_shape_truck_positive_accel_is_inactive_when_disabled_or_low_speed():
|
||||
assert shape_truck_positive_accel(0.12, 26.0, False) == 0.12
|
||||
assert shape_truck_positive_accel(0.12, 6.0, True) == 0.12
|
||||
|
||||
|
||||
def test_use_interceptor_sng_launch_requires_actual_near_stop():
|
||||
CP = SimpleNamespace(vEgoStarting=0.25)
|
||||
|
||||
assert use_interceptor_sng_launch(CP, _sng_cs(0.0, True, True))
|
||||
assert use_interceptor_sng_launch(CP, _sng_cs(0.2, False, True))
|
||||
assert not use_interceptor_sng_launch(CP, _sng_cs(1.2, False, True))
|
||||
assert not use_interceptor_sng_launch(CP, _sng_cs(0.0, True, False))
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_sng_launch_uses_physical_standstill_without_stock_acc_bit():
|
||||
CP = SimpleNamespace(
|
||||
vEgoStarting=0.25,
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
)
|
||||
|
||||
assert use_interceptor_sng_launch(CP, _sng_cs(0.0, True, False))
|
||||
assert use_interceptor_sng_launch(CP, _sng_cs(0.2, False, False))
|
||||
assert not use_interceptor_sng_launch(CP, _sng_cs(1.2, False, False))
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_sng_launch_preserves_stronger_computed_pedal():
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
openpilotLongitudinalControl=True,
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
)
|
||||
params = SimpleNamespace(SNG_INTERCEPTOR_GAS=18. / 255.)
|
||||
|
||||
assert get_interceptor_sng_gas_cmd(CP, 0.2, 0.54, params, False) == pytest.approx(0.2)
|
||||
|
||||
|
||||
def test_other_pedal_sng_launch_keeps_fixed_floor_behavior():
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
openpilotLongitudinalControl=True,
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
)
|
||||
params = SimpleNamespace(SNG_INTERCEPTOR_GAS=18. / 255.)
|
||||
|
||||
assert get_interceptor_sng_gas_cmd(CP, 0.2, 0.54, params, False) == pytest.approx(18. / 255.)
|
||||
|
||||
|
||||
def test_use_interceptor_sng_launch_extends_for_maneuver_mode():
|
||||
CP = SimpleNamespace(vEgoStarting=0.25)
|
||||
|
||||
assert use_interceptor_sng_launch(CP, _sng_cs(1.2, False, True), maneuver_mode=True)
|
||||
assert not use_interceptor_sng_launch(CP, _sng_cs(2.2, False, True), maneuver_mode=True)
|
||||
|
||||
|
||||
def test_testing_ground_1_brake_switch_bias_is_softened_but_still_speed_scaled():
|
||||
assert get_testing_ground_1_brake_switch_bias(0.0) == 40
|
||||
assert get_testing_ground_1_brake_switch_bias(6.0) == 85
|
||||
assert get_testing_ground_1_brake_switch_bias(15.0) == 130
|
||||
assert get_testing_ground_1_brake_switch_bias(30.0) == 170
|
||||
|
||||
|
||||
def test_lka_counter_uses_returned_loopback_counter():
|
||||
cs = SimpleNamespace(
|
||||
loopback_lka_steering_cmd_updated=True,
|
||||
loopback_lka_steering_cmd_counter=2,
|
||||
loopback_lka_steering_cmd_ts_nanos=1,
|
||||
pt_lka_steering_cmd_counter=0,
|
||||
)
|
||||
|
||||
assert get_lka_steering_cmd_counter(0, cs) == 3
|
||||
|
||||
|
||||
def test_lka_counter_keeps_advancing_without_loopback_updates():
|
||||
cs = SimpleNamespace(
|
||||
loopback_lka_steering_cmd_updated=False,
|
||||
loopback_lka_steering_cmd_counter=0,
|
||||
loopback_lka_steering_cmd_ts_nanos=1,
|
||||
pt_lka_steering_cmd_counter=0,
|
||||
)
|
||||
|
||||
next_counter = 1
|
||||
sent = []
|
||||
for _ in range(6):
|
||||
idx = get_lka_steering_cmd_counter(next_counter, cs)
|
||||
sent.append(idx)
|
||||
next_counter = (idx + 1) % 4
|
||||
|
||||
assert sent == [1, 2, 3, 0, 1, 2]
|
||||
|
||||
|
||||
def test_lka_counter_only_seeds_from_pt_counter_once_without_loopback():
|
||||
cs = SimpleNamespace(
|
||||
loopback_lka_steering_cmd_updated=False,
|
||||
loopback_lka_steering_cmd_counter=0,
|
||||
loopback_lka_steering_cmd_ts_nanos=0,
|
||||
pt_lka_steering_cmd_counter=0,
|
||||
)
|
||||
|
||||
next_counter = -1
|
||||
sent = []
|
||||
for _ in range(6):
|
||||
idx = get_lka_steering_cmd_counter(next_counter, cs)
|
||||
sent.append(idx)
|
||||
next_counter = (idx + 1) % 4
|
||||
|
||||
assert sent == [1, 2, 3, 0, 1, 2]
|
||||
@@ -1,16 +1,28 @@
|
||||
import pytest
|
||||
import numpy as np
|
||||
from types import SimpleNamespace
|
||||
from parameterized import parameterized
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus, DT_CTRL
|
||||
from cereal import custom
|
||||
from opendbc.can import CANPacker, CANParser
|
||||
from opendbc.car import Bus, DT_CTRL, structs
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.gm import gmcan
|
||||
from opendbc.car.gm.carcontroller import should_send_acc_dashboard_status, should_send_cc_button_spam, should_spoof_dash_speed
|
||||
from opendbc.car.gm.carstate import CarState as GMCarState, get_hard_cruise_buttons, update_auto_hold_drive_timers
|
||||
from opendbc.car.gm.carcontroller import (
|
||||
VisualAlert,
|
||||
get_acc_dashboard_fcw_alert,
|
||||
get_volt_one_pedal_lift_brake,
|
||||
should_send_acc_dashboard_status,
|
||||
should_send_cc_button_spam,
|
||||
should_spoof_dash_speed,
|
||||
)
|
||||
import opendbc.car.gm.interface as gm_interface
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.gm.fingerprints import FINGERPRINTS
|
||||
from opendbc.car.gm.values import CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, GMFlags, GMSafetyFlags
|
||||
from opendbc.car.gm.values import ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.common.params import Params
|
||||
|
||||
CAMERA_DIAGNOSTIC_ADDRESS = 0x24b
|
||||
VOLT_CARS = (
|
||||
@@ -32,6 +44,7 @@ def _test_starpilot_toggles():
|
||||
cluster_offset=1.0,
|
||||
disable_openpilot_long=False,
|
||||
force_fingerprint=False,
|
||||
remap_cancel_to_distance=False,
|
||||
vEgoStopping=0.5,
|
||||
volt_sng=False,
|
||||
)
|
||||
@@ -52,6 +65,52 @@ class TestGMFingerprint:
|
||||
|
||||
|
||||
class TestGMInterface:
|
||||
def test_bolt_acc_pedal_pid_accel_limits_keep_full_negative_authority(self):
|
||||
cp = SimpleNamespace(
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
)
|
||||
|
||||
accel_min, accel_max = gm_interface.CarInterface.get_pid_accel_limits(cp, 4.73, 0.0)
|
||||
|
||||
assert accel_min == pytest.approx(CarControllerParams.ACCEL_MIN)
|
||||
assert accel_max == pytest.approx(np.interp(4.73, [0.0, 1.5, 4.0, 8.0, 15.0],
|
||||
[0.54, 0.74, 1.03, 1.46, CarControllerParams.ACCEL_MAX]))
|
||||
|
||||
def test_bolt_cc_pedal_pid_accel_limits_remain_regen_limited(self):
|
||||
cp = SimpleNamespace(
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_CC_2022_2023,
|
||||
)
|
||||
|
||||
accel_min, _ = gm_interface.CarInterface.get_pid_accel_limits(cp, 4.73, 0.0)
|
||||
|
||||
assert accel_min == pytest.approx(np.interp(4.73, [0.0, 1.5, 4.0, 8.0, 15.0, 30.0],
|
||||
[-0.93, -1.28, -1.98, -2.58, -2.86, -2.95]))
|
||||
|
||||
def test_missing_hard_cruise_signal_defaults_to_init(self):
|
||||
assert get_hard_cruise_buttons({"ACCButtons": CruiseButtons.RES_ACCEL}) == CruiseButtons.INIT
|
||||
assert get_hard_cruise_buttons({"ACCButtonsHard": CruiseButtons.DECEL_SET}) == CruiseButtons.DECEL_SET
|
||||
|
||||
def test_volt_auto_hold_drive_timer_requires_motion_before_startup_arming(self):
|
||||
auto_hold_time, one_pedal_time = update_auto_hold_drive_timers(True, False, 0.0, 0.0)
|
||||
|
||||
assert auto_hold_time == 0.0
|
||||
assert one_pedal_time == 0.0
|
||||
|
||||
def test_volt_auto_hold_drive_timer_accumulates_only_while_moving(self):
|
||||
auto_hold_time, one_pedal_time = update_auto_hold_drive_timers(True, True, 0.0, 0.0)
|
||||
|
||||
assert auto_hold_time == pytest.approx(DT_CTRL)
|
||||
assert one_pedal_time == pytest.approx(DT_CTRL)
|
||||
|
||||
auto_hold_time, one_pedal_time = update_auto_hold_drive_timers(True, False, auto_hold_time, one_pedal_time)
|
||||
|
||||
assert auto_hold_time == pytest.approx(DT_CTRL)
|
||||
assert one_pedal_time == pytest.approx(DT_CTRL)
|
||||
|
||||
@parameterized.expand(VOLT_CARS)
|
||||
def test_volt_min_steer_speed_is_7_mph(self, car_model):
|
||||
CarInterface = interfaces[car_model]
|
||||
@@ -64,22 +123,37 @@ class TestGMInterface:
|
||||
("interceptor", True),
|
||||
("ascm_int", False),
|
||||
])
|
||||
def test_volt_testing_ground_tune_sets_nonzero_p_and_starting_state(self, _name, pedal_present, monkeypatch):
|
||||
def test_volt_testing_ground_tune_sets_nonzero_p_and_starting_state(self, _name, pedal_present):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
|
||||
fingerprint = _empty_fingerprint()
|
||||
if pedal_present:
|
||||
fingerprint[0][0x201] = 8 # pedal detected
|
||||
fingerprint[0][0x2FF] = 8 # SASCM detected
|
||||
|
||||
monkeypatch.setattr(gm_interface.testing_ground, "use_2", True, raising=False)
|
||||
old_testing_ground = gm_interface.testing_ground
|
||||
gm_interface.testing_ground = SimpleNamespace(use_2=True)
|
||||
params = Params()
|
||||
params.put_bool("GMPedalLongitudinal", True)
|
||||
|
||||
car_params = CarInterface.get_params(CAR.CHEVROLET_VOLT_ASCM, fingerprint, [], alpha_long=False, is_release=False, docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles())
|
||||
try:
|
||||
car_params = CarInterface.get_params(CAR.CHEVROLET_VOLT_ASCM, fingerprint, [], alpha_long=False, is_release=False, docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles())
|
||||
finally:
|
||||
gm_interface.testing_ground = old_testing_ground
|
||||
params.remove("GMPedalLongitudinal")
|
||||
|
||||
assert list(car_params.longitudinalTuning.kpV) == [0.10, 0.072, 0.05, 0.04]
|
||||
assert list(car_params.longitudinalTuning.kiV) == [0.025, 0.03, 0.04, 0.055]
|
||||
assert car_params.startingState
|
||||
assert car_params.startAccel == pytest.approx(1.15)
|
||||
if pedal_present:
|
||||
assert list(car_params.longitudinalTuning.kpV) == pytest.approx([0.10, 0.072, 0.05, 0.04])
|
||||
assert list(car_params.longitudinalTuning.kiV) == pytest.approx([0.025, 0.03, 0.04, 0.055])
|
||||
assert car_params.startingState
|
||||
assert car_params.startAccel == pytest.approx(1.15)
|
||||
else:
|
||||
assert not car_params.openpilotLongitudinalControl
|
||||
assert not car_params.enableGasInterceptorDEPRECATED
|
||||
assert list(car_params.longitudinalTuning.kpV) == [0.0]
|
||||
assert list(car_params.longitudinalTuning.kiV) == [0.5, 0.5]
|
||||
assert not car_params.startingState
|
||||
assert car_params.startAccel == pytest.approx(0.0)
|
||||
|
||||
def test_volt_cc_sparse_fingerprint_without_camera_sets_no_camera(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT_CC]
|
||||
@@ -94,6 +168,42 @@ class TestGMInterface:
|
||||
assert car_params.flags & GMFlags.NO_CAMERA.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
def test_silverado_alpha_long_uses_trimmed_longitudinal_tune(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_SILVERADO]
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SILVERADO][0].copy()
|
||||
|
||||
car_params = CarInterface.get_params(CAR.CHEVROLET_SILVERADO, fingerprint, [], alpha_long=True, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
|
||||
assert car_params.openpilotLongitudinalControl
|
||||
assert not car_params.enableGasInterceptorDEPRECATED
|
||||
assert list(car_params.longitudinalTuning.kpBP) == pytest.approx([0.0, 5.0, 15.0, 35.0])
|
||||
assert list(car_params.longitudinalTuning.kpV) == pytest.approx([0.02, 0.03, 0.028, 0.022])
|
||||
assert list(car_params.longitudinalTuning.kiBP) == pytest.approx([0.0, 5.0, 15.0, 35.0])
|
||||
assert list(car_params.longitudinalTuning.kiV) == pytest.approx([0.28, 0.26, 0.20, 0.16])
|
||||
|
||||
def test_blazer_uses_softer_low_speed_stop_hold_tune(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_BLAZER]
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_BLAZER][0].copy()
|
||||
fingerprint[0][0x2FF] = 8 # SASCM present so alpha-long can enable on this platform
|
||||
|
||||
car_params = CarInterface.get_params(CAR.CHEVROLET_BLAZER, fingerprint, [], alpha_long=True, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
|
||||
assert car_params.openpilotLongitudinalControl
|
||||
assert list(car_params.longitudinalTuning.kpBP) == pytest.approx([0.0, 4.0, 12.0, 35.0])
|
||||
assert list(car_params.longitudinalTuning.kpV) == pytest.approx([0.09, 0.075, 0.055, 0.04])
|
||||
assert list(car_params.longitudinalTuning.kiBP) == pytest.approx([0.0, 4.0, 12.0, 35.0])
|
||||
assert list(car_params.longitudinalTuning.kiV) == pytest.approx([0.03, 0.04, 0.055, 0.07])
|
||||
assert car_params.longitudinalActuatorDelay == pytest.approx(0.7)
|
||||
assert car_params.minEnableSpeed == pytest.approx(5 * CV.KPH_TO_MS)
|
||||
assert car_params.stoppingDecelRate == pytest.approx(1.0)
|
||||
assert car_params.vEgoStopping == pytest.approx(0.35)
|
||||
assert car_params.vEgoStarting == pytest.approx(0.35)
|
||||
assert car_params.stopAccel == pytest.approx(-0.30)
|
||||
|
||||
def test_volt_gateway_without_accel_pos_uses_brake_pedal_message(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT]
|
||||
fingerprint = _empty_fingerprint()
|
||||
@@ -103,11 +213,172 @@ class TestGMInterface:
|
||||
starpilot_toggles=_test_starpilot_toggles())
|
||||
|
||||
assert car_params.flags & GMFlags.NO_ACCELERATOR_POS_MSG.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
pt_parser = CarInterface.CarState.get_can_parsers(car_params)[Bus.pt]
|
||||
assert "ECMAcceleratorPos" not in pt_parser.vl
|
||||
assert "EBCMBrakePedalPosition" in pt_parser.vl
|
||||
|
||||
def test_volt_auto_hold_sets_stock_hold_safety_bit_with_op_long_enabled(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0][0x2FF] = 8
|
||||
|
||||
params = Params()
|
||||
try:
|
||||
params.put_bool("GMAutoHold", True)
|
||||
car_params = CarInterface.get_params(CAR.CHEVROLET_VOLT_ASCM, fingerprint, [], alpha_long=True, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
finally:
|
||||
params.remove("GMAutoHold")
|
||||
|
||||
assert car_params.openpilotLongitudinalControl
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
|
||||
|
||||
def test_volt_one_pedal_sets_stock_hold_safety_bit_without_auto_hold(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0][0x2FF] = 8
|
||||
|
||||
params = Params()
|
||||
try:
|
||||
params.put_bool("GMAutoHold", False)
|
||||
params.put_bool("VoltOnePedalMode", True)
|
||||
car_params = CarInterface.get_params(CAR.CHEVROLET_VOLT_ASCM, fingerprint, [], alpha_long=True, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
finally:
|
||||
params.remove("GMAutoHold")
|
||||
params.remove("VoltOnePedalMode")
|
||||
|
||||
assert car_params.openpilotLongitudinalControl
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_3D1_SCHED.value
|
||||
|
||||
@parameterized.expand(VOLT_CARS)
|
||||
def test_volt_bsm_is_enabled_without_fingerprint_match(self, car_model):
|
||||
CarInterface = interfaces[car_model]
|
||||
car_params = CarInterface.get_params(car_model, _empty_fingerprint(), [], alpha_long=False, is_release=False, docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles())
|
||||
|
||||
assert car_params.enableBsm
|
||||
|
||||
def test_volt_bsm_parser_is_optional(self):
|
||||
cp = SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_ASCM,
|
||||
networkLocation=structs.CarParams.NetworkLocation.fwdCamera,
|
||||
flags=0,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
enableGasInterceptorDEPRECATED=False,
|
||||
enableBsm=True,
|
||||
)
|
||||
|
||||
pt_parser = GMCarState.get_can_parsers(cp)[Bus.pt]
|
||||
bsm_addr = pt_parser.dbc.name_to_msg["BCMBlindSpotMonitor"].address
|
||||
|
||||
assert "BCMBlindSpotMonitor" in pt_parser.vl
|
||||
assert pt_parser.message_states[bsm_addr].ignore_alive
|
||||
|
||||
def test_volt_ascm_cam_parser_includes_optional_aeb_cmd(self):
|
||||
cam_parser = GMCarState.get_can_parsers(SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_ASCM,
|
||||
networkLocation=structs.CarParams.NetworkLocation.fwdCamera,
|
||||
flags=0,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
enableGasInterceptorDEPRECATED=False,
|
||||
enableBsm=False,
|
||||
))[Bus.cam]
|
||||
aeb_addr = cam_parser.dbc.name_to_msg["AEBCmd"].address
|
||||
|
||||
assert "AEBCmd" in cam_parser.vl
|
||||
assert cam_parser.message_states[aeb_addr].ignore_alive
|
||||
|
||||
def test_bolt_gen2_pedal_cancel_remap_sets_alt_exp(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL]
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0][0x201] = 8
|
||||
|
||||
params = Params()
|
||||
toggles = _test_starpilot_toggles()
|
||||
try:
|
||||
params.put_bool("GMPedalLongitudinal", True)
|
||||
params.put_bool("RemapCancelToDistance", True)
|
||||
car_params = CarInterface.get_params(CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, fingerprint, [], alpha_long=False,
|
||||
is_release=False, docs=False, starpilot_toggles=toggles)
|
||||
finally:
|
||||
params.remove("GMPedalLongitudinal")
|
||||
params.remove("RemapCancelToDistance")
|
||||
|
||||
assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.GM_REMAP_CANCEL_TO_DISTANCE
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_BOLT_2022_PEDAL.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
|
||||
|
||||
def test_cadillac_xt5_sdgm_sascm_gates_alpha_long(self):
|
||||
CarInterface = interfaces[CAR.CADILLAC_XT5]
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0][0xBE] = 6
|
||||
|
||||
stock_params = CarInterface.get_params(CAR.CADILLAC_XT5, fingerprint, [], alpha_long=True, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
|
||||
assert stock_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
|
||||
assert stock_params.pcmCruise
|
||||
assert stock_params.alphaLongitudinalAvailable is False
|
||||
assert stock_params.openpilotLongitudinalControl is False
|
||||
assert stock_params.safetyConfigs[0].safetyParam & GMSafetyFlags.HW_SDGM.value
|
||||
|
||||
fingerprint[0][0x2FF] = 8
|
||||
sascm_params = CarInterface.get_params(CAR.CADILLAC_XT5, fingerprint, [], alpha_long=True, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
|
||||
assert sascm_params.flags & GMFlags.SASCM.value
|
||||
assert sascm_params.alphaLongitudinalAvailable
|
||||
assert sascm_params.openpilotLongitudinalControl
|
||||
assert not sascm_params.pcmCruise
|
||||
assert sascm_params.safetyConfigs[0].safetyParam & GMSafetyFlags.HW_CAM_LONG.value
|
||||
|
||||
def test_cadillac_escalade_esv_2019_ascm_uses_sascm_and_2019_tune(self):
|
||||
base_fingerprint = FINGERPRINTS[CAR.CADILLAC_ESCALADE_ESV_2019][0]
|
||||
ascm_fingerprint = FINGERPRINTS[CAR.CADILLAC_ESCALADE_ESV_2019_ASCM][0]
|
||||
|
||||
assert CAR.CADILLAC_ESCALADE_ESV_2019_ASCM in ASCM_INT
|
||||
assert ascm_fingerprint[0x2FF] == 8
|
||||
assert {addr: length for addr, length in ascm_fingerprint.items() if addr != 0x2FF} == base_fingerprint
|
||||
|
||||
CarInterface = interfaces[CAR.CADILLAC_ESCALADE_ESV_2019_ASCM]
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0] = ascm_fingerprint.copy()
|
||||
|
||||
car_params = CarInterface.get_params(CAR.CADILLAC_ESCALADE_ESV_2019_ASCM, fingerprint, [], alpha_long=True, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
|
||||
assert car_params.flags & GMFlags.SASCM.value
|
||||
assert car_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
|
||||
assert car_params.openpilotLongitudinalControl
|
||||
assert not car_params.pcmCruise
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.HW_ASCM_INT.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.HW_CAM_LONG.value
|
||||
assert car_params.lateralTuning.torque.latAccelFactor == pytest.approx(1.15)
|
||||
assert car_params.lateralTuning.torque.friction == pytest.approx(0.2)
|
||||
|
||||
def test_cadillac_xt4_uses_nonlinear_torque_curve_with_center_boost(self):
|
||||
CarInterface = interfaces[CAR.CADILLAC_XT4]
|
||||
car_params = CarInterface.get_non_essential_params(CAR.CADILLAC_XT4)
|
||||
ci = CarInterface(car_params, custom.StarPilotCarParams.new_message())
|
||||
torque_from_lataccel = ci.torque_from_lateral_accel()
|
||||
|
||||
low_lataccel = 0.2
|
||||
high_lataccel = 1.0
|
||||
low_torque = torque_from_lataccel(low_lataccel, car_params.lateralTuning.torque)
|
||||
high_torque = torque_from_lataccel(high_lataccel, car_params.lateralTuning.torque)
|
||||
|
||||
linear_low_torque = low_lataccel / car_params.lateralTuning.torque.latAccelFactor
|
||||
linear_high_torque = high_lataccel / car_params.lateralTuning.torque.latAccelFactor
|
||||
|
||||
assert low_torque > linear_low_torque * 1.15
|
||||
assert low_torque < linear_low_torque * 1.30
|
||||
assert high_torque == pytest.approx(linear_high_torque, rel=0.03)
|
||||
assert torque_from_lataccel(-low_lataccel, car_params.lateralTuning.torque) == pytest.approx(-low_torque, rel=1e-6)
|
||||
|
||||
|
||||
class TestGMCarController:
|
||||
def test_dash_speed_spoof_respects_live_stock_acc_toggles(self):
|
||||
@@ -122,6 +393,11 @@ class TestGMCarController:
|
||||
|
||||
assert should_spoof_dash_speed(cp, SimpleNamespace(disable_openpilot_long=False))
|
||||
|
||||
def test_volt_one_pedal_lift_brake_seeds_low_speed_braking(self):
|
||||
assert get_volt_one_pedal_lift_brake(2.1 * CV.MPH_TO_MS) == 0
|
||||
assert get_volt_one_pedal_lift_brake(2.0 * CV.MPH_TO_MS) == 20
|
||||
assert get_volt_one_pedal_lift_brake(0.10) == 80
|
||||
|
||||
def test_volt_camera_no_camera_sends_acc_dashboard_without_dash_spoof(self):
|
||||
cp = SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_CAMERA, flags=GMFlags.NO_CAMERA.value)
|
||||
|
||||
@@ -213,3 +489,72 @@ class TestGMCarController:
|
||||
msgs = gmcan.create_gm_cc_spam_command(packer, controller, cs, actuators, SimpleNamespace(is_metric=False))
|
||||
|
||||
assert [msg[2] for msg in msgs] == [0]
|
||||
|
||||
def test_acc_dashboard_command_preserves_raw_fcw_alert_level(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_BOLT_ACC_2022_2023][Bus.pt])
|
||||
parser = CANParser(DBC[CAR.CHEVROLET_BOLT_ACC_2022_2023][Bus.pt], [("ASCMActiveCruiseControlStatus", 0)], 0)
|
||||
msg = gmcan.create_acc_dashboard_command(
|
||||
packer,
|
||||
0,
|
||||
True,
|
||||
100,
|
||||
SimpleNamespace(leadDistanceBars=3, leadVisible=True),
|
||||
0x2,
|
||||
)
|
||||
|
||||
parser.update([0, [msg]])
|
||||
|
||||
assert parser.vl["ASCMActiveCruiseControlStatus"]["FCWAlert"] == 2
|
||||
|
||||
def test_acc_dashboard_command_uses_openpilot_hud_when_disengaged(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_ASCM][Bus.pt])
|
||||
parser = CANParser(DBC[CAR.CHEVROLET_VOLT_ASCM][Bus.pt], [("ASCMActiveCruiseControlStatus", 0)], 0)
|
||||
msg = gmcan.create_acc_dashboard_command(
|
||||
packer,
|
||||
0,
|
||||
False,
|
||||
50,
|
||||
SimpleNamespace(leadDistanceBars=2, leadVisible=True),
|
||||
0x3,
|
||||
)
|
||||
|
||||
parser.update([0, [msg]])
|
||||
values = parser.vl["ASCMActiveCruiseControlStatus"]
|
||||
|
||||
assert values["ACCSpeedSetpoint"] == 50
|
||||
assert values["ACCGapLevel"] == 0
|
||||
assert values["ACCCmdActive"] == 0
|
||||
assert values["ACCLeadCar"] == 1
|
||||
assert values["FCWAlert"] == 3
|
||||
|
||||
def test_acc_dashboard_fcw_alert_prefers_openpilot_alert(self):
|
||||
cs = SimpleNamespace(
|
||||
stock_fcw_alert=1,
|
||||
out=SimpleNamespace(stockAeb=False, stockFcw=False),
|
||||
)
|
||||
|
||||
assert get_acc_dashboard_fcw_alert(VisualAlert.fcw, cs) == 0x3
|
||||
|
||||
def test_acc_dashboard_fcw_alert_replays_stock_camera_alert_level(self):
|
||||
cs = SimpleNamespace(
|
||||
stock_fcw_alert=2,
|
||||
out=SimpleNamespace(stockAeb=False, stockFcw=False),
|
||||
)
|
||||
|
||||
assert get_acc_dashboard_fcw_alert(VisualAlert.none, cs) == 2
|
||||
|
||||
def test_acc_dashboard_fcw_alert_falls_back_to_stock_aeb_event(self):
|
||||
cs = SimpleNamespace(
|
||||
stock_fcw_alert=0,
|
||||
out=SimpleNamespace(stockAeb=True, stockFcw=False),
|
||||
)
|
||||
|
||||
assert get_acc_dashboard_fcw_alert(VisualAlert.none, cs) == 0x3
|
||||
|
||||
def test_acc_dashboard_fcw_alert_falls_back_to_stock_fcw_event(self):
|
||||
cs = SimpleNamespace(
|
||||
stock_fcw_alert=0,
|
||||
out=SimpleNamespace(stockAeb=False, stockFcw=True),
|
||||
)
|
||||
|
||||
assert get_acc_dashboard_fcw_alert(VisualAlert.none, cs) == 0x3
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car.gm import gmcan
|
||||
from opendbc.car.gm.values import CAR, DBC
|
||||
|
||||
|
||||
class TestGMCan:
|
||||
def setup_method(self):
|
||||
self.packer = CANPacker(DBC[CAR.CHEVROLET_BOLT_ACC_2022_2023]["pt"])
|
||||
|
||||
def test_gas_regen_command_matches_starpilot_bolt_acc(self):
|
||||
addr, dat, bus = gmcan.create_gas_regen_command(self.packer, 0, 5000, 1, True, False)
|
||||
|
||||
assert addr == 0x2CB
|
||||
assert bus == 0
|
||||
assert dat.hex() == "41429c4000bd63bf"
|
||||
|
||||
def test_gas_regen_command_preserves_always_one3_layout(self):
|
||||
_, dat, _ = gmcan.create_gas_regen_command(self.packer, 0, 0, 1, True, False, include_always_one3=True)
|
||||
|
||||
assert dat.hex() == "4142800000bd7fff"
|
||||
|
||||
def test_gas_regen_command_encodes_high_bit_above_8191(self):
|
||||
_, dat, _ = gmcan.create_gas_regen_command(self.packer, 0, 8848, 1, True, False)
|
||||
decoded = ((dat[1] & 0x1) << 13) | (dat[2] << 5) | ((dat[3] & 0xF8) >> 3)
|
||||
|
||||
assert dat[1] & 0x1
|
||||
assert decoded == 8848
|
||||
|
||||
def test_prndl2_command_matches_bolt_gen2_regen_paddle_spoof(self):
|
||||
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL)
|
||||
|
||||
addr, dat, bus = gmcan.create_prndl2_command(self.packer, 0, False, CP)
|
||||
assert addr == 0x1F5
|
||||
assert bus == 0
|
||||
assert dat.hex() == "0c0c000600000100"
|
||||
|
||||
addr, dat, bus = gmcan.create_prndl2_command(self.packer, 0, True, CP)
|
||||
assert addr == 0x1F5
|
||||
assert bus == 0
|
||||
assert dat.hex() == "0c0c000500020100"
|
||||
|
||||
def test_prndl2_command_matches_bolt_gen1_regen_paddle_spoof(self):
|
||||
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_BOLT_CC_2018_2021)
|
||||
|
||||
addr, dat, bus = gmcan.create_prndl2_command(self.packer, 0, True, CP)
|
||||
|
||||
assert addr == 0x1F5
|
||||
assert bus == 0
|
||||
assert dat.hex() == "0c0c000700020100"
|
||||
|
||||
def test_regen_paddle_command_matches_bolt_spoof(self):
|
||||
addr, dat, bus = gmcan.create_regen_paddle_command(self.packer, 0, False)
|
||||
assert addr == 0xBD
|
||||
assert bus == 0
|
||||
assert dat.hex() == "00000000000000"
|
||||
|
||||
addr, dat, bus = gmcan.create_regen_paddle_command(self.packer, 0, True)
|
||||
assert addr == 0xBD
|
||||
assert bus == 0
|
||||
assert dat.hex() == "20000000000000"
|
||||
|
||||
|
||||
def test_gas_regen_command_matches_starpilot_volt_2019(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_2019]["pt"])
|
||||
addr, dat, bus = gmcan.create_gas_regen_command(packer, 0, 5000, 1, True, False, include_always_one3=True, use_volt_layout=True)
|
||||
|
||||
assert addr == 0x2CB
|
||||
assert bus == 0
|
||||
assert dat.hex() == "41429c4000bd63bf"
|
||||
|
||||
def test_gas_regen_command_matches_starpilot_volt_ascm(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_ASCM]["pt"])
|
||||
addr, dat, bus = gmcan.create_gas_regen_command(packer, 0, 5000, 1, True, False, include_always_one3=True, use_volt_layout=True)
|
||||
|
||||
assert addr == 0x2CB
|
||||
assert bus == 0
|
||||
assert dat.hex() == "41429c4000bd63bf"
|
||||
@@ -233,7 +233,7 @@ class CAR(Platforms):
|
||||
)
|
||||
CHEVROLET_VOLT = GMASCMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Volt 2017-18", min_enable_speed=0, video="https://youtu.be/QeMCN_4TFfQ")],
|
||||
GMCarSpecs(mass=1607, wheelbase=2.69, steerRatio=15.7, centerToFrontRatio=0.45, tireStiffnessFactor=0.469, minEnableSpeed=-1),
|
||||
GMCarSpecs(mass=1607, wheelbase=2.69, steerRatio=15.7, centerToFrontRatio=0.45, tireStiffnessFactor=1.0, minEnableSpeed=-1), #tire stiffness factor hasn't been updated since 2018 and every other gm is on 1.0
|
||||
dbc_dict={
|
||||
Bus.pt: "gm_global_a_powertrain_volt",
|
||||
Bus.radar: "gm_global_a_object",
|
||||
@@ -279,6 +279,10 @@ class CAR(Platforms):
|
||||
[GMCarDocs("Buick LaCrosse 2017-19", "Driver Confidence Package 2")],
|
||||
GMCarSpecs(mass=1712, wheelbase=2.91, steerRatio=15.8, centerToFrontRatio=0.4),
|
||||
)
|
||||
BUICK_LACROSSE_ASCM = GMPlatformConfig(
|
||||
[GMCarDocs("Buick LaCrosse 2017-19 ASCM Harness")],
|
||||
BUICK_LACROSSE.specs,
|
||||
)
|
||||
BUICK_REGAL = GMASCMPlatformConfig(
|
||||
[GMCarDocs("Buick Regal Essence 2018")],
|
||||
GMCarSpecs(mass=1714, wheelbase=2.83, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
@@ -299,6 +303,10 @@ class CAR(Platforms):
|
||||
[GMCarDocs("Cadillac Escalade ESV 2019", "Adaptive Cruise Control (ACC) & LKAS")],
|
||||
CADILLAC_ESCALADE_ESV.specs,
|
||||
)
|
||||
CADILLAC_ESCALADE_ESV_2019_ASCM = GMPlatformConfig(
|
||||
[GMCarDocs("Cadillac Escalade ESV Platinum 2019 ASCM Harness", "Adaptive Cruise Control (ACC) & LKAS")],
|
||||
CADILLAC_ESCALADE_ESV_2019.specs,
|
||||
)
|
||||
CHEVROLET_BOLT_ACC_2022_2023 = GMPlatformConfig(
|
||||
[
|
||||
GMCarDocs("Chevrolet Bolt ACC 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video="https://youtu.be/xvwzGMUA210"),
|
||||
@@ -329,6 +337,13 @@ class CAR(Platforms):
|
||||
],
|
||||
GMCarSpecs(mass=2994, wheelbase=3.75, steerRatio=16.3, tireStiffnessFactor=1.0),
|
||||
)
|
||||
CHEVROLET_SILVERADO_CC = GMPlatformConfig(
|
||||
[
|
||||
GMCarDocs("Chevrolet Silverado 1500 - No-ACC"),
|
||||
GMCarDocs("GMC Sierra 1500 - No-ACC"),
|
||||
],
|
||||
CHEVROLET_SILVERADO.specs,
|
||||
)
|
||||
CHEVROLET_EQUINOX = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Equinox 2019-22")],
|
||||
GMCarSpecs(mass=1588, wheelbase=2.72, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
@@ -353,6 +368,10 @@ class CAR(Platforms):
|
||||
[GMCarDocs("Cadillac XT4 2023", "Driver Assist Package")],
|
||||
GMCarSpecs(mass=1660, wheelbase=2.78, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
)
|
||||
CADILLAC_XT5 = GMSDGMPlatformConfig(
|
||||
[GMCarDocs("Cadillac XT5 2022", "Driver Assist Package")],
|
||||
CarSpecs(mass=1810, wheelbase=2.86, steerRatio=16.34, centerToFrontRatio=0.5),
|
||||
)
|
||||
CADILLAC_XT6 = GMPlatformConfig(
|
||||
[GMCarDocs("Cadillac XT6 2020", "Driver Assist Package")],
|
||||
GMCarSpecs(mass=2050, wheelbase=2.86, steerRatio=16.5, centerToFrontRatio=0.4),
|
||||
@@ -383,7 +402,7 @@ class CAR(Platforms):
|
||||
)
|
||||
CADILLAC_XT5_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Cadillac XT5 - No-ACC")],
|
||||
CarSpecs(mass=1810, wheelbase=2.86, steerRatio=16.34, centerToFrontRatio=0.5),
|
||||
CADILLAC_XT5.specs,
|
||||
)
|
||||
CHEVROLET_EQUINOX_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Equinox 2019-22 - No-ACC")],
|
||||
@@ -527,6 +546,7 @@ ALT_ACCS = {CAR.GMC_YUKON, CAR.GMC_YUKON_CC}
|
||||
# We're integrated at the Safety Data Gateway Module on these cars
|
||||
SDGM_CAR = {
|
||||
CAR.CADILLAC_XT4,
|
||||
CAR.CADILLAC_XT5,
|
||||
CAR.CADILLAC_XT6,
|
||||
CAR.CHEVROLET_TRAVERSE,
|
||||
CAR.CHEVROLET_BLAZER,
|
||||
@@ -549,6 +569,7 @@ CC_ONLY_CAR = {
|
||||
CAR.CADILLAC_XT5_CC,
|
||||
CAR.CHEVROLET_MALIBU_CC,
|
||||
CAR.CHEVROLET_MALIBU_HYBRID_CC,
|
||||
CAR.CHEVROLET_SILVERADO_CC,
|
||||
}
|
||||
CC_REGEN_PADDLE_CAR = {
|
||||
CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
@@ -559,7 +580,14 @@ CC_REGEN_PADDLE_CAR = {
|
||||
CAMERA_ACC_CAR.update(CC_ONLY_CAR)
|
||||
|
||||
# ASCM-INT paths are only enabled when SASCM (0x2FF) is detected at runtime
|
||||
ASCM_INT = {CAR.CHEVROLET_VOLT_ASCM, CAR.GMC_ACADIA_ASCM, CAR.CHEVROLET_MALIBU_ASCM, CAR.CADILLAC_ESCALADE_ASCM}
|
||||
ASCM_INT = {
|
||||
CAR.CHEVROLET_VOLT_ASCM,
|
||||
CAR.GMC_ACADIA_ASCM,
|
||||
CAR.CHEVROLET_MALIBU_ASCM,
|
||||
CAR.CADILLAC_ESCALADE_ASCM,
|
||||
CAR.CADILLAC_ESCALADE_ESV_2019_ASCM,
|
||||
CAR.BUICK_LACROSSE_ASCM,
|
||||
}
|
||||
|
||||
STEER_THRESHOLD = 1.0
|
||||
|
||||
|
||||
@@ -1,16 +1,130 @@
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus, DT_CTRL, rate_limit, make_tester_present_msg, structs
|
||||
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, create_gas_interceptor_command, rate_limit, make_tester_present_msg, structs
|
||||
from opendbc.car.honda import hondacan
|
||||
from opendbc.car.honda.values import CAR, CruiseButtons, HONDA_BOSCH, HONDA_BOSCH_CANFD, HONDA_BOSCH_RADARLESS, \
|
||||
HONDA_BOSCH_TJA_CONTROL, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams
|
||||
from opendbc.car.honda.values import (
|
||||
CAR,
|
||||
CruiseButtons,
|
||||
HONDA_BOSCH,
|
||||
HONDA_BOSCH_CANFD,
|
||||
HONDA_BOSCH_RADARLESS,
|
||||
HONDA_BOSCH_TJA_CONTROL,
|
||||
HONDA_NIDEC_ALT_PCM_ACCEL,
|
||||
CarControllerParams,
|
||||
HondaFlags,
|
||||
)
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from openpilot.common.params import Params
|
||||
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
|
||||
|
||||
def get_civic_bosch_modified_torque_lpf_tau(torque_cmd: float, prev_torque_cmd: float, v_ego: float) -> float:
|
||||
torque_delta = abs(float(torque_cmd) - float(prev_torque_cmd))
|
||||
torque_cmd_abs = abs(float(torque_cmd))
|
||||
sign_change = (float(torque_cmd) * float(prev_torque_cmd)) < 0.0
|
||||
highway = v_ego > (50.0 * 0.44704)
|
||||
low_speed = v_ego < (30.0 * 0.44704)
|
||||
|
||||
if highway:
|
||||
if torque_cmd_abs < 0.12:
|
||||
return 0.18 if sign_change else 0.16
|
||||
if sign_change and torque_delta > 0.15:
|
||||
return 0.10
|
||||
return 0.12
|
||||
|
||||
if sign_change and torque_cmd_abs < 0.25:
|
||||
return 0.28 if low_speed else 0.22
|
||||
|
||||
# Extra damping for the tiny near-center commands where both modified EPS
|
||||
# firmwares still show hunting and escalating sway.
|
||||
if torque_cmd_abs < 0.12:
|
||||
return 0.28 if low_speed else 0.20
|
||||
|
||||
if low_speed:
|
||||
if torque_delta > 0.50:
|
||||
return 0.14
|
||||
elif torque_delta > 0.20:
|
||||
return 0.16
|
||||
elif torque_delta > 0.05:
|
||||
return 0.18
|
||||
else:
|
||||
return 0.22
|
||||
|
||||
if torque_delta > 0.50:
|
||||
return 0.12
|
||||
elif torque_delta > 0.20:
|
||||
return 0.13
|
||||
elif torque_delta > 0.05:
|
||||
return 0.15
|
||||
else:
|
||||
return 0.18
|
||||
|
||||
|
||||
def get_civic_bosch_modified_steering_pressed(
|
||||
raw_pressed: bool, steering_torque: float, torque_cmd: float, filter_s: float, was_pressed: bool
|
||||
) -> tuple[float, bool]:
|
||||
torque_product = steering_torque * torque_cmd
|
||||
torque_cmd_abs = abs(torque_cmd)
|
||||
|
||||
if raw_pressed:
|
||||
if torque_product < 0.0:
|
||||
trigger_s = 0.08 if was_pressed else 0.10
|
||||
rise_rate = 1.0
|
||||
elif torque_cmd_abs < 0.10:
|
||||
trigger_s = 0.20 if was_pressed else 0.24
|
||||
rise_rate = 0.75
|
||||
else:
|
||||
trigger_s = 0.70 if was_pressed else 0.80
|
||||
rise_rate = 0.50
|
||||
|
||||
filter_s = min(1.0, filter_s + (rise_rate * DT_CTRL))
|
||||
steering_pressed = filter_s >= trigger_s
|
||||
else:
|
||||
filter_s = max(0.0, filter_s - 8.0 * DT_CTRL)
|
||||
steering_pressed = filter_s > 0.04 and was_pressed
|
||||
|
||||
return filter_s, steering_pressed
|
||||
|
||||
|
||||
def get_honda_bosch_wind_brake_mps2(v_ego: float) -> float:
|
||||
return float(np.interp(v_ego, [0.0, 13.4, 22.4, 31.3, 40.2], [0.000, 0.049, 0.136, 0.267, 0.441]))
|
||||
|
||||
|
||||
def update_honda_bosch_live_learning(
|
||||
gas_factor: float,
|
||||
wind_factor: float,
|
||||
wind_factor_before_brake: float,
|
||||
desired_accel: float,
|
||||
actual_accel: float,
|
||||
gas_pedal_force: float,
|
||||
wind_brake_mps2: float,
|
||||
brake_pressed: bool,
|
||||
v_ego: float,
|
||||
) -> tuple[float, float, float]:
|
||||
accel_error = desired_accel - actual_accel
|
||||
|
||||
if accel_error != 0.0 and gas_pedal_force > 0.0:
|
||||
gas_factor = float(np.clip(gas_factor + accel_error / 50.0 * gas_pedal_force, 0.1, 3.0))
|
||||
|
||||
if accel_error != 0.0 and not brake_pressed and v_ego > 0.0:
|
||||
wind_adjust = 1.0 + wind_brake_mps2 / 1000.0
|
||||
if accel_error > 0.0:
|
||||
wind_factor = float(np.clip(wind_factor * wind_adjust, 0.1, 3.0))
|
||||
else:
|
||||
wind_factor = float(np.clip(wind_factor / wind_adjust, 0.1, 3.0))
|
||||
|
||||
if gas_pedal_force <= 0.0:
|
||||
wind_factor = max(wind_factor, wind_factor_before_brake)
|
||||
else:
|
||||
wind_factor_before_brake = wind_factor
|
||||
|
||||
return gas_factor, wind_factor, wind_factor_before_brake
|
||||
|
||||
|
||||
def compute_gb_honda_bosch(accel, speed):
|
||||
# TODO returns 0s, is unused
|
||||
return 0.0, 0.0
|
||||
@@ -36,18 +150,18 @@ def compute_gas_brake(accel, speed, fingerprint):
|
||||
# TODO not clear this does anything useful
|
||||
def actuator_hysteresis(brake, braking, brake_steady, v_ego, car_fingerprint):
|
||||
# hyst params
|
||||
brake_hyst_on = 0.02 # to activate brakes exceed this value
|
||||
brake_hyst_on = 0.02 # to activate brakes exceed this value
|
||||
brake_hyst_off = 0.005 # to deactivate brakes below this value
|
||||
brake_hyst_gap = 0.01 # don't change brake command for small oscillations within this value
|
||||
brake_hyst_gap = 0.01 # don't change brake command for small oscillations within this value
|
||||
|
||||
# *** hysteresis logic to avoid brake blinking. go above 0.1 to trigger
|
||||
if (brake < brake_hyst_on and not braking) or brake < brake_hyst_off:
|
||||
brake = 0.
|
||||
braking = brake > 0.
|
||||
brake = 0.0
|
||||
braking = brake > 0.0
|
||||
|
||||
# for small brake oscillations within brake_hyst_gap, don't change the brake command
|
||||
if brake == 0.:
|
||||
brake_steady = 0.
|
||||
if brake == 0.0:
|
||||
brake_steady = 0.0
|
||||
elif brake > brake_steady + brake_hyst_gap:
|
||||
brake_steady = brake - brake_hyst_gap
|
||||
elif brake < brake_steady - brake_hyst_gap:
|
||||
@@ -64,7 +178,7 @@ def brake_pump_hysteresis(apply_brake, apply_brake_last, last_pump_ts, ts):
|
||||
# - there is an increment in brake request
|
||||
# - we are applying steady state brakes and we haven't been running the pump
|
||||
# for more than 20s (to prevent pressure bleeding)
|
||||
if apply_brake > apply_brake_last or (ts - last_pump_ts > 20. and apply_brake > 0):
|
||||
if apply_brake > apply_brake_last or (ts - last_pump_ts > 20.0 and apply_brake > 0):
|
||||
last_pump_ts = ts
|
||||
|
||||
# once the pump is on, run it for at least 0.2s
|
||||
@@ -93,14 +207,15 @@ class CarController(CarControllerBase):
|
||||
super().__init__(dbc_names, CP)
|
||||
self.packer = CANPacker(dbc_names[Bus.pt])
|
||||
self.params = CarControllerParams(CP)
|
||||
self.param_store = Params()
|
||||
self.CAN = hondacan.CanBus(CP)
|
||||
self.tja_control = CP.carFingerprint in HONDA_BOSCH_TJA_CONTROL
|
||||
|
||||
self.braking = False
|
||||
self.brake_steady = 0.
|
||||
self.brake_last = 0.
|
||||
self.brake_steady = 0.0
|
||||
self.brake_last = 0.0
|
||||
self.apply_brake_last = 0
|
||||
self.last_pump_ts = 0.
|
||||
self.last_pump_ts = 0.0
|
||||
self.stopping_counter = 0
|
||||
|
||||
self.accel = 0.0
|
||||
@@ -108,12 +223,41 @@ class CarController(CarControllerBase):
|
||||
self.gas = 0.0
|
||||
self.brake = 0.0
|
||||
self.last_torque = 0.0
|
||||
self.torque_lpf = 0.0
|
||||
self.prev_torque_cmd = 0.0
|
||||
self.steering_pressed_filter_s = 0.0
|
||||
self.steering_pressed_robust_prev = False
|
||||
self.bosch_last_gas = 0.0
|
||||
self.bosch_gas_factor = self.param_store.get_float("HondaGasFactorParams", default=1.0)
|
||||
self.bosch_wind_factor = self.param_store.get_float("HondaWindFactorParams", default=1.0)
|
||||
self.bosch_wind_factor_before_brake = self.bosch_wind_factor
|
||||
self.bosch_gas_factor_before_gasmax = self.bosch_gas_factor
|
||||
self.bosch_wind_factor_before_gasmax = self.bosch_wind_factor
|
||||
self.pitch = 0.0
|
||||
|
||||
def _modified_civic_standard_active(self) -> bool:
|
||||
return self.CP.carFingerprint == CAR.HONDA_CIVIC_BOSCH and bool(self.CP.flags & HondaFlags.EPS_MODIFIED)
|
||||
|
||||
def _filtered_steering_pressed(self, CS, torque_cmd: float) -> bool:
|
||||
self.steering_pressed_filter_s, steering_pressed = get_civic_bosch_modified_steering_pressed(
|
||||
bool(CS.out.steeringPressed),
|
||||
float(getattr(CS.out, "steeringTorque", 0.0)),
|
||||
float(torque_cmd),
|
||||
self.steering_pressed_filter_s,
|
||||
self.steering_pressed_robust_prev,
|
||||
)
|
||||
self.steering_pressed_robust_prev = steering_pressed
|
||||
return steering_pressed
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
actuators = CC.actuators
|
||||
hud_control = CC.hudControl
|
||||
hud_v_cruise = hud_control.setSpeed / CS.v_cruise_factor if hud_control.speedVisible else 255
|
||||
pcm_cancel_cmd = CC.cruiseControl.cancel
|
||||
gas_interceptor_command = 0.0
|
||||
if len(CC.orientationNED) == 3:
|
||||
self.pitch = CC.orientationNED[1]
|
||||
hill_brake = math.sin(self.pitch) * ACCELERATION_DUE_TO_GRAVITY
|
||||
|
||||
if CC.longActive:
|
||||
accel = actuators.accel
|
||||
@@ -122,17 +266,36 @@ class CarController(CarControllerBase):
|
||||
accel = 0.0
|
||||
gas, brake = 0.0, 0.0
|
||||
|
||||
torque_cmd = float(actuators.torque)
|
||||
filtered_steering_pressed = bool(CS.out.steeringPressed)
|
||||
if self._modified_civic_standard_active():
|
||||
if CC.latActive:
|
||||
filtered_steering_pressed = self._filtered_steering_pressed(CS, torque_cmd)
|
||||
if filtered_steering_pressed:
|
||||
self.torque_lpf = 0.0
|
||||
self.prev_torque_cmd = 0.0
|
||||
torque_cmd = 0.0
|
||||
else:
|
||||
tau = get_civic_bosch_modified_torque_lpf_tau(torque_cmd, self.prev_torque_cmd, CS.out.vEgo)
|
||||
alpha = DT_CTRL / (tau + DT_CTRL)
|
||||
self.torque_lpf = alpha * torque_cmd + ((1.0 - alpha) * self.torque_lpf)
|
||||
self.prev_torque_cmd = torque_cmd
|
||||
torque_cmd = self.torque_lpf
|
||||
else:
|
||||
self.torque_lpf = 0.0
|
||||
self.prev_torque_cmd = 0.0
|
||||
self.steering_pressed_filter_s = 0.0
|
||||
self.steering_pressed_robust_prev = False
|
||||
|
||||
# *** rate limit steer ***
|
||||
limited_torque = rate_limit(actuators.torque, self.last_torque, -self.params.STEER_DELTA_DOWN * DT_CTRL,
|
||||
self.params.STEER_DELTA_UP * DT_CTRL)
|
||||
limited_torque = rate_limit(torque_cmd, self.last_torque, -self.params.STEER_DELTA_DOWN * DT_CTRL, self.params.STEER_DELTA_UP * DT_CTRL)
|
||||
self.last_torque = limited_torque
|
||||
|
||||
# *** apply brake hysteresis ***
|
||||
pre_limit_brake, self.braking, self.brake_steady = actuator_hysteresis(brake, self.braking, self.brake_steady,
|
||||
CS.out.vEgo, self.CP.carFingerprint)
|
||||
pre_limit_brake, self.braking, self.brake_steady = actuator_hysteresis(brake, self.braking, self.brake_steady, CS.out.vEgo, self.CP.carFingerprint)
|
||||
|
||||
# *** rate limit after the enable check ***
|
||||
self.brake_last = rate_limit(pre_limit_brake, self.brake_last, -2., DT_CTRL)
|
||||
self.brake_last = rate_limit(pre_limit_brake, self.brake_last, -2.0, DT_CTRL)
|
||||
|
||||
# vehicle hud display, wait for one update from 10Hz 0x304 msg
|
||||
alert_fcw, alert_steer_required = process_hud_alert(hud_control.visualAlert)
|
||||
@@ -140,8 +303,7 @@ class CarController(CarControllerBase):
|
||||
# **** process the car messages ****
|
||||
|
||||
# steer torque is converted back to CAN reference (positive when steering right)
|
||||
apply_torque = int(np.interp(-limited_torque * self.params.STEER_MAX,
|
||||
self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V))
|
||||
apply_torque = int(np.interp(-limited_torque * self.params.STEER_MAX, self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V))
|
||||
|
||||
# Send CAN commands
|
||||
can_sends = []
|
||||
@@ -149,37 +311,29 @@ class CarController(CarControllerBase):
|
||||
# tester present - w/ no response (keeps radar disabled)
|
||||
if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and self.CP.openpilotLongitudinalControl:
|
||||
if self.frame % 10 == 0:
|
||||
can_sends.append(make_tester_present_msg(0x18DAB0F1, 1, suppress_response=True))
|
||||
can_sends.append(make_tester_present_msg(0x18DAB0F1, self.CAN.pt, suppress_response=True))
|
||||
|
||||
# Send steering command.
|
||||
can_sends.append(hondacan.create_steering_control(self.packer, self.CAN, apply_torque, CC.latActive, self.tja_control))
|
||||
|
||||
# wind brake from air resistance decel at high speed
|
||||
wind_brake = np.interp(CS.out.vEgo, [0.0, 2.3, 35.0], [0.001, 0.002, 0.15])
|
||||
wind_brake = float(np.interp(CS.out.vEgo, [0.0, 2.3, 35.0], [0.001, 0.002, 0.15]))
|
||||
wind_brake_mps2 = get_honda_bosch_wind_brake_mps2(CS.out.vEgo)
|
||||
# all of this is only relevant for HONDA NIDEC
|
||||
max_accel = np.interp(CS.out.vEgo, self.params.NIDEC_MAX_ACCEL_BP, self.params.NIDEC_MAX_ACCEL_V)
|
||||
# TODO this 1.44 is just to maintain previous behavior
|
||||
pcm_speed_BP = [-wind_brake,
|
||||
-wind_brake * (3 / 4),
|
||||
0.0,
|
||||
0.5]
|
||||
pcm_speed_BP = [-wind_brake, -wind_brake * (3 / 4), 0.0, 0.5]
|
||||
# The Honda ODYSSEY seems to have different PCM_ACCEL
|
||||
# msgs, is it other cars too?
|
||||
if not CC.longActive:
|
||||
if self.CP.enableGasInterceptorDEPRECATED or not CC.longActive:
|
||||
pcm_speed = 0.0
|
||||
pcm_accel = int(0.0)
|
||||
elif self.CP.carFingerprint in HONDA_NIDEC_ALT_PCM_ACCEL:
|
||||
pcm_speed_V = [0.0,
|
||||
np.clip(CS.out.vEgo - 3.0, 0.0, 100.0),
|
||||
np.clip(CS.out.vEgo + 0.0, 0.0, 100.0),
|
||||
np.clip(CS.out.vEgo + 5.0, 0.0, 100.0)]
|
||||
pcm_speed_V = [0.0, np.clip(CS.out.vEgo - 3.0, 0.0, 100.0), np.clip(CS.out.vEgo + 0.0, 0.0, 100.0), np.clip(CS.out.vEgo + 5.0, 0.0, 100.0)]
|
||||
pcm_speed = float(np.interp(gas - brake, pcm_speed_BP, pcm_speed_V))
|
||||
pcm_accel = int(1.0 * self.params.NIDEC_GAS_MAX)
|
||||
else:
|
||||
pcm_speed_V = [0.0,
|
||||
np.clip(CS.out.vEgo - 2.0, 0.0, 100.0),
|
||||
np.clip(CS.out.vEgo + 2.0, 0.0, 100.0),
|
||||
np.clip(CS.out.vEgo + 5.0, 0.0, 100.0)]
|
||||
pcm_speed_V = [0.0, np.clip(CS.out.vEgo - 2.0, 0.0, 100.0), np.clip(CS.out.vEgo + 2.0, 0.0, 100.0), np.clip(CS.out.vEgo + 5.0, 0.0, 100.0)]
|
||||
pcm_speed = float(np.interp(gas - brake, pcm_speed_BP, pcm_speed_V))
|
||||
pcm_accel = int(np.clip((accel / 1.44) / max_accel, 0.0, 1.0) * self.params.NIDEC_GAS_MAX)
|
||||
|
||||
@@ -199,35 +353,108 @@ class CarController(CarControllerBase):
|
||||
|
||||
if self.CP.carFingerprint in HONDA_BOSCH:
|
||||
self.accel = float(np.clip(accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX))
|
||||
self.gas = float(np.interp(accel, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V))
|
||||
gas_pedal_force = self.accel + hill_brake
|
||||
|
||||
if self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS:
|
||||
gas_pedal_force += wind_brake_mps2 * self.bosch_wind_factor
|
||||
|
||||
if actuators.longControlState == LongCtrlState.pid and not CS.out.gasPressed:
|
||||
gas_error = self.accel - CS.out.aEgo
|
||||
|
||||
if gas_error != 0.0 and gas_pedal_force > 0.0:
|
||||
if self.CP.carFingerprint == CAR.HONDA_INSIGHT:
|
||||
gas_learn_speed = 150.0
|
||||
elif self.CP.carFingerprint in (CAR.ACURA_RDX_3G, CAR.ACURA_RDX_3G_MMR):
|
||||
gas_learn_speed = 300.0
|
||||
else:
|
||||
gas_learn_speed = 50.0
|
||||
self.bosch_gas_factor = float(np.clip(self.bosch_gas_factor + gas_error / gas_learn_speed * gas_pedal_force, 0.1, 3.0))
|
||||
|
||||
if gas_error != 0.0 and not CS.out.brakePressed and CS.out.vEgo > 0.0:
|
||||
wind_learn_speed = 100.0 if self.CP.carFingerprint in (CAR.ACURA_RDX_3G, CAR.ACURA_RDX_3G_MMR) else 1000.0
|
||||
wind_adjust = 1.0 + wind_brake_mps2 / wind_learn_speed
|
||||
if gas_error > 0.0:
|
||||
self.bosch_wind_factor = float(np.clip(self.bosch_wind_factor * wind_adjust, 0.1, 3.0))
|
||||
else:
|
||||
self.bosch_wind_factor = float(np.clip(self.bosch_wind_factor / wind_adjust, 0.1, 3.0))
|
||||
|
||||
if gas_pedal_force <= 0.0:
|
||||
self.bosch_wind_factor = max(self.bosch_wind_factor, self.bosch_wind_factor_before_brake)
|
||||
else:
|
||||
self.bosch_wind_factor_before_brake = self.bosch_wind_factor
|
||||
|
||||
if gas_pedal_force >= self.params.BOSCH_ACCEL_MAX:
|
||||
self.bosch_gas_factor = min(self.bosch_gas_factor, self.bosch_gas_factor_before_gasmax)
|
||||
self.bosch_wind_factor = min(self.bosch_wind_factor, self.bosch_wind_factor_before_gasmax)
|
||||
else:
|
||||
self.bosch_gas_factor_before_gasmax = self.bosch_gas_factor
|
||||
self.bosch_wind_factor_before_gasmax = self.bosch_wind_factor
|
||||
|
||||
self.gas = float(np.interp(gas_pedal_force * self.bosch_gas_factor, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V))
|
||||
self.gas = min(self.gas, max(60.0, self.bosch_last_gas + 60.0))
|
||||
self.bosch_last_gas = self.gas
|
||||
|
||||
stopping = actuators.longControlState == LongCtrlState.stopping
|
||||
self.stopping_counter = self.stopping_counter + 1 if stopping else 0
|
||||
can_sends.extend(hondacan.create_acc_commands(self.packer, self.CAN, CC.enabled, CC.longActive, self.accel, self.gas,
|
||||
self.stopping_counter, self.CP.carFingerprint))
|
||||
can_sends.extend(
|
||||
hondacan.create_acc_commands(self.packer, self.CAN, CC.enabled, CC.longActive, self.accel, self.gas, self.stopping_counter, self.CP.carFingerprint)
|
||||
)
|
||||
else:
|
||||
apply_brake = np.clip(self.brake_last - wind_brake, 0.0, 1.0)
|
||||
apply_brake = int(np.clip(apply_brake * self.params.NIDEC_BRAKE_MAX, 0, self.params.NIDEC_BRAKE_MAX - 1))
|
||||
pump_on, self.last_pump_ts = brake_pump_hysteresis(apply_brake, self.apply_brake_last, self.last_pump_ts, ts)
|
||||
|
||||
pcm_override = True
|
||||
can_sends.append(hondacan.create_brake_command(self.packer, self.CAN, apply_brake, pump_on,
|
||||
pcm_override, pcm_cancel_cmd, alert_fcw,
|
||||
self.CP.carFingerprint, CS.stock_brake))
|
||||
can_sends.append(
|
||||
hondacan.create_brake_command(
|
||||
self.packer, self.CAN, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, alert_fcw, self.CP.carFingerprint, CS.stock_brake
|
||||
)
|
||||
)
|
||||
self.apply_brake_last = apply_brake
|
||||
self.brake = apply_brake / self.params.NIDEC_BRAKE_MAX
|
||||
|
||||
if self.CP.enableGasInterceptorDEPRECATED:
|
||||
gas_error = actuators.accel - CS.out.aEgo
|
||||
|
||||
if not CS.out.gasPressed and actuators.longControlState == LongCtrlState.pid:
|
||||
if gas_error != 0.0 and gas > 0.0:
|
||||
self.bosch_gas_factor = float(np.clip(self.bosch_gas_factor + gas_error / 150.0 * (gas * 4.8), 0.1, 3.0))
|
||||
if gas_error != 0.0 and not CS.out.brakePressed and CS.out.vEgo > 0.0:
|
||||
wind_adjust = 1.0 + (wind_brake * 4.8) / 1000.0
|
||||
if gas_error > 0.0:
|
||||
self.bosch_wind_factor = float(np.clip(self.bosch_wind_factor * wind_adjust, 0.1, 5.0))
|
||||
else:
|
||||
self.bosch_wind_factor = float(np.clip(self.bosch_wind_factor / wind_adjust, 0.1, 5.0))
|
||||
if gas <= 0.0:
|
||||
self.bosch_wind_factor = max(self.bosch_wind_factor, self.bosch_wind_factor_before_brake)
|
||||
else:
|
||||
self.bosch_wind_factor_before_brake = self.bosch_wind_factor
|
||||
|
||||
gas_mult = float(np.interp(CS.out.vEgo, [0.0, 10.0], [0.4, 1.0]))
|
||||
if CC.longActive:
|
||||
gas_interceptor_command = float(np.clip(
|
||||
gas_mult * ((gas * self.bosch_gas_factor) - brake + (wind_brake * self.bosch_wind_factor * 3.0 / 4.0)),
|
||||
0.0,
|
||||
1.0,
|
||||
))
|
||||
idx = (self.frame // 2) % 0x10
|
||||
can_sends.append(create_gas_interceptor_command(self.packer, gas_interceptor_command, idx))
|
||||
|
||||
# Send dashboard UI commands.
|
||||
if self.frame % 10 == 0:
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
# On Nidec, this also controls longitudinal positive acceleration
|
||||
can_sends.append(hondacan.create_acc_hud(self.packer, self.CAN.pt, self.CP, CC.enabled, pcm_speed, pcm_accel,
|
||||
hud_control, hud_v_cruise, CS.is_metric, CS.acc_hud))
|
||||
can_sends.append(
|
||||
hondacan.create_acc_hud(self.packer, self.CAN.pt, self.CP, CC.enabled, pcm_speed, pcm_accel, hud_control, hud_v_cruise, CS.is_metric, CS.acc_hud)
|
||||
)
|
||||
|
||||
steering_available = CS.out.cruiseState.available and CS.out.vEgo > self.CP.minSteerSpeed
|
||||
reduced_steering = CS.out.steeringPressed
|
||||
can_sends.extend(hondacan.create_lkas_hud(self.packer, self.CAN.lkas, self.CP, hud_control, CC.latActive,
|
||||
steering_available, reduced_steering, alert_steer_required, CS.lkas_hud))
|
||||
reduced_steering = filtered_steering_pressed
|
||||
can_sends.extend(
|
||||
hondacan.create_lkas_hud(
|
||||
self.packer, self.CAN.lkas, self.CP, hud_control, CC.latActive, steering_available, reduced_steering, alert_steer_required, CS.lkas_hud
|
||||
)
|
||||
)
|
||||
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
# TODO: combining with create_acc_hud block above will change message order and will need replay logs regenerated
|
||||
@@ -237,7 +464,14 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(hondacan.create_legacy_brake_command(self.packer, self.CAN.pt))
|
||||
if self.CP.carFingerprint not in HONDA_BOSCH:
|
||||
self.speed = pcm_speed
|
||||
self.gas = pcm_accel / self.params.NIDEC_GAS_MAX
|
||||
if self.CP.enableGasInterceptorDEPRECATED:
|
||||
self.gas = gas_interceptor_command
|
||||
else:
|
||||
self.gas = pcm_accel / self.params.NIDEC_GAS_MAX
|
||||
|
||||
if self.frame > 0 and self.frame % 6000 == 0:
|
||||
self.param_store.put_float("HondaGasFactorParams", self.bosch_gas_factor)
|
||||
self.param_store.put_float("HondaWindFactorParams", self.bosch_wind_factor)
|
||||
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.speed = self.speed
|
||||
|
||||
@@ -3,11 +3,11 @@ from collections import defaultdict
|
||||
|
||||
from cereal import custom
|
||||
from opendbc.can import CANDefine, CANParser
|
||||
from opendbc.car import Bus, create_button_events, structs
|
||||
from opendbc.car import Bus, DT_CTRL, create_button_events, structs
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.honda.hondacan import CanBus
|
||||
from opendbc.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_BOSCH_ALT_RADAR, HONDA_BOSCH_CANFD, \
|
||||
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, \
|
||||
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, HONDA_BOSCH_TJA_CONTROL, \
|
||||
HondaFlags, CruiseButtons, CruiseSettings, GearShifter, CarControllerParams, HondaStarPilotFlags
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
|
||||
@@ -54,13 +54,19 @@ class CarState(CarStateBase):
|
||||
self.brake_switch_active = False
|
||||
self.low_speed_alert = False
|
||||
|
||||
self.dynamic_v_cruise_units = self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_ALT_RADAR | HONDA_BOSCH_CANFD)
|
||||
self.dynamic_v_cruise_units = self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_ALT_RADAR |
|
||||
HONDA_BOSCH_TJA_CONTROL | HONDA_BOSCH_CANFD)
|
||||
self.cruise_setting = 0
|
||||
self.v_cruise_pcm_prev = 0
|
||||
|
||||
# When available we use cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] to populate vEgoCluster
|
||||
# However, on cars without a digital speedometer this is not always present (HRV, FIT, CRV 2016, ILX and RDX)
|
||||
self.dash_speed_seen = False
|
||||
self.is_metric = False
|
||||
self.v_cruise_factor = 1.
|
||||
|
||||
self.initial_accFault_cleared = False
|
||||
self.initial_accFault_cleared_timer = int(10 / DT_CTRL)
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
cp = can_parsers[Bus.pt]
|
||||
@@ -81,18 +87,19 @@ class CarState(CarStateBase):
|
||||
self.cruise_buttons = cp.vl["SCM_BUTTONS"]["CRUISE_BUTTONS"]
|
||||
|
||||
# used for car hud message
|
||||
self.is_metric = not cp.vl["CAR_SPEED"]["IMPERIAL_UNIT"]
|
||||
self.is_metric = self.CP.carFingerprint in (CAR.HONDA_ODYSSEY_TWN,) or not cp.vl["CAR_SPEED"]["IMPERIAL_UNIT"]
|
||||
self.v_cruise_factor = CV.MPH_TO_MS if self.dynamic_v_cruise_units and not self.is_metric else CV.KPH_TO_MS
|
||||
|
||||
# ******************* parse out can *******************
|
||||
|
||||
# blend in transmission speed at low speed, since it has more low speed accuracy
|
||||
# STANDSTILL->WHEELS_MOVING bit can be noisy around zero, so use XMISSION_SPEED
|
||||
lowspeed_source = cp.vl["CAR_SPEED"]["CAR_SPEED"] if self.CP.carFingerprint == CAR.ACURA_INTEGRA else cp.vl["ENGINE_DATA"]["XMISSION_SPEED"]
|
||||
v_wheel = sum([cp.vl["WHEEL_SPEEDS"][f"WHEEL_SPEED_{s}"] for s in ("FL", "FR", "RL", "RR")]) / 4.0 * CV.KPH_TO_MS
|
||||
v_weight = float(np.interp(v_wheel, v_weight_bp, v_weight_v))
|
||||
ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel
|
||||
ret.vEgoRaw = (1. - v_weight) * lowspeed_source * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel
|
||||
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
|
||||
ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 1e-5
|
||||
ret.standstill = lowspeed_source < 1e-5
|
||||
|
||||
# doorOpen is true if we can find any door open, but signal locations vary, and we may only see the driver's door
|
||||
# TODO: Test the eight Nidec cars without SCM signals for driver's door state, may be able to consolidate further
|
||||
@@ -107,7 +114,8 @@ class CarState(CarStateBase):
|
||||
ret.seatbeltUnlatched = bool(cp.vl["SEATBELT_STATUS"]["SEATBELT_DRIVER_LAMP"] or not cp.vl["SEATBELT_STATUS"]["SEATBELT_DRIVER_LATCHED"])
|
||||
|
||||
steer_status = self.steer_status_values[cp.vl["STEER_STATUS"]["STEER_STATUS"]]
|
||||
ret.steerFaultPermanent = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_1", "NO_TORQUE_ALERT_2", "LOW_SPEED_LOCKOUT", "TMP_FAULT")
|
||||
ret.steerFaultPermanent = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_1", "NO_TORQUE_ALERT_2", "LOW_SPEED_LOCKOUT", "TJA_LOW_SPEED_LOCKOUT",
|
||||
"TMP_FAULT")
|
||||
if self.CP.carFingerprint in HONDA_BOSCH_ALT_RADAR:
|
||||
# TODO: See if this logic works for all other Honda
|
||||
min_steer_speed = max(CarControllerParams.STEER_GLOBAL_MIN_SPEED, self.CP.minSteerSpeed)
|
||||
@@ -117,7 +125,11 @@ class CarState(CarStateBase):
|
||||
# LOW_SPEED_LOCKOUT is not worth a warning
|
||||
# NO_TORQUE_ALERT_2 can be caused by bump or steering nudge from driver
|
||||
# FIXME: the stock camera stops steering on NO_TORQUE_ALERT_1
|
||||
ret.steerFaultTemporary = steer_status not in ("NORMAL", "LOW_SPEED_LOCKOUT", "NO_TORQUE_ALERT_2")
|
||||
ret.steerFaultTemporary = steer_status not in ("NORMAL", "LOW_SPEED_LOCKOUT", "TJA_LOW_SPEED_LOCKOUT", "NO_TORQUE_ALERT_2")
|
||||
|
||||
if self.CP.carFingerprint == CAR.ACURA_MDX_4G and steer_status == "TJA_LOW_SPEED_LOCKOUT":
|
||||
ret.steerFaultPermanent = False
|
||||
ret.steerFaultTemporary = False
|
||||
|
||||
# All Honda EPS cut off slightly above standstill, some much higher
|
||||
# Don't alert in the near-standstill range, but alert for per-vehicle configured minimums above that
|
||||
@@ -128,22 +140,23 @@ class CarState(CarStateBase):
|
||||
self.low_speed_alert = False
|
||||
ret.lowSpeedAlert = self.low_speed_alert
|
||||
|
||||
if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS:
|
||||
if self.CP.carFingerprint not in HONDA_BOSCH:
|
||||
ret.carFaultedNonCritical = bool(cp_cam.vl["ACC_HUD"]["ACC_PROBLEM"] or cp_cam.vl["LKAS_HUD"]["LKAS_PROBLEM"])
|
||||
elif self.CP.carFingerprint in HONDA_BOSCH_RADARLESS:
|
||||
ret.accFaulted = bool(cp.vl["CRUISE_FAULT_STATUS"]["CRUISE_FAULT"])
|
||||
else:
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
elif self.CP.openpilotLongitudinalControl:
|
||||
if self.CP.carFingerprint in (HONDA_BOSCH_CANFD | HONDA_BOSCH_TJA_CONTROL) and (self.CP.flags & HondaFlags.BOSCH_ALT_BRAKE):
|
||||
ret.accFaulted = bool(cp.vl["BRAKE_MODULE"]["CRUISE_FAULT"])
|
||||
else:
|
||||
ret.accFaulted = bool(cp.vl[self.brake_error_msg]["BRAKE_ERROR_1"] or cp.vl[self.brake_error_msg]["BRAKE_ERROR_2"])
|
||||
|
||||
# Log non-critical stock ACC/LKAS faults if Nidec (camera)
|
||||
if self.CP.carFingerprint not in HONDA_BOSCH:
|
||||
ret.carFaultedNonCritical = bool(cp_cam.vl["ACC_HUD"]["ACC_PROBLEM"] or cp_cam.vl["LKAS_HUD"]["LKAS_PROBLEM"])
|
||||
|
||||
ret.espDisabled = cp.vl["VSA_STATUS"]["ESP_DISABLED"] != 0
|
||||
|
||||
self.dash_speed_seen = self.dash_speed_seen or cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] > 1e-3
|
||||
if self.dash_speed_seen:
|
||||
conversion = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
|
||||
ret.vEgoCluster = cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] * conversion
|
||||
if self.CP.carFingerprint not in (CAR.HONDA_ODYSSEY_TWN,):
|
||||
self.dash_speed_seen = self.dash_speed_seen or cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] > 1e-3
|
||||
if self.dash_speed_seen:
|
||||
conversion = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
|
||||
ret.vEgoCluster = cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] * conversion
|
||||
|
||||
ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE"]
|
||||
ret.steeringRateDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE_RATE"]
|
||||
@@ -154,12 +167,16 @@ class CarState(CarStateBase):
|
||||
ret.parkingBrake = bool(cp.vl[self.car_state_scm_msg]["PARKING_BRAKE_ON"])
|
||||
|
||||
if self.CP.transmissionType == TransmissionType.manual:
|
||||
ret.gearShifter = GearShifter.reverse if bool(cp.vl["SCM_FEEDBACK"]["REVERSE_LIGHT"]) else GearShifter.drive
|
||||
ret.gearShifter = GearShifter.reverse if bool(cp.vl[self.car_state_scm_msg]["REVERSE_LIGHT"]) else GearShifter.drive
|
||||
else:
|
||||
gear_position = self.shifter_values.get(cp.vl[self.gearbox_msg]["GEAR_SHIFTER"], None)
|
||||
ret.gearShifter = self.parse_gear_shifter(gear_position)
|
||||
|
||||
ret.gasPressed = cp.vl["POWERTRAIN_DATA"]["PEDAL_GAS"] > 1e-5
|
||||
if self.CP.enableGasInterceptorDEPRECATED:
|
||||
gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) / 2.
|
||||
ret.gasPressed = gas > 492
|
||||
else:
|
||||
ret.gasPressed = cp.vl["POWERTRAIN_DATA"]["PEDAL_GAS"] > 1e-5
|
||||
|
||||
ret.steeringTorque = cp.vl["STEER_STATUS"]["STEER_TORQUE_SENSOR"]
|
||||
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD.get(self.CP.carFingerprint, 1200)
|
||||
@@ -200,6 +217,16 @@ class CarState(CarStateBase):
|
||||
ret.cruiseState.enabled = cp.vl["POWERTRAIN_DATA"]["ACC_STATUS"] != 0
|
||||
ret.cruiseState.available = bool(cp.vl[self.car_state_scm_msg]["MAIN_ON"])
|
||||
|
||||
if ret.accFaulted:
|
||||
if self.CP.carFingerprint in HONDA_BOSCH and not self.initial_accFault_cleared:
|
||||
ret.accFaulted = False
|
||||
ret.cruiseState.available = False
|
||||
elif self.initial_accFault_cleared_timer == 0:
|
||||
self.initial_accFault_cleared = True
|
||||
|
||||
if self.initial_accFault_cleared_timer > 0:
|
||||
self.initial_accFault_cleared_timer -= 1
|
||||
|
||||
# Gets rid of Pedal Grinding noise when brake is pressed at slow speeds for some models
|
||||
if self.CP.carFingerprint in (CAR.HONDA_PILOT, CAR.HONDA_RIDGELINE):
|
||||
if ret.brake > 0.1:
|
||||
@@ -218,7 +245,7 @@ class CarState(CarStateBase):
|
||||
ret.stockFcw = cp_cam.vl["BRAKE_COMMAND"]["FCW"] != 0
|
||||
self.acc_hud = cp_cam.vl["ACC_HUD"]
|
||||
self.stock_brake = cp_cam.vl["BRAKE_COMMAND"]
|
||||
if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS:
|
||||
if self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_CANFD):
|
||||
self.lkas_hud = cp_cam.vl["LKAS_HUD"]
|
||||
|
||||
if self.CP.enableBsm:
|
||||
@@ -235,6 +262,9 @@ class CarState(CarStateBase):
|
||||
fp_ret = custom.StarPilotCarState.new_message()
|
||||
|
||||
fp_ret.dashboardSpeedLimit = calculate_speed_limit(self.CP, self.FPCP, cp, cp_cam)
|
||||
if self.FPCP.flags & HondaStarPilotFlags.HAS_CAMERA_MESSAGES:
|
||||
sign_bus = cp if (self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD)) else cp_cam
|
||||
fp_ret.dashboardStopSign = 1 if sign_bus.vl["CAMERA_MESSAGES"]["ROAD_SIGN"] == 89 else 0
|
||||
|
||||
return ret, fp_ret
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TBX-H120\x00\x00',
|
||||
b'39990-TVA,A150\x00\x00',
|
||||
b'39990-TVA-A140\x00\x00',
|
||||
b'39990-TVA-A150\x00\x00',
|
||||
b'39990-TVA-A160\x00\x00',
|
||||
@@ -142,6 +143,29 @@ FW_VERSIONS = {
|
||||
b'38897-TWD-J020\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.HONDA_CLARITY: {
|
||||
(Ecu.shiftByWire, 0x18da0bf1, None): [
|
||||
b'54008-TRW-A910\x00\x00',
|
||||
],
|
||||
(Ecu.vsa, 0x18da28f1, None): [
|
||||
b'57114-TRW-A010\x00\x00',
|
||||
b'57114-TRW-A020\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TRW-A020\x00\x00',
|
||||
b'39990-TRW,A020\x00\x00',
|
||||
],
|
||||
(Ecu.srs, 0x18da53f1, None): [
|
||||
b'77959-TRW-A210\x00\x00',
|
||||
b'77959-TRW-A220\x00\x00',
|
||||
],
|
||||
(Ecu.gateway, 0x18daeff1, None): [
|
||||
b'38897-TRW-A010\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x18dab0f1, None): [
|
||||
b'36161-TRW-A110\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.HONDA_CIVIC: {
|
||||
(Ecu.transmission, 0x18da1ef1, None): [
|
||||
b'28101-5CG-A040\x00\x00',
|
||||
@@ -163,6 +187,7 @@ FW_VERSIONS = {
|
||||
b'57114-TEA-Q220\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TBA,A030\x00\x00',
|
||||
b'39990-TBA-A030\x00\x00',
|
||||
b'39990-TBG-A030\x00\x00',
|
||||
b'39990-TEA-T020\x00\x00',
|
||||
@@ -230,11 +255,14 @@ FW_VERSIONS = {
|
||||
b'57114-TGL-G330\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TBA-C120\x00\x00',
|
||||
b'39990-TBA-C020\x00\x00',
|
||||
b'39990-TBA-C120\x00\x00',
|
||||
b'39990-TEA-T330\x00\x00',
|
||||
b'39990-TEA-T820\x00\x00',
|
||||
b'39990-TEZ-T020\x00\x00',
|
||||
b'39990-TGG,A020\x00\x00',
|
||||
b'39990-TGG,A120\x00\x00',
|
||||
b'39990-TGG-A020\x00\x00',
|
||||
b'39990-TGG-A120\x00\x00',
|
||||
b'39990-TGG-J510\x00\x00',
|
||||
@@ -368,6 +396,7 @@ FW_VERSIONS = {
|
||||
b'57114-TMC-Z050\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TLA,A040\x00\x00',
|
||||
b'39990-TLA-A040\x00\x00',
|
||||
b'39990-TLA-A110\x00\x00',
|
||||
b'39990-TLA-A220\x00\x00',
|
||||
|
||||
@@ -167,18 +167,19 @@ def create_acc_hud(packer, bus, CP, enabled, pcm_speed, pcm_accel, hud_control,
|
||||
|
||||
def create_lkas_hud(packer, bus, CP, hud_control, lat_active, steering_available, reduced_steering, alert_steer_required, lkas_hud):
|
||||
commands = []
|
||||
lanes_visible = bool(hud_control.lanesVisible or lat_active)
|
||||
|
||||
lkas_hud_values = {
|
||||
'LKAS_READY': 1,
|
||||
'LKAS_STATE_CHANGE': 1,
|
||||
'STEERING_REQUIRED': alert_steer_required,
|
||||
'SOLID_LANES': hud_control.lanesVisible,
|
||||
'SOLID_LANES': lanes_visible,
|
||||
'BEEP': 0,
|
||||
}
|
||||
|
||||
if CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_CANFD):
|
||||
lkas_hud_values['LANE_LINES'] = 3
|
||||
lkas_hud_values['DASHED_LANES'] = hud_control.lanesVisible
|
||||
lkas_hud_values['DASHED_LANES'] = lanes_visible
|
||||
|
||||
# car likely needs to see LKAS_PROBLEM fall within a specific time frame, so forward from camera
|
||||
# TODO: needed for Bosch CAN FD?
|
||||
@@ -192,8 +193,8 @@ def create_lkas_hud(packer, bus, CP, hud_control, lat_active, steering_available
|
||||
# New HUD concept for selected Bosch cars, overwrites some of the above
|
||||
# TODO: make global across all Honda if feedback is favorable
|
||||
if CP.carFingerprint in HONDA_BOSCH_ALT_RADAR:
|
||||
lkas_hud_values['DASHED_LANES'] = steering_available
|
||||
lkas_hud_values['SOLID_LANES'] = lat_active
|
||||
lkas_hud_values['DASHED_LANES'] = bool(steering_available or lanes_visible)
|
||||
lkas_hud_values['SOLID_LANES'] = lanes_visible
|
||||
lkas_hud_values['LKAS_PROBLEM'] = lat_active and reduced_steering
|
||||
|
||||
if CP.flags & HondaFlags.BOSCH_EXT_HUD and not CP.openpilotLongitudinalControl:
|
||||
|
||||
@@ -23,6 +23,8 @@ class CarInterface(CarInterfaceBase):
|
||||
def get_pid_accel_limits(CP, current_speed, cruise_speed):
|
||||
if CP.carFingerprint in HONDA_BOSCH:
|
||||
return CarControllerParams.BOSCH_ACCEL_MIN, CarControllerParams.BOSCH_ACCEL_MAX
|
||||
elif CP.enableGasInterceptorDEPRECATED:
|
||||
return CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX
|
||||
else:
|
||||
# NIDECs don't allow acceleration near cruise_speed,
|
||||
# so limit limits of pid to prevent windup
|
||||
@@ -46,15 +48,17 @@ class CarInterface(CarInterfaceBase):
|
||||
# Disable the radar and let openpilot control longitudinal
|
||||
# WARNING: THIS DISABLES AEB!
|
||||
# If Bosch radarless, this blocks ACC messages from the camera
|
||||
# TODO: get radar disable working on Bosch CANFD
|
||||
ret.alphaLongitudinalAvailable = candidate not in HONDA_BOSCH_CANFD
|
||||
ret.openpilotLongitudinalControl = alpha_long and (candidate not in HONDA_BOSCH_CANFD)
|
||||
ret.alphaLongitudinalAvailable = True
|
||||
ret.openpilotLongitudinalControl = alpha_long
|
||||
ret.pcmCruise = not ret.openpilotLongitudinalControl
|
||||
else:
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.hondaNidec)]
|
||||
ret.openpilotLongitudinalControl = True
|
||||
|
||||
ret.pcmCruise = True
|
||||
ret.enableGasInterceptorDEPRECATED = 0x201 in fingerprint[CAN.pt]
|
||||
if ret.enableGasInterceptorDEPRECATED:
|
||||
ret.pcmCruise = False
|
||||
|
||||
if candidate == CAR.HONDA_CRV_5G:
|
||||
ret.enableBsm = 0x12f8bfa7 in fingerprint[CAN.radar]
|
||||
@@ -80,6 +84,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
|
||||
ret.lateralTuning.pid.kf = 0.00006 # conservative feed-forward
|
||||
ret.steerActuatorDelay = 0.1
|
||||
ret.stoppingDecelRate = 0.3
|
||||
|
||||
if candidate in HONDA_BOSCH:
|
||||
ret.longitudinalActuatorDelay = 0.5 # s
|
||||
@@ -90,14 +95,32 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.longitudinalTuning.kiBP = [0., 5., 35.]
|
||||
ret.longitudinalTuning.kiV = [1.2, 0.8, 0.5]
|
||||
|
||||
# Disable control if EPS mod detected
|
||||
eps_modified = False
|
||||
for fw in car_fw:
|
||||
if fw.ecu == "eps" and b"," in fw.fwVersion:
|
||||
ret.dashcamOnly = True
|
||||
eps_modified = True
|
||||
|
||||
if eps_modified:
|
||||
ret.flags |= HondaFlags.EPS_MODIFIED.value
|
||||
|
||||
if candidate == CAR.HONDA_CITY_7G:
|
||||
ret.vEgoStopping = 2.0
|
||||
ret.vEgoStarting = ret.vEgoStopping
|
||||
ret.stoppingDecelRate = 0.3
|
||||
|
||||
if candidate == CAR.HONDA_CIVIC:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]]
|
||||
if eps_modified:
|
||||
# stock request input values: 0x0000, 0x00DE, 0x014D, 0x01EF, 0x0290, 0x0377, 0x0454, 0x0610, 0x06EE
|
||||
# stock request output values: 0x0000, 0x0917, 0x0DC5, 0x1017, 0x119F, 0x140B, 0x1680, 0x1680, 0x1680
|
||||
# modified request output values: 0x0000, 0x0917, 0x0DC5, 0x1017, 0x119F, 0x140B, 0x1680, 0x2880, 0x3180
|
||||
# stock filter output values: 0x009F, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108
|
||||
# modified filter output values: 0x009F, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0400, 0x0480
|
||||
# note: max request allowed is 4096, but request is capped at 3840 in firmware, so modifications result in 2x max
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 8000], [0, 2560, 3840]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.1]]
|
||||
else:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]]
|
||||
|
||||
elif candidate in (CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL):
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
@@ -112,7 +135,17 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
elif candidate == CAR.HONDA_ACCORD:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
|
||||
if eps_modified:
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.09]]
|
||||
else:
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
|
||||
if ret.transmissionType == TransmissionType.manual:
|
||||
CarControllerParams.BOSCH_GAS_LOOKUP_BP = [-0.2, 2.0]
|
||||
|
||||
elif candidate == CAR.HONDA_ACCORD_11G:
|
||||
ret.steerActuatorDelay = 0.22
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 5200], [0, 2560, 12747]]
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.ACURA_ILX:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] # TODO: determine if there is a dead zone at the top end
|
||||
@@ -124,8 +157,15 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.wheelSpeedFactor = 1.025
|
||||
|
||||
elif candidate == CAR.HONDA_CRV_5G:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.64], [0.192]]
|
||||
if eps_modified:
|
||||
# stock request input values: 0x0000, 0x00DB, 0x01BB, 0x0296, 0x0377, 0x0454, 0x0532, 0x0610, 0x067F
|
||||
# stock request output values: 0x0000, 0x0500, 0x0A15, 0x0E6D, 0x1100, 0x1200, 0x129A, 0x134D, 0x1400
|
||||
# modified request output values: 0x0000, 0x0500, 0x0A15, 0x0E6D, 0x1100, 0x1200, 0x1ACD, 0x239A, 0x2800
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 10000], [0, 2560, 3840]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.21], [0.07]]
|
||||
else:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.64], [0.192]]
|
||||
ret.wheelSpeedFactor = 1.025
|
||||
|
||||
elif candidate == CAR.HONDA_CRV_HYBRID:
|
||||
@@ -133,6 +173,13 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
|
||||
ret.wheelSpeedFactor = 1.025
|
||||
|
||||
elif candidate == CAR.HONDA_CRV_6G:
|
||||
ret.steerActuatorDelay = 0.15
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 5100], [0, 5100]]
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
if ret.flags & HondaFlags.HYBRID:
|
||||
CarControllerParams.BOSCH_GAS_LOOKUP_BP = [-0.3, 2.0]
|
||||
|
||||
elif candidate == CAR.HONDA_FIT:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.05]]
|
||||
@@ -148,22 +195,52 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.wheelSpeedFactor = 1.025
|
||||
else:
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] # TODO: can probably use some tuning
|
||||
# The 3G HR-V settles more cleanly in lead follow when planner delay
|
||||
# better matches its stronger immediate longitudinal response.
|
||||
ret.longitudinalActuatorDelay = 0.4
|
||||
|
||||
elif candidate == CAR.HONDA_CLARITY:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
|
||||
ret.stopAccel = 0.0
|
||||
|
||||
elif candidate == CAR.ACURA_RDX:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
|
||||
|
||||
elif candidate == CAR.ACURA_RDX_3G:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]]
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4095], [0, 4095]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]]
|
||||
CarControllerParams.BOSCH_GAS_LOOKUP_V = [0, 2200]
|
||||
|
||||
elif candidate == CAR.ACURA_RDX_3G_MMR:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]]
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
CarControllerParams.BOSCH_GAS_LOOKUP_V = [0, 2000]
|
||||
if not ret.openpilotLongitudinalControl:
|
||||
ret.minSteerSpeed = 70. * CV.KPH_TO_MS
|
||||
|
||||
elif candidate == CAR.HONDA_ODYSSEY:
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
|
||||
elif candidate == CAR.HONDA_PILOT:
|
||||
elif candidate == CAR.HONDA_ODYSSEY_TWN:
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]]
|
||||
|
||||
elif candidate in (CAR.HONDA_PILOT, CAR.HONDA_PILOT_4G):
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]]
|
||||
ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kpV = [[0, 10], [0.05, 0.5]]
|
||||
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kiV = [[0, 10], [0.0125, 0.125]]
|
||||
|
||||
elif candidate == CAR.ACURA_MDX_4G:
|
||||
ret.steerActuatorDelay = 0.15
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 4209], [0, 2560, 9150]]
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.ACURA_MDX_4G_MMR:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 4920], [0, 2560, 12000]]
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.HONDA_RIDGELINE:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
@@ -173,7 +250,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
|
||||
|
||||
elif candidate == CAR.HONDA_E:
|
||||
elif candidate in (CAR.HONDA_E, CAR.HONDA_E_ADVANCE):
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] # TODO: can probably use some tuning
|
||||
|
||||
@@ -189,19 +266,48 @@ class CarInterface(CarInterfaceBase):
|
||||
# When using stock ACC, the radar intercepts and filters steering commands the EPS would otherwise accept
|
||||
ret.minSteerSpeed = 70. * CV.KPH_TO_MS
|
||||
|
||||
elif candidate == CAR.ACURA_TLX_2G_MMR:
|
||||
ret.steerActuatorDelay = 0.15
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]]
|
||||
ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kpV = [[0, 10], [0.05, 0.5]]
|
||||
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kiV = [[0, 10], [0.0125, 0.125]]
|
||||
|
||||
elif candidate in (CAR.HONDA_FIT_4G,):
|
||||
ret.steerActuatorDelay = 0.15
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]]
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.ACURA_INTEGRA:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
|
||||
|
||||
elif candidate == CAR.ACURA_ADX:
|
||||
ret.steerActuatorDelay = 0.15
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 5000], [0, 5000]]
|
||||
ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kpV = [[0, 10], [0.05, 0.5]]
|
||||
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kiV = [[0, 10], [0.0125, 0.125]]
|
||||
|
||||
elif candidate == CAR.HONDA_PASSPORT_4G:
|
||||
ret.steerActuatorDelay = 0.15
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 5120], [0, 2560, 12789]]
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
else:
|
||||
ret.steerActuatorDelay = 0.15
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
# These cars use alternate user brake msg (0x1BE)
|
||||
if 0x1BE in fingerprint[CAN.pt] and candidate in (CAR.HONDA_ACCORD, CAR.HONDA_HRV_3G, *HONDA_BOSCH_CANFD):
|
||||
if 0x1BE in fingerprint[CAN.pt] and candidate in (CAR.HONDA_ACCORD, CAR.HONDA_HRV_3G, CAR.ACURA_RDX_3G, CAR.ACURA_MDX_4G,
|
||||
CAR.ACURA_ADX, *HONDA_BOSCH_CANFD):
|
||||
ret.flags |= HondaFlags.BOSCH_ALT_BRAKE.value
|
||||
|
||||
if ret.flags & HondaFlags.BOSCH_ALT_BRAKE:
|
||||
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.ALT_BRAKE.value
|
||||
if candidate in HONDA_NIDEC_ALT_SCM_MESSAGES:
|
||||
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.NIDEC_ALT.value
|
||||
if ret.enableGasInterceptorDEPRECATED:
|
||||
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.GAS_INTERCEPTOR.value
|
||||
if ret.openpilotLongitudinalControl and candidate in HONDA_BOSCH:
|
||||
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.BOSCH_LONG.value
|
||||
if candidate in HONDA_BOSCH_RADARLESS:
|
||||
@@ -212,8 +318,24 @@ class CarInterface(CarInterfaceBase):
|
||||
# min speed to enable ACC. if car can do stop and go, then set enabling speed
|
||||
# to a negative value, so it won't matter. Otherwise, add 0.5 mph margin to not
|
||||
# conflict with PCM acc
|
||||
ret.autoResumeSng = candidate in (HONDA_BOSCH | {CAR.HONDA_CIVIC})
|
||||
ret.minEnableSpeed = -1. if ret.autoResumeSng else 25.51 * CV.MPH_TO_MS
|
||||
if candidate == CAR.HONDA_FIT_4G and not ret.openpilotLongitudinalControl:
|
||||
ret.autoResumeSng = False
|
||||
elif ret.transmissionType == TransmissionType.manual and not ret.openpilotLongitudinalControl:
|
||||
ret.autoResumeSng = False
|
||||
else:
|
||||
ret.autoResumeSng = candidate in (HONDA_BOSCH | {CAR.HONDA_CIVIC, CAR.HONDA_CLARITY}) or ret.enableGasInterceptorDEPRECATED
|
||||
|
||||
if ret.autoResumeSng:
|
||||
ret.minEnableSpeed = -1.
|
||||
elif candidate == CAR.HONDA_ODYSSEY_TWN:
|
||||
ret.minEnableSpeed = 19. * CV.MPH_TO_MS
|
||||
elif candidate == CAR.HONDA_FIT_4G:
|
||||
ret.minEnableSpeed = 30. * CV.KPH_TO_MS
|
||||
else:
|
||||
ret.minEnableSpeed = 25.51 * CV.MPH_TO_MS
|
||||
|
||||
if candidate == CAR.HONDA_PILOT_4G:
|
||||
CarControllerParams.BOSCH_GAS_LOOKUP_V = [0, 2200]
|
||||
|
||||
ret.steerLimitTimer = 0.8
|
||||
ret.radarDelay = 0.1
|
||||
|
||||
@@ -1,12 +1,44 @@
|
||||
import re
|
||||
from types import SimpleNamespace
|
||||
import pytest
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.honda.interface import CarInterface
|
||||
from opendbc.car.honda.carcontroller import (
|
||||
CarController,
|
||||
get_civic_bosch_modified_steering_pressed,
|
||||
get_civic_bosch_modified_torque_lpf_tau,
|
||||
get_honda_bosch_wind_brake_mps2,
|
||||
update_honda_bosch_live_learning,
|
||||
)
|
||||
from opendbc.car.honda.hondacan import create_lkas_hud
|
||||
from opendbc.car.honda.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.honda.values import HONDA_BOSCH, HONDA_BOSCH_TJA_CONTROL
|
||||
from opendbc.car.honda.values import CAR, DBC, HONDA_BOSCH, HONDA_BOSCH_TJA_CONTROL, CarControllerParams, HondaFlags, HondaSafetyFlags, \
|
||||
HondaStarPilotFlags
|
||||
|
||||
HONDA_FW_VERSION_RE = br"[A-Z0-9]{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$"
|
||||
HONDA_FW_VERSION_RE = rb"[A-Z0-9]{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$"
|
||||
|
||||
|
||||
def get_test_toggles() -> SimpleNamespace:
|
||||
return SimpleNamespace(always_on_lateral_lkas=False, force_torque_controller=False, nnff=False, nnff_lite=False)
|
||||
|
||||
|
||||
class TestHondaFingerprint:
|
||||
def test_honda_lkas_hud_shows_lane_lines_when_lateral_only_is_active(self):
|
||||
class FakePacker:
|
||||
@staticmethod
|
||||
def make_can_msg(name, bus, values):
|
||||
return name, bus, values
|
||||
|
||||
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH)
|
||||
hud_control = SimpleNamespace(lanesVisible=False)
|
||||
|
||||
cmds = create_lkas_hud(FakePacker(), 0, CP, hud_control, True, True, False, False, {})
|
||||
|
||||
assert cmds[0][2]["SOLID_LANES"] is True
|
||||
|
||||
def test_fw_version_format(self):
|
||||
# Asserts all FW versions follow an expected format
|
||||
for fw_by_ecu in FW_VERSIONS.values():
|
||||
@@ -16,3 +48,230 @@ class TestHondaFingerprint:
|
||||
|
||||
def test_tja_bosch_only(self):
|
||||
assert set(HONDA_BOSCH_TJA_CONTROL).issubset(set(HONDA_BOSCH)), "Nidec car found in TJA control list"
|
||||
|
||||
def test_modified_civic_torque_lpf_tau_reacts_to_sign_change(self):
|
||||
assert get_civic_bosch_modified_torque_lpf_tau(0.7, -0.1, 25.0) == 0.10
|
||||
assert get_civic_bosch_modified_torque_lpf_tau(0.02, -0.01, 8.0) == 0.28
|
||||
assert get_civic_bosch_modified_torque_lpf_tau(0.02, 0.01, 12.0) == 0.28
|
||||
assert get_civic_bosch_modified_torque_lpf_tau(0.02, 0.01, 20.0) == 0.20
|
||||
assert get_civic_bosch_modified_torque_lpf_tau(0.02, 0.01, 25.0) == 0.16
|
||||
assert get_civic_bosch_modified_torque_lpf_tau(0.30, 0.0, 12.0) == 0.16
|
||||
assert get_civic_bosch_modified_torque_lpf_tau(0.30, 0.0, 20.0) == 0.13
|
||||
|
||||
def test_modified_civic_steering_pressed_filter_rejects_short_same_direction_spikes(self):
|
||||
filter_s, pressed = get_civic_bosch_modified_steering_pressed(True, 1500.0, 0.8, 0.01, False)
|
||||
assert not pressed
|
||||
assert filter_s > 0.01
|
||||
|
||||
filter_s = 0.79
|
||||
filter_s, pressed = get_civic_bosch_modified_steering_pressed(True, 1500.0, 0.8, filter_s, False)
|
||||
assert not pressed
|
||||
|
||||
filter_s = 0.80
|
||||
filter_s, pressed = get_civic_bosch_modified_steering_pressed(True, 1500.0, 0.8, filter_s, False)
|
||||
assert pressed
|
||||
|
||||
def test_modified_civic_steering_pressed_filter_allows_opposing_driver_torque_quickly(self):
|
||||
filter_s, pressed = get_civic_bosch_modified_steering_pressed(True, -1500.0, 0.8, 0.10, False)
|
||||
assert pressed
|
||||
|
||||
def test_honda_bosch_wind_brake_curve_matches_reference_points(self):
|
||||
assert get_honda_bosch_wind_brake_mps2(0.0) == pytest.approx(0.0)
|
||||
assert get_honda_bosch_wind_brake_mps2(22.4) == pytest.approx(0.136)
|
||||
assert get_honda_bosch_wind_brake_mps2(40.2) == pytest.approx(0.441)
|
||||
|
||||
def test_honda_bosch_live_learning_increases_factors_when_under_accelerating(self):
|
||||
gas_factor, wind_factor, wind_factor_before_brake = update_honda_bosch_live_learning(
|
||||
1.0,
|
||||
1.0,
|
||||
0.0,
|
||||
desired_accel=1.0,
|
||||
actual_accel=0.5,
|
||||
gas_pedal_force=1.2,
|
||||
wind_brake_mps2=0.136,
|
||||
brake_pressed=False,
|
||||
v_ego=22.4,
|
||||
)
|
||||
|
||||
assert gas_factor == pytest.approx(1.012)
|
||||
assert wind_factor == pytest.approx(1.000136)
|
||||
assert wind_factor_before_brake == pytest.approx(wind_factor)
|
||||
|
||||
def test_honda_bosch_live_learning_restores_wind_factor_while_braking(self):
|
||||
gas_factor, wind_factor, wind_factor_before_brake = update_honda_bosch_live_learning(
|
||||
1.4,
|
||||
1.1,
|
||||
1.3,
|
||||
desired_accel=-0.2,
|
||||
actual_accel=0.0,
|
||||
gas_pedal_force=-0.1,
|
||||
wind_brake_mps2=0.136,
|
||||
brake_pressed=True,
|
||||
v_ego=22.4,
|
||||
)
|
||||
|
||||
assert gas_factor == pytest.approx(1.4)
|
||||
assert wind_factor == pytest.approx(1.3)
|
||||
assert wind_factor_before_brake == pytest.approx(1.3)
|
||||
|
||||
def test_official_modified_eps_firmwares_restored(self):
|
||||
assert b'39990-TVA,A150\x00\x00' in FW_VERSIONS[CAR.HONDA_ACCORD][(CarParams.Ecu.eps, 0x18DA30F1, None)]
|
||||
assert b'39990-TBA,A030\x00\x00' in FW_VERSIONS[CAR.HONDA_CIVIC][(CarParams.Ecu.eps, 0x18DA30F1, None)]
|
||||
assert b'39990-TBA-C120\x00\x00' in FW_VERSIONS[CAR.HONDA_CIVIC_BOSCH][(CarParams.Ecu.eps, 0x18DA30F1, None)]
|
||||
assert b'39990-TGG,A020\x00\x00' in FW_VERSIONS[CAR.HONDA_CIVIC_BOSCH][(CarParams.Ecu.eps, 0x18DA30F1, None)]
|
||||
assert b'39990-TLA,A040\x00\x00' in FW_VERSIONS[CAR.HONDA_CRV_5G][(CarParams.Ecu.eps, 0x18DA30F1, None)]
|
||||
|
||||
def test_modified_eps_candidates_keep_support_and_restore_upstream_tunes(self):
|
||||
toggles = SimpleNamespace(force_torque_controller=False, nnff=False, nnff_lite=False)
|
||||
|
||||
civic_fw = [CarParams.CarFw(ecu=CarParams.Ecu.eps, fwVersion=b'39990-TBA,A030\x00\x00', address=0x18DA30F1, subAddress=0)]
|
||||
civic_cp = CarInterface.get_params(CAR.HONDA_CIVIC, gen_empty_fingerprint(), civic_fw, False, False, False, toggles)
|
||||
assert not civic_cp.dashcamOnly
|
||||
assert civic_cp.flags & HondaFlags.EPS_MODIFIED
|
||||
assert list(civic_cp.lateralParams.torqueBP) == [0, 2560, 8000]
|
||||
assert list(civic_cp.lateralParams.torqueV) == [0, 2560, 3840]
|
||||
assert list(civic_cp.lateralTuning.pid.kpV) == pytest.approx([0.3])
|
||||
assert list(civic_cp.lateralTuning.pid.kiV) == pytest.approx([0.1])
|
||||
|
||||
civic_bosch_fw = [CarParams.CarFw(ecu=CarParams.Ecu.eps, fwVersion=b'39990-TGG,A020\x00\x00', address=0x18DA30F1, subAddress=0)]
|
||||
civic_bosch_cp = CarInterface.get_params(CAR.HONDA_CIVIC_BOSCH, gen_empty_fingerprint(), civic_bosch_fw, False, False, False, toggles)
|
||||
assert not civic_bosch_cp.dashcamOnly
|
||||
assert civic_bosch_cp.flags & HondaFlags.EPS_MODIFIED
|
||||
|
||||
accord_fw = [CarParams.CarFw(ecu=CarParams.Ecu.eps, fwVersion=b'39990-TVA,A150\x00\x00', address=0x18DA30F1, subAddress=0)]
|
||||
accord_cp = CarInterface.get_params(CAR.HONDA_ACCORD, gen_empty_fingerprint(), accord_fw, False, False, False, toggles)
|
||||
assert not accord_cp.dashcamOnly
|
||||
assert accord_cp.flags & HondaFlags.EPS_MODIFIED
|
||||
assert list(accord_cp.lateralTuning.pid.kpV) == pytest.approx([0.3])
|
||||
assert list(accord_cp.lateralTuning.pid.kiV) == pytest.approx([0.09])
|
||||
|
||||
crv_fw = [CarParams.CarFw(ecu=CarParams.Ecu.eps, fwVersion=b'39990-TLA,A040\x00\x00', address=0x18DA30F1, subAddress=0)]
|
||||
crv_cp = CarInterface.get_params(CAR.HONDA_CRV_5G, gen_empty_fingerprint(), crv_fw, False, False, False, toggles)
|
||||
assert not crv_cp.dashcamOnly
|
||||
assert crv_cp.flags & HondaFlags.EPS_MODIFIED
|
||||
assert list(crv_cp.lateralParams.torqueBP) == [0, 2560, 10000]
|
||||
assert list(crv_cp.lateralParams.torqueV) == [0, 2560, 3840]
|
||||
assert list(crv_cp.lateralTuning.pid.kpV) == pytest.approx([0.21])
|
||||
assert list(crv_cp.lateralTuning.pid.kiV) == pytest.approx([0.07])
|
||||
|
||||
def test_modified_civic_bosch_keeps_official_support(self):
|
||||
toggles = SimpleNamespace(force_torque_controller=False, nnff=False, nnff_lite=False)
|
||||
car_fw = [CarParams.CarFw(ecu=CarParams.Ecu.eps, fwVersion=b'39990-TGG,A020\x00\x00', address=0x18DA30F1, subAddress=0)]
|
||||
|
||||
CP = CarInterface.get_params(CAR.HONDA_CIVIC_BOSCH, gen_empty_fingerprint(), car_fw, False, False, False, toggles)
|
||||
|
||||
assert not CP.dashcamOnly
|
||||
assert CP.flags & HondaFlags.EPS_MODIFIED
|
||||
assert CP.lateralTuning.which() == "torque"
|
||||
|
||||
def test_honda_clarity_supports_pid_and_torque_paths(self):
|
||||
pid_toggles = SimpleNamespace(force_torque_controller=False, nnff=False, nnff_lite=False)
|
||||
car_fw = [CarParams.CarFw(ecu=CarParams.Ecu.eps, fwVersion=b'39990-TRW,A020\x00\x00', address=0x18DA30F1, subAddress=0)]
|
||||
|
||||
pid_cp = CarInterface.get_params(CAR.HONDA_CLARITY, gen_empty_fingerprint(), car_fw, False, False, False, pid_toggles)
|
||||
|
||||
assert not pid_cp.dashcamOnly
|
||||
assert pid_cp.flags & HondaFlags.EPS_MODIFIED
|
||||
assert pid_cp.lateralTuning.which() == "pid"
|
||||
assert list(pid_cp.lateralParams.torqueBP) == [0, 2560]
|
||||
assert list(pid_cp.lateralParams.torqueV) == [0, 2560]
|
||||
assert list(pid_cp.lateralTuning.pid.kpV) == pytest.approx([0.8])
|
||||
assert list(pid_cp.lateralTuning.pid.kiV) == pytest.approx([0.24])
|
||||
assert pid_cp.autoResumeSng
|
||||
assert pid_cp.minEnableSpeed == pytest.approx(-1.0)
|
||||
assert pid_cp.stopAccel == pytest.approx(0.0)
|
||||
|
||||
torque_toggles = SimpleNamespace(force_torque_controller=True, nnff=False, nnff_lite=False)
|
||||
torque_cp = CarInterface.get_params(CAR.HONDA_CLARITY, gen_empty_fingerprint(), car_fw, False, False, False, torque_toggles)
|
||||
|
||||
assert torque_cp.lateralTuning.which() == "torque"
|
||||
|
||||
def test_canfd_bosch_alpha_long_is_available(self):
|
||||
toggles = get_test_toggles()
|
||||
|
||||
CP = CarInterface.get_params(CAR.HONDA_PILOT_4G, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
|
||||
assert CP.alphaLongitudinalAvailable
|
||||
assert CP.openpilotLongitudinalControl
|
||||
assert CP.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_CANFD
|
||||
assert CP.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_LONG
|
||||
|
||||
def test_nidec_pedal_detection_enables_interceptor_path(self):
|
||||
toggles = get_test_toggles()
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0][0x201] = 6
|
||||
|
||||
CP = CarInterface.get_params(CAR.HONDA_CIVIC, fingerprint, [], False, False, False, toggles)
|
||||
accel_limits = CarInterface.get_pid_accel_limits(CP, current_speed=5.0, cruise_speed=12.0)
|
||||
|
||||
assert CP.enableGasInterceptorDEPRECATED
|
||||
assert not CP.pcmCruise
|
||||
assert accel_limits == (CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX)
|
||||
|
||||
def test_honda_camera_message_flag_uses_fingerprint_detection(self):
|
||||
toggles = get_test_toggles()
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0][0x35E] = 8
|
||||
|
||||
CP = CarInterface.get_params(CAR.HONDA_ACCORD, fingerprint, [], True, False, False, toggles)
|
||||
FPCP = CarInterface.get_starpilot_params(CAR.HONDA_ACCORD, fingerprint, [], CP, toggles)
|
||||
|
||||
assert FPCP.flags & HondaStarPilotFlags.HAS_CAMERA_MESSAGES
|
||||
|
||||
def test_honda_live_learning_params_reload(self, monkeypatch):
|
||||
toggles = get_test_toggles()
|
||||
|
||||
class FakeParams:
|
||||
def get_float(self, key, block=False, return_default=False, default=0.0):
|
||||
if key == "HondaGasFactorParams":
|
||||
return 1.25
|
||||
if key == "HondaWindFactorParams":
|
||||
return 0.85
|
||||
return default
|
||||
|
||||
monkeypatch.setattr("opendbc.car.honda.carcontroller.Params", lambda: FakeParams())
|
||||
|
||||
CP = CarInterface.get_params(CAR.HONDA_ACCORD, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
|
||||
assert controller.bosch_gas_factor == pytest.approx(1.25)
|
||||
assert controller.bosch_wind_factor == pytest.approx(0.85)
|
||||
|
||||
def test_honda_bosch_controller_does_not_deepen_planner_braking(self, monkeypatch):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.HONDA_HRV_3G, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
|
||||
monkeypatch.setattr("opendbc.car.honda.carcontroller.hondacan.create_steering_control", lambda *args, **kwargs: (0, []))
|
||||
monkeypatch.setattr("opendbc.car.honda.carcontroller.hondacan.create_acc_commands", lambda *args, **kwargs: [])
|
||||
|
||||
CC = structs.CarControl.new_message()
|
||||
CC.enabled = True
|
||||
CC.longActive = True
|
||||
CC.latActive = False
|
||||
CC.cruiseControl.cancel = False
|
||||
CC.cruiseControl.resume = False
|
||||
CC.hudControl.speedVisible = False
|
||||
CC.hudControl.setSpeed = 0.0
|
||||
CC.hudControl.visualAlert = structs.CarControl.HUDControl.VisualAlert.none
|
||||
CC.actuators.accel = -0.3
|
||||
CC.actuators.torque = 0.0
|
||||
CC.actuators.longControlState = structs.CarControl.Actuators.LongControlState.pid
|
||||
|
||||
controller.frame = 2
|
||||
CS = SimpleNamespace(
|
||||
out=SimpleNamespace(vEgo=25.0, aEgo=1.5, steeringPressed=False, gasPressed=False, brakePressed=False),
|
||||
v_cruise_factor=1.0,
|
||||
)
|
||||
|
||||
new_actuators, _ = controller.update(CC.as_reader(), CS, 0, toggles)
|
||||
assert new_actuators.accel == pytest.approx(-0.3)
|
||||
|
||||
def test_honda_hrv_3g_uses_matched_longitudinal_delay(self):
|
||||
toggles = get_test_toggles()
|
||||
|
||||
hrv3g_cp = CarInterface.get_params(CAR.HONDA_HRV_3G, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
accord_cp = CarInterface.get_params(CAR.HONDA_ACCORD, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
|
||||
assert hrv3g_cp.longitudinalActuatorDelay == pytest.approx(0.4)
|
||||
assert accord_cp.longitudinalActuatorDelay == pytest.approx(0.5)
|
||||
|
||||
@@ -56,6 +56,7 @@ class HondaSafetyFlags(IntFlag):
|
||||
NIDEC_ALT = 4
|
||||
RADARLESS = 8
|
||||
BOSCH_CANFD = 16
|
||||
GAS_INTERCEPTOR = 32
|
||||
|
||||
|
||||
class HondaFlags(IntFlag):
|
||||
@@ -79,6 +80,7 @@ class HondaFlags(IntFlag):
|
||||
ALLOW_MANUAL_TRANS = 1024
|
||||
HYBRID = 2048
|
||||
BOSCH_TJA_CONTROL = 4096
|
||||
EPS_MODIFIED = 8192
|
||||
|
||||
|
||||
# Car button codes
|
||||
@@ -246,6 +248,12 @@ class CAR(Platforms):
|
||||
{Bus.pt: 'acura_rdx_2020_can_generated'},
|
||||
flags=HondaFlags.BOSCH_ALT_BRAKE,
|
||||
)
|
||||
ACURA_RDX_3G_MMR = HondaBoschPlatformConfig(
|
||||
[HondaCarDocs("Acura RDX 2022-26", "All", min_steer_speed=70. * CV.KPH_TO_MS)],
|
||||
CarSpecs(mass=4079 * CV.LB_TO_KG, wheelbase=2.75, centerToFrontRatio=0.41, steerRatio=16.2),
|
||||
{Bus.pt: 'acura_rdx_2020_can_generated'},
|
||||
flags=HondaFlags.BOSCH_ALT_BRAKE | HondaFlags.BOSCH_ALT_RADAR,
|
||||
)
|
||||
HONDA_INSIGHT = HondaBoschPlatformConfig(
|
||||
[HondaCarDocs("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=2987 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.0, centerToFrontRatio=0.39, tireStiffnessFactor=0.82), # as spec
|
||||
@@ -256,6 +264,11 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=3338.8 * CV.LB_TO_KG, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82),
|
||||
{Bus.pt: 'acura_rdx_2020_can_generated'},
|
||||
)
|
||||
HONDA_E_ADVANCE = HondaBoschPlatformConfig(
|
||||
[], # don't show in docs, base trim already in docs
|
||||
CarSpecs(mass=1527, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82),
|
||||
{Bus.pt: 'honda_e_advance_2020_can_generated'},
|
||||
)
|
||||
HONDA_PILOT_4G = HondaBoschCANFDPlatformConfig(
|
||||
[HondaCarDocs("Honda Pilot 2023-25", "All")],
|
||||
CarSpecs(mass=4660 * CV.LB_TO_KG, wheelbase=2.89, centerToFrontRatio=0.442, steerRatio=17.5),
|
||||
@@ -264,6 +277,12 @@ class CAR(Platforms):
|
||||
[HondaCarDocs("Honda Passport 2026", "All")],
|
||||
CarSpecs(mass=4620 * CV.LB_TO_KG, wheelbase=2.89, centerToFrontRatio=0.442, steerRatio=18.5),
|
||||
)
|
||||
ACURA_MDX_4G = HondaBoschPlatformConfig(
|
||||
[HondaCarDocs("Acura MDX 2022-24", "All", min_steer_speed=70. * CV.KPH_TO_MS)],
|
||||
CarSpecs(mass=4788 * CV.LB_TO_KG, wheelbase=2.89, steerRatio=15.8, centerToFrontRatio=0.428),
|
||||
{Bus.pt: 'honda_common_canfd_generated'},
|
||||
flags=HondaFlags.BOSCH_ALT_RADAR | HondaFlags.BOSCH_TJA_CONTROL,
|
||||
)
|
||||
# mid-model refresh
|
||||
ACURA_MDX_4G_MMR = HondaBoschCANFDPlatformConfig(
|
||||
[HondaCarDocs("Acura MDX 2025", "All except Type S")],
|
||||
@@ -281,6 +300,34 @@ class CAR(Platforms):
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
|
||||
flags=HondaFlags.BOSCH_ALT_RADAR,
|
||||
)
|
||||
ACURA_TLX_2G_MMR = HondaBoschCANFDPlatformConfig(
|
||||
[HondaCarDocs("Acura TLX 2024-25", "All")],
|
||||
CarSpecs(mass=3990 * CV.LB_TO_KG, wheelbase=2.87, centerToFrontRatio=0.43, steerRatio=13.7),
|
||||
)
|
||||
HONDA_FIT_4G = HondaBoschPlatformConfig(
|
||||
[
|
||||
HondaCarDocs("Honda Fit (Taiwan) 2021", "All"),
|
||||
HondaCarDocs("Honda Fit (Taiwan) 2024-25", "All"),
|
||||
],
|
||||
CarSpecs(mass=1229, wheelbase=2.53, steerRatio=19.7, centerToFrontRatio=0.39, minSteerSpeed=23. * CV.KPH_TO_MS),
|
||||
{Bus.pt: 'honda_bosch_radarless_generated'},
|
||||
flags=HondaFlags.BOSCH_RADARLESS,
|
||||
)
|
||||
ACURA_INTEGRA = HondaBoschPlatformConfig(
|
||||
[
|
||||
HondaCarDocs("Acura Integra 2023-26", "All"),
|
||||
HondaCarDocs("Honda Prelude 2026", "All"),
|
||||
],
|
||||
CarSpecs(mass=3338.8 * CV.LB_TO_KG, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82),
|
||||
{Bus.pt: 'honda_bosch_radarless_generated'},
|
||||
flags=HondaFlags.BOSCH_RADARLESS,
|
||||
)
|
||||
ACURA_ADX = HondaBoschPlatformConfig(
|
||||
[HondaCarDocs("Acura ADX 2025-26", "All")],
|
||||
CarSpecs(mass=3578 * CV.LB_TO_KG, wheelbase=2.65, steerRatio=16.6, centerToFrontRatio=0.43),
|
||||
{Bus.pt: 'honda_bosch_radarless_generated'},
|
||||
flags=HondaFlags.BOSCH_RADARLESS,
|
||||
)
|
||||
|
||||
# Nidec Cars
|
||||
ACURA_ILX = HondaNidecPlatformConfig(
|
||||
@@ -304,6 +351,12 @@ class CAR(Platforms):
|
||||
radar_dbc_dict('honda_crv_touring_2016_can_generated'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES | HondaFlags.HAS_ALL_DOOR_STATES,
|
||||
)
|
||||
HONDA_CRV_SA = HondaNidecPlatformConfig(
|
||||
[], # South Africa version of CRV Touring, don't show in docs
|
||||
HONDA_CRV.specs,
|
||||
radar_dbc_dict('acura_rdx_2018_can_generated'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES | HondaFlags.HAS_ALL_DOOR_STATES,
|
||||
)
|
||||
HONDA_FIT = HondaNidecPlatformConfig(
|
||||
[HondaCarDocs("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=2644 * CV.LB_TO_KG, wheelbase=2.53, steerRatio=13.06, centerToFrontRatio=0.39, tireStiffnessFactor=0.75),
|
||||
@@ -322,12 +375,27 @@ class CAR(Platforms):
|
||||
radar_dbc_dict('acura_ilx_2016_can_generated'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
HONDA_CLARITY = HondaNidecPlatformConfig(
|
||||
[HondaCarDocs("Honda Clarity 2018-21", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=1838, wheelbase=2.75, centerToFrontRatio=0.4, steerRatio=16.5),
|
||||
radar_dbc_dict('honda_clarity_hybrid_2018_can_generated'),
|
||||
flags=HondaFlags.HAS_ALL_DOOR_STATES,
|
||||
)
|
||||
HONDA_ODYSSEY = HondaNidecPlatformConfig(
|
||||
[HondaCarDocs("Honda Odyssey 2018-20")],
|
||||
CarSpecs(mass=1900, wheelbase=3.0, steerRatio=14.35, centerToFrontRatio=0.41, tireStiffnessFactor=0.82),
|
||||
radar_dbc_dict('honda_odyssey_exl_2018_generated'),
|
||||
flags=HondaFlags.NIDEC_ALT_PCM_ACCEL | HondaFlags.HAS_ALL_DOOR_STATES,
|
||||
)
|
||||
HONDA_ODYSSEY_TWN = HondaNidecPlatformConfig(
|
||||
[
|
||||
HondaCarDocs("Honda Odyssey (Taiwan) 2018-19"),
|
||||
HondaCarDocs("Honda Odyssey (Singapore) 2021"),
|
||||
],
|
||||
CarSpecs(mass=1865, wheelbase=2.9, steerRatio=14.35, centerToFrontRatio=0.44, tireStiffnessFactor=0.82),
|
||||
radar_dbc_dict('honda_odyssey_twn_2018_generated'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
ACURA_RDX = HondaNidecPlatformConfig(
|
||||
[HondaCarDocs("Acura RDX 2016-18", "AcuraWatch Plus or Advance Package", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=3925 * CV.LB_TO_KG, wheelbase=2.68, steerRatio=15.0, centerToFrontRatio=0.38, tireStiffnessFactor=0.444), # as spec
|
||||
@@ -355,6 +423,43 @@ class CAR(Platforms):
|
||||
radar_dbc_dict('honda_civic_touring_2016_can_generated'),
|
||||
flags=HondaFlags.HAS_ALL_DOOR_STATES
|
||||
)
|
||||
HONDA_ACCORD_9G = HondaNidecPlatformConfig(
|
||||
[
|
||||
HondaCarDocs("Honda Accord 2016-17"),
|
||||
HondaCarDocs("Honda Accord Hybrid 2017", "All"),
|
||||
],
|
||||
CarSpecs(mass=3343 * CV.LB_TO_KG, wheelbase=2.78, steerRatio=17.5, centerToFrontRatio=0.37),
|
||||
radar_dbc_dict('honda_accord_2017_can_ext_generated'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES | HondaFlags.HAS_ALL_DOOR_STATES,
|
||||
)
|
||||
ACURA_MDX_3G = HondaNidecPlatformConfig(
|
||||
[
|
||||
HondaCarDocs("Acura MDX 2014-16", "Advance Package"),
|
||||
HondaCarDocs("Acura MDX 2017-19", "All"),
|
||||
HondaCarDocs("Acura MDX Hybrid 2017-19", "All"),
|
||||
],
|
||||
CarSpecs(mass=4215 * CV.LB_TO_KG, wheelbase=2.82, steerRatio=16.8, centerToFrontRatio=0.428),
|
||||
radar_dbc_dict('acura_mdx_2017_can_ext_generated'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
ACURA_MDX_3G_MMR = HondaNidecPlatformConfig(
|
||||
[
|
||||
HondaCarDocs("Acura MDX 2020", "All"),
|
||||
HondaCarDocs("Acura MDX Hybrid 2020", "All"),
|
||||
],
|
||||
CarSpecs(mass=4215 * CV.LB_TO_KG, wheelbase=2.82, steerRatio=16.8, centerToFrontRatio=0.428),
|
||||
radar_dbc_dict('acura_ilx_2016_can_generated'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
ACURA_TLX_1G = HondaNidecPlatformConfig(
|
||||
[
|
||||
HondaCarDocs("Acura TLX 2015-17", "Advance Package"),
|
||||
HondaCarDocs("Acura TLX 2018-20", "All"),
|
||||
],
|
||||
CarSpecs(mass=3680 * CV.LB_TO_KG, wheelbase=2.78, steerRatio=17.0, centerToFrontRatio=0.40, tireStiffnessFactor=0.18),
|
||||
radar_dbc_dict('acura_mdx_2017_can_ext_generated'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES | HondaFlags.HAS_ALL_DOOR_STATES,
|
||||
)
|
||||
|
||||
|
||||
HONDA_NIDEC_ALT_PCM_ACCEL = CAR.with_flags(HondaFlags.NIDEC_ALT_PCM_ACCEL)
|
||||
@@ -389,6 +494,11 @@ STEER_THRESHOLD = {
|
||||
CAR.HONDA_CRV_6G: 600,
|
||||
CAR.HONDA_CITY_7G: 600,
|
||||
CAR.HONDA_NBOX_2G: 600,
|
||||
CAR.HONDA_ODYSSEY_5G_MMR: 600,
|
||||
CAR.HONDA_ACCORD_9G: 30,
|
||||
CAR.ACURA_MDX_3G: 30,
|
||||
CAR.ACURA_MDX_3G_MMR: 30,
|
||||
CAR.ACURA_TLX_1G: 30,
|
||||
}
|
||||
|
||||
|
||||
@@ -433,9 +543,11 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
# Note that we still attempt to match with them when they are present
|
||||
# This is or'd with (ALL_ECUS - ESSENTIAL_ECUS) from fw_versions.py
|
||||
non_essential_ecus={
|
||||
Ecu.eps: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_E, *HONDA_BOSCH_ALT_RADAR, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD],
|
||||
Ecu.eps: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_E, CAR.HONDA_E_ADVANCE, CAR.ACURA_MDX_4G, CAR.HONDA_CRV_SA,
|
||||
CAR.ACURA_MDX_3G, CAR.HONDA_ACCORD_9G, *HONDA_BOSCH_ALT_RADAR, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD],
|
||||
Ecu.vsa: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G, CAR.HONDA_CRV_HYBRID,
|
||||
CAR.HONDA_E, CAR.HONDA_INSIGHT, CAR.HONDA_NBOX_2G, *HONDA_BOSCH_ALT_RADAR, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD],
|
||||
CAR.HONDA_E, CAR.HONDA_E_ADVANCE, CAR.HONDA_INSIGHT, CAR.HONDA_NBOX_2G, CAR.ACURA_MDX_4G, CAR.HONDA_ACCORD_9G,
|
||||
*HONDA_BOSCH_ALT_RADAR, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD],
|
||||
},
|
||||
extra_ecus=[
|
||||
(Ecu.combinationMeter, 0x18da60f1, None),
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, structs
|
||||
from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, rate_limit, structs
|
||||
from opendbc.car.common.filter_simple import FirstOrderFilter
|
||||
from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_angle_limits_vm, common_fault_avoidance
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.hyundai import hyundaicanfd, hyundaican
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CAR
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CAR, CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \
|
||||
kia_ev6_gt_line_longitudinal_tuning
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
from openpilot.common.params import Params
|
||||
@@ -18,6 +22,236 @@ LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
MAX_ANGLE = 85
|
||||
MAX_ANGLE_FRAMES = 89
|
||||
MAX_ANGLE_CONSECUTIVE_FRAMES = 2
|
||||
CANFD_BLINDSPOT_STATUS_STALE_NS = 200_000_000
|
||||
CANFD_CAMERA_LEAD_STALE_NS = 300_000_000
|
||||
CANFD_LEAD_MIN_DISTANCE = 0.1
|
||||
CANFD_FALLBACK_LEAD_DISTANCE = 20.0
|
||||
HYUNDAI_DASH_DISENGAGE_BLINK_TIME = 1.0
|
||||
HYUNDAI_CANFD_SCC_ACCEL_STEP = 5.0 / 50.0
|
||||
HYUNDAI_CANFD_SCC_DECEL_STEP = 12.5 / 50.0
|
||||
IONIQ_6_RESPONSE_MULTIPLIER = 1.2
|
||||
IONIQ_6_CANFD_SCC_ACCEL_STEP = (6.0 / 50.0) * IONIQ_6_RESPONSE_MULTIPLIER
|
||||
IONIQ_6_CANFD_SCC_DECEL_STEP = (15.0 / 50.0) * IONIQ_6_RESPONSE_MULTIPLIER
|
||||
GENESIS_G90_STOP_HOLD_SPEED_BP = [0.0, 0.03, 0.08, 0.16, 0.3, 0.5, 0.8, 1.2, 2.0, 3.0]
|
||||
GENESIS_G90_STOP_HOLD_ACCEL_V = [-0.10, -0.10, -0.12, -0.18, -0.30, -0.50, -0.75, -1.00, -1.40, -1.80]
|
||||
GENESIS_G90_STOP_HOLD_RELAX_SPEED_BP = [0.0, 0.08, 0.16, 0.3, 0.5, 0.8, 1.2, 2.0, 3.0]
|
||||
GENESIS_G90_STOP_HOLD_RELAX_STEP_V = [0.10, 0.10, 0.08, 0.06, 0.04, 0.035, 0.03, 0.022, 0.018]
|
||||
GENESIS_G90_RELEASE_SPEED_BP = [0.0, 0.3, 0.6]
|
||||
GENESIS_G90_RELEASE_ACCEL_STEP_V = [0.05, 0.07, 0.11]
|
||||
GENESIS_G90_RELEASE_DECEL_STEP_V = [0.16, 0.18, 0.18]
|
||||
GENESIS_G90_RELEASE_MAX_SPEED = 0.8
|
||||
IONIQ_6_LONG_MIN_JERK = 0.5 * IONIQ_6_RESPONSE_MULTIPLIER
|
||||
IONIQ_6_LONG_JERK_LIMIT = 4.8 * IONIQ_6_RESPONSE_MULTIPLIER
|
||||
IONIQ_6_LONG_LOOKAHEAD_JERK_BP = [2.0, 5.0, 20.0]
|
||||
IONIQ_6_LONG_LOOKAHEAD_JERK_V = [0.3 / IONIQ_6_RESPONSE_MULTIPLIER,
|
||||
0.45 / IONIQ_6_RESPONSE_MULTIPLIER,
|
||||
0.6 / IONIQ_6_RESPONSE_MULTIPLIER]
|
||||
IONIQ_6_DYNAMIC_LOWER_JERK_BP = [-2.0, -1.5, -1.0, -0.25, -0.1, -0.025, -0.01, -0.005]
|
||||
IONIQ_6_DYNAMIC_LOWER_JERK_V = [3.3, 1.5, 1.0, 0.8, 0.7, 0.65, 0.55, 0.5]
|
||||
IONIQ_6_LAUNCH_HOLD_SPEED_BP = [0.0, 0.6, 1.25, 2.5]
|
||||
IONIQ_6_LAUNCH_HOLD_SPEED_V = [0.75, 0.6, 0.4, 0.0]
|
||||
IONIQ_6_STOP_BRAKE_CAP_MAX_SPEED = 2.0
|
||||
IONIQ_6_STOP_BRAKE_CAP_SPEED_BP = [0.0, 0.08, 0.25, 0.6, 1.2, 2.0, 3.0]
|
||||
IONIQ_6_STOP_BRAKE_CAP_ACCEL_V = [-0.15, -0.16, -0.22, -0.42, -0.78, -1.15, -1.40]
|
||||
IONIQ_6_STOP_HOLD_JERK_BP = [0.0, 0.15, 0.6, 1.2, 2.0, 3.0]
|
||||
IONIQ_6_STOP_HOLD_JERK_V = [0.35, 0.40, 0.48, 0.65, 0.85, 1.10]
|
||||
IONIQ_6_STOP_RELEASE_JERK_BP = [0.0, 0.15, 0.5]
|
||||
IONIQ_6_STOP_RELEASE_JERK_V = [3.6 * IONIQ_6_RESPONSE_MULTIPLIER,
|
||||
4.2 * IONIQ_6_RESPONSE_MULTIPLIER,
|
||||
4.8 * IONIQ_6_RESPONSE_MULTIPLIER]
|
||||
REDNECK_BUTTON_COPIES = 2
|
||||
REDNECK_BUTTON_COPIES_TIME = 7
|
||||
REDNECK_BUTTON_COPIES_TIME_IMPERIAL = [REDNECK_BUTTON_COPIES_TIME + 3, 70]
|
||||
REDNECK_BUTTON_COPIES_TIME_METRIC = [REDNECK_BUTTON_COPIES_TIME, 40]
|
||||
ANGLE_SAFETY_BASELINE_MODEL = str(CAR.KIA_SPORTAGE_HEV_2026)
|
||||
DEFAULT_ANGLE_SMOOTHING_VEGO_BP = [5.0, 10.0, 20.0]
|
||||
DEFAULT_ANGLE_SMOOTHING_ALPHA_V = [0.2, 0.1, 0.0]
|
||||
EV9_HIGH_ANGLE_GAIN_BP = [70.0, 120.0, 220.0, 320.0]
|
||||
EV9_HIGH_ANGLE_GAIN_CAP_V = [0.85, 0.55, 0.30, 0.16]
|
||||
EV9_HIGH_ANGLE_GAIN_MIN = 0.004
|
||||
|
||||
|
||||
def egmp_dynamic_longitudinal_tuning(CP) -> bool:
|
||||
return CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 or \
|
||||
kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, getattr(CP, "carVin", ""))
|
||||
|
||||
|
||||
def should_reset_ev6_gt_line_longitudinal_tuning(CP, long_control_state: LongCtrlState) -> bool:
|
||||
return kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, getattr(CP, "carVin", "")) and \
|
||||
long_control_state == LongCtrlState.off
|
||||
|
||||
|
||||
@dataclass
|
||||
class Ioniq6LongitudinalTuningState:
|
||||
desired_accel: float = 0.0
|
||||
actual_accel: float = 0.0
|
||||
accel_last: float = 0.0
|
||||
jerk_upper: float = 0.0
|
||||
jerk_lower: float = 0.0
|
||||
launch_active: bool = False
|
||||
stopping: bool = False
|
||||
stopping_count: int = 0
|
||||
long_control_state_last: LongCtrlState = LongCtrlState.off
|
||||
|
||||
|
||||
def reset_ev6_gt_line_longitudinal_tuning(state: Ioniq6LongitudinalTuningState, CP,
|
||||
long_control_state: LongCtrlState) -> Ioniq6LongitudinalTuningState:
|
||||
if should_reset_ev6_gt_line_longitudinal_tuning(CP, long_control_state):
|
||||
return Ioniq6LongitudinalTuningState(long_control_state_last=long_control_state)
|
||||
return state
|
||||
|
||||
|
||||
@dataclass
|
||||
class GenesisG90LongitudinalTuningState:
|
||||
actual_accel: float = 0.0
|
||||
release_active: bool = False
|
||||
long_control_state_last: LongCtrlState = LongCtrlState.off
|
||||
|
||||
|
||||
def _jerk_limited_integrator(desired_accel: float, last_accel: float, jerk_upper: float, jerk_lower: float) -> float:
|
||||
step = (jerk_upper if desired_accel >= last_accel else jerk_lower) * DT_CTRL * 5.0
|
||||
return float(np.clip(desired_accel, last_accel - step, last_accel + step))
|
||||
|
||||
|
||||
def _calculate_ioniq_6_dynamic_lower_jerk(accel_error: float) -> float:
|
||||
if accel_error < 0.0:
|
||||
scaled_values = np.array(IONIQ_6_DYNAMIC_LOWER_JERK_V) * (IONIQ_6_LONG_JERK_LIMIT / IONIQ_6_DYNAMIC_LOWER_JERK_V[0])
|
||||
return float(np.interp(accel_error, IONIQ_6_DYNAMIC_LOWER_JERK_BP, scaled_values))
|
||||
return IONIQ_6_LONG_MIN_JERK
|
||||
|
||||
|
||||
def update_ioniq_6_longitudinal_tuning(state: Ioniq6LongitudinalTuningState, accel_cmd: float, v_ego: float, a_ego: float,
|
||||
long_control_state: LongCtrlState, long_active: bool) -> Ioniq6LongitudinalTuningState:
|
||||
starting = long_control_state == LongCtrlState.starting
|
||||
stopping = long_control_state == LongCtrlState.stopping
|
||||
restart_from_stop = state.long_control_state_last in (LongCtrlState.stopping, LongCtrlState.starting) and \
|
||||
long_control_state in (LongCtrlState.starting, LongCtrlState.pid) and accel_cmd > 0.0 and v_ego < 0.5
|
||||
|
||||
state.stopping = long_active and stopping
|
||||
state.stopping_count = state.stopping_count + 1 if state.stopping else 0
|
||||
|
||||
if not long_active:
|
||||
state.desired_accel = 0.0
|
||||
state.actual_accel = 0.0
|
||||
state.accel_last = 0.0
|
||||
state.jerk_upper = 0.0
|
||||
state.jerk_lower = 0.0
|
||||
state.launch_active = False
|
||||
state.long_control_state_last = long_control_state
|
||||
return state
|
||||
|
||||
if accel_cmd <= 0.0 or v_ego >= IONIQ_6_LAUNCH_HOLD_SPEED_BP[-1]:
|
||||
state.launch_active = False
|
||||
elif starting or (state.launch_active and v_ego < IONIQ_6_LAUNCH_HOLD_SPEED_BP[-1]) or \
|
||||
(state.long_control_state_last == LongCtrlState.starting and long_control_state == LongCtrlState.pid and v_ego < IONIQ_6_LAUNCH_HOLD_SPEED_BP[-1]):
|
||||
state.launch_active = True
|
||||
|
||||
upper_speed_limit = float(np.interp(v_ego, [0.0, 5.0, 20.0], [2.0, 3.0, 2.0])) * IONIQ_6_RESPONSE_MULTIPLIER if long_control_state == LongCtrlState.pid else IONIQ_6_LONG_MIN_JERK
|
||||
lower_speed_limit = float(np.interp(v_ego, [0.0, 5.0, 20.0], [5.0, 3.5, 3.0])) * IONIQ_6_RESPONSE_MULTIPLIER
|
||||
|
||||
future_t_upper = float(np.interp(v_ego, IONIQ_6_LONG_LOOKAHEAD_JERK_BP, IONIQ_6_LONG_LOOKAHEAD_JERK_V))
|
||||
future_t_lower = float(np.interp(v_ego, IONIQ_6_LONG_LOOKAHEAD_JERK_BP, IONIQ_6_LONG_LOOKAHEAD_JERK_V))
|
||||
|
||||
accel_error = accel_cmd - state.accel_last
|
||||
j_ego_upper = float(np.clip(accel_error / future_t_upper, -IONIQ_6_LONG_JERK_LIMIT, IONIQ_6_LONG_JERK_LIMIT))
|
||||
j_ego_lower = float(np.clip(accel_error / future_t_lower, -IONIQ_6_LONG_JERK_LIMIT, IONIQ_6_LONG_JERK_LIMIT))
|
||||
desired_jerk_upper = min(max(j_ego_upper, IONIQ_6_LONG_MIN_JERK), upper_speed_limit)
|
||||
|
||||
dynamic_accel_error = a_ego - state.accel_last
|
||||
dynamic_lower_jerk = _calculate_ioniq_6_dynamic_lower_jerk(dynamic_accel_error)
|
||||
state.jerk_upper = desired_jerk_upper
|
||||
state.jerk_lower = min(dynamic_lower_jerk, lower_speed_limit)
|
||||
|
||||
if state.stopping:
|
||||
if v_ego <= IONIQ_6_STOP_BRAKE_CAP_MAX_SPEED:
|
||||
stop_brake_cap = float(np.interp(v_ego, IONIQ_6_STOP_BRAKE_CAP_SPEED_BP, IONIQ_6_STOP_BRAKE_CAP_ACCEL_V))
|
||||
state.desired_accel = min(0.0, max(accel_cmd, stop_brake_cap))
|
||||
state.jerk_upper = min(state.jerk_upper, float(np.interp(v_ego, IONIQ_6_STOP_HOLD_JERK_BP, IONIQ_6_STOP_HOLD_JERK_V)) * IONIQ_6_RESPONSE_MULTIPLIER)
|
||||
else:
|
||||
state.desired_accel = float(np.clip(accel_cmd, CarControllerParams.ACCEL_MIN, 0.0))
|
||||
else:
|
||||
state.desired_accel = float(np.clip(accel_cmd, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
|
||||
if state.launch_active:
|
||||
state.desired_accel = max(state.desired_accel, float(np.interp(v_ego, IONIQ_6_LAUNCH_HOLD_SPEED_BP, IONIQ_6_LAUNCH_HOLD_SPEED_V)))
|
||||
state.jerk_upper = max(state.jerk_upper, float(np.interp(v_ego, [0.0, 2.5], [4.8, 3.2])) * IONIQ_6_RESPONSE_MULTIPLIER)
|
||||
state.jerk_lower = max(state.jerk_lower, 1.0)
|
||||
if restart_from_stop:
|
||||
state.jerk_upper = min(state.jerk_upper, float(np.interp(v_ego, IONIQ_6_STOP_RELEASE_JERK_BP, IONIQ_6_STOP_RELEASE_JERK_V)))
|
||||
|
||||
state.actual_accel = _jerk_limited_integrator(state.desired_accel, state.accel_last, state.jerk_upper, state.jerk_lower)
|
||||
state.accel_last = state.actual_accel
|
||||
state.long_control_state_last = long_control_state
|
||||
return state
|
||||
|
||||
|
||||
def update_genesis_g90_longitudinal_tuning(state: GenesisG90LongitudinalTuningState, accel_cmd: float, v_ego: float,
|
||||
long_control_state: LongCtrlState, long_active: bool) -> GenesisG90LongitudinalTuningState:
|
||||
if not long_active:
|
||||
state.actual_accel = 0.0
|
||||
state.release_active = False
|
||||
state.long_control_state_last = long_control_state
|
||||
return state
|
||||
|
||||
stopping = long_control_state == LongCtrlState.stopping
|
||||
if stopping and v_ego <= GENESIS_G90_STOP_HOLD_SPEED_BP[-1]:
|
||||
state.release_active = False
|
||||
stop_brake_cap = float(np.interp(v_ego, GENESIS_G90_STOP_HOLD_SPEED_BP, GENESIS_G90_STOP_HOLD_ACCEL_V))
|
||||
target_hold = min(0.0, max(accel_cmd, stop_brake_cap))
|
||||
if state.actual_accel < target_hold:
|
||||
relax_step = float(np.interp(v_ego, GENESIS_G90_STOP_HOLD_RELAX_SPEED_BP, GENESIS_G90_STOP_HOLD_RELAX_STEP_V))
|
||||
state.actual_accel = min(state.actual_accel + relax_step, target_hold)
|
||||
else:
|
||||
state.actual_accel = target_hold
|
||||
else:
|
||||
if state.long_control_state_last == LongCtrlState.stopping and long_control_state == LongCtrlState.pid and \
|
||||
accel_cmd > 0.0 and v_ego < GENESIS_G90_RELEASE_MAX_SPEED:
|
||||
state.release_active = True
|
||||
|
||||
if state.release_active:
|
||||
accel_step = float(np.interp(v_ego, GENESIS_G90_RELEASE_SPEED_BP, GENESIS_G90_RELEASE_ACCEL_STEP_V))
|
||||
decel_step = float(np.interp(v_ego, GENESIS_G90_RELEASE_SPEED_BP, GENESIS_G90_RELEASE_DECEL_STEP_V))
|
||||
state.actual_accel = float(np.clip(accel_cmd, state.actual_accel - decel_step, state.actual_accel + accel_step))
|
||||
if v_ego >= GENESIS_G90_RELEASE_MAX_SPEED or accel_cmd <= 0.0 or state.actual_accel >= accel_cmd - 1e-3:
|
||||
state.release_active = False
|
||||
else:
|
||||
state.actual_accel = accel_cmd
|
||||
|
||||
state.long_control_state_last = long_control_state
|
||||
return state
|
||||
|
||||
|
||||
def get_baseline_safety_cp():
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
return CarInterface.get_non_essential_params(ANGLE_SAFETY_BASELINE_MODEL)
|
||||
|
||||
|
||||
def get_angle_smoothing_alpha(CP, v_ego: float) -> float:
|
||||
return float(np.interp(v_ego, DEFAULT_ANGLE_SMOOTHING_VEGO_BP, DEFAULT_ANGLE_SMOOTHING_ALPHA_V))
|
||||
|
||||
|
||||
def compute_torque_reduction_gain(steering_torque, v_ego, lat_active, last_gain):
|
||||
if lat_active:
|
||||
ceiling = np.interp(v_ego, [0.5, 1.5], [1.0, 0.85])
|
||||
shelf = np.interp(v_ego, [2.0, 11.0], [0.45, 0.6])
|
||||
floor = np.interp(v_ego, [2.0, 22.0], [0.1, 0.3])
|
||||
bp1 = np.interp(v_ego, [2.0, 11.0], [75.0, 125.0])
|
||||
bp2 = np.interp(v_ego, [2.0, 11.0], [125.0, 150.0])
|
||||
bp3 = np.interp(v_ego, [2.0, 11.0], [175.0, 275.0])
|
||||
bp4 = np.interp(v_ego, [2.0, 22.0], [400.0, 700.0])
|
||||
target = np.interp(abs(steering_torque), [bp1, bp2, bp3, bp4], [ceiling, shelf, shelf, floor])
|
||||
else:
|
||||
target = 0.0
|
||||
|
||||
gain = rate_limit(target, last_gain, -0.014, 0.004)
|
||||
return round(gain / 0.004) * 0.004
|
||||
|
||||
|
||||
def apply_ev9_high_angle_gain_cap(CP, gain: float, steering_angle_deg: float, lat_active: bool) -> float:
|
||||
if CP.carFingerprint != CAR.KIA_EV9 or not CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING or not lat_active:
|
||||
return gain
|
||||
|
||||
cap = float(np.interp(abs(steering_angle_deg), EV9_HIGH_ANGLE_GAIN_BP, EV9_HIGH_ANGLE_GAIN_CAP_V))
|
||||
return max(EV9_HIGH_ANGLE_GAIN_MIN, min(gain, cap))
|
||||
|
||||
|
||||
def process_hud_alert(enabled, fingerprint, hud_control):
|
||||
@@ -52,41 +286,145 @@ class CarController(CarControllerBase):
|
||||
self.packer = CANPacker(dbc_names[Bus.pt])
|
||||
self.angle_limit_counter = 0
|
||||
self.VM = VehicleModel(CP)
|
||||
self.BASELINE_VM = VehicleModel(get_baseline_safety_cp()) if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING else self.VM
|
||||
self.angle_filter = FirstOrderFilter(0.0, 0.2, DT_CTRL)
|
||||
|
||||
self.accel_last = 0
|
||||
self.apply_torque_last = 0
|
||||
self.apply_angle_last = 0.0
|
||||
self.car_fingerprint = CP.carFingerprint
|
||||
self.last_button_frame = 0
|
||||
self.redneck_button_frame = 0
|
||||
self.ecu_disable_failed = False
|
||||
self._ecu_disable_checked = False
|
||||
self._params = Params()
|
||||
self.long_active_ecu = self.CP.openpilotLongitudinalControl
|
||||
self._ioniq_6_lane_change_ui_side = None
|
||||
self._ioniq_6_lane_change_ui_frames = 0
|
||||
self._ioniq_6_long_tuning = Ioniq6LongitudinalTuningState()
|
||||
self._genesis_g90_long_tuning = GenesisG90LongitudinalTuningState()
|
||||
self._dash_lat_disengage_blink_frame = 0
|
||||
self._dash_lat_disengage_init = False
|
||||
self._dash_prev_lat_active = False
|
||||
|
||||
def _update_dash_icon_state(self, CC):
|
||||
if CC.latActive:
|
||||
self._dash_lat_disengage_init = False
|
||||
elif self._dash_prev_lat_active:
|
||||
self._dash_lat_disengage_init = True
|
||||
|
||||
if not self._dash_lat_disengage_init:
|
||||
self._dash_lat_disengage_blink_frame = self.frame
|
||||
|
||||
disengaging = self._dash_lat_disengage_init and \
|
||||
(self.frame - self._dash_lat_disengage_blink_frame) * DT_CTRL < HYUNDAI_DASH_DISENGAGE_BLINK_TIME
|
||||
self._dash_prev_lat_active = CC.latActive
|
||||
lat_or_enabled = CC.enabled or CC.latActive
|
||||
lka_icon = 2 if lat_or_enabled else 3 if disengaging else 1
|
||||
lfa_icon = 2 if lat_or_enabled else 3 if disengaging else 0
|
||||
|
||||
return lka_icon, lfa_icon
|
||||
|
||||
def _get_canfd_scc_lead_state(self, CC, CS, now_nanos):
|
||||
openpilot_lead_visible = bool(getattr(CS, "openpilot_lead_visible", False) or CC.hudControl.leadVisible)
|
||||
openpilot_lead_distance = float(np.clip(getattr(CS, "openpilot_lead_distance", 0.0), 0.0, 204.7))
|
||||
openpilot_lead_rel_speed = float(np.clip(getattr(CS, "openpilot_lead_rel_speed", 0.0), -16.4, 34.7))
|
||||
stock_camera_lead_fresh = now_nanos - getattr(CS, "stock_camera_lead_ts", 0) <= CANFD_CAMERA_LEAD_STALE_NS
|
||||
stock_camera_lead_visible = stock_camera_lead_fresh and getattr(CS, "stock_camera_lead_visible", False)
|
||||
|
||||
if openpilot_lead_visible and openpilot_lead_distance > CANFD_LEAD_MIN_DISTANCE:
|
||||
return True, openpilot_lead_distance, openpilot_lead_rel_speed
|
||||
if stock_camera_lead_visible:
|
||||
lead_distance = float(np.clip(getattr(CS, "stock_camera_lead_distance", 0.0), 0.0, 204.7))
|
||||
lead_rel_speed = float(np.clip(getattr(CS, "stock_camera_lead_rel_speed", 0.0), -16.4, 34.7))
|
||||
return True, lead_distance, lead_rel_speed
|
||||
if openpilot_lead_visible:
|
||||
return True, CANFD_FALLBACK_LEAD_DISTANCE, 0.0
|
||||
|
||||
return False, 0.0, 0.0
|
||||
|
||||
@staticmethod
|
||||
def _get_redneck_button(CS):
|
||||
return {
|
||||
1: Buttons.RES_ACCEL,
|
||||
2: Buttons.SET_DECEL,
|
||||
}.get(getattr(CS, "redneck_send_button", 0), Buttons.NONE)
|
||||
|
||||
def _create_can_redneck_button_messages(self, CS):
|
||||
send_button = self._get_redneck_button(CS)
|
||||
if send_button == Buttons.NONE or (self.frame - self.last_button_frame) * DT_CTRL <= 0.1:
|
||||
return []
|
||||
|
||||
copies_xp = REDNECK_BUTTON_COPIES_TIME_METRIC if CS.is_metric else REDNECK_BUTTON_COPIES_TIME_IMPERIAL
|
||||
copies = int(np.interp(REDNECK_BUTTON_COPIES_TIME, copies_xp, [1, REDNECK_BUTTON_COPIES]))
|
||||
can_sends = [hyundaican.create_clu11(self.packer, self.frame, CS.clu11, send_button, self.CP)] * copies
|
||||
CS.redneck_last_sent_button = getattr(CS, "redneck_send_button", 0)
|
||||
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15:
|
||||
self.last_button_frame = self.frame
|
||||
|
||||
return can_sends
|
||||
|
||||
def _create_canfd_redneck_button_messages(self, CS):
|
||||
send_button = self._get_redneck_button(CS)
|
||||
if send_button == Buttons.NONE or self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS or \
|
||||
(self.frame - self.last_button_frame) * DT_CTRL <= 0.2:
|
||||
return []
|
||||
|
||||
self.redneck_button_frame += 1
|
||||
button_counter_offset = [1, 1, 0, None][self.redneck_button_frame % 4]
|
||||
if button_counter_offset is None:
|
||||
return []
|
||||
|
||||
can_sends = [
|
||||
hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, (CS.buttons_counter + button_counter_offset) % 0xF, send_button)
|
||||
for _ in range(20)
|
||||
]
|
||||
CS.redneck_last_sent_button = getattr(CS, "redneck_send_button", 0)
|
||||
self.last_button_frame = self.frame
|
||||
return can_sends
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
actuators = CC.actuators
|
||||
hud_control = CC.hudControl
|
||||
lka_icon, lfa_icon = self._update_dash_icon_state(CC)
|
||||
|
||||
self.params = CarControllerParams(self.CP, CS.out.vEgoRaw)
|
||||
if not self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
self.params = CarControllerParams(self.CP, CS.out.vEgoRaw)
|
||||
apply_angle = CS.out.steeringAngleDeg
|
||||
|
||||
if self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
v_ego_raw = CS.out.vEgoRaw
|
||||
desired_angle = float(np.clip(actuators.steeringAngleDeg,
|
||||
-self.params.ANGLE_LIMITS.STEER_ANGLE_MAX,
|
||||
self.params.ANGLE_LIMITS.STEER_ANGLE_MAX))
|
||||
apply_angle = apply_steer_angle_limits_vm(desired_angle, self.apply_angle_last, CS.out.vEgoRaw,
|
||||
|
||||
self.angle_filter.update_alpha(get_angle_smoothing_alpha(self.CP, CS.out.vEgo))
|
||||
desired_angle = self.angle_filter.update(desired_angle)
|
||||
|
||||
apply_angle = apply_steer_angle_limits_vm(desired_angle, self.apply_angle_last, v_ego_raw,
|
||||
CS.out.steeringAngleDeg, CC.latActive, self.params, self.VM)
|
||||
|
||||
if CS.out.steeringPressed and abs(CS.out.steeringTorque) > self.params.STEER_THRESHOLD:
|
||||
apply_torque = self.params.ANGLE_MIN_TORQUE_REDUCTION_GAIN
|
||||
elif CC.latActive and CS.out.vEgoRaw < 0.3:
|
||||
apply_torque = self.params.ANGLE_ACTIVE_TORQUE_REDUCTION_GAIN
|
||||
else:
|
||||
apply_torque = self.params.ANGLE_MAX_TORQUE_REDUCTION_GAIN if CC.latActive else 0.0
|
||||
if str(self.CP.carFingerprint) != ANGLE_SAFETY_BASELINE_MODEL:
|
||||
apply_angle = apply_steer_angle_limits_vm(apply_angle or desired_angle, self.apply_angle_last, v_ego_raw,
|
||||
CS.out.steeringAngleDeg, CC.latActive, self.params, self.BASELINE_VM)
|
||||
|
||||
apply_steer_req = CC.latActive and apply_torque > 0.0
|
||||
apply_torque = compute_torque_reduction_gain(CS.out.steeringTorque, v_ego_raw, CC.latActive, self.apply_torque_last)
|
||||
apply_torque = apply_ev9_high_angle_gain_cap(self.CP, apply_torque, CS.out.steeringAngleDeg, CC.latActive)
|
||||
apply_steer_req = CC.latActive and apply_torque != 0.0
|
||||
torque_fault = False
|
||||
|
||||
if apply_angle is None:
|
||||
apply_torque = 0
|
||||
apply_angle = CS.out.steeringAngleDeg
|
||||
apply_steer_req = False
|
||||
|
||||
self.apply_angle_last = apply_angle
|
||||
if not CC.latActive:
|
||||
self.apply_angle_last = float(np.clip(CS.out.steeringAngleDeg,
|
||||
-self.params.ANGLE_LIMITS.STEER_ANGLE_MAX,
|
||||
self.params.ANGLE_LIMITS.STEER_ANGLE_MAX))
|
||||
self.angle_filter.x = self.apply_angle_last
|
||||
else:
|
||||
# steering torque
|
||||
new_torque = int(round(actuators.torque * self.params.STEER_MAX))
|
||||
@@ -107,9 +445,11 @@ class CarController(CarControllerBase):
|
||||
self.apply_torque_last = apply_torque
|
||||
|
||||
# accel + longitudinal
|
||||
accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
|
||||
accel_cmd = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
|
||||
accel = accel_cmd
|
||||
stopping = actuators.longControlState == LongCtrlState.stopping
|
||||
set_speed_in_units = hud_control.setSpeed * (CV.MS_TO_KPH if CS.is_metric else CV.MS_TO_MPH)
|
||||
CS.redneck_last_sent_button = 0
|
||||
|
||||
can_sends = []
|
||||
|
||||
@@ -122,12 +462,49 @@ class CarController(CarControllerBase):
|
||||
# longitudinal messages - stock ECU is still active and these would conflict
|
||||
self.long_active_ecu = self.CP.openpilotLongitudinalControl and not self.ecu_disable_failed
|
||||
|
||||
use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \
|
||||
actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping)
|
||||
if should_reset_ev6_gt_line_longitudinal_tuning(self.CP, actuators.longControlState):
|
||||
self._ioniq_6_long_tuning = reset_ev6_gt_line_longitudinal_tuning(self._ioniq_6_long_tuning, self.CP,
|
||||
actuators.longControlState)
|
||||
elif use_egmp_dynamic_long_tuning and self.frame % 5 == 0:
|
||||
self._ioniq_6_long_tuning = update_ioniq_6_longitudinal_tuning(self._ioniq_6_long_tuning, accel_cmd,
|
||||
CS.out.vEgo, CS.out.aEgo,
|
||||
actuators.longControlState, self.long_active_ecu)
|
||||
use_egmp_smoothed_accel = use_egmp_dynamic_long_tuning and (
|
||||
accel_cmd >= self._ioniq_6_long_tuning.actual_accel or
|
||||
self._ioniq_6_long_tuning.launch_active or
|
||||
self._ioniq_6_long_tuning.stopping
|
||||
)
|
||||
if use_egmp_dynamic_long_tuning:
|
||||
if use_egmp_smoothed_accel:
|
||||
accel = self._ioniq_6_long_tuning.actual_accel
|
||||
stopping = self._ioniq_6_long_tuning.stopping
|
||||
else:
|
||||
accel = float(np.clip(accel_cmd,
|
||||
self.accel_last - IONIQ_6_CANFD_SCC_DECEL_STEP,
|
||||
self.accel_last + IONIQ_6_CANFD_SCC_ACCEL_STEP))
|
||||
self._ioniq_6_long_tuning.desired_accel = accel_cmd
|
||||
self._ioniq_6_long_tuning.actual_accel = accel
|
||||
self._ioniq_6_long_tuning.accel_last = accel
|
||||
self._ioniq_6_long_tuning.jerk_upper = 3.0
|
||||
self._ioniq_6_long_tuning.jerk_lower = 5.0 if CC.enabled else 1.0
|
||||
self._ioniq_6_long_tuning.launch_active = False
|
||||
self._ioniq_6_long_tuning.stopping = stopping
|
||||
self._ioniq_6_long_tuning.long_control_state_last = actuators.longControlState
|
||||
|
||||
if self.CP.carFingerprint == CAR.GENESIS_G90 and self.long_active_ecu:
|
||||
self._genesis_g90_long_tuning = update_genesis_g90_longitudinal_tuning(self._genesis_g90_long_tuning, accel_cmd,
|
||||
CS.out.vEgo, actuators.longControlState,
|
||||
self.long_active_ecu)
|
||||
accel = self._genesis_g90_long_tuning.actual_accel
|
||||
|
||||
# *** common hyundai stuff ***
|
||||
|
||||
# tester present - w/ no response (keeps relevant ECU disabled)
|
||||
if self.frame % 100 == 0 and not (self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC) and self.long_active_ecu:
|
||||
# for longitudinal control, either radar or ADAS driving ECU
|
||||
addr, bus = 0x7d0, self.CAN.ECAN if self.CP.flags & HyundaiFlags.CANFD else 0
|
||||
addr, bus = 0x7d0, self.CAN.ECAN if self.CP.flags & (HyundaiFlags.CANFD | HyundaiFlags.CAN_CANFD_BLENDED) else 0
|
||||
if self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING.value:
|
||||
addr, bus = 0x730, self.CAN.ECAN
|
||||
can_sends.append(make_tester_present_msg(addr, bus, suppress_response=True))
|
||||
@@ -138,11 +515,11 @@ class CarController(CarControllerBase):
|
||||
|
||||
# *** CAN/CAN FD specific ***
|
||||
if self.CP.flags & HyundaiFlags.CANFD:
|
||||
can_sends.extend(self.create_canfd_msgs(apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel,
|
||||
stopping, hud_control, CS, CC))
|
||||
can_sends.extend(self.create_canfd_msgs(now_nanos, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel,
|
||||
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))
|
||||
stopping, hud_control, actuators, CS, CC, lfa_icon))
|
||||
|
||||
new_actuators = actuators.as_builder()
|
||||
if self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
@@ -157,17 +534,24 @@ 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):
|
||||
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):
|
||||
can_sends = []
|
||||
can_canfd_blended = bool(self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED)
|
||||
|
||||
# HUD messages
|
||||
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
|
||||
hud_control)
|
||||
|
||||
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))
|
||||
if can_canfd_blended:
|
||||
can_sends.extend(hyundaican.create_lkas11_can_canfd_blended(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, CS.msg_364))
|
||||
else:
|
||||
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))
|
||||
|
||||
# Button messages
|
||||
if not self.long_active_ecu:
|
||||
@@ -180,66 +564,163 @@ class CarController(CarControllerBase):
|
||||
can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP)] * 25)
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15:
|
||||
self.last_button_frame = self.frame
|
||||
else:
|
||||
can_sends.extend(self._create_can_redneck_button_messages(CS))
|
||||
|
||||
if self.long_active_ecu and can_canfd_blended:
|
||||
can_sends.extend(hyundaican.create_radar_aux_messages(self.packer, self.CAN, self.frame))
|
||||
|
||||
if self.frame % 2 == 0 and self.long_active_ecu:
|
||||
# TODO: unclear if this is needed
|
||||
jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0
|
||||
use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value
|
||||
can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2),
|
||||
hud_control, set_speed_in_units, stopping,
|
||||
CC.cruiseControl.override, use_fca, self.CP))
|
||||
if can_canfd_blended:
|
||||
can_sends.extend(hyundaican.create_acc_commands_can_canfd_blended(self.packer, CC.enabled, accel, jerk,
|
||||
int(self.frame / 2), hud_control,
|
||||
set_speed_in_units, stopping,
|
||||
CC.cruiseControl.override, use_fca, self.CP))
|
||||
else:
|
||||
can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2),
|
||||
hud_control, set_speed_in_units, stopping,
|
||||
CC.cruiseControl.override, use_fca, self.CP))
|
||||
|
||||
# 20 Hz LFA MFA message
|
||||
if self.frame % 5 == 0 and self.CP.flags & HyundaiFlags.SEND_LFA.value:
|
||||
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled))
|
||||
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled, self.frame, self.CP, lfa_icon))
|
||||
|
||||
# 5 Hz ACC options
|
||||
if self.frame % 20 == 0 and self.long_active_ecu:
|
||||
if self.frame % 20 == 0 and self.long_active_ecu and not can_canfd_blended:
|
||||
can_sends.extend(hyundaican.create_acc_opt(self.packer, self.CP))
|
||||
|
||||
# 2 Hz front radar options
|
||||
if self.frame % 50 == 0 and self.long_active_ecu:
|
||||
if self.frame % 50 == 0 and self.long_active_ecu and not can_canfd_blended:
|
||||
can_sends.append(hyundaican.create_frt_radar_opt(self.packer))
|
||||
|
||||
return can_sends
|
||||
|
||||
def create_canfd_msgs(self, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel, stopping, hud_control, CS, CC):
|
||||
def create_canfd_msgs(self, now_nanos, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel, stopping,
|
||||
hud_control, CS, CC, starpilot_toggles, lka_icon, lfa_icon):
|
||||
can_sends = []
|
||||
|
||||
lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING
|
||||
lka_steering_long = lka_steering and self.long_active_ecu
|
||||
ccnc_non_hda2 = self.CP.flags & HyundaiFlags.CCNC and not lka_steering
|
||||
use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \
|
||||
CC.actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping)
|
||||
use_egmp_smoothed_accel = use_egmp_dynamic_long_tuning and (
|
||||
CC.actuators.accel >= self._ioniq_6_long_tuning.actual_accel or
|
||||
self._ioniq_6_long_tuning.launch_active or
|
||||
self._ioniq_6_long_tuning.stopping
|
||||
)
|
||||
|
||||
# steering control
|
||||
preserve_stock_lkas = bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING) and not self.long_active_ecu
|
||||
steering_msg_active = apply_steer_req
|
||||
if self.CP.carFingerprint == CAR.KIA_EV9 and self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
# EV9 faults if the angle-steering status drops inactive during torque limiting.
|
||||
# Hold the angle status active while lateral is active; VM/safety limits handle actuation.
|
||||
steering_msg_active = CC.latActive
|
||||
|
||||
can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN, CC.enabled,
|
||||
apply_steer_req, apply_torque, apply_angle))
|
||||
steering_msg_active, apply_torque, apply_angle,
|
||||
CS.stock_lfa_msg,
|
||||
CS.stock_lkas_msg if preserve_stock_lkas else None,
|
||||
lka_icon=lka_icon))
|
||||
|
||||
# prevent LFA from activating on LKA steering cars by sending "no lane lines detected" to ADAS ECU
|
||||
if self.frame % 5 == 0 and lka_steering:
|
||||
suppress_lfa = bool(lka_steering)
|
||||
if self.frame % 5 == 0 and suppress_lfa:
|
||||
can_sends.append(hyundaicanfd.create_suppress_lfa(self.packer, self.CAN, CS.lfa_block_msg,
|
||||
self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT))
|
||||
|
||||
# LFA and HDA icons
|
||||
if self.frame % 5 == 0 and (not lka_steering or lka_steering_long):
|
||||
can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, self.CAN, CC.enabled))
|
||||
if ccnc_non_hda2:
|
||||
can_sends.extend(hyundaicanfd.create_ccnc(self.packer, self.CAN, self.long_active_ecu, CC.enabled, CC.hudControl,
|
||||
CC.leftBlinker, CC.rightBlinker, CS.msg_161, CS.msg_162, CS.msg_1b5,
|
||||
CS.is_metric, CS.out, CS.out.cruiseState.available, lfa_icon))
|
||||
else:
|
||||
can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, self.CAN, CC.enabled, CS.stock_lfahda_cluster_msg,
|
||||
lfa_icon=lfa_icon))
|
||||
|
||||
# blinkers
|
||||
if lka_steering and self.CP.flags & HyundaiFlags.ENABLE_BLINKERS:
|
||||
can_sends.extend(hyundaicanfd.create_spas_messages(self.packer, self.CAN, CC.leftBlinker, CC.rightBlinker))
|
||||
|
||||
lane_change_ui_side = None
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
if CC.leftBlinker and not CC.rightBlinker:
|
||||
lane_change_ui_side = "left"
|
||||
elif CC.rightBlinker and not CC.leftBlinker:
|
||||
lane_change_ui_side = "right"
|
||||
|
||||
if lane_change_ui_side != self._ioniq_6_lane_change_ui_side:
|
||||
self._ioniq_6_lane_change_ui_side = lane_change_ui_side
|
||||
self._ioniq_6_lane_change_ui_frames = 0
|
||||
|
||||
if lane_change_ui_side is None or not self.long_active_ecu:
|
||||
self._ioniq_6_lane_change_ui_frames = 0
|
||||
else:
|
||||
# The stock Ioniq 6 lane-change animation stops when the ADAS ECU is disabled,
|
||||
# so replay the captured ECAN cluster frames ourselves while OP long is active.
|
||||
can_sends.extend(hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(self.CAN,
|
||||
self._ioniq_6_lane_change_ui_frames,
|
||||
lane_change_ui_side))
|
||||
self._ioniq_6_lane_change_ui_frames += 1
|
||||
|
||||
if self.long_active_ecu:
|
||||
if lka_steering:
|
||||
can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame))
|
||||
else:
|
||||
# Ioniq 5/6: front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat
|
||||
# and stops publishing object tracks when it disappears. Spoof it periodically on
|
||||
# PT bus so the radar keeps tracking.
|
||||
if self.CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR and self.frame % 4 == 0:
|
||||
can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // 4, CS.out.brakePressed,
|
||||
CS.out.gasPressed, self.CP.carFingerprint))
|
||||
elif not ccnc_non_hda2:
|
||||
can_sends.extend(hyundaicanfd.create_fca_warning_light(self.packer, self.CAN, self.frame))
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and self.frame % 5 == 0:
|
||||
rear_stale = now_nanos - CS.blindspots_rear_corners_ts > CANFD_BLINDSPOT_STATUS_STALE_NS
|
||||
front_stale = now_nanos - CS.blindspots_front_corner_1_ts > CANFD_BLINDSPOT_STATUS_STALE_NS
|
||||
if CS.blindspots_rear_corners_ts > 0 and CS.blindspots_front_corner_1_ts > 0 and rear_stale and front_stale:
|
||||
can_sends.extend(hyundaicanfd.create_blindspot_status_messages(self.packer, self.CAN,
|
||||
CS.blindspots_rear_corners,
|
||||
CS.blindspots_front_corner_1,
|
||||
CS.left_blindspot_from_radar,
|
||||
CS.right_blindspot_from_radar,
|
||||
CC.leftBlinker,
|
||||
CC.rightBlinker))
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and lane_change_ui_side is None:
|
||||
can_sends.extend(hyundaicanfd.create_ioniq_6_cluster_blindspot_messages(self.CAN, self.frame,
|
||||
CS.left_blindspot_from_radar,
|
||||
CS.right_blindspot_from_radar,
|
||||
CC.leftBlinker,
|
||||
CC.rightBlinker))
|
||||
if self.frame % 2 == 0:
|
||||
lead_visible, lead_distance, lead_rel_speed = self._get_canfd_scc_lead_state(CC, CS, now_nanos)
|
||||
acc_kwargs = {
|
||||
"main_mode_acc": int(CS.out.cruiseState.available),
|
||||
"direct_accel": True,
|
||||
"jerk_lower": 5.0,
|
||||
"jerk_upper": 3.0 if CC.actuators.longControlState == LongCtrlState.pid else 1.0,
|
||||
"lead_distance": lead_distance,
|
||||
"lead_rel_speed": lead_rel_speed,
|
||||
"lead_visible": lead_visible,
|
||||
}
|
||||
if use_egmp_dynamic_long_tuning:
|
||||
if use_egmp_smoothed_accel:
|
||||
acc_kwargs["jerk_lower"] = self._ioniq_6_long_tuning.jerk_lower
|
||||
acc_kwargs["jerk_upper"] = self._ioniq_6_long_tuning.jerk_upper
|
||||
can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override,
|
||||
set_speed_in_units, hud_control))
|
||||
set_speed_in_units, hud_control, cruise_info=CS.cruise_info if ccnc_non_hda2 else None,
|
||||
**acc_kwargs))
|
||||
self.accel_last = accel
|
||||
else:
|
||||
# button presses
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL > 0.25:
|
||||
# cruise cancel
|
||||
if CC.cruiseControl.cancel:
|
||||
# cruise cancel - suppress when stock ACC is the fallback (ECU disable failed),
|
||||
# so openpilot doesn't fight/cancel the user's stock cruise
|
||||
if CC.cruiseControl.cancel and not self.ecu_disable_failed:
|
||||
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
|
||||
can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info))
|
||||
self.last_button_frame = self.frame
|
||||
@@ -257,5 +738,7 @@ class CarController(CarControllerBase):
|
||||
for _ in range(20):
|
||||
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.RES_ACCEL))
|
||||
self.last_button_frame = self.frame
|
||||
else:
|
||||
can_sends.extend(self._create_canfd_redneck_button_messages(CS))
|
||||
|
||||
return can_sends
|
||||
|
||||
@@ -7,7 +7,8 @@ from opendbc.can import CANDefine, CANParser
|
||||
from opendbc.car import Bus, create_button_events, structs
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiStarPilotFlags, CAR, DBC, Buttons, CarControllerParams
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, CAR, DBC, Buttons, CarControllerParams, \
|
||||
hyundai_cancel_button_enables_cruise, ALT_BUS_LDA_BUTTON_CARS, ALT_BUS_LDA_BUTTON_SWL_STAT_CARS
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
@@ -21,6 +22,19 @@ ENABLE_BUTTONS = (Buttons.RES_ACCEL, Buttons.SET_DECEL, Buttons.CANCEL)
|
||||
BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: ButtonType.decelCruise,
|
||||
Buttons.GAP_DIST: ButtonType.gapAdjustCruise, Buttons.CANCEL: ButtonType.cancel}
|
||||
|
||||
IONIQ_6_BLINDSPOT_RIGHT_MASK = 0x08
|
||||
IONIQ_6_BLINDSPOT_LEFT_MASK = 0x10
|
||||
CANFD_CAMERA_LEAD_MIN_DISTANCE = 0.1
|
||||
ALT_BUS_LDA_BUTTON_BURST_DEBOUNCE_NS = int(1.3e9)
|
||||
|
||||
|
||||
def get_non_scc_cruise_signals(CP) -> tuple[str, str, str, str, str, str]:
|
||||
if CP.flags & HyundaiFlags.EV:
|
||||
return "LABEL11", "CC_React", "EMS12", "ACC_ACT", "E_EMS11", "Cruise_Limit_Target"
|
||||
if CP.flags & HyundaiFlags.HYBRID:
|
||||
return "E_CRUISE_CONTROL", "CRUISE_LAMP_M", "E_CRUISE_CONTROL", "CRUISE_LAMP_S", "ELECT_GEAR", "SLC_SET_SPEED"
|
||||
return "EMS16", "CRUISE_LAMP_M", "EMS16", "CRUISE_LAMP_S", "LVR12", "CF_Lvr_CruiseSet"
|
||||
|
||||
|
||||
def calculate_canfd_speed_limit(CP, FPCP, cp, cp_cam, speed_factor):
|
||||
if not (FPCP.flags & HyundaiStarPilotFlags.SPEED_LIMIT_AVAILABLE):
|
||||
@@ -34,6 +48,19 @@ def calculate_canfd_speed_limit(CP, FPCP, cp, cp_cam, speed_factor):
|
||||
return 0.0
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def decode_canfd_camera_lead(distance: float, rel_speed: float) -> tuple[bool, float, float]:
|
||||
lead_distance = float(distance)
|
||||
lead_visible = lead_distance > CANFD_CAMERA_LEAD_MIN_DISTANCE
|
||||
if not lead_visible:
|
||||
return False, 0.0, 0.0
|
||||
return True, lead_distance, float(rel_speed)
|
||||
|
||||
|
||||
class CarState(CarStateBase):
|
||||
@staticmethod
|
||||
def get_canfd_blinker_sig_names(car_fingerprint, use_alt_lamp: bool) -> tuple[str, str]:
|
||||
@@ -48,9 +75,22 @@ class CarState(CarStateBase):
|
||||
self.cruise_buttons: deque = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES)
|
||||
self.main_buttons: deque = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES)
|
||||
self.lda_button = 0
|
||||
self.sonata_hybrid_lkas_source = None
|
||||
self.sonata_hybrid_lkas_sources = {
|
||||
"bcm": 0,
|
||||
"clu13": 0,
|
||||
"swl_stat": 0,
|
||||
}
|
||||
self.lda_button_raw = 0
|
||||
self.lda_button_raw_initialized = False
|
||||
self.lda_button_last_raw_rise_ts_nanos = 0
|
||||
self.left_paddle = 0
|
||||
self.mode_button = 0
|
||||
self.custom_button = 0
|
||||
self.cancel_button_enable_in_progress = False
|
||||
self.cruise_buttons_msg = {}
|
||||
self.redneck_send_button = Buttons.NONE
|
||||
self.redneck_v_target = 0
|
||||
|
||||
self.gear_msg_canfd = "ACCELERATOR" if CP.flags & HyundaiFlags.EV else \
|
||||
"GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \
|
||||
@@ -78,6 +118,24 @@ class CarState(CarStateBase):
|
||||
self.buttons_counter = 0
|
||||
|
||||
self.cruise_info = {}
|
||||
self.msg_161 = {}
|
||||
self.msg_162 = {}
|
||||
self.msg_1b5 = {}
|
||||
self.msg_364 = {}
|
||||
self.stock_lkas_msg = {}
|
||||
self.stock_lfa_msg = {}
|
||||
self.stock_lfahda_cluster_msg = {}
|
||||
self.stock_camera_lead_visible = False
|
||||
self.stock_camera_lead_distance = 0.0
|
||||
self.stock_camera_lead_rel_speed = 0.0
|
||||
self.stock_camera_lead_ts = 0
|
||||
self.stock_blinker_stalks_ts = 0
|
||||
self.blindspots_rear_corners = {}
|
||||
self.blindspots_front_corner_1 = {}
|
||||
self.blindspots_rear_corners_ts = 0
|
||||
self.blindspots_front_corner_1_ts = 0
|
||||
self.left_blindspot_from_radar = False
|
||||
self.right_blindspot_from_radar = False
|
||||
|
||||
# On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz
|
||||
self.cluster_speed = 0
|
||||
@@ -91,9 +149,109 @@ class CarState(CarStateBase):
|
||||
# Main button also can trigger an engagement on these cars
|
||||
return any(btn in ENABLE_BUTTONS for btn in self.cruise_buttons) or any(self.main_buttons)
|
||||
|
||||
def create_cruise_button_events(self, cur_button: int, prev_button: int) -> list[structs.CarState.ButtonEvent]:
|
||||
if cur_button != prev_button and prev_button != Buttons.CANCEL and cur_button == Buttons.CANCEL:
|
||||
self.cancel_button_enable_in_progress = (
|
||||
self.CP.openpilotLongitudinalControl and
|
||||
hyundai_cancel_button_enables_cruise(self.CP.carFingerprint) and
|
||||
not self.out.cruiseState.enabled
|
||||
)
|
||||
|
||||
buttons_dict = BUTTONS_DICT
|
||||
if self.cancel_button_enable_in_progress:
|
||||
buttons_dict = BUTTONS_DICT | {Buttons.CANCEL: ButtonType.accelCruise}
|
||||
|
||||
events = create_button_events(cur_button, prev_button, buttons_dict)
|
||||
|
||||
if cur_button != Buttons.CANCEL:
|
||||
self.cancel_button_enable_in_progress = False
|
||||
|
||||
return events
|
||||
|
||||
def update_button_enable(self, buttonEvents: list[structs.CarState.ButtonEvent]):
|
||||
if super().update_button_enable(buttonEvents):
|
||||
return True
|
||||
|
||||
if not self.CP.pcmCruise and hyundai_cancel_button_enables_cruise(self.CP.carFingerprint):
|
||||
for b in buttonEvents:
|
||||
# Some Palisade 2023 routes still surface the pause/resume interaction as a
|
||||
# plain cancel event even though stock ACC engages on the release edge.
|
||||
if b.type == ButtonType.cancel and not b.pressed and not self.out.cruiseState.enabled:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_alt_bus_lda_button_raw_state(self, cp_source: CANParser) -> tuple[int, int]:
|
||||
if self.CP.carFingerprint in ALT_BUS_LDA_BUTTON_SWL_STAT_CARS:
|
||||
return int(cp_source.vl["CLU13"]["CF_Clu_SWL_Stat"] == 4), cp_source.ts_nanos["CLU13"]["CF_Clu_SWL_Stat"]
|
||||
return int(cp_source.vl["CLU13"]["CF_Clu_LdwsLkasSW"]), cp_source.ts_nanos["CLU13"]["CF_Clu_LdwsLkasSW"]
|
||||
|
||||
def create_alt_bus_lda_button_events(self, cp_source: CANParser) -> list[structs.CarState.ButtonEvent]:
|
||||
raw_lda_button, raw_lda_button_ts_nanos = self.get_alt_bus_lda_button_raw_state(cp_source)
|
||||
button_events: list[structs.CarState.ButtonEvent] = []
|
||||
|
||||
if not self.lda_button_raw_initialized:
|
||||
self.lda_button_raw_initialized = True
|
||||
self.lda_button_raw = raw_lda_button
|
||||
return button_events
|
||||
|
||||
# Some alt-bus LKAS button layouts pulse several times per physical press burst.
|
||||
# Collapse each burst into a single synthetic press/release pair.
|
||||
if raw_lda_button and not self.lda_button_raw:
|
||||
if self.lda_button_last_raw_rise_ts_nanos == 0 or \
|
||||
raw_lda_button_ts_nanos - self.lda_button_last_raw_rise_ts_nanos > ALT_BUS_LDA_BUTTON_BURST_DEBOUNCE_NS:
|
||||
button_events = [
|
||||
structs.CarState.ButtonEvent(pressed=True, type=ButtonType.lkas),
|
||||
structs.CarState.ButtonEvent(pressed=False, type=ButtonType.lkas),
|
||||
]
|
||||
self.lda_button_last_raw_rise_ts_nanos = raw_lda_button_ts_nanos
|
||||
|
||||
self.lda_button_raw = raw_lda_button
|
||||
return button_events
|
||||
|
||||
def create_lkas_button_events(self, cp: CANParser, prev_lda_button: int) -> list[structs.CarState.ButtonEvent]:
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID:
|
||||
self.lda_button = self.get_sonata_hybrid_lkas_button_state(cp)
|
||||
# Some classic HKG platforms publish the LKAS button on the cluster bus instead of BCM_PO_11.
|
||||
elif cp.ts_nanos["CLU13"]["CF_Clu_LdwsLkasSW"] > 0:
|
||||
self.lda_button = int(cp.vl["CLU13"]["CF_Clu_LdwsLkasSW"])
|
||||
elif cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0:
|
||||
self.lda_button = int(cp.vl["BCM_PO_11"]["LDA_BTN"])
|
||||
else:
|
||||
self.lda_button = 0
|
||||
|
||||
return create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas})
|
||||
|
||||
def get_sonata_hybrid_lkas_button_state(self, cp: CANParser) -> int:
|
||||
source_states = {
|
||||
"bcm": int(cp.vl["BCM_PO_11"]["LDA_BTN"]) if cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0 else 0,
|
||||
"clu13": int(cp.vl["CLU13"]["CF_Clu_LdwsLkasSW"]) if cp.ts_nanos["CLU13"]["CF_Clu_LdwsLkasSW"] > 0 else 0,
|
||||
"swl_stat": int(cp.vl["CLU13"]["CF_Clu_SWL_Stat"] == 4) if cp.ts_nanos["CLU13"]["CF_Clu_SWL_Stat"] > 0 else 0,
|
||||
}
|
||||
|
||||
changed_sources = [source for source, state in source_states.items() if state != self.sonata_hybrid_lkas_sources[source]]
|
||||
active_sources = [source for source, state in source_states.items() if state]
|
||||
|
||||
selected_source = None
|
||||
if self.sonata_hybrid_lkas_source in changed_sources:
|
||||
selected_source = self.sonata_hybrid_lkas_source
|
||||
elif active_sources:
|
||||
selected_source = active_sources[0]
|
||||
elif changed_sources:
|
||||
selected_source = changed_sources[0]
|
||||
elif self.sonata_hybrid_lkas_source is not None:
|
||||
selected_source = self.sonata_hybrid_lkas_source
|
||||
|
||||
self.sonata_hybrid_lkas_sources.update(source_states)
|
||||
if selected_source is not None:
|
||||
self.sonata_hybrid_lkas_source = selected_source
|
||||
return source_states[selected_source]
|
||||
return 0
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
cp_alt = can_parsers.get(Bus.alt)
|
||||
|
||||
if self.CP.flags & HyundaiFlags.CANFD:
|
||||
return self.update_canfd(can_parsers)
|
||||
@@ -147,22 +305,22 @@ class CarState(CarStateBase):
|
||||
ret.cruiseState.standstill = False
|
||||
ret.cruiseState.nonAdaptive = False
|
||||
elif no_scc:
|
||||
cruise_enabled = cp.vl["TCS13"]["ACC_REQ"] == 1
|
||||
cruise_set_speed = cp.vl["LVR12"]["CF_Lvr_CruiseSet"]
|
||||
|
||||
# Regular-cruise Forte trims don't publish SCC11/SCC12; use the stock cruise request and set speed.
|
||||
ret.cruiseState.available = cruise_enabled or cp.vl["TCS13"]["ACCEnable"] == 0
|
||||
ret.cruiseState.enabled = cruise_enabled
|
||||
cruise_available_msg, cruise_available_sig, cruise_enabled_msg, cruise_enabled_sig, cruise_speed_msg, cruise_speed_sig = get_non_scc_cruise_signals(self.CP)
|
||||
ret.cruiseState.available = cp.vl[cruise_available_msg][cruise_available_sig] != 0
|
||||
ret.cruiseState.enabled = cp.vl[cruise_enabled_msg][cruise_enabled_sig] != 0
|
||||
ret.cruiseState.standstill = False
|
||||
ret.cruiseState.nonAdaptive = False
|
||||
if 0 < cruise_set_speed < 255:
|
||||
ret.cruiseState.speed = cruise_set_speed * speed_conv
|
||||
ret.cruiseState.speed = cp.vl[cruise_speed_msg][cruise_speed_sig] * speed_conv
|
||||
else:
|
||||
ret.cruiseState.available = cp_cruise.vl["SCC11"]["MainMode_ACC"] == 1
|
||||
scc_msg = "SCC12" if self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED else "SCC11"
|
||||
ret.cruiseState.available = cp_cruise.vl[scc_msg]["MainMode_ACC"] == 1
|
||||
ret.cruiseState.enabled = cp_cruise.vl["SCC12"]["ACCMode"] != 0
|
||||
ret.cruiseState.standstill = cp_cruise.vl["SCC11"]["SCCInfoDisplay"] == 4.
|
||||
ret.cruiseState.nonAdaptive = cp_cruise.vl["SCC11"]["SCCInfoDisplay"] == 2. # Shows 'Cruise Control' on dash
|
||||
ret.cruiseState.speed = cp_cruise.vl["SCC11"]["VSetDis"] * speed_conv
|
||||
ret.cruiseState.standstill = cp_cruise.vl[scc_msg]["SCCInfoDisplay"] == 4.
|
||||
ret.cruiseState.nonAdaptive = cp_cruise.vl[scc_msg]["SCCInfoDisplay"] == 2. # Shows 'Cruise Control' on dash
|
||||
ret.cruiseState.speed = cp_cruise.vl[scc_msg]["VSetDis"] * speed_conv
|
||||
|
||||
if self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED:
|
||||
self.msg_364 = copy.copy(cp_cam.vl["ALERTS_364"])
|
||||
|
||||
# TODO: Find brake pressure
|
||||
ret.brake = 0
|
||||
@@ -198,14 +356,23 @@ class CarState(CarStateBase):
|
||||
|
||||
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear))
|
||||
|
||||
if not self.CP.openpilotLongitudinalControl or self.CP.flags & HyundaiFlags.CAMERA_SCC:
|
||||
aeb_src = "FCA11" if self.CP.flags & HyundaiFlags.USE_FCA.value else "SCC12"
|
||||
aeb_sig = "FCA_CmdAct" if self.CP.flags & HyundaiFlags.USE_FCA.value else "AEB_CmdAct"
|
||||
aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0
|
||||
scc_warning = cp_cruise.vl["SCC12"]["TakeOverReq"] == 1 # sometimes only SCC system shows an FCW
|
||||
aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src][aeb_sig] != 0
|
||||
ret.stockFcw = (aeb_warning or scc_warning) and not aeb_braking
|
||||
ret.stockAeb = aeb_warning and aeb_braking
|
||||
if (not self.CP.openpilotLongitudinalControl or self.CP.flags & HyundaiFlags.CAMERA_SCC) and \
|
||||
not (self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED):
|
||||
if no_scc:
|
||||
if not (self.CP.flags & HyundaiFlags.NON_SCC_NO_FCA):
|
||||
cp_fca = cp if self.CP.flags & HyundaiFlags.NON_SCC_RADAR_FCA else cp_cam
|
||||
aeb_warning = cp_fca.vl["FCA11"]["CF_VSM_Warn"] != 0
|
||||
aeb_braking = cp_fca.vl["FCA11"]["CF_VSM_DecCmdAct"] != 0 or cp_fca.vl["FCA11"]["FCA_CmdAct"] != 0
|
||||
ret.stockFcw = aeb_warning and not aeb_braking
|
||||
ret.stockAeb = aeb_warning and aeb_braking
|
||||
else:
|
||||
aeb_src = "FCA11" if self.CP.flags & HyundaiFlags.USE_FCA.value else "SCC12"
|
||||
aeb_sig = "FCA_CmdAct" if self.CP.flags & HyundaiFlags.USE_FCA.value else "AEB_CmdAct"
|
||||
aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0
|
||||
scc_warning = cp_cruise.vl["SCC12"]["TakeOverReq"] == 1
|
||||
aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src][aeb_sig] != 0
|
||||
ret.stockFcw = (aeb_warning or scc_warning) and not aeb_braking
|
||||
ret.stockAeb = aeb_warning and aeb_braking
|
||||
|
||||
if self.CP.enableBsm:
|
||||
ret.leftBlindspot = cp.vl["LCA11"]["CF_Lca_IndLeft"] != 0
|
||||
@@ -218,14 +385,17 @@ class CarState(CarStateBase):
|
||||
prev_cruise_buttons = self.cruise_buttons[-1]
|
||||
prev_main_buttons = self.main_buttons[-1]
|
||||
prev_lda_button = self.lda_button
|
||||
lkas_button_events = []
|
||||
self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"])
|
||||
self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"])
|
||||
if self.CP.flags & HyundaiFlags.HAS_LDA_BUTTON:
|
||||
self.lda_button = cp.vl["BCM_PO_11"]["LDA_BTN"]
|
||||
if self.CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS and cp_alt is not None and self.get_alt_bus_lda_button_raw_state(cp_alt)[1] > 0:
|
||||
lkas_button_events = self.create_alt_bus_lda_button_events(cp_alt)
|
||||
else:
|
||||
lkas_button_events = self.create_lkas_button_events(cp, prev_lda_button)
|
||||
|
||||
ret.buttonEvents = [*create_button_events(self.cruise_buttons[-1], prev_cruise_buttons, BUTTONS_DICT),
|
||||
ret.buttonEvents = [*self.create_cruise_button_events(self.cruise_buttons[-1], prev_cruise_buttons),
|
||||
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
|
||||
*create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas})]
|
||||
*lkas_button_events]
|
||||
|
||||
ret.blockPcmEnable = not self.recent_button_interaction()
|
||||
|
||||
@@ -279,17 +449,41 @@ class CarState(CarStateBase):
|
||||
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5)
|
||||
ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0
|
||||
|
||||
left_blinker_sig, right_blinker_sig = self.get_canfd_blinker_sig_names(self.CP.carFingerprint,
|
||||
cp.vl["BLINKERS"]["USE_ALT_LAMP"] == 1)
|
||||
ccnc_non_hda2 = self.CP.flags & HyundaiFlags.CCNC and not self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING
|
||||
if ccnc_non_hda2:
|
||||
self.msg_161 = copy.copy(cp_cam.vl["CCNC_0x161"])
|
||||
self.msg_162 = copy.copy(cp_cam.vl["CCNC_0x162"])
|
||||
self.msg_1b5 = copy.copy(cp_cam.vl["FR_CMR_03_50ms"])
|
||||
cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp
|
||||
self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"])
|
||||
|
||||
use_alt_lamp = cp.vl["BLINKERS"]["USE_ALT_LAMP"] == 1 or bool(self.CP.flags & HyundaiFlags.CCNC)
|
||||
left_blinker_sig, right_blinker_sig = self.get_canfd_blinker_sig_names(self.CP.carFingerprint, use_alt_lamp)
|
||||
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"][left_blinker_sig],
|
||||
cp.vl["BLINKERS"][right_blinker_sig])
|
||||
self.left_blindspot_from_radar = False
|
||||
self.right_blindspot_from_radar = False
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
self.left_blindspot_from_radar, self.right_blindspot_from_radar = decode_ioniq_6_blindspot_radar_state(
|
||||
cp.vl["BLINDSPOTS_FRONT_CORNER_2"]["SIDE_DETECT_STATE"])
|
||||
if self.CP.enableBsm:
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
ret.leftBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["LEFT_MB"] != 0
|
||||
ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["MORE_LEFT_PROB"] != 0
|
||||
ret.leftBlindspot = (bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_LtIndSta"]) or
|
||||
self.left_blindspot_from_radar)
|
||||
ret.rightBlindspot = (bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_RtIndSta"]) or
|
||||
self.right_blindspot_from_radar)
|
||||
elif self.CP.flags & HyundaiFlags.CCNC:
|
||||
ret.leftBlindspot = bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_LtIndSta"])
|
||||
ret.rightBlindspot = bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_RtIndSta"])
|
||||
else:
|
||||
ret.leftBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FL_INDICATOR"] != 0
|
||||
ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FR_INDICATOR"] != 0
|
||||
ret.leftBlindspot = bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_LtIndSta"])
|
||||
ret.rightBlindspot = bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_RtIndSta"])
|
||||
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
self.blindspots_rear_corners = copy.copy(cp.vl["BLINDSPOTS_REAR_CORNERS"])
|
||||
self.blindspots_front_corner_1 = copy.copy(cp.vl["BLINDSPOTS_FRONT_CORNER_1"])
|
||||
self.blindspots_rear_corners_ts = cp.ts_nanos["BLINDSPOTS_REAR_CORNERS"]["CHECKSUM"]
|
||||
self.blindspots_front_corner_1_ts = cp.ts_nanos["BLINDSPOTS_FRONT_CORNER_1"]["CHECKSUM"]
|
||||
|
||||
# cruise state
|
||||
# CAN FD cars enable on main button press, set available if no TCS faults preventing engagement
|
||||
@@ -312,7 +506,6 @@ class CarState(CarStateBase):
|
||||
# TODO: find this message on ICE & HYBRID cars + cruise control signals (if exists)
|
||||
if self.CP.flags & HyundaiFlags.EV:
|
||||
ret.cruiseState.nonAdaptive = cp.vl["MANUAL_SPEED_LIMIT_ASSIST"]["MSLA_ENABLED"] == 1
|
||||
|
||||
prev_cruise_buttons = self.cruise_buttons[-1]
|
||||
prev_main_buttons = self.main_buttons[-1]
|
||||
prev_lda_button = self.lda_button
|
||||
@@ -322,15 +515,32 @@ class CarState(CarStateBase):
|
||||
self.lda_button = cp.vl[self.cruise_btns_msg_canfd]["LDA_BTN"]
|
||||
self.left_paddle = 0
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
self.cruise_buttons_msg = copy.copy(cp.vl["CRUISE_BUTTONS"])
|
||||
self.left_paddle = cp.vl["CRUISE_BUTTONS"]["LEFT_PADDLE"]
|
||||
self.buttons_counter = cp.vl[self.cruise_btns_msg_canfd]["COUNTER"]
|
||||
ret.accFaulted = cp.vl["TCS"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED
|
||||
|
||||
if self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
|
||||
lkas_msg = "LKAS_ALT" if self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT else "LKAS"
|
||||
self.lfa_block_msg = copy.copy(cp_cam.vl["CAM_0x362"] if self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT
|
||||
else cp_cam.vl["CAM_0x2a4"])
|
||||
if cp_cam.ts_nanos[lkas_msg]["CHECKSUM"] > 0:
|
||||
self.stock_lkas_msg = copy.copy(cp_cam.vl[lkas_msg])
|
||||
lead_cp = cp if self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING else cp_cam
|
||||
if lead_cp.ts_nanos["FR_CMR_03_50ms"]["FR_CMR_Crc3Val"] > 0:
|
||||
self.stock_camera_lead_ts = lead_cp.ts_nanos["FR_CMR_03_50ms"]["FR_CMR_Crc3Val"]
|
||||
self.stock_camera_lead_visible, self.stock_camera_lead_distance, self.stock_camera_lead_rel_speed = decode_canfd_camera_lead(
|
||||
lead_cp.vl["FR_CMR_03_50ms"]["Longitudinal_Distance"],
|
||||
lead_cp.vl["FR_CMR_03_50ms"]["Relative_Velocity"],
|
||||
)
|
||||
if cp.ts_nanos["LFA"]["CHECKSUM"] > 0:
|
||||
self.stock_lfa_msg = copy.copy(cp.vl["LFA"])
|
||||
if cp.ts_nanos["LFAHDA_CLUSTER"]["CHECKSUM"] > 0:
|
||||
self.stock_lfahda_cluster_msg = copy.copy(cp.vl["LFAHDA_CLUSTER"])
|
||||
if cp.ts_nanos["BLINKER_STALKS"]["CHECKSUM_MAYBE"] > 0:
|
||||
self.stock_blinker_stalks_ts = cp.ts_nanos["BLINKER_STALKS"]["CHECKSUM_MAYBE"]
|
||||
|
||||
ret.buttonEvents = [*create_button_events(self.cruise_buttons[-1], prev_cruise_buttons, BUTTONS_DICT),
|
||||
ret.buttonEvents = [*self.create_cruise_button_events(self.cruise_buttons[-1], prev_cruise_buttons),
|
||||
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
|
||||
*create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas}),
|
||||
*create_button_events(self.left_paddle, prev_left_paddle, {1: ButtonType.altButton2})]
|
||||
@@ -350,6 +560,10 @@ class CarState(CarStateBase):
|
||||
fp_ret.modePressed = bool(self.mode_button)
|
||||
fp_ret.customPressed = bool(self.custom_button)
|
||||
|
||||
# ADAS camera dashboard stop-sign signal (CANFD HKG only). Registered as optional
|
||||
# (freq=0); cars without it never publish, so the read returns 0 and the field stays 0.
|
||||
fp_ret.dashboardStopSign = 1 if bool(cp_cam.vl["ADAS_0x380"]["STOP_SIGN"]) else 0
|
||||
|
||||
return ret, fp_ret
|
||||
|
||||
def get_can_parsers_canfd(self, CP):
|
||||
@@ -363,11 +577,26 @@ class CarState(CarStateBase):
|
||||
]
|
||||
if CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
|
||||
msgs.append(("FR_CMR_02_100ms", 10))
|
||||
msgs.append(("FR_CMR_03_50ms", 0))
|
||||
cam_msgs.append(("LKAS_ALT" if CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT else "LKAS", 0))
|
||||
else:
|
||||
cam_msgs.append(("FR_CMR_02_100ms", 0)) # optional: not all non-LKA CANFD cars have this on CAM bus
|
||||
cam_msgs.append(("FR_CMR_03_50ms", 0)) # optional: camera lead/cipv data is not present on every CAN-FD trim
|
||||
if CP.flags & HyundaiFlags.CCNC:
|
||||
cam_msgs += [
|
||||
("CCNC_0x161", 0),
|
||||
("CCNC_0x162", 0),
|
||||
]
|
||||
msgs += [
|
||||
("LFA", 0), # optional: may stop once OP takes over, but preserve stock UI fields when present
|
||||
("LFAHDA_CLUSTER", 0), # optional: carries cluster icon state on some variants
|
||||
("BLINKER_STALKS", 0), # optional: some trims publish live stalk/light state on ECAN during turn camera events
|
||||
]
|
||||
if CP.flags & HyundaiFlags.EV:
|
||||
msgs.append(("DRIVE_MODE_EV", 0)) # optional: not all CAN-FD EV variants publish drive mode
|
||||
msgs.append(("MANUAL_SPEED_LIMIT_ASSIST", 0)) # optional: used for non-adaptive cruise state and Ioniq 6 i-Pedal latch detection
|
||||
msgs.append(("STEERING_WHEEL_MEDIA_BUTTONS", 0)) # optional: absent or slower on some CAN-FD variants
|
||||
cam_msgs.append(("ADAS_0x380", 0)) # optional: dashboard stop-sign signal, only on ADAS-equipped HKG CANFD
|
||||
return {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], msgs, CanBus(CP).ECAN),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_msgs, CanBus(CP).CAM),
|
||||
@@ -377,7 +606,17 @@ class CarState(CarStateBase):
|
||||
if CP.flags & HyundaiFlags.CANFD:
|
||||
return self.get_can_parsers_canfd(CP)
|
||||
|
||||
return {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 0),
|
||||
msgs = [
|
||||
("BCM_PO_11", 0),
|
||||
("CLU13", 0),
|
||||
]
|
||||
if CP.flags & HyundaiFlags.NON_SCC and not (CP.flags & HyundaiFlags.NON_SCC_NO_FCA):
|
||||
msgs.append(("FCA11", 0)) # Non-SCC trims can stop publishing FCA11; don't let it poison canValid
|
||||
|
||||
parsers = {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], msgs, 0),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 2),
|
||||
}
|
||||
if CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS:
|
||||
parsers[Bus.alt] = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CLU13", 0)], 1)
|
||||
return parsers
|
||||
|
||||
@@ -38,6 +38,14 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00IGhe SCC FHCUP 1.00 1.02 99110-M9000 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_AZERA_HEV_7TH_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00GN7HMFC AT KOR LHD 1.00 1.01 99211-N1110 240423',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00GN7_ RDR ----- 1.00 1.00 99110-N1100 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_GENESIS: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00DH LKAS 1.1 -150210',
|
||||
@@ -467,6 +475,33 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00ON MFC AT USA LHD 1.00 1.04 99211-S9100 211227',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_PALISADE_2023: {
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00ON MDPS C 1.00 1.01 56300-S9500 2922',
|
||||
b'\xf1\x00LXP MDPS C 1.00 1.00 56310-S8620 4LXPC100',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00ON MFC AT USA LHD 1.00 1.00 99211-S9170 240531',
|
||||
b'\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9160 230802',
|
||||
b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.04 99211-S8150 220622',
|
||||
b'\xf1\x00LX2 MFC AT RUS LHD 1.00 1.04 99211-S8150 220622',
|
||||
b'\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9150 220708',
|
||||
b'\xf1\x00ON MFC AT USA LHD 1.00 1.00 99211-S9160 230303',
|
||||
b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.01 99211-S8600 230817',
|
||||
b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8700 240221',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00ON__ SCC FHCUP 1.00 1.00 99110-S9170 ',
|
||||
b'\xf1\x00ON__ SCC FHCUP 1.00 1.00 99110-S9160 ',
|
||||
b'\xf1\x00LX2_ SCC ----- 1.00 1.01 99110-S8150 ',
|
||||
b'\xf1\x00ON__ SCC ----- 1.00 1.01 99110-S9150 ',
|
||||
b'\xf1\x00LX2_ SCC FHCUP 1.00 1.01 99110-S8150 ',
|
||||
b'\xf1\x00ON__ SCC FHCUP 1.00 1.01 99110-S9150 ',
|
||||
b'\xf1\x00LX2 SCC FHCUP 1.00 1.00 99110-S8600 ',
|
||||
b'\xf1\x00LX2_ SCC FHCUP 1.00 1.01 99110-S8700 ',
|
||||
b'\xf1\x00LX2_ SCC F-CUP 1.00 1.01 99110-S8150 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_VELOSTER: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00JS__ SCC H-CUP 1.00 1.02 95650-J3200 ',
|
||||
@@ -580,15 +615,28 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00CD ESC \x0b 101 \x10\x03 58910-J7AC0',
|
||||
],
|
||||
},
|
||||
CAR.KIA_XCEED_PHEV: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00CDph SCC F-CUP 1.00 1.01 99110-CR100 ',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00CDe MDPS C 1.00 1.01 56310-XX000 4CDHC101',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00CD2 LKAS AT EUR LHD 1.00 1.01 99211-CR010 621',
|
||||
],
|
||||
},
|
||||
CAR.KIA_FORTE: {
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00BD MDPS C 1.00 1.02 56310-XX000 4BD2C102',
|
||||
b'\xf1\x00BD MDPS C 1.00 1.07 56310/M6300 4BDDC107',
|
||||
b'\xf1\x00BD MDPS C 1.00 1.08 56310/M6300 4BDDC108',
|
||||
b'\xf1\x00BD MDPS C 1.00 1.08 56310M6300\x00 4BDDC108',
|
||||
b'\xf1\x00BDm MDPS C A.01 1.01 56310M7800\x00 4BPMC101',
|
||||
b'\xf1\x00BDm MDPS C A.01 1.03 56310M7800\x00 4BPMC103',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00BD LKAS AT USA LHD 1.00 1.02 95740-M6000 J31',
|
||||
b'\xf1\x00BD LKAS AT USA LHD 1.00 1.04 95740-M6000 J33',
|
||||
b'\xf1\x00BDP LKAS AT USA LHD 1.00 1.05 99211-M6500 744',
|
||||
],
|
||||
@@ -712,6 +760,27 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00SX2EMFC AT KOR LHD 1.00 1.00 99211-BF000 230410',
|
||||
b'\xf1\x00SX2EMFC AT USA LHD 1.00 1.02 99211-BF000 230823',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_KONA_2ND_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00SX2 MFC AT USA LHD 1.00 1.03 99211-BE000 230517',
|
||||
b'\xf1\x00SX2 MFC AT USA LHD 1.00 1.07 99211-BE000 240611',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00SX2_ RDR ----- 1.00 1.02 99110-BE000 ',
|
||||
b'\xf1\x00SX2_ RDR ----- 1.00 1.02 99110-BE500 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_KONA_HEV_2ND_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00SX2HMFC AT AUS RHD 1.00 1.00 99211-BE001 241015',
|
||||
b'\xf1\x00SX2HMFC AT EUR RHD 1.00 1.04 99211-BE000 231010',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00SX2_ RDR ----- 1.00 1.02 99110-BE000 ',
|
||||
b'\xf1\x00SX2_ RDR ----- 1.00 1.02 99110-BE500 ',
|
||||
],
|
||||
},
|
||||
CAR.KIA_NIRO_EV: {
|
||||
@@ -1066,6 +1135,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00CE MFC AT EUR LHD 1.00 1.04 99211-KL000 221213',
|
||||
b'\xf1\x00CE MFC AT USA LHD 1.00 1.04 99211-KL000 221213',
|
||||
b'\xf1\x00CE MFC AT USA LHD 1.00 1.06 99211-KL000 230915',
|
||||
b'\xf1\x00CE MFC AT CAN LHD 1.00 1.06 99211-KL000 230915',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_TUCSON_4TH_GEN: {
|
||||
@@ -1129,6 +1199,186 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00NQ5__ 1.00 1.04 99110CH100 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_SANTA_FE_HEV_5TH_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00MX5HMFC AT CAN LHD 1.00 1.07 99211-P6000 231218',
|
||||
b'\xf1\x00MX5HMFC AT KOR LHD 1.00 1.07 99211-P6000 231218',
|
||||
b'\xf1\x00MX5HMFC AT USA LHD 1.00 1.06 99211-R6000 231218',
|
||||
b'\xf1\x00MX5HMFC AT USA LHD 1.00 1.09 99211-R6200 250519',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00MX5_ RDR ----- 1.00 1.01 99110-P6000 ',
|
||||
b'\xf1\x00MX5_ RDR ----- 1.00 1.01 99110-R6000 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_SONATA_2024: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.01 99211-L1800 230512',
|
||||
b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.01 99211-L1800 230512',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00DN8_ RDR ----- 1.00 1.00 99110-L1800 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_SONATA_HEV_2024: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00DN8HMFC AT KOR LHD 1.00 1.01 99211-L1800 230512',
|
||||
b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.01 99211-L1800 230512',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00DN8_ RDR ----- 1.00 1.00 99110-L1800 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_IONIQ_5_PE: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00NE__ RDR ----- 1.00 1.00 99110-PI000 ',
|
||||
b'\xf1\x00NE__ RDR ----- 1.00 1.01 99110-GI500 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NE MFC AT USA LHD 1.00 1.01 99211-PI000 240905',
|
||||
b'\xf1\x00NE MFC AT EUR LHD 1.00 1.03 99211-GI500 240809',
|
||||
b'\xf1\x00NE MFC AT USA LHD 1.00 1.00 99211-PI010 250407',
|
||||
b'\xf1\x00NE MFC AT EUR LHD 1.00 1.00 99211-GI510 250513',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_IONIQ_5_N: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00NE1N RDR ----- 1.00 1.00 99110-NI000 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NE1NMFC AT KOR LHD 1.00 1.04 99211-NI000 231219',
|
||||
b'\xf1\x00NE1NMFC AT KOR LHD 1.00 1.00 99211-NI010 240712',
|
||||
b'\xf1\x00NE1NMFC AT USA LHD 1.00 1.04 99211-NI000 231219',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_IONIQ_9: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00MEev RDR ----- 1.00 1.00 99110-GO000 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00ME MFC AT KOR LHD 1.00 1.00 99211-GO000 241007',
|
||||
b'\xf1\x00ME MFC AT KOR LHD 1.00 1.01 99211-GO000 250103',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_TUCSON_2025: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N7050 C5A',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00NX4__ 1.00 1.03 99110N7100 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_TUCSON_HEV_2025: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N7030 C55',
|
||||
b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 1.00 99211-N7030 C55',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00NX4__ 1.00 1.02 99110N7000 ',
|
||||
b'\xf1\x00NX4__ 1.00 1.02 99110N7100 ',
|
||||
b'\xf1\x00NX4__ 1.00 1.03 99110N7100 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_TUCSON_PHEV_2025: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.00 99211-N7030 C55',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00NX4__ 1.00 1.02 99110N7100 ',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_SANTA_CRUZ_2025: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N7030 C55',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00NX4__ 1.00 1.00 99110K5500 ',
|
||||
],
|
||||
},
|
||||
CAR.KIA_K4_2025: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00CL4 MFC AT CAN LHD 1.00 1.02 99210-GG000 240708',
|
||||
b'\xf1\x00CL4 MFC AT USA LHD 1.00 1.02 99210-GG000 240708',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00CL4_ RDR ----- 1.00 1.01 99110-GG000 ',
|
||||
b'\xf1\x00CL4_ RDR ----- 1.00 1.01 99110-GG100 ',
|
||||
],
|
||||
},
|
||||
CAR.KIA_K5_2025: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.04 99210-L2500 240117',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00DL3_ RDR ----- 1.00 1.01 99110-L2500 ',
|
||||
],
|
||||
},
|
||||
CAR.KIA_SPORTAGE_2026: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NQ51.011.021.012551000HKP_NQ524_50509099211P1110',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00NQ5__ 1.00 1.04 99110P1100 ',
|
||||
],
|
||||
},
|
||||
CAR.KIA_SORENTO_2024: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00MQ4 MFC AT AUS RHD 1.01 1.04 99210-P2550 231127',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00MQ4_ RDR ----- 1.00 1.01 99110-P2500 ',
|
||||
],
|
||||
},
|
||||
CAR.KIA_SORENTO_HEV_4TH_GEN_LFA2: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.00 99210-P2600 250617',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00MQ4_ RDR ----- 1.00 1.01 99110-P2500 ',
|
||||
],
|
||||
},
|
||||
CAR.KIA_EV6_2025: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00CV__ RDR ----- 1.00 1.00 99110-XG500 ',
|
||||
b'\xf1\x00CV__ RDR ----- 1.00 1.01 99110-CV500 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00CV MFC AT KOR LHD 1.00 1.01 99210-CV500 240405',
|
||||
b'\xf1\x00CV MFC AT USA LHD 1.00 1.02 99210-XG500 241223',
|
||||
],
|
||||
},
|
||||
CAR.KIA_EV9: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00MV__ RDR ----- 1.00 1.02 99110-DO000 ',
|
||||
b'\xf1\x00MV__ RDR ----- 1.00 1.03 99110-DO000 ',
|
||||
b'\xf1\x00MV__ RDR ----- 1.00 1.04 99110-DO000 ',
|
||||
b'\xf1\x00MV__ RDR ----- 1.00 1.02 99110-DO700 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00MV MFC AT KOR LHD 1.00 1.01 99211-DO000 230419',
|
||||
b'\xf1\x00MV MFC AT USA LHD 1.00 1.02 99211-DO000 230616',
|
||||
b'\xf1\x00MV MFC AT EUR LHD 1.00 1.02 99211-DO000 230616',
|
||||
b'\xf1\x00MV MFC AT CAN LHD 1.00 1.00 99211-DO100 240403',
|
||||
b'\xf1\x00MV MFC AT USA LHD 1.00 1.01 99211-XA000 241023',
|
||||
b'\xf1\x00MV MFC AT CAN LHD 1.00 1.01 99211-DO100 241023',
|
||||
],
|
||||
},
|
||||
CAR.GENESIS_GV70_ELECTRIFIED_2ND_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00JK MFC AT USA LHD 1.00 1.03 99211-DS600 241125',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00JK__ RDR ----- 1.00 1.01 99110-DS500 ',
|
||||
],
|
||||
},
|
||||
CAR.GENESIS_GV80_2025: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00JX__ RDR ----- 1.00 1.03 99110-T6500 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00JX MFC AT USA LHD 1.00 1.03 99211-T6510 240124',
|
||||
],
|
||||
},
|
||||
CAR.GENESIS_GV70_1ST_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00JK1 MFC AT CAN LHD 1.00 1.02 99211-IY000 230627',
|
||||
@@ -1282,4 +1532,109 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00T01G00BL T01I00A1 DOS2T16X4XI00NS0\x99L\xeeq',
|
||||
],
|
||||
},
|
||||
CAR.KIA_CEED_PHEV_2022_NON_SCC: {
|
||||
(Ecu.eps, 0x7D4, None): [
|
||||
b'\xf1\x00CD MDPS C 1.00 1.01 56310-XX000 4CPHC101',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7C4, None): [
|
||||
b'\xf1\x00CDH LKAS AT EUR LHD 1.00 1.01 99211-CR700 931',
|
||||
],
|
||||
},
|
||||
CAR.GENESIS_G70_2021_NON_SCC: {
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9200 4I2CL108',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00IK__ SCC --CUP 1.00 1.02 96400-G9100 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00IK MFC MT USA LHD 1.00 1.01 95740-G9000 170920',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_KONA_NON_SCC: {
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00OS MDPS C 1.00 1.05 56310J9030\x00 4OSDC105',
|
||||
b'\xf1\x00OS MDPS C 1.00 1.04 56310J9030\x00 4OSDC104',
|
||||
b'\xf1\x00OS MDPS C 1.00 1.05 56310/J9500 4OSDC105',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9200 g30',
|
||||
b'\xf1\x00OS9 LKAS AT AUS RHD 1.00 1.00 95740-J9200 g30',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00OS__ FCA --CUP 1.00 1.00 95655-J9100 ',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x006T6J0_C2\x00\x006T6K1051\x00\x00TOS4N20NS2\x00\x00\x00\x00',
|
||||
b'\xf1\x006U2V0_C2\x00\x006U2V1051\x00\x00DOS4T16AS2\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.KIA_FORTE_2019_NON_SCC: {
|
||||
(Ecu.eps, 0x7D4, None): [
|
||||
b'\xf1\x00BD MDPS C 1.00 1.04 56310/M6000 4BDDC104',
|
||||
b'\xf1\x00BD MDPS C 1.00 1.05 56310/M6000 4BDDC105',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7C4, None): [
|
||||
b'\xf1\x00BD LKAS AT USA LHD 1.00 1.02 95740-M6000 J31',
|
||||
],
|
||||
},
|
||||
CAR.KIA_FORTE_2021_NON_SCC: {
|
||||
(Ecu.eps, 0x7D4, None): [
|
||||
b'\xf1\x00BD MDPS C 1.00 1.07 56310/M6300 4BDDC107',
|
||||
b'\xf1\x00BD MDPS C 1.00 1.08 56310M6000\x00 4BDDC108',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7C4, None): [
|
||||
b'\xf1\x00BD LKAS AT USA LHD 1.00 1.02 95740-M6000 J31',
|
||||
b'\xf1\x00BD LKAS AT USA LHD 1.00 1.04 95740-M6000 J33',
|
||||
],
|
||||
},
|
||||
CAR.KIA_SELTOS_2023_NON_SCC: {
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
b'\xf1\x00SP ESC \t 101\"\t\x01 58910-Q5510',
|
||||
b'\xf1\x00SP ESC \r 100\"\x04\x01 58910-Q5510',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00SP2 MDPS C 1.00 1.04 56310Q5240 4SPSC104',
|
||||
b'\xf1\x00SP2 MDPS C 1.00 1.01 56300Q5920 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00SP2 MFC AT USA LHD 1.00 1.03 99210-Q5500 230208',
|
||||
b'\xf1\x00SP2 MFC AT AUS RHD 1.00 1.02 99210-Q5500 220624',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x006V2B0_C2\x00\x006V2D5051\x00\x00CSP2N20NL0\x00\x00\x00\x00',
|
||||
b'\xf1\x006V2B0_C2\x00\x006V2D4051\x00\x00CSP2N20KL1\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_ELANTRA_2022_NON_SCC: {
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00CN7 MDPS R 1.00 1.04 57700-IB000 4CNNP104',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-IB000 210531',
|
||||
],
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
b'\xf1\x00CN ESC \t 100!\x05\x01 58910-IB000',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x00T02601BL T02900A1 WCN7T20XXX900NS4\xf7\xccz\xf6',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_ELANTRA_HEV_2022_NON_SCC: {
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.04 99210-AA000 210205',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_BAYON_1ST_GEN_NON_SCC: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00BC3 LKA AT EUR LHD 1.00 1.01 99211-Q0100 261',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crcmod
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
|
||||
hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf)
|
||||
@@ -38,7 +39,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
|
||||
CAR.HYUNDAI_IONIQ_EV_2020, CAR.HYUNDAI_IONIQ_PHEV, CAR.KIA_SELTOS, CAR.HYUNDAI_ELANTRA_2021, CAR.GENESIS_G70_2020,
|
||||
CAR.HYUNDAI_ELANTRA_HEV_2021, CAR.HYUNDAI_SONATA_HYBRID, CAR.HYUNDAI_KONA_EV, CAR.HYUNDAI_KONA_HEV, CAR.HYUNDAI_KONA_EV_2022,
|
||||
CAR.HYUNDAI_SANTA_FE_2022, CAR.KIA_K5_2021, CAR.HYUNDAI_IONIQ_HEV_2022, CAR.HYUNDAI_SANTA_FE_HEV_2022,
|
||||
CAR.HYUNDAI_SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED,
|
||||
CAR.HYUNDAI_SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED, CAR.KIA_XCEED_PHEV,
|
||||
CAR.HYUNDAI_AZERA_6TH_GEN, CAR.HYUNDAI_AZERA_HEV_6TH_GEN, CAR.HYUNDAI_CUSTIN_1ST_GEN, CAR.HYUNDAI_KONA_2022):
|
||||
values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1)
|
||||
values["CF_Lkas_LdwsOpt_USM"] = 2
|
||||
@@ -96,6 +97,47 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
|
||||
return packer.make_can_msg("LKAS11", 0, values)
|
||||
|
||||
|
||||
def create_checksum_can_canfd_blended(packer, bus, addr, values):
|
||||
dat = packer.make_can_msg(addr, bus, values)[1]
|
||||
return hyundai_checksum(dat[1:8])
|
||||
|
||||
|
||||
def create_lkas11_can_canfd_blended(packer, frame, CP, apply_steer, steer_req,
|
||||
torque_fault, lkas11, sys_warning, sys_state, enabled,
|
||||
left_lane, right_lane,
|
||||
left_lane_depart, right_lane_depart, msg_364):
|
||||
bus = CanBus(CP).ECAN
|
||||
values = {
|
||||
"CF_Lkas_LdwsActivemode": int(left_lane) + (int(right_lane) << 1),
|
||||
"CF_Lkas_LdwsLHWarning": left_lane_depart,
|
||||
"CF_Lkas_LdwsRHWarning": right_lane_depart,
|
||||
"CF_Lkas_FcwOpt_USM": 2 if enabled else 1,
|
||||
"CR_Lkas_StrToqReq": apply_steer,
|
||||
"CF_Lkas_ActToi": steer_req,
|
||||
"CF_Lkas_ToiFlt": torque_fault,
|
||||
"CF_Lkas_MsgCount": frame % 0x10,
|
||||
"NEW_SIGNAL_1": 0,
|
||||
"NEW_SIGNAL_5": 100,
|
||||
}
|
||||
values["CF_Lkas_Chksum"] = create_checksum_can_canfd_blended(packer, bus, "LKAS11", values)
|
||||
|
||||
alerts_364 = {k: v for k, v in msg_364.items() if k not in ("CHECKSUM", "COUNTER")} if msg_364 else {}
|
||||
alerts_364.setdefault("BYTE2", 0)
|
||||
alerts_364.setdefault("BYTE3", 0)
|
||||
alerts_364.setdefault("DAW_Status", 0)
|
||||
alerts_364["DAW_Warning"] = 0
|
||||
alerts_364.setdefault("BYTE5", 0)
|
||||
alerts_364.setdefault("BYTE6", 0)
|
||||
alerts_364.setdefault("BYTE7", 0)
|
||||
alerts_364["COUNTER"] = frame % 0x10
|
||||
alerts_364["CHECKSUM"] = create_checksum_can_canfd_blended(packer, bus, "ALERTS_364", alerts_364)
|
||||
|
||||
return [
|
||||
packer.make_can_msg("LKAS11", bus, values),
|
||||
packer.make_can_msg("ALERTS_364", bus, alerts_364),
|
||||
]
|
||||
|
||||
|
||||
def create_clu11(packer, frame, clu11, button, CP):
|
||||
values = {s: clu11[s] for s in [
|
||||
"CF_Clu_CruiseSwState",
|
||||
@@ -114,15 +156,80 @@ def create_clu11(packer, frame, clu11, button, CP):
|
||||
values["CF_Clu_CruiseSwState"] = button
|
||||
values["CF_Clu_AliveCnt1"] = frame % 0x10
|
||||
# send buttons to camera on camera-scc based cars
|
||||
bus = 2 if CP.flags & HyundaiFlags.CAMERA_SCC else 0
|
||||
if CP.flags & HyundaiFlags.CAMERA_SCC:
|
||||
bus = 2
|
||||
elif CP.flags & HyundaiFlags.CAN_CANFD_BLENDED:
|
||||
bus = CanBus(CP).ECAN
|
||||
else:
|
||||
bus = 0
|
||||
return packer.make_can_msg("CLU11", bus, values)
|
||||
|
||||
|
||||
def create_lfahda_mfc(packer, enabled):
|
||||
def create_lfahda_mfc(packer, enabled, frame=None, CP=None, lfa_icon=None):
|
||||
if lfa_icon is None:
|
||||
lfa_icon = 2 if enabled else 0
|
||||
|
||||
values = {
|
||||
"LFA_Icon_State": 2 if enabled else 0,
|
||||
"LFA_Icon_State": lfa_icon,
|
||||
}
|
||||
return packer.make_can_msg("LFAHDA_MFC", 0, values)
|
||||
can_canfd_blended = CP is not None and bool(CP.flags & HyundaiFlags.CAN_CANFD_BLENDED)
|
||||
bus = CanBus(CP).ECAN if can_canfd_blended else 0
|
||||
if can_canfd_blended:
|
||||
values["COUNTER"] = 0 if frame is None else frame % 0x10
|
||||
values["CHECKSUM"] = create_checksum_can_canfd_blended(packer, bus, "LFAHDA_MFC", values)
|
||||
return packer.make_can_msg("LFAHDA_MFC", bus, values)
|
||||
|
||||
|
||||
def create_acc_commands_can_canfd_blended(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed,
|
||||
stopping, long_override, use_fca, CP):
|
||||
commands = []
|
||||
bus = CanBus(CP).ECAN
|
||||
|
||||
scc11_values = {
|
||||
"aReqRaw": accel,
|
||||
"aReqValue": accel,
|
||||
"JerkUpperLimit": upper_jerk,
|
||||
"JerkLowerLimit": 5.0,
|
||||
"ComfortBandUpper": 0.0,
|
||||
"ComfortBandLower": 0.0,
|
||||
"COUNTER": idx % 0x10,
|
||||
}
|
||||
scc11_values["CHECKSUM"] = create_checksum_can_canfd_blended(packer, bus, "SCC11", scc11_values)
|
||||
commands.append(packer.make_can_msg("SCC11", bus, scc11_values))
|
||||
|
||||
scc12_values = {
|
||||
"MainMode_ACC": 1,
|
||||
"ACCMode_Inactive": 0 if enabled else 1,
|
||||
"TauGapSet": hud_control.leadDistanceBars,
|
||||
"VSetDis": set_speed if enabled else 0,
|
||||
"ACC_ObjDist": 1,
|
||||
"ACCMode": 2 if enabled and long_override else 1 if enabled else 0,
|
||||
"StopReq": 1 if stopping else 0,
|
||||
"ACC_ObjDist_Ref": 1,
|
||||
"COUNTER": idx % 0x10,
|
||||
}
|
||||
scc12_values["CHECKSUM"] = create_checksum_can_canfd_blended(packer, bus, "SCC12", scc12_values)
|
||||
commands.append(packer.make_can_msg("SCC12", bus, scc12_values))
|
||||
|
||||
scc14_values = {
|
||||
"ACC_ObjRelSpd": 0,
|
||||
"ObjValid": 1,
|
||||
"ObjStatus": 1,
|
||||
"COUNTER": idx % 0x10,
|
||||
}
|
||||
scc14_values["CHECKSUM"] = create_checksum_can_canfd_blended(packer, bus, "SCC14", scc14_values)
|
||||
commands.append(packer.make_can_msg("SCC14", bus, scc14_values))
|
||||
|
||||
if use_fca and not (CP.flags & HyundaiFlags.CAMERA_SCC):
|
||||
fca11_values = {
|
||||
"cr_vsm_deccmd": 255,
|
||||
"cf_vsm_deccmdact": 127,
|
||||
"COUNTER": idx % 0x10,
|
||||
}
|
||||
fca11_values["CHECKSUM"] = create_checksum_can_canfd_blended(packer, bus, "FCA11", fca11_values)
|
||||
commands.append(packer.make_can_msg("FCA11", bus, fca11_values))
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP):
|
||||
@@ -149,11 +256,10 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, se
|
||||
"CR_VSM_Alive": idx % 0xF,
|
||||
}
|
||||
|
||||
# show AEB disabled indicator on dash with SCC12 if not sending FCA messages.
|
||||
# these signals also prevent a TCS fault on non-FCA cars with alpha longitudinal
|
||||
# Keep ESC/TCS happy on non-FCA cars without explicitly showing the disabled AEB icon.
|
||||
if not use_fca:
|
||||
scc12_values["CF_VSM_ConfMode"] = 1
|
||||
scc12_values["AEB_Status"] = 1 # AEB disabled
|
||||
scc12_values["AEB_Status"] = 2
|
||||
|
||||
scc12_dat = packer.make_can_msg("SCC12", 0, scc12_values)[1]
|
||||
scc12_values["CR_VSM_ChkSum"] = 0x10 - sum(sum(divmod(i, 16)) for i in scc12_dat) % 0x10
|
||||
@@ -179,7 +285,7 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, se
|
||||
"CR_FCA_Alive": idx % 0xF,
|
||||
"PAINT1_Status": 1,
|
||||
"FCA_DrvSetStatus": 1,
|
||||
"FCA_Status": 1, # AEB disabled
|
||||
"FCA_Status": 2,
|
||||
}
|
||||
fca11_dat = packer.make_can_msg("FCA11", 0, fca11_values)[1]
|
||||
fca11_values["CR_FCA_ChkSum"] = hyundai_checksum(fca11_dat[:7])
|
||||
@@ -203,7 +309,7 @@ def create_acc_opt(packer, CP):
|
||||
if not (CP.flags & HyundaiFlags.CAMERA_SCC):
|
||||
fca12_values = {
|
||||
"FCA_DrvSetState": 2,
|
||||
"FCA_USM": 1, # AEB disabled
|
||||
"FCA_USM": 2,
|
||||
}
|
||||
commands.append(packer.make_can_msg("FCA12", 0, fca12_values))
|
||||
|
||||
@@ -215,3 +321,20 @@ def create_frt_radar_opt(packer):
|
||||
"CF_FCA_Equip_Front_Radar": 1,
|
||||
}
|
||||
return packer.make_can_msg("FRT_RADAR11", 0, frt_radar11_values)
|
||||
|
||||
|
||||
def create_radar_aux_messages(packer, CAN, frame):
|
||||
commands = []
|
||||
|
||||
for addr, freq, values in (
|
||||
("RADAR_0x363", 2, {"FCA_ESA": 1}),
|
||||
("RADAR_0x398", 5, {"BYTE4": 0x80, "BYTE5": 0x10}),
|
||||
):
|
||||
if frame % freq != 0:
|
||||
continue
|
||||
|
||||
msg_values = values | {"COUNTER": frame % 0x10}
|
||||
msg_values["CHECKSUM"] = create_checksum_can_canfd_blended(packer, CAN.ECAN, addr, msg_values)
|
||||
commands.append(packer.make_can_msg(addr, CAN.ECAN, msg_values))
|
||||
|
||||
return commands
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import copy
|
||||
import numpy as np
|
||||
from opendbc.car import CanBusBase
|
||||
from opendbc.car import CanBusBase, CanData
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.crc import CRC16_XMODEM
|
||||
from opendbc.car.hyundai.values import HyundaiFlags
|
||||
|
||||
@@ -91,29 +92,86 @@ def _create_angle_adas_cmd_msg(packer, CAN, apply_angle: float, lat_active: bool
|
||||
return packer.make_can_msg("ADAS_CMD_35_10ms", CAN.ECAN, values)
|
||||
|
||||
|
||||
def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, apply_angle):
|
||||
common_values = {
|
||||
def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, apply_angle,
|
||||
lfa_base_values=None, lkas_base_values=None, lka_icon=None):
|
||||
if lka_icon is None:
|
||||
lka_icon = 2 if enabled else 1
|
||||
ev9_angle_lkas_alt = str(CP.carFingerprint) == "KIA_EV9" and CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and \
|
||||
CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT
|
||||
|
||||
control_values = {
|
||||
"LKA_MODE": 2,
|
||||
"LKA_ICON": 2 if enabled else 1,
|
||||
"LKA_ICON": lka_icon,
|
||||
"TORQUE_REQUEST": 0 if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING else apply_torque,
|
||||
"LKA_ASSIST": 0,
|
||||
"STEER_REQ": 0 if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING else (1 if lat_active else 0),
|
||||
"STEER_MODE": 0,
|
||||
"HAS_LANE_SAFETY": 0, # hide LKAS settings
|
||||
"NEW_SIGNAL_2": 0,
|
||||
"DAMP_FACTOR": 100, # can potentially tuned for better perf [3, 200]
|
||||
}
|
||||
|
||||
lkas_values = copy.copy(common_values)
|
||||
lkas_values["LKA_AVAILABLE"] = 0
|
||||
if lkas_base_values:
|
||||
lkas_values = {k: v for k, v in lkas_base_values.items() if k not in ("CHECKSUM", "COUNTER")}
|
||||
lkas_values.update(control_values)
|
||||
else:
|
||||
lkas_values = copy.copy(control_values)
|
||||
lkas_values["LKA_AVAILABLE"] = 0
|
||||
|
||||
lfa_values = copy.copy(common_values)
|
||||
lfa_values["NEW_SIGNAL_1"] = 0
|
||||
if lfa_base_values:
|
||||
# Preserve stock UI/status fields and only override the actuation-relevant signals.
|
||||
lfa_values = {k: v for k, v in lfa_base_values.items() if k not in ("CHECKSUM", "COUNTER")}
|
||||
lfa_values.update(control_values)
|
||||
else:
|
||||
lfa_values = copy.copy(control_values)
|
||||
lfa_values["HAS_LANE_SAFETY"] = 0 # hide LKAS settings
|
||||
lfa_values["NEW_SIGNAL_1"] = 0
|
||||
lfa_values["NEW_SIGNAL_2"] = 0
|
||||
lfa_values["DAMP_FACTOR"] = 100 # can potentially tuned for better perf [3, 200]
|
||||
|
||||
if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT:
|
||||
lkas_values["ADAS_StrAnglReqVal"] = apply_angle
|
||||
lkas_values["LKAS_ANGLE_ACTIVE"] = 2 if lat_active else 1
|
||||
lkas_values["ADAS_ACIAnglTqRedcGainVal"] = apply_torque if lat_active else 0.0
|
||||
if ev9_angle_lkas_alt:
|
||||
if lat_active:
|
||||
lkas_values.update({
|
||||
"LKA_MODE": 0,
|
||||
"LKA_AVAILABLE": 3,
|
||||
"LKA_WARNING": 0,
|
||||
"LKA_ICON": lka_icon,
|
||||
"FCA_SYSWARN": 0,
|
||||
"TORQUE_REQUEST": 0,
|
||||
"STEER_REQ": 0,
|
||||
"LFA_BUTTON": 0,
|
||||
"LKA_ASSIST": 0,
|
||||
"DAMP_FACTOR": 100,
|
||||
"HAS_LANE_SAFETY": 0,
|
||||
})
|
||||
elif lkas_base_values:
|
||||
for signal in ("LKA_MODE", "LKA_AVAILABLE", "LKA_WARNING", "LKA_ICON", "FCA_SYSWARN",
|
||||
"LFA_BUTTON", "LKA_ASSIST", "DAMP_FACTOR", "HAS_LANE_SAFETY"):
|
||||
if signal in lkas_base_values:
|
||||
lkas_values[signal] = lkas_base_values[signal]
|
||||
lkas_values.update({
|
||||
"TORQUE_REQUEST": 0,
|
||||
"STEER_REQ": 0,
|
||||
})
|
||||
else:
|
||||
lkas_values.update({
|
||||
"LKA_MODE": 0,
|
||||
"LKA_AVAILABLE": 0,
|
||||
"LKA_WARNING": 0,
|
||||
"LKA_ICON": lka_icon,
|
||||
"FCA_SYSWARN": 0,
|
||||
"TORQUE_REQUEST": 0,
|
||||
"STEER_REQ": 0,
|
||||
"LFA_BUTTON": 0,
|
||||
"LKA_ASSIST": 0,
|
||||
"DAMP_FACTOR": 100,
|
||||
"HAS_LANE_SAFETY": 0,
|
||||
})
|
||||
# These signals overlap DAMP_FACTOR in the local DBC naming; omitting them
|
||||
# preserves the stock angle-steering damping byte expected by the ADAS ECU.
|
||||
lkas_values.pop("STEER_MODE", None)
|
||||
lkas_values.pop("NEW_SIGNAL_2", None)
|
||||
|
||||
ret = []
|
||||
if CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
|
||||
@@ -124,6 +182,9 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
|
||||
else:
|
||||
if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
if CP.flags & HyundaiFlags.SEND_LFA:
|
||||
# Some CAN-FD angle-steering trims still expect the stock-style LFA status/UI
|
||||
# message to remain present even though angle actuation comes through ADAS_CMD.
|
||||
ret.append(packer.make_can_msg("LFA", CAN.ECAN, lfa_values))
|
||||
ret.append(_create_angle_adas_cmd_msg(packer, CAN, apply_angle, lat_active, apply_torque))
|
||||
else:
|
||||
ret.append(_create_angle_lfa_msg(packer, CAN, lfa_values, apply_angle, lat_active, apply_torque))
|
||||
@@ -146,12 +207,15 @@ def create_suppress_lfa(packer, CAN, lfa_block_msg, lka_steering_alt):
|
||||
return packer.make_can_msg(suppress_msg, CAN.ACAN, values)
|
||||
|
||||
|
||||
def create_buttons(packer, CP, CAN, cnt, btn):
|
||||
values = {
|
||||
def create_buttons(packer, CP, CAN, cnt, btn=0, base_values=None, left_paddle=False, right_paddle=False):
|
||||
values = {k: v for k, v in base_values.items() if k not in ("_CHECKSUM", "COUNTER")} if base_values else {}
|
||||
values.update({
|
||||
"COUNTER": cnt,
|
||||
"SET_ME_1": 1,
|
||||
"CRUISE_BUTTONS": btn,
|
||||
}
|
||||
"LEFT_PADDLE": int(left_paddle),
|
||||
"RIGHT_PADDLE": int(right_paddle),
|
||||
})
|
||||
|
||||
bus = CAN.ECAN if CP.flags & HyundaiFlags.CANFD_LKA_STEERING else CAN.CAM
|
||||
return packer.make_can_msg("CRUISE_BUTTONS", bus, values)
|
||||
@@ -188,41 +252,403 @@ def create_acc_cancel(packer, CP, CAN, cruise_info_copy):
|
||||
return packer.make_can_msg("SCC_CONTROL", CAN.ECAN, values)
|
||||
|
||||
|
||||
def create_lfahda_cluster(packer, CAN, enabled):
|
||||
values = {
|
||||
def create_lfahda_cluster(packer, CAN, enabled, base_values=None, lfa_icon=None):
|
||||
if lfa_icon is None:
|
||||
lfa_icon = 2 if enabled else 0
|
||||
|
||||
values = {k: v for k, v in base_values.items() if k not in ("CHECKSUM", "COUNTER")} if base_values else {}
|
||||
values.update({
|
||||
"HDA_ICON": 1 if enabled else 0,
|
||||
"LFA_ICON": 2 if enabled else 0,
|
||||
}
|
||||
"LFA_ICON": lfa_icon,
|
||||
})
|
||||
return packer.make_can_msg("LFAHDA_CLUSTER", CAN.ECAN, values)
|
||||
|
||||
|
||||
def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control):
|
||||
def create_ccnc(packer, CAN, openpilot_longitudinal, enabled, hud, left_blinker, right_blinker, msg_161, msg_162, msg_1b5,
|
||||
is_metric, out, main_cruise_enabled, lfa_icon):
|
||||
for fault in ("FAULT_LSS", "FAULT_HDA", "FAULT_DAS", "FAULT_LFA", "FAULT_DAW", "FAULT_ESS"):
|
||||
msg_162[fault] = 0
|
||||
|
||||
if msg_161["ALERTS_2"] == 5:
|
||||
msg_161.update({"ALERTS_2": 0, "SOUNDS_2": 0})
|
||||
if msg_161["ALERTS_3"] == 17:
|
||||
msg_161["ALERTS_3"] = 0
|
||||
if msg_161["ALERTS_5"] in (2, 5):
|
||||
msg_161["ALERTS_5"] = 0
|
||||
if msg_161["SOUNDS_4"] == 2 and msg_161["LFA_ICON"] in (3, 0):
|
||||
msg_161["SOUNDS_4"] = 0
|
||||
|
||||
lane_change_speed_min = 8.9408
|
||||
any_blinker = left_blinker or right_blinker
|
||||
curvature = {i: (31 if i == -1 else 13 - abs(i + 15)) if i < 0 else 15 + i for i in range(-15, 16)}
|
||||
|
||||
msg_161.update({
|
||||
"DAW_ICON": 0,
|
||||
"LKA_ICON": 0,
|
||||
"LFA_ICON": 2 if lfa_icon else 0,
|
||||
"CENTERLINE": 1 if lfa_icon else 0,
|
||||
"LANELINE_CURVATURE": curvature.get(max(-15, min(int(out.steeringAngleDeg / 4.5), 15)), 14) if lfa_icon and not any_blinker else 15,
|
||||
"LANELINE_LEFT": 0 if not lfa_icon else 1 if not hud.leftLaneVisible else 4 if hud.leftLaneDepart else 6 if any_blinker else 2,
|
||||
"LANELINE_RIGHT": 0 if not lfa_icon else 1 if not hud.rightLaneVisible else 4 if hud.rightLaneDepart else 6 if any_blinker else 2,
|
||||
"LCA_LEFT_ICON": 0 if not lfa_icon or out.vEgo < lane_change_speed_min else 1 if out.leftBlindspot else 2 if any_blinker else 4,
|
||||
"LCA_RIGHT_ICON": 0 if not lfa_icon or out.vEgo < lane_change_speed_min else 1 if out.rightBlindspot else 2 if any_blinker else 4,
|
||||
"LCA_LEFT_ARROW": 2 if left_blinker else 0,
|
||||
"LCA_RIGHT_ARROW": 2 if right_blinker else 0,
|
||||
})
|
||||
|
||||
if lfa_icon and any_blinker:
|
||||
left_lane_raw = msg_1b5["Info_LftLnPosVal"]
|
||||
right_lane_raw = msg_1b5["Info_RtLnPosVal"]
|
||||
scale_per_m = 15 / 1.7
|
||||
left_lane = abs(int(round(15 + (left_lane_raw - 1.7) * scale_per_m)))
|
||||
right_lane = abs(int(round(15 + (right_lane_raw - 1.7) * scale_per_m)))
|
||||
|
||||
if msg_1b5["Info_LftLnQualSta"] not in (2, 3):
|
||||
left_lane = 0
|
||||
if msg_1b5["Info_RtLnQualSta"] not in (2, 3):
|
||||
right_lane = 0
|
||||
|
||||
if left_lane_raw == -2.0248375:
|
||||
left_lane = 30 - right_lane
|
||||
if right_lane_raw == 2.0248375:
|
||||
right_lane = 30 - left_lane
|
||||
|
||||
if left_lane_raw == right_lane_raw == 0:
|
||||
left_lane = right_lane = 15
|
||||
elif left_lane_raw == 0:
|
||||
left_lane = 30 - right_lane
|
||||
elif right_lane_raw == 0:
|
||||
right_lane = 30 - left_lane
|
||||
|
||||
total = left_lane + right_lane
|
||||
if total == 0:
|
||||
left_lane = right_lane = 15
|
||||
else:
|
||||
left_lane = round((left_lane / total) * 30)
|
||||
right_lane = 30 - left_lane
|
||||
|
||||
msg_161["LANELINE_LEFT_POSITION"] = left_lane
|
||||
msg_161["LANELINE_RIGHT_POSITION"] = right_lane
|
||||
|
||||
if hud.leftLaneDepart or hud.rightLaneDepart:
|
||||
msg_162["VIBRATE"] = 1
|
||||
|
||||
if openpilot_longitudinal:
|
||||
if msg_161["ALERTS_3"] in (1, 2, 3, 4, 7, 8, 9, 10):
|
||||
msg_161["ALERTS_3"] = 0
|
||||
if msg_161["ALERTS_5"] == 4:
|
||||
msg_161["ALERTS_5"] = 0
|
||||
if msg_161["SOUNDS_3"] == 5:
|
||||
msg_161["SOUNDS_3"] = 0
|
||||
|
||||
cruise_speed = round(out.vCruiseCluster * (1 if is_metric else CV.KPH_TO_MPH))
|
||||
msg_161.update({
|
||||
"SETSPEED": 3 if enabled else 1,
|
||||
"SETSPEED_HUD": 0 if not main_cruise_enabled else 2 if enabled else 1,
|
||||
"SETSPEED_SPEED": 255 if not main_cruise_enabled else (40 if is_metric else 25) if cruise_speed > (145 if is_metric else 90) else cruise_speed,
|
||||
"DISTANCE": hud.leadDistanceBars,
|
||||
"DISTANCE_SPACING": 0 if not main_cruise_enabled else 1 if enabled else 3,
|
||||
"DISTANCE_LEAD": 0 if not main_cruise_enabled else 2 if enabled and hud.leadVisible else 1 if hud.leadVisible else 0,
|
||||
"DISTANCE_CAR": 0 if not main_cruise_enabled else 2 if enabled else 1,
|
||||
"SLA_ICON": 0,
|
||||
"NAV_ICON": 0,
|
||||
"TARGET": 0,
|
||||
})
|
||||
msg_162["LEAD"] = 0 if not main_cruise_enabled else 2 if enabled else 1
|
||||
msg_162["LEAD_DISTANCE"] = msg_1b5["Longitudinal_Distance"]
|
||||
|
||||
return [packer.make_can_msg(msg, CAN.ECAN, values) for msg, values in (("CCNC_0x161", msg_161), ("CCNC_0x162", msg_162))]
|
||||
|
||||
|
||||
def create_blindspot_status_messages(packer, CAN, rear_values, front_corner_values,
|
||||
left_blindspot=False, right_blindspot=False,
|
||||
left_blinker=False, right_blinker=False):
|
||||
# Reuse the last known-good payload but regenerate the rolling counter/checksum.
|
||||
rear = {k: v for k, v in rear_values.items() if k not in ("CHECKSUM", "COUNTER")}
|
||||
front = {k: v for k, v in front_corner_values.items() if k not in ("CHECKSUM", "COUNTER")}
|
||||
left_state = 2 if left_blindspot and left_blinker else (1 if left_blindspot else 0)
|
||||
right_state = 2 if right_blindspot and right_blinker else (1 if right_blindspot else 0)
|
||||
|
||||
rear["BCW_Sta"] = int(left_blindspot or right_blindspot)
|
||||
rear["BCW_LtIndSta"] = left_state
|
||||
rear["BCW_RtIndSta"] = right_state
|
||||
rear["BCW_IndSta"] = max(left_state, right_state)
|
||||
rear["OSMrrLamp_LtIndSta"] = left_state
|
||||
rear["OSMrrLamp_RtIndSta"] = right_state
|
||||
# Keep the older fields aligned where they still correlate on some platforms.
|
||||
rear["FL_INDICATOR"] = left_state
|
||||
rear["FR_INDICATOR"] = right_state
|
||||
if "NEW_SIGNAL_3" not in front:
|
||||
front["NEW_SIGNAL_3"] = 1
|
||||
|
||||
return [
|
||||
packer.make_can_msg("BLINDSPOTS_REAR_CORNERS", CAN.ECAN, rear),
|
||||
packer.make_can_msg("BLINDSPOTS_FRONT_CORNER_1", CAN.ECAN, front),
|
||||
]
|
||||
|
||||
|
||||
IONIQ_6_CLUSTER_BLINDSPOT_31A = {
|
||||
"right": (
|
||||
bytes.fromhex("fa7c10f0f0ffff03898aff0b0a8678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("ac0e11f0f0ffff03898aff0c0a8678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("76ce12f0f0ffff03898aff0b0a8678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("309713f0f0ffff03898aff0b0a8678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("d32214f0f0ffff03898aff0c0a8678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("957b15f0f0ffff03898aff0c0a8678ff000000007e0055550000000000000000"),
|
||||
),
|
||||
"left": (
|
||||
bytes.fromhex("851828f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("c34129f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("09aa2af0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("4ff32bf0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("bc6d2cf0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("fa342df0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
),
|
||||
}
|
||||
|
||||
IONIQ_6_CLUSTER_BLINDSPOT_3B5 = {
|
||||
"right": (
|
||||
bytes.fromhex("caa95c00000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("8cf05d00000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("461b5e00000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("00425f00000000464600000000000000d7020000000069070000000000000000"),
|
||||
),
|
||||
"left": (
|
||||
bytes.fromhex("2c69c500000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("e682c600000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("21afc800000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("67f6c900000000464600000000000000da020000000069070000000000000000"),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
IONIQ_6_CLUSTER_LANE_CHANGE_3C1 = {
|
||||
"right": {
|
||||
"trigger": bytes.fromhex("e910300041000000"),
|
||||
"steady": bytes.fromhex("ab20300001000000"),
|
||||
},
|
||||
"left": {
|
||||
"trigger": bytes.fromhex("3d40304010000000"),
|
||||
"steady": bytes.fromhex("3e50300000000000"),
|
||||
},
|
||||
}
|
||||
|
||||
# Captured from a stock Ioniq 6 route that shows the cluster lane-change animation
|
||||
# on ECAN after the trigger/hold 0x3C1 states above.
|
||||
IONIQ_6_CLUSTER_LANE_CHANGE_3B5 = {
|
||||
"right": (
|
||||
bytes.fromhex("9f687600000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("d9317700000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("58457800000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("1e1c7900000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("d4f77a00000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("92ae7b00000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("61307c00000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("27697d00000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("ed827e00000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("abdb7f00000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("dd978000000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("9bce8100000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("51258200000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("177c8300000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("e4e28400000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("18ba8500000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("68508600000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("94088700000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("157c8800000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("53258900000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("99ce8a00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("df978b00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("2c098c00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("6a508d00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("a0bb8e00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("e6e28f00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("a2529000000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("e40b9100000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("2ee09200000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("d2b89300000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("9b279400000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("677f9500000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("17959600000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("ebcd9700000000464600000000000000d7020000000069070000000000000000"),
|
||||
bytes.fromhex("d0b89800000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("96e19900000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("5c0a9a00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("1a539b00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("e9cd9c00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("af949d00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("657f9e00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("23269f00000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("cc0fa000000000464600000000000000d8020000000069070000000000000000"),
|
||||
bytes.fromhex("bba6a100000000464600000000000000d9020000000069070000000000000000"),
|
||||
bytes.fromhex("714da200000000464600000000000000d9020000000069070000000000000000"),
|
||||
bytes.fromhex("3714a300000000464600000000000000d9020000000069070000000000000000"),
|
||||
),
|
||||
"left": (
|
||||
bytes.fromhex("e682c600000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("d2dbc700000000464600000000000000d9020000000069070000000000000000"),
|
||||
bytes.fromhex("21afc800000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("67f6c900000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("ad1dca00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("eb44cb00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("18dacc00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("5e83cd00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("9468ce00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("d231cf00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("9681d000000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("d0d8d100000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("1a33d200000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("5c6ad300000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("ddf4d400000000464600000000000000d9020000000069070000000000000000"),
|
||||
bytes.fromhex("e9add500000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("2346d600000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("651fd700000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("e46bd800000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("d032d900000000464600000000000000d9020000000069070000000000000000"),
|
||||
bytes.fromhex("68d9da00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("2e80db00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("dd1edc00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("9b47dd00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("51acde00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("17f5df00000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("f8dce000000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("be85e100000000464600000000000000da020000000069070000000000000000"),
|
||||
bytes.fromhex("746ee200000000464600000000000000da020000000069070000000000000000"),
|
||||
),
|
||||
}
|
||||
|
||||
IONIQ_6_CLUSTER_LANE_CHANGE_31A = {
|
||||
"right": (
|
||||
bytes.fromhex("eb4518f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("757119f0f0ffff03898aff0a088678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("bf9a1af0f0ffff03898aff0a088678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("f9c31bf0f0ffff03898aff0a088678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("0a5d1cf0f0ffff03898aff0a088678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("4c041df0f0ffff03898aff0a088678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("86ef1ef0f0ffff03898aff0a088678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("18db1ff0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("f7f220f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
),
|
||||
"left": (
|
||||
bytes.fromhex("851828f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("c34129f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("09aa2af0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("4ff32bf0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("bc6d2cf0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
bytes.fromhex("fa342df0f0ffff03898aff0a098678ff000000007e0055550000000000000000"),
|
||||
),
|
||||
}
|
||||
|
||||
IONIQ_6_CLUSTER_LANE_CHANGE_3C1_BURST = {
|
||||
0: "trigger",
|
||||
4: "trigger",
|
||||
7: "steady",
|
||||
10: "steady",
|
||||
13: "steady",
|
||||
16: "steady",
|
||||
}
|
||||
|
||||
IONIQ_6_CLUSTER_LANE_CHANGE_3C1_STEADY_START = 34
|
||||
IONIQ_6_CLUSTER_LANE_CHANGE_3B5_START = 4
|
||||
IONIQ_6_CLUSTER_LANE_CHANGE_31A_START = 30
|
||||
|
||||
|
||||
def create_ioniq_6_cluster_blindspot_messages(CAN, frame, left_blindspot=False, right_blindspot=False,
|
||||
left_blinker=False, right_blinker=False):
|
||||
side = None
|
||||
if left_blindspot and not right_blindspot:
|
||||
side = "left"
|
||||
elif right_blindspot and not left_blindspot:
|
||||
side = "right"
|
||||
elif left_blindspot and right_blindspot:
|
||||
if left_blinker and not right_blinker:
|
||||
side = "left"
|
||||
elif right_blinker and not left_blinker:
|
||||
side = "right"
|
||||
|
||||
if side is None:
|
||||
return []
|
||||
|
||||
ret = []
|
||||
if frame % 20 == 0:
|
||||
seq_3b5 = IONIQ_6_CLUSTER_BLINDSPOT_3B5[side]
|
||||
ret.append((0x3B5, seq_3b5[(frame // 20) % len(seq_3b5)], CAN.ECAN))
|
||||
if frame % 100 == 0:
|
||||
seq_31a = IONIQ_6_CLUSTER_BLINDSPOT_31A[side]
|
||||
ret.append((0x31A, seq_31a[(frame // 100) % len(seq_31a)], CAN.ECAN))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def create_ioniq_6_cluster_lane_change_messages(CAN, frame, side=None):
|
||||
if side not in IONIQ_6_CLUSTER_LANE_CHANGE_3C1:
|
||||
return []
|
||||
|
||||
ret = []
|
||||
frame_phase = IONIQ_6_CLUSTER_LANE_CHANGE_3C1_BURST.get(frame)
|
||||
if frame_phase is None and frame >= IONIQ_6_CLUSTER_LANE_CHANGE_3C1_STEADY_START and \
|
||||
(frame - IONIQ_6_CLUSTER_LANE_CHANGE_3C1_STEADY_START) % 20 == 0:
|
||||
frame_phase = "steady"
|
||||
if frame_phase is not None:
|
||||
ret.append((0x3C1, IONIQ_6_CLUSTER_LANE_CHANGE_3C1[side][frame_phase], CAN.ECAN))
|
||||
|
||||
if frame >= IONIQ_6_CLUSTER_LANE_CHANGE_3B5_START and (frame - IONIQ_6_CLUSTER_LANE_CHANGE_3B5_START) % 20 == 0:
|
||||
seq_3b5 = IONIQ_6_CLUSTER_LANE_CHANGE_3B5[side]
|
||||
ret.append((0x3B5, seq_3b5[((frame - IONIQ_6_CLUSTER_LANE_CHANGE_3B5_START) // 20) % len(seq_3b5)], CAN.ECAN))
|
||||
if frame >= IONIQ_6_CLUSTER_LANE_CHANGE_31A_START and (frame - IONIQ_6_CLUSTER_LANE_CHANGE_31A_START) % 100 == 0:
|
||||
seq_31a = IONIQ_6_CLUSTER_LANE_CHANGE_31A[side]
|
||||
ret.append((0x31A, seq_31a[((frame - IONIQ_6_CLUSTER_LANE_CHANGE_31A_START) // 100) % len(seq_31a)], CAN.ECAN))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control,
|
||||
main_mode_acc=1, jerk_lower=None, jerk_upper=None, direct_accel=False,
|
||||
lead_distance=None, lead_rel_speed=None, lead_visible=None, cruise_info=None):
|
||||
jerk = 5
|
||||
jn = jerk / 50
|
||||
if not enabled or gas_override:
|
||||
a_val, a_raw = 0, 0
|
||||
elif direct_accel:
|
||||
a_raw = accel
|
||||
a_val = accel
|
||||
else:
|
||||
a_raw = accel
|
||||
a_val = np.clip(accel, accel_last - jn, accel_last + jn)
|
||||
|
||||
if lead_distance is None and lead_rel_speed is None and lead_visible is None:
|
||||
acc_obj_dist = 1.0
|
||||
acc_obj_rel_spd = 0.0
|
||||
obj_valid = 0
|
||||
obj_status = 2
|
||||
else:
|
||||
lead_visible = bool(lead_visible)
|
||||
acc_obj_dist = float(np.clip(lead_distance if lead_visible else 0.0, 0.0, 204.7))
|
||||
acc_obj_rel_spd = float(np.clip(lead_rel_speed if lead_visible else 0.0, -16.4, 34.7))
|
||||
obj_valid = int(not lead_visible)
|
||||
obj_status = 0 if not (enabled and lead_visible) else (1 if gas_override else 2)
|
||||
|
||||
values = {
|
||||
"ACCMode": 0 if not enabled else (2 if gas_override else 1),
|
||||
"MainMode_ACC": 1,
|
||||
"MainMode_ACC": main_mode_acc,
|
||||
"StopReq": 1 if stopping else 0,
|
||||
"aReqValue": a_val,
|
||||
"aReqRaw": a_raw,
|
||||
"VSetDis": set_speed,
|
||||
"JerkLowerLimit": jerk if enabled else 1,
|
||||
"JerkUpperLimit": 3.0,
|
||||
"JerkLowerLimit": jerk_lower if jerk_lower is not None else (jerk if enabled else 1),
|
||||
"JerkUpperLimit": jerk_upper if jerk_upper is not None else 3.0,
|
||||
|
||||
"ACC_ObjDist": 1,
|
||||
"ObjValid": 0,
|
||||
"OBJ_STATUS": 2,
|
||||
"ACC_ObjDist": acc_obj_dist,
|
||||
"ACC_ObjRelSpd": acc_obj_rel_spd,
|
||||
"ObjValid": obj_valid,
|
||||
"OBJ_STATUS": obj_status,
|
||||
"SET_ME_2": 0x4,
|
||||
"SET_ME_3": 0x3,
|
||||
"SET_ME_TMP_64": 0x64,
|
||||
"DISTANCE_SETTING": hud_control.leadDistanceBars,
|
||||
}
|
||||
if cruise_info:
|
||||
values.update({s: cruise_info[s] for s in ("ACC_ObjDist", "ACC_ObjRelSpd")})
|
||||
|
||||
return packer.make_can_msg("SCC_CONTROL", CAN.ECAN, values)
|
||||
|
||||
@@ -320,3 +746,26 @@ def hkg_can_fd_checksum(address: int, sig, d: bytearray) -> int:
|
||||
elif len(d) == 32:
|
||||
crc ^= 0x9F5B
|
||||
return crc
|
||||
|
||||
|
||||
# Ioniq 5/6 / HKG LKA-steering: ADAS_DRV broadcasts ACCELERATOR_BRAKE_ALT (0x100) on bus 0.
|
||||
# The front radar uses this as its "host alive" heartbeat. When we disable ADAS_DRV the
|
||||
# radar stops publishing real object tracks. Spoof this message ourselves with valid CRC
|
||||
# and current pedal state so the radar keeps tracking.
|
||||
# Length is 24 bytes on Ioniq 6 (DBC declares 32 for ICE Hyundais, but EV firmware uses 24).
|
||||
# Byte templates captured from real ADAS broadcasts; only checksum, counter,
|
||||
# brake, and accelerator bits are updated for the radar heartbeat.
|
||||
_ACCEL_BRAKE_ALT_TEMPLATE = bytes.fromhex("000000020000fcff000000000020000055ff000068000000")
|
||||
_KIA_EV9_ACCEL_BRAKE_ALT_TEMPLATE = bytes.fromhex("00000000ff006f00e80400001201030055ffff0000000000")
|
||||
|
||||
def create_accelerator_brake_alt_spoof(bus: int, counter: int, brake_pressed: bool, accelerator_pressed: bool,
|
||||
car_fingerprint=None) -> CanData:
|
||||
template = _KIA_EV9_ACCEL_BRAKE_ALT_TEMPLATE if str(car_fingerprint) == "KIA_EV9" else _ACCEL_BRAKE_ALT_TEMPLATE
|
||||
d = bytearray(template)
|
||||
d[2] = counter & 0xFF # COUNTER (bit 16, 8-bit)
|
||||
d[4] = (d[4] & ~0x01) | (0x01 if brake_pressed else 0x00) # BRAKE_PRESSED (bit 32)
|
||||
d[22] = (d[22] & ~0x01) | (0x01 if accelerator_pressed else 0x00) # ACCELERATOR_PEDAL_PRESSED (bit 176)
|
||||
crc = hkg_can_fd_checksum(0x100, None, d)
|
||||
d[0] = crc & 0xFF
|
||||
d[1] = (crc >> 8) & 0xFF
|
||||
return CanData(0x100, bytes(d), bus)
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import time
|
||||
from opendbc.car import Bus, get_safety_config, structs, uds
|
||||
from opendbc.car import get_safety_config, structs, uds
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, CAR, DBC, \
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
|
||||
CANFD_UNSUPPORTED_LONGITUDINAL_CAR, \
|
||||
CANFD_SECURITYACCESS_CAR, \
|
||||
UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags
|
||||
from opendbc.car.hyundai.radar_interface import RADAR_START_ADDR
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \
|
||||
RADAR_LIVE_LONGITUDINAL_CAR, \
|
||||
UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \
|
||||
LEGACY_LONGITUDINAL_CAR, \
|
||||
HyundaiStarPilotSafetyFlags, \
|
||||
hyundai_cancel_button_enables_cruise, \
|
||||
kia_ev6_gt_line_longitudinal_tuning
|
||||
from opendbc.car.hyundai.radar_interface import get_radar_track_config, radar_tracks_available
|
||||
from opendbc.car.interfaces import CarInterfaceBase, ACCEL_MIN
|
||||
from opendbc.car.disable_ecu import disable_ecu, ecu_log
|
||||
from opendbc.car.hyundai.carcontroller import CarController
|
||||
from opendbc.car.hyundai.carstate import CarState
|
||||
@@ -20,6 +27,44 @@ ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.can
|
||||
|
||||
# Track when ECU disable happened - used to permanently suppress CAN errors from disabled ECU
|
||||
ECU_DISABLE_TIMESTAMP = 0.0
|
||||
KONA_NON_SCC_FCA_RADAR_ADDR = 0x602
|
||||
|
||||
|
||||
def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
|
||||
if not (ret.flags & HyundaiFlags.CANFD):
|
||||
return
|
||||
|
||||
ret.startingState = True
|
||||
ret.startAccel = 1.0
|
||||
ret.longitudinalActuatorDelay = 0.5
|
||||
ret.vEgoStopping = 0.3
|
||||
ret.vEgoStarting = 0.1
|
||||
ret.stoppingDecelRate = 0.4
|
||||
|
||||
|
||||
def apply_kia_ev6_gt_line_longitudinal_params(ret: structs.CarParams) -> None:
|
||||
ret.startAccel = 1.4
|
||||
ret.longitudinalActuatorDelay = 0.35
|
||||
ret.vEgoStarting = 0.5
|
||||
|
||||
|
||||
def apply_ecu_disable_failure_fallback(CP: structs.CarParams, params) -> None:
|
||||
params.put_bool("EcuDisableFailed", True)
|
||||
CP.safetyConfigs[-1].safetyParam &= ~HyundaiSafetyFlags.LONG.value
|
||||
CP.openpilotLongitudinalControl = False
|
||||
CP.pcmCruise = True
|
||||
|
||||
|
||||
def detect_kona_non_scc_radar_fca(candidate, fingerprint, car_fw) -> bool:
|
||||
if candidate != CAR.HYUNDAI_KONA_NON_SCC:
|
||||
return False
|
||||
|
||||
if any(fw.ecu == Ecu.fwdRadar for fw in car_fw):
|
||||
return True
|
||||
|
||||
# Some non-SCC Kona trims have FCA radar tracks without SCC. Use PT FCA11
|
||||
# status on those cars; camera-bus FCA11 is not continuously published.
|
||||
return KONA_NON_SCC_FCA_RADAR_ADDR in fingerprint[1]
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@@ -27,6 +72,15 @@ class CarInterface(CarInterfaceBase):
|
||||
CarController = CarController
|
||||
RadarInterface = RadarInterface
|
||||
|
||||
@staticmethod
|
||||
def get_pid_accel_limits(CP, current_speed, cruise_speed):
|
||||
return ACCEL_MIN, CarControllerParams.ACCEL_MAX
|
||||
|
||||
@staticmethod
|
||||
def apply_post_fingerprint_params(CP: structs.CarParams, candidate, fingerprint, car_fw) -> None:
|
||||
if kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin):
|
||||
apply_kia_ev6_gt_line_longitudinal_params(CP)
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
ret.brand = "hyundai"
|
||||
@@ -46,6 +100,9 @@ class CarInterface(CarInterfaceBase):
|
||||
# this needs to be figured out for cars without an ADAS ECU
|
||||
# Cars in CANFD_SECURITYACCESS_CAR are known to have ADAS ECUs that work with SecurityAccess
|
||||
ret.alphaLongitudinalAvailable = False
|
||||
if lka_steering and ret.flags & HyundaiFlags.CANFD_ANGLE_STEERING and candidate not in CANFD_ANGLE_LONGITUDINAL_CAR:
|
||||
# Most angle-steering LKA platforms still need stock longitudinal validation.
|
||||
ret.alphaLongitudinalAvailable = False
|
||||
|
||||
ret.enableBsm = 0x1ba in fingerprint[CAN.ECAN]
|
||||
|
||||
@@ -91,19 +148,23 @@ class CarInterface(CarInterfaceBase):
|
||||
if ret.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
ret.steerControlType = structs.CarParams.SteerControlType.angle
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CANFD_ANGLE_STEERING.value
|
||||
if ret.flags & HyundaiFlags.CCNC and not ret.flags & HyundaiFlags.CANFD_LKA_STEERING:
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CCNC.value
|
||||
|
||||
else:
|
||||
# Shared configuration for non CAN-FD cars
|
||||
ret.alphaLongitudinalAvailable = candidate not in UNSUPPORTED_LONGITUDINAL_CAR
|
||||
ret.enableBsm = 0x58b in fingerprint[0]
|
||||
ret.alphaLongitudinalAvailable = candidate not in UNSUPPORTED_LONGITUDINAL_CAR or candidate in LEGACY_LONGITUDINAL_CAR
|
||||
ret.enableBsm = 0x58b in fingerprint[CAN.ECAN]
|
||||
|
||||
# Send LFA message on cars with HDA
|
||||
if 0x485 in fingerprint[2]:
|
||||
if 0x485 in fingerprint[CAN.CAM]:
|
||||
ret.flags |= HyundaiFlags.SEND_LFA.value
|
||||
|
||||
# These cars use the FCA11 message for the AEB and FCW signals, all others use SCC12
|
||||
if 0x38d in fingerprint[0] or 0x38d in fingerprint[2]:
|
||||
if 0x38d in fingerprint[CAN.ECAN] or 0x38d in fingerprint[CAN.CAM]:
|
||||
ret.flags |= HyundaiFlags.USE_FCA.value
|
||||
if detect_kona_non_scc_radar_fca(candidate, fingerprint, car_fw):
|
||||
ret.flags |= HyundaiFlags.NON_SCC_RADAR_FCA.value
|
||||
|
||||
if ret.flags & HyundaiFlags.LEGACY:
|
||||
# these cars require a special panda safety mode due to missing counters and checksums in the messages
|
||||
@@ -114,15 +175,13 @@ class CarInterface(CarInterfaceBase):
|
||||
if ret.flags & HyundaiFlags.CAMERA_SCC:
|
||||
ret.safetyConfigs[0].safetyParam |= HyundaiSafetyFlags.CAMERA_SCC.value
|
||||
|
||||
# These cars have the LFA button on the steering wheel
|
||||
if 0x391 in fingerprint[0]:
|
||||
ret.flags |= HyundaiFlags.HAS_LDA_BUTTON.value
|
||||
|
||||
if candidate == CAR.KIA_FORTE:
|
||||
has_scc_fw = any(fw.ecu == Ecu.fwdRadar for fw in car_fw)
|
||||
has_scc_can = any(addr in fingerprint[bus] for bus in (0, 2) for addr in (0x420, 0x421))
|
||||
if not (has_scc_fw or has_scc_can):
|
||||
ret.flags |= HyundaiFlags.NON_SCC.value
|
||||
# These cars expose an LKAS/LFA steering-wheel button that StarPilot can customize.
|
||||
if 0x391 in fingerprint[0] or ret.flags & HyundaiFlags.CAN_CANFD_BLENDED:
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON.value
|
||||
if ret.flags & HyundaiFlags.CAN_CANFD_BLENDED:
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CAN_CANFD_BLENDED.value
|
||||
if hyundai_cancel_button_enables_cruise(candidate):
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CANCEL_BTN_ENABLE.value
|
||||
|
||||
# Common lateral control setup
|
||||
|
||||
@@ -141,21 +200,21 @@ class CarInterface(CarInterfaceBase):
|
||||
# see https://github.com/commaai/opendbc/pull/1137/
|
||||
ret.dashcamOnly = True
|
||||
|
||||
if ret.flags & HyundaiFlags.NON_SCC:
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.NON_SCC.value
|
||||
|
||||
# Common longitudinal control setup
|
||||
|
||||
ret.radarUnavailable = RADAR_START_ADDR not in fingerprint[1] or Bus.radar not in DBC[ret.carFingerprint]
|
||||
radar_config = get_radar_track_config(ret.carFingerprint, ret.flags)
|
||||
radar_available = radar_tracks_available(radar_config, fingerprint)
|
||||
ret.radarUnavailable = not radar_available
|
||||
if ret.flags & HyundaiFlags.NON_SCC:
|
||||
ret.alphaLongitudinalAvailable = False
|
||||
ret.openpilotLongitudinalControl = alpha_long and ret.alphaLongitudinalAvailable
|
||||
# When longitudinal is enabled, we disable the ADAS ECU which stops radar messages
|
||||
# Force radarUnavailable to prevent CAN Error from missing radar messages
|
||||
if ret.openpilotLongitudinalControl:
|
||||
if ret.openpilotLongitudinalControl and not (candidate in RADAR_LIVE_LONGITUDINAL_CAR and radar_available):
|
||||
ret.radarUnavailable = True
|
||||
ret.pcmCruise = not ret.openpilotLongitudinalControl
|
||||
ret.startingState = True
|
||||
ret.vEgoStarting = 0.1
|
||||
ret.startAccel = 1.0
|
||||
ret.longitudinalActuatorDelay = 0.5
|
||||
apply_platform_longitudinal_params(ret)
|
||||
|
||||
if ret.openpilotLongitudinalControl:
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.LONG.value
|
||||
@@ -168,6 +227,25 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
# Car specific configuration overrides
|
||||
|
||||
if candidate == CAR.GENESIS_G90:
|
||||
ret.stoppingDecelRate = 0.55
|
||||
ret.vEgoStopping = 0.8
|
||||
|
||||
if candidate == CAR.HYUNDAI_PALISADE_2023:
|
||||
ret.startAccel = 1.3
|
||||
ret.stopAccel = -0.85
|
||||
ret.stoppingDecelRate = 0.35
|
||||
ret.vEgoStarting = 0.5
|
||||
ret.vEgoStopping = 0.35
|
||||
|
||||
if candidate == CAR.HYUNDAI_IONIQ_6:
|
||||
ret.longitudinalActuatorDelay = 0.6
|
||||
|
||||
if candidate == CAR.KIA_NIRO_PHEV_2022:
|
||||
ret.stopAccel = -1.4
|
||||
ret.stoppingDecelRate = 0.5
|
||||
ret.vEgoStopping = 0.7
|
||||
|
||||
if candidate == CAR.KIA_OPTIMA_G4_FL:
|
||||
ret.steerActuatorDelay = 0.2
|
||||
|
||||
@@ -185,7 +263,7 @@ class CarInterface(CarInterfaceBase):
|
||||
params = Params()
|
||||
|
||||
if communication_control is None:
|
||||
if CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
if CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR:
|
||||
# Don't use 0x80 suppress bit so we can read the ECU response.
|
||||
# Use ENABLE_RX_DISABLE_TX (0x01) so the ECU can still receive from rear radars for BSM
|
||||
# while blocking SCC TX.
|
||||
@@ -197,7 +275,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===")
|
||||
|
||||
if CP.openpilotLongitudinalControl and not (CP.flags & (HyundaiFlags.CANFD_CAMERA_SCC | HyundaiFlags.CAMERA_SCC)):
|
||||
addr, bus = 0x7d0, CanBus(CP).ECAN if CP.flags & HyundaiFlags.CANFD else 0
|
||||
addr, bus = 0x7d0, CanBus(CP).ECAN if CP.flags & (HyundaiFlags.CANFD | HyundaiFlags.CAN_CANFD_BLENDED) else 0
|
||||
if CP.flags & HyundaiFlags.CANFD_LKA_STEERING.value:
|
||||
addr, bus = 0x730, CanBus(CP).ECAN
|
||||
|
||||
@@ -205,7 +283,8 @@ class CarInterface(CarInterfaceBase):
|
||||
# If it fails (READY mode returns NRC 0x22, or timeout), strip LONG safety flag
|
||||
# so panda forwards stock SCC messages normally (lateral-only mode).
|
||||
ecu_log(f"=== ECU DISABLE attempt: addr=0x{addr:x}, bus={bus} ===")
|
||||
ecu_disabled = disable_ecu(can_recv, can_send, bus=bus, addr=addr, com_cont_req=communication_control)
|
||||
ecu_disabled = disable_ecu(can_recv, can_send, bus=bus, addr=addr, com_cont_req=communication_control,
|
||||
reset=bool(CP.flags & HyundaiFlags.CAN_CANFD_BLENDED))
|
||||
|
||||
if CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
# Ioniq 6: track success/failure to auto-switch between openpilot long and stock ACC
|
||||
@@ -215,15 +294,15 @@ class CarInterface(CarInterfaceBase):
|
||||
params.put_bool("ExperimentalMode", True)
|
||||
ecu_log("=== ECU DISABLE SUCCESS - Longitudinal + Experimental ENABLED ===")
|
||||
else:
|
||||
params.put_bool("EcuDisableFailed", True)
|
||||
CP.safetyConfigs[-1].safetyParam &= ~HyundaiSafetyFlags.LONG.value
|
||||
apply_ecu_disable_failure_fallback(CP, params)
|
||||
ecu_log(f"=== ECU DISABLE FAILED - safetyParam stripped to {CP.safetyConfigs[-1].safetyParam}, lateral-only mode ===")
|
||||
else:
|
||||
# Other cars: just log, don't change safety params or params store
|
||||
if ecu_disabled:
|
||||
params.put_bool("EcuDisableFailed", False)
|
||||
ecu_log("=== ECU DISABLE SUCCESS ===")
|
||||
else:
|
||||
ecu_log("=== ECU DISABLE FAILED ===")
|
||||
apply_ecu_disable_failure_fallback(CP, params)
|
||||
ecu_log(f"=== ECU DISABLE FAILED - safetyParam stripped to {CP.safetyConfigs[-1].safetyParam}, lateral-only mode ===")
|
||||
|
||||
# for blinkers
|
||||
if CP.flags & HyundaiFlags.ENABLE_BLINKERS:
|
||||
|
||||
@@ -1,40 +1,141 @@
|
||||
import math
|
||||
from dataclasses import dataclass, replace
|
||||
|
||||
from opendbc.can import CANParser
|
||||
from opendbc.can.dbc import DBC as DBCReader
|
||||
from opendbc.can.parser import get_raw_value
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.interfaces import RadarInterfaceBase
|
||||
from opendbc.car.hyundai.values import DBC
|
||||
from opendbc.car.hyundai.values import CAR, DBC, HyundaiFlags, HYUNDAI_MANDO_FRONT_RADAR_DBC, HYUNDAI_MRREVO14F_RADAR_DBC, \
|
||||
HYUNDAI_MRR30_RADAR_DBC, HYUNDAI_MRR35_RADAR_DBC
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
|
||||
RADAR_START_ADDR = 0x500
|
||||
RADAR_MSG_COUNT = 32
|
||||
G90_RADAR_MSG_COUNT = 64
|
||||
MRREVO14F_RADAR_START_ADDR = 0x602
|
||||
MRREVO14F_RADAR_MSG_COUNT = 16
|
||||
MRR30_RADAR_START_ADDR = 0x210
|
||||
MRR30_RADAR_MSG_COUNT = 16
|
||||
MRR35_RADAR_START_ADDR = 0x3A5
|
||||
MRR35_RADAR_MSG_COUNT = 32
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RadarTrackConfig:
|
||||
start_addr: int
|
||||
msg_count: int
|
||||
radar_type: str
|
||||
bus: int = 1
|
||||
frequency: int = 50
|
||||
parser_msg_count: int | None = None
|
||||
expected_length: int | None = None
|
||||
|
||||
@property
|
||||
def can_parser_msg_count(self) -> int:
|
||||
return self.parser_msg_count if self.parser_msg_count is not None else self.msg_count
|
||||
|
||||
|
||||
RADAR_TRACK_CONFIGS = {
|
||||
HYUNDAI_MANDO_FRONT_RADAR_DBC: RadarTrackConfig(RADAR_START_ADDR, RADAR_MSG_COUNT, "mando"),
|
||||
HYUNDAI_MRREVO14F_RADAR_DBC: RadarTrackConfig(MRREVO14F_RADAR_START_ADDR, MRREVO14F_RADAR_MSG_COUNT, "mrrevo14f"),
|
||||
HYUNDAI_MRR30_RADAR_DBC: RadarTrackConfig(MRR30_RADAR_START_ADDR, MRR30_RADAR_MSG_COUNT, "mrr30", bus=0, expected_length=32),
|
||||
HYUNDAI_MRR35_RADAR_DBC: RadarTrackConfig(MRR35_RADAR_START_ADDR, MRR35_RADAR_MSG_COUNT, "mrr35", bus=0, frequency=20, expected_length=24),
|
||||
}
|
||||
|
||||
# POC for parsing corner radars: https://github.com/commaai/openpilot/pull/24221/
|
||||
|
||||
|
||||
def get_radar_can_parser(CP):
|
||||
if Bus.radar not in DBC[CP.carFingerprint]:
|
||||
def get_radar_track_config(car_fingerprint, flags: int = 0) -> RadarTrackConfig | None:
|
||||
radar_dbc = DBC[car_fingerprint].get(Bus.radar)
|
||||
if car_fingerprint == CAR.GENESIS_G90 and radar_dbc == HYUNDAI_MANDO_FRONT_RADAR_DBC:
|
||||
return RadarTrackConfig(RADAR_START_ADDR, G90_RADAR_MSG_COUNT, "mando", parser_msg_count=RADAR_MSG_COUNT)
|
||||
|
||||
radar_config = RADAR_TRACK_CONFIGS.get(radar_dbc)
|
||||
if radar_config is None:
|
||||
return None
|
||||
|
||||
messages = [(f"RADAR_TRACK_{addr:x}", 50) for addr in range(RADAR_START_ADDR, RADAR_START_ADDR + RADAR_MSG_COUNT)]
|
||||
return CANParser(DBC[CP.carFingerprint][Bus.radar], messages, 1)
|
||||
if car_fingerprint == CAR.HYUNDAI_IONIQ_6 and flags & HyundaiFlags.CANFD_CAMERA_SCC:
|
||||
return replace(radar_config, bus=1)
|
||||
|
||||
return radar_config
|
||||
|
||||
|
||||
def radar_tracks_available(radar_config: RadarTrackConfig | None, fingerprint) -> bool:
|
||||
if radar_config is None:
|
||||
return False
|
||||
|
||||
msg_len = fingerprint[radar_config.bus].get(radar_config.start_addr)
|
||||
if msg_len is None:
|
||||
return False
|
||||
|
||||
return radar_config.expected_length is None or msg_len == radar_config.expected_length
|
||||
|
||||
|
||||
def get_radar_can_parser(CP, radar_config):
|
||||
if radar_config is None:
|
||||
return None
|
||||
|
||||
messages = [(f"RADAR_TRACK_{addr:x}", radar_config.frequency)
|
||||
for addr in range(radar_config.start_addr, radar_config.start_addr + radar_config.can_parser_msg_count)]
|
||||
return CANParser(DBC[CP.carFingerprint][Bus.radar], messages, radar_config.bus)
|
||||
|
||||
|
||||
class RadarInterface(RadarInterfaceBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
self.radar_config = get_radar_track_config(CP.carFingerprint, CP.flags)
|
||||
self.updated_messages = set()
|
||||
self.trigger_msg = RADAR_START_ADDR + RADAR_MSG_COUNT - 1
|
||||
self.trigger_msg = (self.radar_config.start_addr + self.radar_config.can_parser_msg_count - 1
|
||||
if self.radar_config is not None else RADAR_START_ADDR)
|
||||
self.track_id = 0
|
||||
self.g90_extended_mando = (CP.carFingerprint == CAR.GENESIS_G90 and self.radar_config is not None and
|
||||
self.radar_config.msg_count > self.radar_config.can_parser_msg_count)
|
||||
self.g90_mando_signals = []
|
||||
if self.g90_extended_mando:
|
||||
radar_dbc = DBCReader(DBC[CP.carFingerprint][Bus.radar])
|
||||
self.g90_mando_signals = list(radar_dbc.addr_to_msg[RADAR_START_ADDR].sigs.values())
|
||||
|
||||
self.radar_off_can = CP.radarUnavailable
|
||||
self.rcp = get_radar_can_parser(CP)
|
||||
# Probe whether radar tracks still exist on the Ioniq 6 while OP long is active,
|
||||
# without changing planner behavior yet.
|
||||
self.ioniq_6_radar_probe = CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and CP.openpilotLongitudinalControl and self.radar_off_can
|
||||
self.ioniq_6_radar_probe_logged = False
|
||||
self.ioniq_6_radar_probe_updates = 0
|
||||
self.rcp = get_radar_can_parser(CP, self.radar_config)
|
||||
|
||||
# Precompute (addr, "RADAR_TRACK_xxx") pairs once. _update runs on the
|
||||
# CAN-driven card loop (core 4, shared with controlsd/selfdrived), so avoid
|
||||
# rebuilding 32 f-strings per radar frame.
|
||||
self.track_addrs: list[tuple[int, str]] = []
|
||||
if self.radar_config is not None:
|
||||
self.track_addrs = [(addr, f"RADAR_TRACK_{addr:x}")
|
||||
for addr in range(self.radar_config.start_addr,
|
||||
self.radar_config.start_addr + self.radar_config.can_parser_msg_count)]
|
||||
|
||||
def update(self, can_strings):
|
||||
if self.ioniq_6_radar_probe and self.rcp is not None and not self.ioniq_6_radar_probe_logged:
|
||||
vls = self.rcp.update(can_strings)
|
||||
self.updated_messages.update(vls)
|
||||
self.ioniq_6_radar_probe_updates += 1
|
||||
|
||||
if self.trigger_msg in self.updated_messages:
|
||||
rr = self._update(self.updated_messages)
|
||||
cloudlog.warning(f"Ioniq 6 radar probe: saw {len(rr.points)} radar tracks with radarUnavailable forced on")
|
||||
self.updated_messages.clear()
|
||||
self.ioniq_6_radar_probe_logged = True
|
||||
elif self.ioniq_6_radar_probe_updates >= 500:
|
||||
cloudlog.warning("Ioniq 6 radar probe: no radar track frames observed after startup")
|
||||
self.ioniq_6_radar_probe_logged = True
|
||||
self.updated_messages.clear()
|
||||
|
||||
if self.radar_off_can or (self.rcp is None):
|
||||
return super().update(None)
|
||||
|
||||
vls = self.rcp.update(can_strings)
|
||||
self.updated_messages.update(vls)
|
||||
if self.g90_extended_mando:
|
||||
self._update_g90_extended_mando_tracks(can_strings)
|
||||
|
||||
if self.trigger_msg not in self.updated_messages:
|
||||
return None
|
||||
@@ -44,6 +145,46 @@ class RadarInterface(RadarInterfaceBase):
|
||||
|
||||
return rr
|
||||
|
||||
def _decode_g90_mando_values(self, dat: bytes):
|
||||
vals = {}
|
||||
for sig in self.g90_mando_signals:
|
||||
raw = get_raw_value(dat, sig)
|
||||
if sig.is_signed:
|
||||
raw -= ((raw >> (sig.size - 1)) & 1) * (1 << sig.size)
|
||||
vals[sig.name] = raw * sig.factor + sig.offset
|
||||
return vals
|
||||
|
||||
def _update_g90_extended_mando_tracks(self, can_strings):
|
||||
if self.radar_config is None:
|
||||
return
|
||||
|
||||
start_addr = self.radar_config.start_addr + self.radar_config.can_parser_msg_count
|
||||
end_addr = self.radar_config.start_addr + self.radar_config.msg_count
|
||||
|
||||
for _, frames in can_strings:
|
||||
for address, dat, src in frames:
|
||||
if src != self.radar_config.bus or not (start_addr <= address < end_addr) or len(dat) < 8:
|
||||
continue
|
||||
|
||||
self.updated_messages.add(address)
|
||||
msg = self._decode_g90_mando_values(dat)
|
||||
valid = msg["STATE"] in (3, 4)
|
||||
if valid:
|
||||
if address not in self.pts:
|
||||
self.pts[address] = structs.RadarData.RadarPoint()
|
||||
self.pts[address].trackId = self.track_id
|
||||
self.track_id += 1
|
||||
|
||||
azimuth = math.radians(msg["AZIMUTH"])
|
||||
self.pts[address].measured = True
|
||||
self.pts[address].dRel = math.cos(azimuth) * msg["LONG_DIST"]
|
||||
self.pts[address].yRel = 0.5 * -math.sin(azimuth) * msg["LONG_DIST"]
|
||||
self.pts[address].vRel = msg["REL_SPEED"]
|
||||
self.pts[address].aRel = msg["REL_ACCEL"]
|
||||
self.pts[address].yvRel = float("nan")
|
||||
elif address in self.pts:
|
||||
del self.pts[address]
|
||||
|
||||
def _update(self, updated_messages):
|
||||
ret = structs.RadarData()
|
||||
if self.rcp is None:
|
||||
@@ -52,8 +193,77 @@ class RadarInterface(RadarInterfaceBase):
|
||||
if not self.rcp.can_valid:
|
||||
ret.errors.canError = True
|
||||
|
||||
for addr in range(RADAR_START_ADDR, RADAR_START_ADDR + RADAR_MSG_COUNT):
|
||||
msg = self.rcp.vl[f"RADAR_TRACK_{addr:x}"]
|
||||
if self.radar_config is None:
|
||||
return ret
|
||||
|
||||
radar_type = self.radar_config.radar_type
|
||||
vl = self.rcp.vl
|
||||
|
||||
for addr, track_name in self.track_addrs:
|
||||
msg = vl[track_name]
|
||||
|
||||
if radar_type == "mrr30":
|
||||
for i in ("1", "2"):
|
||||
track_key = addr * 2 + int(i) - 1
|
||||
if track_key not in self.pts:
|
||||
self.pts[track_key] = structs.RadarData.RadarPoint()
|
||||
self.pts[track_key].trackId = self.track_id
|
||||
self.track_id += 1
|
||||
|
||||
valid = msg[f"{i}_STATE"] in (3, 4)
|
||||
if valid:
|
||||
pt = self.pts[track_key]
|
||||
pt.measured = True
|
||||
pt.dRel = msg[f"{i}_LONG_DIST"]
|
||||
pt.yRel = msg[f"{i}_LAT_DIST"]
|
||||
pt.vRel = msg[f"{i}_REL_SPEED"]
|
||||
pt.aRel = float("nan")
|
||||
pt.yvRel = float("nan")
|
||||
else:
|
||||
del self.pts[track_key]
|
||||
continue
|
||||
|
||||
if radar_type == "mrrevo14f":
|
||||
for i in ("1", "2"):
|
||||
track_key = addr * 2 + int(i) - 1
|
||||
valid = msg[f"{i}_DISTANCE"] != 255.75
|
||||
if valid:
|
||||
pt = self.pts.get(track_key)
|
||||
if pt is None:
|
||||
pt = structs.RadarData.RadarPoint()
|
||||
pt.trackId = self.track_id
|
||||
self.track_id += 1
|
||||
self.pts[track_key] = pt
|
||||
pt.measured = True
|
||||
pt.dRel = msg[f"{i}_DISTANCE"]
|
||||
pt.yRel = msg[f"{i}_LATERAL"]
|
||||
pt.vRel = msg[f"{i}_SPEED"]
|
||||
pt.aRel = float("nan")
|
||||
pt.yvRel = float("nan")
|
||||
elif track_key in self.pts:
|
||||
del self.pts[track_key]
|
||||
continue
|
||||
|
||||
if radar_type == "mrr35":
|
||||
# Most of the 32 channels are empty each frame. Only allocate a point
|
||||
# when the channel is valid; drop it otherwise. Avoids the per-frame
|
||||
# alloc-then-delete churn on the ~27 idle channels.
|
||||
if msg["STATE"] in (3, 4):
|
||||
pt = self.pts.get(addr)
|
||||
if pt is None:
|
||||
pt = structs.RadarData.RadarPoint()
|
||||
pt.trackId = self.track_id
|
||||
self.track_id += 1
|
||||
self.pts[addr] = pt
|
||||
pt.measured = True
|
||||
pt.dRel = msg["LONG_DIST"]
|
||||
pt.yRel = msg["LAT_DIST"]
|
||||
pt.vRel = msg["REL_SPEED"]
|
||||
pt.aRel = msg["REL_ACCEL"]
|
||||
pt.yvRel = float("nan")
|
||||
elif addr in self.pts:
|
||||
del self.pts[addr]
|
||||
continue
|
||||
|
||||
if addr not in self.pts:
|
||||
self.pts[addr] = structs.RadarData.RadarPoint()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,32 +1,33 @@
|
||||
import re
|
||||
from dataclasses import dataclass, field, replace
|
||||
from dataclasses import dataclass, field
|
||||
from enum import IntFlag
|
||||
|
||||
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
|
||||
from opendbc.car.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL, ISO_LATERAL_JERK
|
||||
from opendbc.car.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car.docs_definitions import CarHarness, CarDocs, CarParts
|
||||
from opendbc.car.docs_definitions import CarHarness, CarDocs, CarParts, SupportType
|
||||
from opendbc.car.fw_query_definitions import FwQueryConfig, Request, p16
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
AVERAGE_ROAD_ROLL = 0.06 # conservative roll margin used by Hyundai CAN-FD angle steering safety
|
||||
HYUNDAI_MANDO_FRONT_RADAR_DBC = "hyundai_kia_mando_front_radar_generated"
|
||||
HYUNDAI_MRREVO14F_RADAR_DBC = "hyundai_mrrevo14f_radar_generated"
|
||||
HYUNDAI_MRR30_RADAR_DBC = "hyundai_mrr30_radar_generated"
|
||||
HYUNDAI_MRR35_RADAR_DBC = "hyundai_mrr35_radar_generated"
|
||||
|
||||
|
||||
class CarControllerParams:
|
||||
ACCEL_MIN = -3.5 # m/s
|
||||
ACCEL_MAX = 2.0 # m/s
|
||||
ACCEL_MAX = 3.5 # m/s
|
||||
ANGLE_LIMITS: AngleSteeringLimits = AngleSteeringLimits(
|
||||
180,
|
||||
360,
|
||||
([], []),
|
||||
([], []),
|
||||
MAX_LATERAL_ACCEL=ISO_LATERAL_ACCEL + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL),
|
||||
MAX_LATERAL_JERK=ISO_LATERAL_JERK + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL),
|
||||
MAX_LATERAL_JERK=3.0 + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL),
|
||||
MAX_ANGLE_RATE=5,
|
||||
)
|
||||
ANGLE_MAX_TORQUE_REDUCTION_GAIN = 1.0
|
||||
ANGLE_MIN_TORQUE_REDUCTION_GAIN = 0.6
|
||||
ANGLE_ACTIVE_TORQUE_REDUCTION_GAIN = 0.6
|
||||
|
||||
def __init__(self, CP, vEgoRaw=100.):
|
||||
self.ANGLE_LIMITS = self.ANGLE_LIMITS
|
||||
@@ -53,17 +54,14 @@ class CarControllerParams:
|
||||
if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
self.STEER_THRESHOLD = 175
|
||||
|
||||
# The Sportage angle port is rough at low speed on the higher global jerk limit.
|
||||
# Keep the branch-wide higher limit for other cars, but restore the older calmer
|
||||
# jerk ceiling on this port only.
|
||||
if CP.carFingerprint == CAR.KIA_SPORTAGE_HEV_2026:
|
||||
self.ANGLE_LIMITS = replace(self.ANGLE_LIMITS,
|
||||
MAX_LATERAL_JERK=3.0 + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL))
|
||||
elif CP.flags & HyundaiFlags.CANFD:
|
||||
pass
|
||||
|
||||
# To determine the limit for your car, find the maximum value that the stock LKAS will request.
|
||||
# If the max stock LKAS request is <384, add your car to this list.
|
||||
elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.HYUNDAI_ELANTRA, CAR.HYUNDAI_ELANTRA_GT_I30, CAR.HYUNDAI_IONIQ,
|
||||
CAR.HYUNDAI_IONIQ_EV_LTD, CAR.HYUNDAI_SANTA_FE_PHEV_2022, CAR.HYUNDAI_SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV,
|
||||
CAR.KIA_FORTE_2019_NON_SCC, CAR.KIA_FORTE_2021_NON_SCC,
|
||||
CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL, CAR.KIA_SORENTO):
|
||||
self.STEER_MAX = 255
|
||||
|
||||
@@ -78,6 +76,13 @@ class CarControllerParams:
|
||||
self.STEER_DELTA_UP = 2
|
||||
self.STEER_DELTA_DOWN = 3
|
||||
|
||||
elif CP.flags & HyundaiFlags.CAN_CANFD_BLENDED:
|
||||
self.STEER_MAX = 404
|
||||
self.STEER_DRIVER_ALLOWANCE = 50
|
||||
self.STEER_THRESHOLD = 150
|
||||
self.STEER_DELTA_UP = 2
|
||||
self.STEER_DELTA_DOWN = 3
|
||||
|
||||
# Default for most HKG
|
||||
else:
|
||||
self.STEER_MAX = 384
|
||||
@@ -95,6 +100,10 @@ class HyundaiSafetyFlags(IntFlag):
|
||||
FCEV_GAS = 256
|
||||
ALT_LIMITS_2 = 512
|
||||
CANFD_ANGLE_STEERING = 1024
|
||||
NON_SCC = 4096
|
||||
CAN_CANFD_BLENDED = 8192
|
||||
CANCEL_BTN_ENABLE = 16384
|
||||
CCNC = 32768
|
||||
|
||||
|
||||
class HyundaiStarPilotSafetyFlags(IntFlag):
|
||||
@@ -158,8 +167,6 @@ class HyundaiFlags(IntFlag):
|
||||
|
||||
MIN_STEER_32_MPH = 2 ** 23
|
||||
|
||||
HAS_LDA_BUTTON = 2 ** 24
|
||||
|
||||
FCEV = 2 ** 25
|
||||
|
||||
ALT_LIMITS_2 = 2 ** 26
|
||||
@@ -170,30 +177,71 @@ class HyundaiFlags(IntFlag):
|
||||
# Hyundai CAN-FD angle-based steering path used on newer ADAS platforms.
|
||||
CANFD_ANGLE_STEERING = 2 ** 28
|
||||
|
||||
# Palisade/Telluride 2023+ uses CAN routing with CAN-FD-style checksums.
|
||||
CAN_CANFD_BLENDED = 2 ** 29
|
||||
|
||||
# Non-SCC platforms do not all share the same FCA source or availability.
|
||||
NON_SCC_NO_FCA = 2 ** 30
|
||||
NON_SCC_RADAR_FCA = 2 ** 31
|
||||
|
||||
# Connected Car Navigation Cockpit CAN-FD platforms.
|
||||
CCNC = 2 ** 24
|
||||
|
||||
|
||||
@dataclass
|
||||
class HyundaiCarDocs(CarDocs):
|
||||
package: str = "Smart Cruise Control (SCC)"
|
||||
|
||||
|
||||
@dataclass
|
||||
class HyundaiNonSccCarDocs(CarDocs):
|
||||
package: str = "No Smart Cruise Control (Non-SCC)"
|
||||
support_type: SupportType = SupportType.COMMUNITY
|
||||
support_link: str = "community"
|
||||
|
||||
|
||||
@dataclass
|
||||
class HyundaiPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: "hyundai_kia_generic"})
|
||||
radar_dbc: str | None = None
|
||||
|
||||
def init(self):
|
||||
if self.flags & HyundaiFlags.MANDO_RADAR:
|
||||
self.dbc_dict = {Bus.pt: "hyundai_kia_generic", Bus.radar: 'hyundai_kia_mando_front_radar_generated'}
|
||||
if self.radar_dbc is not None:
|
||||
self.dbc_dict = {Bus.pt: "hyundai_kia_generic", Bus.radar: self.radar_dbc}
|
||||
elif self.flags & HyundaiFlags.MANDO_RADAR:
|
||||
self.dbc_dict = {Bus.pt: "hyundai_kia_generic", Bus.radar: HYUNDAI_MANDO_FRONT_RADAR_DBC}
|
||||
|
||||
if self.flags & HyundaiFlags.MIN_STEER_32_MPH:
|
||||
self.specs = self.specs.override(minSteerSpeed=32 * CV.MPH_TO_MS)
|
||||
|
||||
if self.flags & HyundaiFlags.CAN_CANFD_BLENDED:
|
||||
self.dbc_dict = {Bus.pt: "hyundai_palisade_2023_generated"}
|
||||
|
||||
|
||||
@dataclass
|
||||
class HyundaiCanFDPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: "hyundai_canfd_generated"})
|
||||
radar_dbc: str | None = None
|
||||
|
||||
def init(self):
|
||||
self.flags |= HyundaiFlags.CANFD
|
||||
if self.radar_dbc is not None:
|
||||
self.dbc_dict = {Bus.pt: "hyundai_canfd_generated", Bus.radar: self.radar_dbc}
|
||||
|
||||
|
||||
@dataclass
|
||||
class HyundaiNonSccPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: "hyundai_kia_generic"})
|
||||
radar_dbc: str | None = None
|
||||
|
||||
def init(self):
|
||||
if self.radar_dbc is not None:
|
||||
self.dbc_dict = {Bus.pt: "hyundai_kia_generic", Bus.radar: self.radar_dbc}
|
||||
|
||||
self.flags |= HyundaiFlags.NON_SCC
|
||||
|
||||
if self.flags & HyundaiFlags.MIN_STEER_32_MPH:
|
||||
self.specs = self.specs.override(minSteerSpeed=32 * CV.MPH_TO_MS)
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
@@ -210,6 +258,14 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=1675, wheelbase=2.885, steerRatio=14.5),
|
||||
flags=HyundaiFlags.HYBRID,
|
||||
)
|
||||
HYUNDAI_AZERA_HEV_7TH_GEN = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Hyundai Azera Hybrid (with HDA II & LFA2) 2025", "Highway Driving Assist II & Lane Follow Assist 2",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_s])),
|
||||
],
|
||||
CarSpecs(mass=1720, wheelbase=2.895, steerRatio=13.5),
|
||||
flags=HyundaiFlags.CANFD_ANGLE_STEERING,
|
||||
)
|
||||
HYUNDAI_ELANTRA = HyundaiPlatformConfig(
|
||||
[
|
||||
# TODO: 2017-18 could be Hyundai G
|
||||
@@ -251,7 +307,7 @@ class CAR(Platforms):
|
||||
HYUNDAI_IONIQ = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Ioniq Hybrid 2017-19", car_parts=CarParts.common([CarHarness.hyundai_c]))],
|
||||
CarSpecs(mass=1490, wheelbase=2.7, steerRatio=13.73, tireStiffnessFactor=0.385),
|
||||
flags=HyundaiFlags.HYBRID | HyundaiFlags.MIN_STEER_32_MPH,
|
||||
flags=HyundaiFlags.HYBRID | HyundaiFlags.MIN_STEER_32_MPH | HyundaiFlags.MANDO_RADAR,
|
||||
)
|
||||
HYUNDAI_IONIQ_HEV_2022 = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Ioniq Hybrid 2020-22", car_parts=CarParts.common([CarHarness.hyundai_h]))],
|
||||
@@ -288,6 +344,16 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=1491, wheelbase=2.6, steerRatio=13.42, tireStiffnessFactor=0.385),
|
||||
flags=HyundaiFlags.CAMERA_SCC | HyundaiFlags.ALT_LIMITS_2,
|
||||
)
|
||||
HYUNDAI_KONA_2ND_GEN = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Kona (without HDA II) 2024-25", car_parts=CarParts.common([CarHarness.hyundai_l]))],
|
||||
CarSpecs(mass=1590, wheelbase=2.66, steerRatio=13.6, tireStiffnessFactor=0.385),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
HYUNDAI_KONA_HEV_2ND_GEN = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Kona Hybrid (without HDA II) 2024", car_parts=CarParts.common([CarHarness.hyundai_l]))],
|
||||
CarSpecs(mass=1590, wheelbase=2.66, steerRatio=13.6, tireStiffnessFactor=0.385),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
HYUNDAI_KONA_EV = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Kona Electric 2018-21", car_parts=CarParts.common([CarHarness.hyundai_g]))],
|
||||
CarSpecs(mass=1685, wheelbase=2.6, steerRatio=13.42, tireStiffnessFactor=0.385),
|
||||
@@ -297,12 +363,17 @@ class CAR(Platforms):
|
||||
[HyundaiCarDocs("Hyundai Kona Electric 2022-23", car_parts=CarParts.common([CarHarness.hyundai_o]))],
|
||||
CarSpecs(mass=1743, wheelbase=2.6, steerRatio=13.42, tireStiffnessFactor=0.385),
|
||||
flags=HyundaiFlags.CAMERA_SCC | HyundaiFlags.EV | HyundaiFlags.ALT_LIMITS,
|
||||
radar_dbc=HYUNDAI_MRREVO14F_RADAR_DBC,
|
||||
)
|
||||
HYUNDAI_KONA_EV_2ND_GEN = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Kona Electric (with HDA II, Korea only) 2023", video="https://www.youtube.com/watch?v=U2fOCmcQ8hw",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_r]))],
|
||||
[
|
||||
HyundaiCarDocs("Hyundai Kona Electric (with HDA II, Korea only) 2023", video="https://www.youtube.com/watch?v=U2fOCmcQ8hw",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_r])),
|
||||
HyundaiCarDocs("Hyundai Kona Electric (without HDA II) 2024", car_parts=CarParts.common([CarHarness.hyundai_a])),
|
||||
],
|
||||
CarSpecs(mass=1740, wheelbase=2.66, steerRatio=13.6, tireStiffnessFactor=0.385),
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_NO_RADAR_DISABLE,
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_NO_RADAR_DISABLE | HyundaiFlags.CCNC,
|
||||
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
|
||||
)
|
||||
HYUNDAI_KONA_HEV = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Kona Hybrid 2020", car_parts=CarParts.common([CarHarness.hyundai_i]))], # TODO: check packages,
|
||||
@@ -324,17 +395,27 @@ class CAR(Platforms):
|
||||
[HyundaiCarDocs("Hyundai Santa Fe 2021-23", "All", video="https://youtu.be/VnHzSTygTS4",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_l]))],
|
||||
HYUNDAI_SANTA_FE.specs,
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8,
|
||||
flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8,
|
||||
)
|
||||
HYUNDAI_SANTA_FE_HEV_2022 = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Santa Fe Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l]))],
|
||||
HYUNDAI_SANTA_FE.specs,
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
|
||||
flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
|
||||
)
|
||||
HYUNDAI_SANTA_FE_PHEV_2022 = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Santa Fe Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l]))],
|
||||
HYUNDAI_SANTA_FE.specs,
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
|
||||
flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
|
||||
)
|
||||
HYUNDAI_SANTA_FE_HEV_5TH_GEN = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Hyundai Santa Fe Hybrid (with HDA II & LFA2) 2024-25", "Highway Driving Assist II & Lane Follow Assist 2",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_p])),
|
||||
HyundaiCarDocs("Hyundai Santa Fe Hybrid (without HDA II, LFA2) 2025-26", "Lane Follow Assist 2",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_l])),
|
||||
],
|
||||
CarSpecs(mass=2035, wheelbase=2.81, steerRatio=13.72),
|
||||
flags=HyundaiFlags.CANFD_ANGLE_STEERING | HyundaiFlags.CCNC,
|
||||
)
|
||||
HYUNDAI_SONATA = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Sonata 2020-23", "All", video="https://www.youtube.com/watch?v=ix63r9kE3Fw",
|
||||
@@ -342,6 +423,11 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=1513, wheelbase=2.84, steerRatio=13.27 * 1.15, tireStiffnessFactor=0.65), # 15% higher at the center seems reasonable
|
||||
flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8,
|
||||
)
|
||||
HYUNDAI_SONATA_2024 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Sonata (without HDA II) 2024-25", car_parts=CarParts.common([CarHarness.hyundai_a]))],
|
||||
CarSpecs(mass=1556, wheelbase=2.84, steerRatio=12.81),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
HYUNDAI_SONATA_LF = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Sonata 2018-19", car_parts=CarParts.common([CarHarness.hyundai_e]))],
|
||||
CarSpecs(mass=1536, wheelbase=2.804, steerRatio=13.27 * 1.15), # 15% higher at the center seems reasonable
|
||||
@@ -367,6 +453,16 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=1999, wheelbase=2.9, steerRatio=15.6 * 1.15, tireStiffnessFactor=0.63),
|
||||
flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8,
|
||||
)
|
||||
HYUNDAI_PALISADE_2023 = HyundaiPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Hyundai Palisade (without HDA II) 2023-25", "Highway Driving Assist",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_a])),
|
||||
HyundaiCarDocs("Kia Telluride (without HDA II) 2023-25", "Highway Driving Assist",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_l])),
|
||||
],
|
||||
HYUNDAI_PALISADE.specs,
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.CAN_CANFD_BLENDED | HyundaiFlags.RADAR_SCC,
|
||||
)
|
||||
HYUNDAI_VELOSTER = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_e]))],
|
||||
CarSpecs(mass=2917 * CV.LB_TO_KG, wheelbase=2.8, steerRatio=13.75 * 1.15, tireStiffnessFactor=0.5),
|
||||
@@ -377,6 +473,11 @@ class CAR(Platforms):
|
||||
HYUNDAI_SONATA.specs,
|
||||
flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
|
||||
)
|
||||
HYUNDAI_SONATA_HEV_2024 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Sonata Hybrid (without HDA II) 2024-25", car_parts=CarParts.common([CarHarness.hyundai_a]))],
|
||||
CarSpecs(mass=1616, wheelbase=2.84, steerRatio=13.27),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
HYUNDAI_IONIQ_5 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Hyundai Ioniq 5 (Southeast Asia and Europe only) 2022-24", "All", car_parts=CarParts.common([CarHarness.hyundai_q])),
|
||||
@@ -385,11 +486,37 @@ class CAR(Platforms):
|
||||
],
|
||||
CarSpecs(mass=1948, wheelbase=2.97, steerRatio=14.26, tireStiffnessFactor=0.65),
|
||||
flags=HyundaiFlags.EV,
|
||||
radar_dbc=HYUNDAI_MRR30_RADAR_DBC,
|
||||
)
|
||||
HYUNDAI_IONIQ_5_PE = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Hyundai Ioniq 5 PE (with HDA II & LFA2) 2025-26", "Highway Driving Assist II & Lane Follow Assist 2",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_q]))
|
||||
],
|
||||
HYUNDAI_IONIQ_5.specs,
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING,
|
||||
radar_dbc=HYUNDAI_MRR30_RADAR_DBC,
|
||||
)
|
||||
HYUNDAI_IONIQ_5_N = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Ioniq 5 N (with HDA II) 2024", car_parts=CarParts.common([CarHarness.hyundai_s]))],
|
||||
CarSpecs(mass=2205, wheelbase=3.00, steerRatio=14.26, tireStiffnessFactor=1.3),
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CCNC,
|
||||
radar_dbc=HYUNDAI_MRR30_RADAR_DBC,
|
||||
)
|
||||
HYUNDAI_IONIQ_6 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Ioniq 6 (with HDA II) 2023-24", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p]))],
|
||||
HYUNDAI_IONIQ_5.specs,
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT | HyundaiFlags.CANFD_NO_RADAR_DISABLE,
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_NO_RADAR_DISABLE,
|
||||
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
|
||||
)
|
||||
HYUNDAI_IONIQ_9 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Hyundai Ioniq 9 (with HDA II & LFA2) 2025-26", "Highway Driving Assist II & Lane Follow Assist 2",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_m]))
|
||||
],
|
||||
CarSpecs(mass=2700, wheelbase=3.13, steerRatio=16.02),
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING,
|
||||
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
|
||||
)
|
||||
HYUNDAI_TUCSON_4TH_GEN = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
@@ -400,11 +527,31 @@ class CAR(Platforms):
|
||||
],
|
||||
CarSpecs(mass=1630, wheelbase=2.756, steerRatio=13.7, tireStiffnessFactor=0.385),
|
||||
)
|
||||
HYUNDAI_TUCSON_2025 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Tucson (without HDA II) 2025-26", car_parts=CarParts.common([CarHarness.hyundai_n]))],
|
||||
CarSpecs(mass=1630, wheelbase=2.756, steerRatio=13.7, tireStiffnessFactor=0.385),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
HYUNDAI_TUCSON_HEV_2025 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Tucson Hybrid (without HDA II) 2025-26", car_parts=CarParts.common([CarHarness.hyundai_n]))],
|
||||
CarSpecs(mass=1630, wheelbase=2.756, steerRatio=13.7, tireStiffnessFactor=0.385),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
HYUNDAI_TUCSON_PHEV_2025 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Tucson Plug-in Hybrid (without HDA II) 2025", car_parts=CarParts.common([CarHarness.hyundai_n]))],
|
||||
CarSpecs(mass=1630, wheelbase=2.756, steerRatio=13.7, tireStiffnessFactor=0.385),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
HYUNDAI_SANTA_CRUZ_1ST_GEN = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Santa Cruz 2022-24", car_parts=CarParts.common([CarHarness.hyundai_n]))],
|
||||
# weight from Limited trim - the only supported trim, steering ratio according to Hyundai News https://www.hyundainews.com/assets/documents/original/48035-2022SantaCruzProductGuideSpecsv2081521.pdf
|
||||
CarSpecs(mass=1870, wheelbase=3, steerRatio=14.2),
|
||||
)
|
||||
HYUNDAI_SANTA_CRUZ_2025 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Santa Cruz (without HDA II) 2025", car_parts=CarParts.common([CarHarness.hyundai_n]))],
|
||||
CarSpecs(mass=1920, wheelbase=3, steerRatio=14.2),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
HYUNDAI_CUSTIN_1ST_GEN = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Custin 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_k]))],
|
||||
CarSpecs(mass=1690, wheelbase=3.055, steerRatio=17), # mass: from https://www.hyundai-motor.com.tw/clicktobuy/custin#spec_0, steerRatio: from learner
|
||||
@@ -419,11 +566,24 @@ class CAR(Platforms):
|
||||
],
|
||||
CarSpecs(mass=2878 * CV.LB_TO_KG, wheelbase=2.8, steerRatio=13.75, tireStiffnessFactor=0.5)
|
||||
)
|
||||
KIA_K4_2025 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Kia K4 (without HDA II) 2025", car_parts=CarParts.common([CarHarness.hyundai_a])),
|
||||
HyundaiCarDocs("Kia K4 (with HDA II) 2025", car_parts=CarParts.common([CarHarness.hyundai_r])),
|
||||
],
|
||||
CarSpecs(mass=2987 * CV.LB_TO_KG, wheelbase=2.72, steerRatio=13.4),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
KIA_K5_2021 = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Kia K5 2021-24", car_parts=CarParts.common([CarHarness.hyundai_a]))],
|
||||
CarSpecs(mass=3381 * CV.LB_TO_KG, wheelbase=2.85, steerRatio=13.27, tireStiffnessFactor=0.5), # 2021 Kia K5 Steering Ratio (all trims)
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8,
|
||||
)
|
||||
KIA_K5_2025 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Kia K5 (without HDA II) 2025", car_parts=CarParts.common([CarHarness.hyundai_m]))],
|
||||
CarSpecs(mass=3230 * CV.LB_TO_KG, wheelbase=2.85, steerRatio=13.27),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
KIA_K5_HEV_2020 = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Kia K5 Hybrid 2020-22", car_parts=CarParts.common([CarHarness.hyundai_a]))],
|
||||
KIA_K5_2021.specs,
|
||||
@@ -516,6 +676,13 @@ class CAR(Platforms):
|
||||
# weight from SX and above trims, average of FWD and AWD version, steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sportage/2023/specifications
|
||||
CarSpecs(mass=1725, wheelbase=2.756, steerRatio=13.6),
|
||||
)
|
||||
KIA_SPORTAGE_2026 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Kia Sportage (without HDA II) 2026", car_parts=CarParts.common([CarHarness.hyundai_n])),
|
||||
],
|
||||
CarSpecs(mass=1735, wheelbase=2.756, steerRatio=13.7),
|
||||
flags=HyundaiFlags.CANFD_ANGLE_STEERING | HyundaiFlags.CCNC,
|
||||
)
|
||||
KIA_SPORTAGE_HEV_2026 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Kia Sportage Hybrid 2026", car_parts=CarParts.common([CarHarness.hyundai_n]))],
|
||||
CarSpecs(mass=1812, wheelbase=2.756, steerRatio=13.7),
|
||||
@@ -535,6 +702,11 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=3957 * CV.LB_TO_KG, wheelbase=2.81, steerRatio=13.5), # average of the platforms
|
||||
flags=HyundaiFlags.RADAR_SCC,
|
||||
)
|
||||
KIA_SORENTO_2024 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Kia Sorento (without HDA II) 2024-25", car_parts=CarParts.common([CarHarness.hyundai_a]))],
|
||||
CarSpecs(mass=3957 * CV.LB_TO_KG, wheelbase=2.81, steerRatio=13.5),
|
||||
flags=HyundaiFlags.CCNC,
|
||||
)
|
||||
KIA_SORENTO_HEV_4TH_GEN = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Kia Sorento Hybrid 2021-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])),
|
||||
@@ -543,6 +715,13 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=4395 * CV.LB_TO_KG, wheelbase=2.81, steerRatio=13.5), # average of the platforms
|
||||
flags=HyundaiFlags.RADAR_SCC,
|
||||
)
|
||||
KIA_SORENTO_HEV_4TH_GEN_LFA2 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Kia Sorento Hybrid 2026", "All", car_parts=CarParts.common([CarHarness.hyundai_q])),
|
||||
],
|
||||
CarSpecs(mass=1970, wheelbase=2.814, steerRatio=13.27, tireStiffnessFactor=0.65),
|
||||
flags=HyundaiFlags.CANFD_ANGLE_STEERING,
|
||||
)
|
||||
KIA_STINGER = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Kia Stinger 2018-20", video="https://www.youtube.com/watch?v=MJ94qoofYw0",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_c]))],
|
||||
@@ -557,14 +736,36 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=1450, wheelbase=2.65, steerRatio=13.75, tireStiffnessFactor=0.5),
|
||||
flags=HyundaiFlags.LEGACY,
|
||||
)
|
||||
KIA_XCEED_PHEV = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Kia XCeed Plug-in Hybrid 2021", car_parts=CarParts.common([CarHarness.hyundai_b]))],
|
||||
CarSpecs(mass=1650, wheelbase=2.65, steerRatio=13.75, tireStiffnessFactor=0.5),
|
||||
flags=HyundaiFlags.LEGACY | HyundaiFlags.HYBRID | HyundaiFlags.MANDO_RADAR,
|
||||
)
|
||||
KIA_EV6 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Kia EV6 (Southeast Asia only) 2022-24", "All", car_parts=CarParts.common([CarHarness.hyundai_p])),
|
||||
HyundaiCarDocs("Kia EV6 (without HDA II) 2022-24", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_l])),
|
||||
HyundaiCarDocs("Kia EV6 (with HDA II) 2022-24", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p]))
|
||||
],
|
||||
CarSpecs(mass=2055, wheelbase=2.9, steerRatio=16, tireStiffnessFactor=0.65),
|
||||
CarSpecs(mass=2055, wheelbase=2.9, steerRatio=14.25, tireStiffnessFactor=0.65),
|
||||
flags=HyundaiFlags.EV,
|
||||
radar_dbc=HYUNDAI_MRR30_RADAR_DBC,
|
||||
)
|
||||
KIA_EV6_2025 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Kia EV6 (with HDA I) 2025", "Highway Driving Assist I", car_parts=CarParts.common([CarHarness.hyundai_p]))
|
||||
],
|
||||
CarSpecs(mass=2055, wheelbase=2.9, steerRatio=14.26, tireStiffnessFactor=0.65),
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING,
|
||||
radar_dbc=HYUNDAI_MRR30_RADAR_DBC,
|
||||
)
|
||||
KIA_EV9 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Kia EV9 2025-26", car_parts=CarParts.common([CarHarness.hyundai_r]))
|
||||
],
|
||||
CarSpecs(mass=2664, wheelbase=3.1, steerRatio=16),
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING,
|
||||
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
|
||||
)
|
||||
KIA_CARNIVAL_4TH_GEN = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
@@ -583,6 +784,7 @@ class CAR(Platforms):
|
||||
],
|
||||
CarSpecs(mass=2205, wheelbase=2.9, steerRatio=17.6),
|
||||
flags=HyundaiFlags.EV,
|
||||
radar_dbc=HYUNDAI_MRR30_RADAR_DBC,
|
||||
)
|
||||
GENESIS_G70 = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Genesis G70 2018", "All", car_parts=CarParts.common([CarHarness.hyundai_f]))],
|
||||
@@ -616,6 +818,14 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=2260, wheelbase=2.87, steerRatio=17.1),
|
||||
flags=HyundaiFlags.EV,
|
||||
)
|
||||
GENESIS_GV70_ELECTRIFIED_2ND_GEN = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Genesis GV70 Electrified 2026", "All", car_parts=CarParts.common([CarHarness.hyundai_m])),
|
||||
],
|
||||
GENESIS_GV70_ELECTRIFIED_1ST_GEN.specs,
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING,
|
||||
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
|
||||
)
|
||||
GENESIS_G80 = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Genesis G80 2018-19", "All", car_parts=CarParts.common([CarHarness.hyundai_h]))],
|
||||
CarSpecs(mass=2060, wheelbase=3.01, steerRatio=16.5),
|
||||
@@ -628,12 +838,76 @@ class CAR(Platforms):
|
||||
GENESIS_G90 = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Genesis G90 2017-20", "All", car_parts=CarParts.common([CarHarness.hyundai_c]))],
|
||||
CarSpecs(mass=2200, wheelbase=3.15, steerRatio=12.069),
|
||||
flags=HyundaiFlags.MANDO_RADAR,
|
||||
)
|
||||
GENESIS_GV80 = HyundaiCanFDPlatformConfig(
|
||||
[HyundaiCarDocs("Genesis GV80 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_m]))],
|
||||
CarSpecs(mass=2258, wheelbase=2.95, steerRatio=14.14),
|
||||
flags=HyundaiFlags.RADAR_SCC,
|
||||
)
|
||||
GENESIS_GV80_2025 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Genesis GV80 (3.5T Prestige Trim, with HDA II & LFA2) 2025", "Highway Driving Assist II & Lane Follow Assist 2",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_q])),
|
||||
HyundaiCarDocs("Genesis GV80 Coupe (with HDA II & LFA2) 2025", "Highway Driving Assist II & Lane Follow Assist 2",
|
||||
car_parts=CarParts.common([CarHarness.hyundai_q])),
|
||||
],
|
||||
GENESIS_GV80.specs,
|
||||
flags=HyundaiFlags.CANFD_ANGLE_STEERING,
|
||||
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
|
||||
)
|
||||
|
||||
# Hyundai non-SCC extensions
|
||||
HYUNDAI_BAYON_1ST_GEN_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Hyundai Bayon Non-SCC 2021", car_parts=CarParts.common([CarHarness.hyundai_n]))],
|
||||
CarSpecs(mass=1150, wheelbase=2.58, steerRatio=13.27 * 1.15),
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8,
|
||||
)
|
||||
HYUNDAI_ELANTRA_2022_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Hyundai Elantra Non-SCC 2022", car_parts=CarParts.common([CarHarness.hyundai_k]))],
|
||||
HYUNDAI_ELANTRA_2021.specs,
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8,
|
||||
)
|
||||
HYUNDAI_ELANTRA_HEV_2022_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Hyundai Elantra Hybrid Non-SCC 2022", car_parts=CarParts.common([CarHarness.hyundai_k]))],
|
||||
HYUNDAI_ELANTRA_HEV_2021.specs,
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
|
||||
)
|
||||
HYUNDAI_KONA_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Hyundai Kona Non-SCC 2019", car_parts=CarParts.common([CarHarness.hyundai_b]))],
|
||||
HYUNDAI_KONA.specs,
|
||||
flags=HyundaiFlags.ALT_LIMITS,
|
||||
radar_dbc=HYUNDAI_MRREVO14F_RADAR_DBC,
|
||||
)
|
||||
HYUNDAI_KONA_EV_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Hyundai Kona Electric Non-SCC 2019", car_parts=CarParts.common([CarHarness.hyundai_g]))],
|
||||
HYUNDAI_KONA_EV.specs,
|
||||
flags=HyundaiFlags.EV | HyundaiFlags.ALT_LIMITS,
|
||||
)
|
||||
KIA_CEED_PHEV_2022_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Kia Ceed Plug-in Hybrid Non-SCC 2022", car_parts=CarParts.common([CarHarness.hyundai_i]))],
|
||||
CarSpecs(mass=1650, wheelbase=2.65, steerRatio=13.75, tireStiffnessFactor=0.5),
|
||||
flags=HyundaiFlags.HYBRID,
|
||||
)
|
||||
KIA_FORTE_2019_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Kia Forte Non-SCC 2019", car_parts=CarParts.common([CarHarness.hyundai_g]))],
|
||||
KIA_FORTE.specs,
|
||||
flags=HyundaiFlags.NON_SCC_NO_FCA,
|
||||
)
|
||||
KIA_FORTE_2021_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Kia Forte Non-SCC 2021", car_parts=CarParts.common([CarHarness.hyundai_g]))],
|
||||
KIA_FORTE.specs,
|
||||
)
|
||||
KIA_SELTOS_2023_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Kia Seltos Non-SCC 2023-24", car_parts=CarParts.common([CarHarness.hyundai_l]))],
|
||||
KIA_SELTOS.specs,
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8,
|
||||
)
|
||||
GENESIS_G70_2021_NON_SCC = HyundaiNonSccPlatformConfig(
|
||||
[HyundaiNonSccCarDocs("Genesis G70 Non-SCC 2021", car_parts=CarParts.common([CarHarness.hyundai_f]))],
|
||||
GENESIS_G70_2020.specs,
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.NON_SCC_RADAR_FCA,
|
||||
)
|
||||
|
||||
|
||||
class Buttons:
|
||||
@@ -644,6 +918,38 @@ class Buttons:
|
||||
CANCEL = 4 # on newer models, this is a pause/resume button
|
||||
|
||||
|
||||
CANCEL_BUTTON_ENABLE_CARS = frozenset({
|
||||
CAR.HYUNDAI_PALISADE_2023,
|
||||
})
|
||||
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES = frozenset({
|
||||
"C4DLC",
|
||||
})
|
||||
|
||||
|
||||
# These classic HKG platforms publish the LKAS button on CLU13 over the alt bus.
|
||||
# Keep G90 excluded until its alt-bus path is route-proven without the recent
|
||||
# engage/disengage regression.
|
||||
ALT_BUS_LDA_BUTTON_CARS = frozenset({
|
||||
CAR.HYUNDAI_SONATA,
|
||||
})
|
||||
|
||||
# On these Sonata layouts the alt-bus LKAS button pulses through the CLU13
|
||||
# steering-wheel-status field instead of the dedicated LKAS bit.
|
||||
ALT_BUS_LDA_BUTTON_SWL_STAT_CARS = frozenset({
|
||||
CAR.HYUNDAI_SONATA,
|
||||
})
|
||||
|
||||
|
||||
def hyundai_cancel_button_enables_cruise(car_fingerprint) -> bool:
|
||||
return car_fingerprint in CANCEL_BUTTON_ENABLE_CARS
|
||||
|
||||
|
||||
def kia_ev6_gt_line_longitudinal_tuning(car_fingerprint, vin: str) -> bool:
|
||||
return car_fingerprint == CAR.KIA_EV6 and isinstance(vin, str) and \
|
||||
len(vin) == 17 and vin[3:8] in KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES
|
||||
|
||||
|
||||
def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | None]]:
|
||||
# Returns unique, platform-specific identification codes for a set of versions
|
||||
codes = set() # (code-Optional[part], date)
|
||||
@@ -733,7 +1039,7 @@ PART_NUMBER_FW_PATTERN = re.compile(b'(?<=[0-9][.,][0-9]{2} )([0-9]{5}[-/]?[A-Z]
|
||||
# We've seen both ICE and hybrid for these platforms, and they have hybrid descriptors (e.g. MQ4 vs MQ4H)
|
||||
CANFD_FUZZY_WHITELIST = {CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_K8_HEV_1ST_GEN,
|
||||
# TODO: the hybrid variant is not out yet
|
||||
CAR.KIA_CARNIVAL_4TH_GEN}
|
||||
CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN_LFA2}
|
||||
|
||||
# List of ECUs expected to have platform codes, camera and radar should exist on all cars
|
||||
# TODO: use abs, it has the platform code and part number on many platforms
|
||||
@@ -798,11 +1104,10 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
# We lose these ECUs without the comma power on these cars.
|
||||
# Note that we still attempt to match with them when they are present
|
||||
non_essential_ecus={
|
||||
# Some Forte trims are lateral-only and omit the SCC radar entirely.
|
||||
Ecu.fwdRadar: [CAR.KIA_FORTE],
|
||||
Ecu.abs: [CAR.HYUNDAI_PALISADE, CAR.HYUNDAI_SONATA, CAR.HYUNDAI_SANTA_FE_2022, CAR.KIA_K5_2021, CAR.HYUNDAI_ELANTRA_2021,
|
||||
CAR.HYUNDAI_SANTA_FE, CAR.HYUNDAI_KONA_EV_2022, CAR.HYUNDAI_KONA_EV, CAR.HYUNDAI_CUSTIN_1ST_GEN, CAR.KIA_SORENTO,
|
||||
CAR.KIA_CEED, CAR.KIA_SELTOS],
|
||||
CAR.KIA_CEED, CAR.KIA_XCEED_PHEV, CAR.KIA_SELTOS],
|
||||
Ecu.fwdRadar: [CAR.HYUNDAI_KONA_NON_SCC],
|
||||
},
|
||||
extra_ecus=[
|
||||
(Ecu.adas, 0x730, None), # ADAS Driving ECU on platforms with LKA steering
|
||||
@@ -829,9 +1134,22 @@ CAN_GEARS = {
|
||||
CANFD_CAR = CAR.with_flags(HyundaiFlags.CANFD)
|
||||
CANFD_RADAR_SCC_CAR = CAR.with_flags(HyundaiFlags.RADAR_SCC) # TODO: merge with UNSUPPORTED_LONGITUDINAL_CAR
|
||||
|
||||
# Cars with CANFD_NO_RADAR_DISABLE that now work with SecurityAccess handshake
|
||||
CANFD_SECURITYACCESS_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_KONA_EV_2ND_GEN}
|
||||
# CAN-FD cars with ADAS ECUs that work with the communication-control path.
|
||||
CANFD_SECURITYACCESS_CAR = {CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_KONA_EV_2ND_GEN}
|
||||
CANFD_UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.CANFD_NO_RADAR_DISABLE) - CANFD_SECURITYACCESS_CAR # TODO: merge with UNSUPPORTED_LONGITUDINAL_CAR
|
||||
CANFD_ANGLE_LONGITUDINAL_CAR = set()
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_CAR = {CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.GENESIS_GV60_EV_1ST_GEN}
|
||||
RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | {
|
||||
CAR.HYUNDAI_IONIQ,
|
||||
CAR.HYUNDAI_KONA_EV_2022,
|
||||
CAR.HYUNDAI_SANTA_FE_2022,
|
||||
CAR.HYUNDAI_SANTA_FE_HEV_2022,
|
||||
CAR.HYUNDAI_SANTA_FE_PHEV_2022,
|
||||
CAR.HYUNDAI_SONATA,
|
||||
CAR.HYUNDAI_SONATA_HYBRID,
|
||||
CAR.KIA_XCEED_PHEV,
|
||||
CAR.GENESIS_G90,
|
||||
}
|
||||
|
||||
CAMERA_SCC_CAR = CAR.with_flags(HyundaiFlags.CAMERA_SCC)
|
||||
|
||||
@@ -841,8 +1159,12 @@ EV_CAR = CAR.with_flags(HyundaiFlags.EV)
|
||||
|
||||
LEGACY_SAFETY_MODE_CAR = CAR.with_flags(HyundaiFlags.LEGACY)
|
||||
|
||||
NON_SCC_CAR = CAR.with_flags(HyundaiFlags.NON_SCC)
|
||||
|
||||
# TODO: another PR with (HyundaiFlags.LEGACY | HyundaiFlags.UNSUPPORTED_LONGITUDINAL | HyundaiFlags.CAMERA_SCC |
|
||||
# HyundaiFlags.CANFD_RADAR_SCC | HyundaiFlags.CANFD_NO_RADAR_DISABLE | )
|
||||
UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.LEGACY) | CAR.with_flags(HyundaiFlags.UNSUPPORTED_LONGITUDINAL)
|
||||
|
||||
LEGACY_LONGITUDINAL_CAR = {CAR.KIA_XCEED_PHEV}
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
@@ -18,14 +18,16 @@ from opendbc.car.common.basedir import BASEDIR
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.common.simple_kalman import KF1D, get_kalman_gain
|
||||
from opendbc.car.gm.values import CAR as GM
|
||||
from opendbc.car.honda.values import CAR as HONDA, HONDA_BOSCH, HONDA_CAMERA_MESSAGE_CARS, HondaSafetyFlags, HondaStarPilotFlags
|
||||
from opendbc.car.honda.values import CAR as HONDA, HONDA_BOSCH, HondaFlags, HondaSafetyFlags, HondaStarPilotFlags
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import CAR as HYUNDAI, CANFD_CAR, HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags
|
||||
from opendbc.car.hyundai.values import CAR as HYUNDAI, CANFD_CAR, HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, ALT_BUS_LDA_BUTTON_CARS
|
||||
from opendbc.car.mock.values import CAR as MOCK
|
||||
from opendbc.car.subaru.values import CAR as SUBARU, SubaruSafetyFlags
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA, NO_DSU_CAR, TSS2_CAR, UNSUPPORTED_DSU_CAR, ToyotaStarPilotFlags, ToyotaSafetyFlags
|
||||
from opendbc.car.values import PLATFORMS
|
||||
from opendbc.can import CANParser
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
|
||||
GearShifter = structs.CarState.GearShifter
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
@@ -132,6 +134,7 @@ class CarInterfaceBase(ABC):
|
||||
|
||||
dbc_names = {bus: cp.dbc_name for bus, cp in self.can_parsers.items()}
|
||||
self.CC: CarControllerBase = self.CarController(dbc_names, CP)
|
||||
self.CC.FPCP = FPCP
|
||||
|
||||
self.FPCP = FPCP
|
||||
|
||||
@@ -173,8 +176,11 @@ class CarInterfaceBase(ABC):
|
||||
|
||||
ret = cls._get_params(ret, candidate, fingerprint, car_fw, alpha_long, is_release, docs)
|
||||
|
||||
trailer_load_kg = float(np.clip(getattr(starpilot_toggles, "trailer_load_kg", 0.0) or 0.0, 0.0, 15000.0 * CV.LB_TO_KG))
|
||||
|
||||
# Vehicle mass is published curb weight plus assumed payload such as a human driver; notCars have no assumed payload
|
||||
if not ret.notCar:
|
||||
ret.mass = ret.mass + trailer_load_kg
|
||||
ret.mass = ret.mass + STD_CARGO_KG
|
||||
|
||||
# Set params dependent on values set by the car interface
|
||||
@@ -182,7 +188,14 @@ class CarInterfaceBase(ABC):
|
||||
ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, ret.tireStiffnessFactor)
|
||||
|
||||
toggles_to_check = ("force_torque_controller", "nnff", "nnff_lite")
|
||||
if ret.steerControlType != structs.CarParams.SteerControlType.angle and any(getattr(starpilot_toggles, toggle, False) for toggle in toggles_to_check):
|
||||
modified_civic_force_torque = (
|
||||
candidate == HONDA.HONDA_CIVIC_BOSCH and
|
||||
bool(ret.flags & HondaFlags.EPS_MODIFIED)
|
||||
)
|
||||
if ret.steerControlType != structs.CarParams.SteerControlType.angle and (
|
||||
any(getattr(starpilot_toggles, toggle, False) for toggle in toggles_to_check) or
|
||||
modified_civic_force_torque
|
||||
):
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
return ret
|
||||
@@ -190,6 +203,8 @@ class CarInterfaceBase(ABC):
|
||||
@classmethod
|
||||
def get_starpilot_params(cls, candidate: str, fingerprint: dict[int, dict[int, int]], car_fw: list[structs.CarParams.CarFw], CP: structs.CarParams, starpilot_toggles: SimpleNamespace):
|
||||
fp_ret = custom.StarPilotCarParams.new_message()
|
||||
fp_ret.pcmCruiseSpeed = True
|
||||
params = Params(return_defaults=True)
|
||||
|
||||
platform = PLATFORMS[candidate]
|
||||
|
||||
@@ -201,14 +216,16 @@ class CarInterfaceBase(ABC):
|
||||
if candidate == CHRYSLER.RAM_HD_5TH_GEN:
|
||||
if 570 not in fingerprint[0]:
|
||||
fp_ret.flags |= ChryslerStarPilotFlags.RAM_HD_ALT_BUTTONS.value
|
||||
if 0x4FF in fingerprint[0]:
|
||||
fp_ret.flags |= ChryslerStarPilotFlags.NO_MIN_STEERING_SPEED.value
|
||||
CP.minSteerSpeed = 0.
|
||||
|
||||
elif platform in GM:
|
||||
fp_ret.canUsePedal = True
|
||||
|
||||
elif platform in HONDA:
|
||||
fp_ret.canUsePedal = candidate not in HONDA_BOSCH
|
||||
# Only enable TSR parsing on Hondas confirmed to publish CAMERA_MESSAGES.
|
||||
if candidate in HONDA_CAMERA_MESSAGE_CARS:
|
||||
if any(0x35E in bus_fingerprint for bus_fingerprint in fingerprint.values()):
|
||||
fp_ret.flags |= int(HondaStarPilotFlags.HAS_CAMERA_MESSAGES)
|
||||
|
||||
elif platform in HYUNDAI:
|
||||
@@ -220,11 +237,23 @@ class CarInterfaceBase(ABC):
|
||||
if 0x1FA in fingerprint[CAN.ECAN]:
|
||||
fp_ret.flags |= HyundaiStarPilotFlags.SPEED_LIMIT_AVAILABLE.value
|
||||
|
||||
if CP.flags & HyundaiFlags.HAS_LDA_BUTTON:
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON.value
|
||||
if starpilot_toggles.always_on_lateral_lkas:
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
fp_ret.redneckCruiseAvailable = bool(CP.flags & HyundaiFlags.NON_SCC) and not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS)
|
||||
if fp_ret.redneckCruiseAvailable and params.get_bool("RedneckCruise") and \
|
||||
not CP.openpilotLongitudinalControl:
|
||||
fp_ret.pcmCruiseSpeed = False
|
||||
|
||||
hyundai_has_lda_button = (
|
||||
0x391 in fingerprint[0] or
|
||||
0x50C in fingerprint[0] or
|
||||
candidate in ALT_BUS_LDA_BUTTON_CARS or
|
||||
bool(CP.flags & HyundaiFlags.CAN_CANFD_BLENDED)
|
||||
)
|
||||
if hyundai_has_lda_button:
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON.value
|
||||
|
||||
# LKASButtonControl == 9 means BUTTON_FUNCTIONS["AOL_TOGGLE"] in starpilot_variables.
|
||||
if params.get_bool("AlwaysOnLateral") and params.get_int("LKASButtonControl") == 9:
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
elif platform in TOYOTA:
|
||||
fp_ret.canUsePedal = not CP.autoResumeSng
|
||||
fp_ret.canUseSDSU = candidate not in UNSUPPORTED_DSU_CAR and candidate not in TSS2_CAR
|
||||
@@ -239,6 +268,13 @@ class CarInterfaceBase(ABC):
|
||||
if 0x23 in fingerprint[0]:
|
||||
fp_ret.flags |= ToyotaStarPilotFlags.ZSS.value
|
||||
|
||||
elif platform.config.platform_str == "TESLA_MODEL_S_PREAP":
|
||||
fp_ret.canUsePedal = True
|
||||
|
||||
elif platform in SUBARU:
|
||||
if getattr(starpilot_toggles, "subaru_sng", False):
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.STOP_AND_GO.value
|
||||
|
||||
return fp_ret
|
||||
|
||||
@staticmethod
|
||||
@@ -473,6 +509,7 @@ class CarStateBase(ABC):
|
||||
class CarControllerBase(ABC):
|
||||
def __init__(self, dbc_names: dict[StrEnum, str], CP: structs.CarParams):
|
||||
self.CP = CP
|
||||
self.FPCP: custom.StarPilotCarParams | None = None
|
||||
self.frame = 0
|
||||
self.secoc_key: bytes = b"00" * 16
|
||||
|
||||
|
||||
@@ -118,11 +118,13 @@ class CarState(CarStateBase):
|
||||
|
||||
fp_ret = custom.StarPilotCarState.new_message()
|
||||
|
||||
fp_ret.dashboardStopSign = 1 if cp_cam.vl["CAM_TRAFFIC_SIGNS"]["STOP_SIGN"] == 9 else 0
|
||||
|
||||
return ret, fp_ret
|
||||
|
||||
@staticmethod
|
||||
def get_can_parsers(CP):
|
||||
return {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 0),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 2),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [("CAM_TRAFFIC_SIGNS", 0)], 2),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import numpy as np
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus, make_tester_present_msg
|
||||
from opendbc.car import Bus, DT_CTRL, make_tester_present_msg
|
||||
from opendbc.car.lateral import apply_driver_steer_torque_limits, common_fault_avoidance
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.subaru import subarucan
|
||||
@@ -26,14 +26,9 @@ class CarController(CarControllerBase):
|
||||
self.p = CarControllerParams(CP)
|
||||
self.packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
|
||||
self.manual_hold = False
|
||||
self.prev_standstill = False
|
||||
self.sng_acc_resume = False
|
||||
|
||||
self.prev_close_distance = 0
|
||||
self.prev_cruise_state = 0
|
||||
self.sng_acc_resume_cnt = 0
|
||||
self.standstill_start = 0
|
||||
self.epb_resume_frames_remaining = -1
|
||||
self.last_standstill_frame = 0
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
actuators = CC.actuators
|
||||
@@ -70,8 +65,9 @@ class CarController(CarControllerBase):
|
||||
self.apply_torque_last = apply_torque
|
||||
|
||||
# *** stop and go ***
|
||||
subaru_sng_manual_parking_brake = getattr(starpilot_toggles, "subaru_sng_manual_parking_brake", False)
|
||||
if starpilot_toggles.subaru_sng:
|
||||
throttle_cmd, speed_cmd = self.stop_and_go(CC, CS)
|
||||
throttle_cmd, speed_cmd = self.stop_and_go(CC, CS, subaru_sng_manual_parking_brake)
|
||||
|
||||
# *** longitudinal ***
|
||||
|
||||
@@ -110,7 +106,11 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(subarucan.create_preglobal_es_distance(self.packer, cruise_button, CS.es_distance_msg))
|
||||
|
||||
if starpilot_toggles.subaru_sng:
|
||||
can_sends.append(subarucan.create_preglobal_throttle(self.packer, CS.throttle_msg["COUNTER"] + 1, CS.throttle_msg, throttle_cmd))
|
||||
can_sends.append(subarucan.create_preglobal_throttle(self.packer, CS.throttle_msg["COUNTER"] + 1, CS.throttle_msg,
|
||||
throttle_cmd))
|
||||
if self.frame % 2 == 0:
|
||||
can_sends.append(subarucan.create_preglobal_brake_pedal(self.packer, CS.brake_pedal_msg,
|
||||
speed_cmd))
|
||||
else:
|
||||
if self.frame % 10 == 0:
|
||||
can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled,
|
||||
@@ -124,9 +124,11 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(subarucan.create_es_infotainment(self.packer, self.frame // 10, CS.es_infotainment_msg, hud_control.visualAlert))
|
||||
|
||||
if starpilot_toggles.subaru_sng:
|
||||
can_sends.append(subarucan.create_throttle(self.packer, CS.throttle_msg["COUNTER"] + 1, CS.throttle_msg, throttle_cmd))
|
||||
can_sends.append(subarucan.create_throttle(self.packer, CS.throttle_msg["COUNTER"] + 1, CS.throttle_msg,
|
||||
throttle_cmd))
|
||||
if self.frame % 2 == 0:
|
||||
can_sends.append(subarucan.create_brake_pedal(self.packer, self.frame // 2, CS.brake_pedal_msg, speed_cmd, pcm_cancel_cmd))
|
||||
can_sends.append(subarucan.create_brake_pedal(self.packer, self.frame // 2, CS.brake_pedal_msg,
|
||||
speed_cmd, pcm_cancel_cmd))
|
||||
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
if self.frame % 5 == 0:
|
||||
@@ -166,49 +168,37 @@ class CarController(CarControllerBase):
|
||||
self.frame += 1
|
||||
return new_actuators, can_sends
|
||||
|
||||
def stop_and_go(self, CC, CS, speed_cmd=False, throttle_cmd=False):
|
||||
if self.CP.flags & SubaruFlags.PREGLOBAL:
|
||||
trigger_resume = CC.enabled
|
||||
trigger_resume &= CS.car_follow == 1
|
||||
trigger_resume &= CS.close_distance > self.prev_close_distance
|
||||
trigger_resume &= CS.out.standstill
|
||||
trigger_resume &= _SNG_ACC_MIN_DIST < CS.close_distance < _SNG_ACC_MAX_DIST
|
||||
def stop_and_go(self, CC, CS, manual_parking_brake=False):
|
||||
throttle_cmd = False
|
||||
speed_cmd = False
|
||||
|
||||
if trigger_resume:
|
||||
self.sng_acc_resume = True
|
||||
else:
|
||||
if CS.car_follow == 0 and CS.cruise_state == 3 and CS.out.standstill and self.prev_cruise_state == 1:
|
||||
self.manual_hold = True
|
||||
if not CC.enabled or not CC.hudControl.leadVisible:
|
||||
return throttle_cmd, speed_cmd
|
||||
|
||||
if not CS.out.standstill:
|
||||
self.manual_hold = False
|
||||
close_distance = CS.close_distance
|
||||
if not CS.out.standstill:
|
||||
self.last_standstill_frame = self.frame
|
||||
|
||||
trigger_resume = CC.enabled
|
||||
trigger_resume &= CS.car_follow == 1
|
||||
trigger_resume &= CS.close_distance > self.prev_close_distance
|
||||
trigger_resume &= CS.cruise_state == 3
|
||||
trigger_resume &= not self.manual_hold
|
||||
trigger_resume &= _SNG_ACC_MIN_DIST < CS.close_distance < _SNG_ACC_MAX_DIST
|
||||
standstill_timers = (0.75, 0.8) if self.CP.flags & SubaruFlags.PREGLOBAL else (0.5, 0.55)
|
||||
standstill_duration = (self.frame - self.last_standstill_frame) * DT_CTRL
|
||||
in_standstill_hold = standstill_duration > standstill_timers[0]
|
||||
if standstill_duration >= standstill_timers[1]:
|
||||
self.last_standstill_frame = self.frame
|
||||
|
||||
if trigger_resume:
|
||||
self.sng_acc_resume = True
|
||||
if manual_parking_brake or not (self.CP.flags & SubaruFlags.PREGLOBAL):
|
||||
speed_cmd = in_standstill_hold
|
||||
|
||||
if CC.enabled and CS.car_follow == 1 and CS.out.standstill and self.frame > self.standstill_start + 50:
|
||||
speed_cmd = True
|
||||
should_resume = (
|
||||
CS.out.standstill and
|
||||
_SNG_ACC_MIN_DIST < close_distance < _SNG_ACC_MAX_DIST and
|
||||
close_distance > self.prev_close_distance
|
||||
)
|
||||
if should_resume:
|
||||
self.epb_resume_frames_remaining = 15
|
||||
|
||||
if CS.out.standstill and not self.prev_standstill:
|
||||
self.standstill_start = self.frame
|
||||
throttle_cmd = self.epb_resume_frames_remaining > 0
|
||||
if self.epb_resume_frames_remaining > 0:
|
||||
self.epb_resume_frames_remaining -= 1
|
||||
|
||||
self.prev_standstill = CS.out.standstill
|
||||
self.prev_cruise_state = CS.cruise_state
|
||||
|
||||
if self.sng_acc_resume:
|
||||
if self.sng_acc_resume_cnt < 5:
|
||||
throttle_cmd = True
|
||||
self.sng_acc_resume_cnt += 1
|
||||
else:
|
||||
self.sng_acc_resume = False
|
||||
self.sng_acc_resume_cnt = -1
|
||||
|
||||
self.prev_close_distance = CS.close_distance
|
||||
self.prev_close_distance = close_distance
|
||||
return throttle_cmd, speed_cmd
|
||||
|
||||
@@ -358,6 +358,19 @@ def create_brake_pedal(packer, frame, brake_pedal_msg, speed_cmd, brake_cmd):
|
||||
return packer.make_can_msg("Brake_Pedal", CanBus.camera, values)
|
||||
|
||||
|
||||
def create_preglobal_brake_pedal(packer, brake_pedal_msg, speed_cmd):
|
||||
values = {s: brake_pedal_msg[s] for s in sorted([
|
||||
"Brake_Pedal",
|
||||
"Signal1",
|
||||
"Speed",
|
||||
])}
|
||||
|
||||
if speed_cmd:
|
||||
values["Speed"] = 1
|
||||
|
||||
return packer.make_can_msg("Brake_Pedal", CanBus.camera, values)
|
||||
|
||||
|
||||
def create_throttle(packer, frame, throttle_msg, throttle_cmd):
|
||||
values = {s: throttle_msg[s] for s in sorted([
|
||||
"CHECKSUM",
|
||||
|
||||
@@ -1,4 +1,57 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from opendbc.car.subaru.carcontroller import CarController
|
||||
from opendbc.car.subaru.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.subaru.values import SubaruFlags
|
||||
|
||||
|
||||
def make_sng_controller(flags=0, prev_close_distance=4.0):
|
||||
controller = object.__new__(CarController)
|
||||
controller.CP = SimpleNamespace(flags=flags)
|
||||
controller.frame = 60
|
||||
controller.last_standstill_frame = 0
|
||||
controller.prev_close_distance = prev_close_distance
|
||||
controller.epb_resume_frames_remaining = -1
|
||||
return controller
|
||||
|
||||
|
||||
def make_sng_state(close_distance=4.0, standstill=True):
|
||||
cc = SimpleNamespace(enabled=True, hudControl=SimpleNamespace(leadVisible=True))
|
||||
cs = SimpleNamespace(
|
||||
close_distance=close_distance,
|
||||
out=SimpleNamespace(standstill=standstill),
|
||||
)
|
||||
return cc, cs
|
||||
|
||||
|
||||
def test_global_sng_keeps_standstill_alive_without_manual_parking_brake_toggle():
|
||||
controller = make_sng_controller()
|
||||
cc, cs = make_sng_state()
|
||||
|
||||
throttle_cmd, speed_cmd = controller.stop_and_go(cc, cs, manual_parking_brake=False)
|
||||
|
||||
assert throttle_cmd is False
|
||||
assert speed_cmd is True
|
||||
|
||||
|
||||
def test_manual_parking_brake_sng_still_sends_resume_throttle():
|
||||
controller = make_sng_controller(prev_close_distance=3.9)
|
||||
cc, cs = make_sng_state(close_distance=4.0)
|
||||
|
||||
throttle_cmd, speed_cmd = controller.stop_and_go(cc, cs, manual_parking_brake=True)
|
||||
|
||||
assert throttle_cmd is True
|
||||
assert speed_cmd is True
|
||||
|
||||
|
||||
def test_preglobal_sng_does_not_send_standstill_keepalive_without_manual_toggle():
|
||||
controller = make_sng_controller(flags=SubaruFlags.PREGLOBAL)
|
||||
cc, cs = make_sng_state()
|
||||
|
||||
throttle_cmd, speed_cmd = controller.stop_and_go(cc, cs, manual_parking_brake=False)
|
||||
|
||||
assert throttle_cmd is False
|
||||
assert speed_cmd is False
|
||||
|
||||
|
||||
class TestSubaruFingerprint:
|
||||
|
||||
@@ -59,6 +59,7 @@ class SubaruSafetyFlags(IntFlag):
|
||||
GEN2 = 1
|
||||
LONG = 2
|
||||
PREGLOBAL_REVERSED_DRIVER_TORQUE = 4
|
||||
STOP_AND_GO = 8
|
||||
|
||||
|
||||
class SubaruFlags(IntFlag):
|
||||
|
||||
@@ -4,15 +4,14 @@ from opendbc.car import Bus
|
||||
from opendbc.car.lateral import apply_steer_angle_limits_vm
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.tesla.teslacan import TeslaCAN
|
||||
from opendbc.car.tesla.values import CarControllerParams
|
||||
from opendbc.car.tesla.preap.carcontroller import PreAPLongController, init_preap_can
|
||||
from opendbc.car.tesla.preap.stock_cc_spoofer import StockCCSpoofer
|
||||
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
|
||||
|
||||
def get_safety_CP():
|
||||
# We use the TESLA_MODEL_Y platform for lateral limiting to match safety
|
||||
# A Model 3 at 40 m/s using the Model Y limits sees a <0.3% difference in max angle (from curvature factor)
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
return CarInterface.get_non_essential_params("TESLA_MODEL_Y")
|
||||
return CarInterface.get_non_essential_params(CAR.TESLA_MODEL_Y)
|
||||
|
||||
|
||||
class CarController(CarControllerBase):
|
||||
@@ -21,11 +20,23 @@ class CarController(CarControllerBase):
|
||||
self.apply_angle_last = 0
|
||||
self.packer = CANPacker(dbc_names[Bus.party])
|
||||
self.tesla_can = TeslaCAN(self.packer)
|
||||
self.preap_long = None
|
||||
self.stock_cc = None
|
||||
|
||||
# Vehicle model used for lateral limiting
|
||||
self.VM = VehicleModel(get_safety_CP())
|
||||
|
||||
if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
self.tesla_can = init_preap_can(dbc_names)
|
||||
self.preap_long = PreAPLongController()
|
||||
self.stock_cc = StockCCSpoofer()
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
self.VM = VehicleModel(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_PREAP))
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
return self._update_preap(CC, CS)
|
||||
|
||||
actuators = CC.actuators
|
||||
can_sends = []
|
||||
|
||||
@@ -64,3 +75,45 @@ class CarController(CarControllerBase):
|
||||
|
||||
self.frame += 1
|
||||
return new_actuators, can_sends
|
||||
|
||||
def _update_preap(self, CC, CS):
|
||||
actuators = CC.actuators
|
||||
can_sends = []
|
||||
lat_active = CC.latActive and CS.hands_on_level < 3
|
||||
|
||||
if CC.cruiseControl.cancel and CS.cruiseEnabled:
|
||||
CS.cruiseEnabled = False
|
||||
CS.enableLongControl = False
|
||||
CS.enableJustCC = False
|
||||
CS.pedal_speed_kph = 0.0
|
||||
CS.preap_cc_cancel_needed = True
|
||||
if hasattr(CS, "engagement"):
|
||||
CS.engagement.cruiseEnabled = False
|
||||
CS.engagement.enableLongControl = False
|
||||
CS.engagement.enableJustCC = False
|
||||
CS.engagement.pending_enable = False
|
||||
CS.engagement.pedal_speed_kph = 0.0
|
||||
|
||||
if self.frame % 2 == 0:
|
||||
self.apply_angle_last = apply_steer_angle_limits_vm(
|
||||
actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw, CS.out.steeringAngleDeg,
|
||||
lat_active, CarControllerParams, self.VM,
|
||||
)
|
||||
cntr = (self.frame // 2) % 16
|
||||
can_sends.append(self.tesla_can.create_steering_control(cntr, self.apply_angle_last, lat_active))
|
||||
can_sends.append(self.tesla_can.create_epas_control(cntr, 1))
|
||||
|
||||
CS.pccEvent = None
|
||||
if self.CP.openpilotLongitudinalControl and self.preap_long is not None:
|
||||
can_sends.extend(self.preap_long.update(CC, CS, self.frame, self.tesla_can, CANBUS.party))
|
||||
|
||||
if self.stock_cc is not None:
|
||||
can_sends.extend(self.stock_cc.update(CS, self.frame, self.tesla_can, CANBUS.party))
|
||||
if self.stock_cc.pcc_event:
|
||||
CS.pccEvent = self.stock_cc.pcc_event
|
||||
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steeringAngleDeg = self.apply_angle_last
|
||||
|
||||
self.frame += 1
|
||||
return new_actuators, can_sends
|
||||
|
||||
@@ -5,6 +5,10 @@ from opendbc.car import Bus, structs
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
from opendbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_THRESHOLD, CAR
|
||||
from opendbc.car.tesla.preap.carstate import get_preap_can_parsers, update_preap
|
||||
from opendbc.car.tesla.preap.engagement import PreAPEngagement
|
||||
from opendbc.car.tesla.preap.nap_conf import nap_conf
|
||||
from opendbc.car.tesla.preap.pedal_feedback import PedalFeedback
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
|
||||
@@ -13,7 +17,8 @@ class CarState(CarStateBase):
|
||||
def __init__(self, CP, FPCP):
|
||||
super().__init__(CP, FPCP)
|
||||
self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party])
|
||||
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"]
|
||||
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] if CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP else \
|
||||
self.can_define.dv["DI_torque2"]["DI_gear"]
|
||||
|
||||
self.autopark = False
|
||||
self.autopark_prev = False
|
||||
@@ -21,6 +26,25 @@ class CarState(CarStateBase):
|
||||
|
||||
self.hands_on_level = 0
|
||||
self.das_control = None
|
||||
self.cruise_buttons = 0
|
||||
self.prev_cruise_buttons = 0
|
||||
self.msg_stw_actn_req = None
|
||||
self.speed_units = "MPH"
|
||||
|
||||
if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
self.engagement = PreAPEngagement(nap_conf.double_pull_enabled, nap_conf.double_pull_window_ms)
|
||||
self.cruiseEnabled = False
|
||||
self.enableLongControl = False
|
||||
self.enableJustCC = False
|
||||
self.pedal_speed_kph = 0.0
|
||||
self.prev_stalk_follow = 0
|
||||
self.pccEvent = None
|
||||
self.preap_cc_cancel_needed = False
|
||||
self.preap_cc_engage_needed = False
|
||||
self.di_cruise_state = "OFF"
|
||||
self.pedal = PedalFeedback()
|
||||
self.pedal_interceptor_value = 0.0
|
||||
self.pedal_timeout = True
|
||||
|
||||
def update_autopark_state(self, autopark_state: str, cruise_enabled: bool):
|
||||
autopark_now = autopark_state in ("ACTIVE", "COMPLETE", "SELFPARK_STARTED")
|
||||
@@ -31,7 +55,15 @@ class CarState(CarStateBase):
|
||||
self.autopark_prev = autopark_now
|
||||
self.cruise_enabled_prev = cruise_enabled
|
||||
|
||||
def update_button_enable(self, buttonEvents: list[structs.CarState.ButtonEvent]):
|
||||
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
return False
|
||||
return super().update_button_enable(buttonEvents)
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
return update_preap(self, can_parsers)
|
||||
|
||||
cp_party = can_parsers[Bus.party]
|
||||
cp_ap_party = can_parsers[Bus.ap_party]
|
||||
ret = structs.CarState()
|
||||
@@ -124,6 +156,8 @@ class CarState(CarStateBase):
|
||||
|
||||
@staticmethod
|
||||
def get_can_parsers(CP):
|
||||
if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
return get_preap_can_parsers(CP)
|
||||
return {
|
||||
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
|
||||
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party)
|
||||
|
||||
@@ -45,3 +45,22 @@ FW_VERSIONS = {
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
FINGERPRINTS = {
|
||||
CAR.TESLA_MODEL_S_PREAP: [
|
||||
{
|
||||
1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 277: 6, 280: 6, 293: 4, 296: 4,
|
||||
309: 5, 325: 8, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 520: 4,
|
||||
522: 8, 524: 8, 527: 8, 536: 8, 551: 4, 552: 2, 556: 8, 568: 8, 582: 5, 638: 8, 643: 8,
|
||||
693: 8, 696: 8, 712: 8, 728: 8, 744: 8, 760: 8, 771: 2, 772: 8, 775: 8, 776: 8, 778: 8,
|
||||
780: 2, 783: 8, 785: 8, 787: 8, 788: 8, 791: 8, 792: 8, 796: 2, 799: 8, 804: 8, 805: 8,
|
||||
807: 8, 808: 1, 812: 8, 815: 8, 820: 8, 823: 8, 824: 8, 831: 8, 836: 8, 840: 8, 856: 4,
|
||||
863: 8, 872: 8, 880: 8, 888: 8, 896: 8, 901: 6, 904: 3, 920: 8, 936: 8, 949: 8, 952: 8,
|
||||
953: 6, 968: 8, 984: 8, 1000: 8, 1006: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1,
|
||||
1034: 8, 1048: 1, 1064: 8, 1080: 8, 1281: 8, 1285: 8, 1332: 8, 1335: 8, 1362: 6, 1368: 8,
|
||||
1412: 8, 1436: 8, 1456: 8, 1463: 8, 1476: 8, 1524: 8, 1527: 8, 1601: 8, 1605: 8, 1617: 8,
|
||||
1621: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8,
|
||||
1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -2,17 +2,29 @@ from opendbc.car import get_safety_config, structs
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.car.tesla.carcontroller import CarController
|
||||
from opendbc.car.tesla.carstate import CarState
|
||||
from opendbc.car.tesla.radar_interface import RadarInterface
|
||||
from opendbc.car.tesla.values import TeslaSafetyFlags, CAR
|
||||
from opendbc.car.tesla.preap.interface import get_preap_accel_limits, get_preap_params
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
CarState = CarState
|
||||
CarController = CarController
|
||||
RadarInterface = RadarInterface
|
||||
|
||||
@staticmethod
|
||||
def get_pid_accel_limits(CP, current_speed, cruise_speed):
|
||||
if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
return get_preap_accel_limits(current_speed)
|
||||
return CarInterfaceBase.get_pid_accel_limits(CP, current_speed, cruise_speed)
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
ret.brand = "tesla"
|
||||
|
||||
if candidate == CAR.TESLA_MODEL_S_PREAP:
|
||||
return get_preap_params(ret)
|
||||
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla)]
|
||||
|
||||
ret.steerLimitTimer = 0.4
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
from numpy import clip, interp
|
||||
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.tesla.preap.nap_conf import (
|
||||
ACCEL_MAX,
|
||||
PEDAL_BP,
|
||||
PEDAL_DI_MIN,
|
||||
PEDAL_DI_ZERO,
|
||||
PEDAL_MAX_VALUES,
|
||||
REGEN_MAX,
|
||||
nap_conf,
|
||||
)
|
||||
|
||||
PEDAL_RAMP_RATE_UP = 5.0
|
||||
PEDAL_RAMP_RATE_DOWN = 2.5
|
||||
ACCEL_DEADBAND = 0.15
|
||||
PEDAL_HYST_GAP = 1.0
|
||||
TORQUE_LEVEL_ACC = 0.0
|
||||
TORQUE_LEVEL_DECEL = -30.0
|
||||
ZERO_TORQUE_MIN_SPEED = 10.0 * CV.MPH_TO_MS
|
||||
|
||||
|
||||
class PedalZeroTorque:
|
||||
def __init__(self):
|
||||
self.value = PEDAL_DI_ZERO
|
||||
self._best_torque = TORQUE_LEVEL_DECEL
|
||||
|
||||
def update(self, torque_level: float, current_pedal_di: float, v_ego: float) -> None:
|
||||
if v_ego < ZERO_TORQUE_MIN_SPEED:
|
||||
return
|
||||
|
||||
if TORQUE_LEVEL_DECEL < torque_level < TORQUE_LEVEL_ACC and abs(torque_level) < abs(self._best_torque):
|
||||
self.value = current_pedal_di
|
||||
self._best_torque = torque_level
|
||||
|
||||
def get(self, v_ego: float) -> float:
|
||||
if v_ego < 5.0 * CV.MPH_TO_MS:
|
||||
return PEDAL_DI_ZERO
|
||||
return self.value
|
||||
|
||||
|
||||
_zero_torque = PedalZeroTorque()
|
||||
|
||||
|
||||
def get_zero_torque() -> PedalZeroTorque:
|
||||
return _zero_torque
|
||||
|
||||
|
||||
def compute_pedal_command(accel_request: float, v_ego: float, prev_pedal_di: float) -> tuple[float, float]:
|
||||
max_pedal_value = float(interp(v_ego, PEDAL_BP, PEDAL_MAX_VALUES))
|
||||
zero_torque_di = _zero_torque.get(v_ego)
|
||||
|
||||
if abs(accel_request) < ACCEL_DEADBAND:
|
||||
accel_request = 0.0
|
||||
|
||||
pedal_di = float(interp(accel_request, [REGEN_MAX, 0.0, ACCEL_MAX], [PEDAL_DI_MIN, zero_torque_di, max_pedal_value]))
|
||||
pedal_di = float(clip(pedal_di, PEDAL_DI_MIN, max_pedal_value))
|
||||
pedal_di = float(clip(pedal_di, prev_pedal_di - PEDAL_RAMP_RATE_DOWN, prev_pedal_di + PEDAL_RAMP_RATE_UP))
|
||||
|
||||
if abs(pedal_di - prev_pedal_di) < PEDAL_HYST_GAP:
|
||||
pedal_di = prev_pedal_di
|
||||
|
||||
return nap_conf.di_to_pedal(pedal_di), pedal_di
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import numpy as np
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus
|
||||
from opendbc.car.carlog import carlog
|
||||
from opendbc.car.tesla.pedal.controller import get_zero_torque
|
||||
from opendbc.car.tesla.preap.interface import get_preap_accel_limits
|
||||
from opendbc.car.tesla.preap.nap_conf import PEDAL_DI_MIN, PEDAL_DI_ZERO, nap_conf
|
||||
from opendbc.car.tesla.preap.teslacan import TeslaCANPreAP
|
||||
from opendbc.car.tesla.preap.virtual_das import VirtualDAS
|
||||
from opendbc.car.tesla.values import CANBUS, CruiseButtons
|
||||
|
||||
ENGAGE_GRACE_FRAMES = 50
|
||||
|
||||
|
||||
def init_preap_can(dbc_names):
|
||||
tesla_can = TeslaCANPreAP(CANPacker(dbc_names[Bus.party]))
|
||||
tesla_can.pedal_can_bus = nap_conf.pedal_can_bus
|
||||
return tesla_can
|
||||
|
||||
|
||||
class PreAPLongController:
|
||||
def __init__(self):
|
||||
self.prev_pedal_di = 0.0
|
||||
self.prev_requested_long = False
|
||||
self.preap_long_engage_frame = -1_000_000
|
||||
self.engage_a_max = 0.0
|
||||
self.vdas = VirtualDAS(dt=0.02)
|
||||
|
||||
def update(self, CC, CS, frame: int, tesla_can, can_bus_party: int):
|
||||
can_sends = []
|
||||
actuators = CC.actuators
|
||||
requested_long = CS.cruiseEnabled and CS.enableLongControl
|
||||
long_active = requested_long and CC.longActive
|
||||
use_pedal = nap_conf.use_pedal and self._pedal_ready()
|
||||
|
||||
if (not self.prev_requested_long) and requested_long:
|
||||
self.preap_long_engage_frame = frame
|
||||
zero_torque_di = get_zero_torque().get(CS.out.vEgo)
|
||||
self.prev_pedal_di = max(CS.pedal_interceptor_value, zero_torque_di)
|
||||
self.vdas.reset(a_init=0.0, pedal_di_init=self.prev_pedal_di)
|
||||
_, self.engage_a_max = get_preap_accel_limits(CS.out.vEgo)
|
||||
|
||||
if use_pedal:
|
||||
pedal_button_press = CS.cruise_buttons != CS.prev_cruise_buttons and CS.cruise_buttons != CruiseButtons.IDLE
|
||||
if ((not self.prev_requested_long) and requested_long) or (self.prev_requested_long and not requested_long) or pedal_button_press:
|
||||
CS.preap_cc_cancel_needed = True
|
||||
|
||||
self.prev_requested_long = requested_long
|
||||
|
||||
if frame % 2 == 0:
|
||||
if use_pedal:
|
||||
get_zero_torque().update(CS.pedal.torque_level, self.prev_pedal_di, CS.out.vEgo)
|
||||
|
||||
if requested_long and use_pedal:
|
||||
try:
|
||||
if CS.out.gasPressed:
|
||||
self.prev_pedal_di = max(CS.pedal_interceptor_value, PEDAL_DI_ZERO)
|
||||
can_sends.append(tesla_can.create_pedal_command(0, enable=0))
|
||||
elif long_active:
|
||||
accel_request = float(actuators.accel)
|
||||
engage_elapsed_frames = frame - self.preap_long_engage_frame
|
||||
in_engage_grace = engage_elapsed_frames < ENGAGE_GRACE_FRAMES
|
||||
if in_engage_grace:
|
||||
accel_request = max(0.0, min(accel_request, (engage_elapsed_frames / ENGAGE_GRACE_FRAMES) * self.engage_a_max))
|
||||
|
||||
self.prev_pedal_di = self.vdas.update(
|
||||
accel_request, CS.out.vEgo, self.prev_pedal_di, a_ego=CS.out.aEgo,
|
||||
freeze_integrator=in_engage_grace, orientation_ned=list(CC.orientationNED),
|
||||
)
|
||||
can_sends.append(tesla_can.create_pedal_command(nap_conf.di_to_pedal(self.prev_pedal_di), enable=1))
|
||||
if self.prev_pedal_di <= 0.95 * PEDAL_DI_MIN and not in_engage_grace:
|
||||
CS.pccEvent = "pedalMaxRegen"
|
||||
else:
|
||||
zero_torque_di = get_zero_torque().get(CS.out.vEgo)
|
||||
self.prev_pedal_di = zero_torque_di
|
||||
can_sends.append(tesla_can.create_pedal_command(nap_conf.di_to_pedal(zero_torque_di), enable=1))
|
||||
except Exception:
|
||||
carlog.exception("Pre-AP pedal command failed; sending disabled")
|
||||
can_sends.append(tesla_can.create_pedal_command(nap_conf.di_to_pedal(PEDAL_DI_ZERO), enable=0))
|
||||
self.prev_pedal_di = 0.0
|
||||
else:
|
||||
if nap_conf.use_pedal:
|
||||
can_sends.append(tesla_can.create_pedal_command(nap_conf.di_to_pedal(PEDAL_DI_ZERO), enable=0))
|
||||
self.prev_pedal_di = 0.0
|
||||
|
||||
return can_sends
|
||||
|
||||
@staticmethod
|
||||
def _pedal_ready() -> bool:
|
||||
return nap_conf.pedal_calibrated and abs(nap_conf.pedal_factor) > 1e-6
|
||||
@@ -0,0 +1,154 @@
|
||||
import copy
|
||||
import math
|
||||
import time
|
||||
|
||||
from cereal import custom
|
||||
from opendbc.can import CANParser
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.tesla.preap.nap_params import NAPParamKeys
|
||||
from opendbc.car.tesla.preap.engagement import PreAPEngagement
|
||||
from opendbc.car.tesla.preap.nap_conf import PEDAL_DI_PRESSED, nap_conf
|
||||
from opendbc.car.tesla.preap.pedal_feedback import PedalFeedback
|
||||
from opendbc.car.tesla.values import CANBUS, DBC, GEAR_MAP, STEER_THRESHOLD
|
||||
|
||||
try:
|
||||
from openpilot.common.params import Params as _NAPParams
|
||||
_nap_params = _NAPParams()
|
||||
except ImportError:
|
||||
_nap_params = None
|
||||
|
||||
_DOORS = ("DOOR_STATE_FL", "DOOR_STATE_FR", "DOOR_STATE_RL", "DOOR_STATE_RR", "DOOR_STATE_FrontTrunk", "BOOT_STATE")
|
||||
|
||||
|
||||
def _current_time_millis() -> int:
|
||||
return int(round(time.time() * 1000))
|
||||
|
||||
|
||||
def update_preap(cs, can_parsers):
|
||||
cp_ap_party = can_parsers[Bus.ap_party]
|
||||
cp_pt = can_parsers[Bus.pt]
|
||||
cp_chassis = can_parsers[Bus.chassis]
|
||||
ret = structs.CarState()
|
||||
fp_ret = custom.StarPilotCarState.new_message()
|
||||
|
||||
ret.vEgoRaw = cp_chassis.vl["ESP_B"]["ESP_vehicleSpeed"] * CV.KPH_TO_MS
|
||||
ret.vEgo, ret.aEgo = cs.update_speed_kf(ret.vEgoRaw)
|
||||
ret.gasPressed = cp_pt.vl["DI_torque1"]["DI_pedalPos"] > PEDAL_DI_PRESSED
|
||||
real_brake_pressed = cp_chassis.vl["BrakeMessage"]["driverBrakeStatus"] == 2
|
||||
ret.brake = 0
|
||||
ret.brakePressed = real_brake_pressed
|
||||
|
||||
epas_status = cp_chassis.vl["EPAS_sysStatus"]
|
||||
cs.hands_on_level = epas_status["EPAS_handsOnLevel"]
|
||||
ret.steeringAngleDeg = -epas_status["EPAS_internalSAS"]
|
||||
ret.steeringRateDeg = -cp_chassis.vl["STW_ANGLHP_STAT"]["StW_AnglHP_Spd"]
|
||||
ret.steeringTorque = -epas_status["EPAS_torsionBarTorque"]
|
||||
ret.steeringPressed = cs.update_steering_pressed(abs(ret.steeringTorque) > STEER_THRESHOLD, 5)
|
||||
|
||||
eac_status = cs.can_define.dv["EPAS_sysStatus"]["EPAS_eacStatus"].get(int(epas_status["EPAS_eacStatus"]), None)
|
||||
ret.steerFaultPermanent = eac_status == "EAC_FAULT"
|
||||
ret.steerFaultTemporary = False
|
||||
|
||||
eac_error_code = cs.can_define.dv["EPAS_sysStatus"]["EPAS_eacErrorCode"].get(int(epas_status["EPAS_eacErrorCode"]), None)
|
||||
ret.steeringDisengage = cs.hands_on_level >= 3 or (eac_status == "EAC_INHIBITED" and eac_error_code in (
|
||||
"EAC_ERROR_HIGH_ANGLE_REQ", "EAC_ERROR_HIGH_ANGLE_RATE_REQ", "EAC_ERROR_HIGH_ANGLE_SAFETY", "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY",
|
||||
))
|
||||
cs.engagement.handle_steering_disengage(ret.steeringDisengage)
|
||||
|
||||
cruise_state = cs.can_define.dv["DI_state"]["DI_cruiseState"].get(int(cp_chassis.vl["DI_state"]["DI_cruiseState"]), None)
|
||||
cs.di_cruise_state = cruise_state or "OFF"
|
||||
speed_units = cs.can_define.dv["DI_state"]["DI_speedUnits"].get(int(cp_chassis.vl["DI_state"]["DI_speedUnits"]), None)
|
||||
if speed_units is not None:
|
||||
cs.speed_units = speed_units
|
||||
|
||||
pedal_transform_valid = math.isfinite(nap_conf.pedal_factor) and abs(nap_conf.pedal_factor) > 1e-6
|
||||
use_pedal = nap_conf.use_pedal and cs.CP.openpilotLongitudinalControl and pedal_transform_valid
|
||||
pedal_long_allowed = use_pedal
|
||||
long_control_allowed = True if not use_pedal else pedal_long_allowed
|
||||
|
||||
ret.cruiseState.available = True
|
||||
if cs.enableLongControl and use_pedal:
|
||||
ret.cruiseState.speed = cs.pedal_speed_kph * CV.KPH_TO_MS
|
||||
elif speed_units == "KPH":
|
||||
ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_digitalSpeed"] * CV.KPH_TO_MS, 1e-3)
|
||||
elif speed_units == "MPH":
|
||||
ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_digitalSpeed"] * CV.MPH_TO_MS, 1e-3)
|
||||
ret.cruiseState.standstill = False
|
||||
ret.standstill = cruise_state == "STANDSTILL"
|
||||
ret.accFaulted = cruise_state == "FAULT"
|
||||
|
||||
ret.gearShifter = GEAR_MAP[cs.can_define.dv["DI_torque2"]["DI_gear"].get(int(cp_chassis.vl["DI_torque2"]["DI_gear"]), "DI_GEAR_INVALID")]
|
||||
ret.doorOpen = any((cs.can_define.dv["GTW_carState"][door].get(int(cp_chassis.vl["GTW_carState"][door]), "OPEN") == "OPEN") for door in _DOORS)
|
||||
ret.leftBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorLStatus"] == 1
|
||||
ret.rightBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorRStatus"] == 1
|
||||
ret.seatbeltUnlatched = False
|
||||
ret.stockAeb = False
|
||||
ret.stockLkas = False
|
||||
|
||||
cs.prev_cruise_buttons = cs.cruise_buttons
|
||||
cs.cruise_buttons = int(cp_chassis.vl["STW_ACTN_RQ"]["SpdCtrlLvr_Stat"])
|
||||
cs.msg_stw_actn_req = copy.copy(cp_chassis.vl["STW_ACTN_RQ"])
|
||||
|
||||
if _nap_params is not None:
|
||||
dtr_dist = int(cp_chassis.vl["STW_ACTN_RQ"]["DTR_Dist_Rq"])
|
||||
if dtr_dist != 255:
|
||||
stalk_follow = min((dtr_dist // 33) + 1, 7)
|
||||
if stalk_follow != cs.prev_stalk_follow:
|
||||
_nap_params.put(NAPParamKeys.FOLLOW_DISTANCE, str(stalk_follow))
|
||||
cs.prev_stalk_follow = stalk_follow
|
||||
|
||||
curr_time_ms = _current_time_millis()
|
||||
ret.buttonEvents = cs.engagement.process_buttons(
|
||||
cs.cruise_buttons, cs.prev_cruise_buttons, curr_time_ms, ret.vEgo, cs.speed_units,
|
||||
use_pedal, pedal_long_allowed, long_control_allowed, real_brake_pressed, cs.di_cruise_state,
|
||||
)
|
||||
ret.brakePressed = False
|
||||
|
||||
can_engage = cs.engagement.check_can_engage(ret.doorOpen, ret.gearShifter, ret.seatbeltUnlatched)
|
||||
ret.cruiseState.enabled = cs.engagement.cruiseEnabled and can_engage
|
||||
|
||||
cs.cruiseEnabled = cs.engagement.cruiseEnabled
|
||||
cs.enableLongControl = cs.engagement.enableLongControl
|
||||
cs.enableJustCC = cs.engagement.enableJustCC
|
||||
cs.pedal_speed_kph = cs.engagement.pedal_speed_kph
|
||||
cs.preap_cc_cancel_needed = cs.engagement.preap_cc_cancel_needed
|
||||
cs.preap_cc_engage_needed = cs.engagement.preap_cc_engage_needed
|
||||
|
||||
if nap_conf.use_pedal:
|
||||
gas_sensor = cp_ap_party.vl.get("GAS_SENSOR", {})
|
||||
cs.pedal.update(gas_sensor, curr_time_ms)
|
||||
cs.pedal.update_torque(cp_pt.vl.get("DI_torque1", {}))
|
||||
cs.pedal_interceptor_value = cs.pedal.interceptor_value
|
||||
cs.pedal_timeout = cs.pedal.timeout
|
||||
if use_pedal:
|
||||
ret.gasPressed = cs.pedal.gas_pressed
|
||||
|
||||
cs.das_control = None
|
||||
cs.cruise_enabled_prev = ret.cruiseState.enabled
|
||||
fp_ret.pedalMaxRegen = cs.pccEvent == "pedalMaxRegen"
|
||||
fp_ret.teslaCCEngaged = cs.pccEvent == "teslaCCEngaged"
|
||||
fp_ret.teslaCCDisengaged = cs.pccEvent == "teslaCCDisengaged"
|
||||
fp_ret.teslaCCNotArmed = (
|
||||
not nap_conf.use_pedal and
|
||||
cs.cruiseEnabled and
|
||||
cs.enableLongControl and
|
||||
cs.di_cruise_state not in ("STANDBY", "ENABLED")
|
||||
)
|
||||
fp_ret.pedalLongActive = cs.enableLongControl and nap_conf.use_pedal
|
||||
return ret, fp_ret
|
||||
|
||||
|
||||
def get_preap_can_parsers(CP):
|
||||
chassis_messages = [
|
||||
("ESP_B", 0), ("BrakeMessage", 0), ("DI_state", 0), ("DI_torque2", 0),
|
||||
("GTW_carState", 0), ("GTW_epasControl", 0), ("STW_ANGLHP_STAT", 0), ("EPAS_sysStatus", 0), ("STW_ACTN_RQ", 0),
|
||||
]
|
||||
pt_messages = [("DI_torque1", 0)]
|
||||
pedal_messages = [("GAS_SENSOR", 50)] if nap_conf.use_pedal else []
|
||||
return {
|
||||
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
|
||||
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], pedal_messages, nap_conf.pedal_can_bus),
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CANBUS.party),
|
||||
Bus.chassis: CANParser(DBC[CP.carFingerprint][Bus.chassis], chassis_messages, CANBUS.party),
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
ACCEL_PREAP_BP = [0.0, 1.3, 7.5, 15.0, 25.0, 40.0]
|
||||
ACCEL_PREAP_FOLLOW = [0.35, 0.55, 0.80, 0.75, 0.65, 0.50]
|
||||
ACCEL_PREAP_PROFILES = {
|
||||
0: [0.3, 0.8, 1.1, 1.0, 0.85, 0.7],
|
||||
1: [0.3, 0.7, 1.0, 0.9, 0.8, 0.65],
|
||||
2: [0.3, 0.6, 0.9, 0.8, 0.7, 0.55],
|
||||
}
|
||||
PEDAL_LONG_K_BP = [0.0, 3.0, 6.0, 35.0]
|
||||
PEDAL_LONG_KP_V = [0.0, 0.0, 0.0, 0.0]
|
||||
PEDAL_LONG_KI_V = [0.05, 0.08, 0.10, 0.15]
|
||||
VDAS_INNER_K_BP = [0.0, 5.0, 35.0]
|
||||
VDAS_INNER_KP_V = [0.0, 0.0, 0.0]
|
||||
VDAS_INNER_KI_V = [0.3, 0.2, 0.15]
|
||||
VDAS_FUTURE_T_BP = [2.0, 5.0]
|
||||
VDAS_FUTURE_T_V = [0.30, 0.55]
|
||||
VDAS_AEGO_FILTER_RC = 0.25
|
||||
@@ -0,0 +1,148 @@
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.carlog import carlog
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.tesla.values import CruiseButtons
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
CANCEL_ECHO_WINDOW_MS = 600
|
||||
SPOOF_ECHO_WINDOW_MS = 300
|
||||
|
||||
|
||||
class PreAPEngagement:
|
||||
def __init__(self, double_pull_enabled: bool, double_pull_window_ms: int):
|
||||
self.enableDoublePull = double_pull_enabled
|
||||
self.double_pull_window_ms = double_pull_window_ms
|
||||
self.cruiseEnabled = False
|
||||
self.enableLongControl = False
|
||||
self.enableJustCC = False
|
||||
self.pending_enable = False
|
||||
self.stalk_pull_time_ms = 0
|
||||
self.prev_stalk_pull_time_ms = -1000
|
||||
self.pedal_speed_kph = 0.0
|
||||
self.preap_cc_cancel_needed = False
|
||||
self.preap_cc_engage_needed = False
|
||||
self.preap_last_cc_spoof_ms = 0
|
||||
self.preap_brake_pressed_prev = False
|
||||
self.last_stalk_non_cancel_ms = -10000
|
||||
self.prev_steering_disengage = False
|
||||
|
||||
def handle_steering_disengage(self, steering_disengage: bool) -> None:
|
||||
if steering_disengage and not self.prev_steering_disengage:
|
||||
self.cruiseEnabled = False
|
||||
self.enableLongControl = False
|
||||
self.enableJustCC = False
|
||||
self.pending_enable = False
|
||||
self.pedal_speed_kph = 0.0
|
||||
self.stalk_pull_time_ms = 0
|
||||
self.prev_stalk_pull_time_ms = -1000
|
||||
self.prev_steering_disengage = steering_disengage
|
||||
|
||||
def process_buttons(self, cruise_buttons: int, prev_cruise_buttons: int, curr_time_ms: int, v_ego: float, speed_units: str,
|
||||
use_pedal: bool, pedal_long_allowed: bool, long_control_allowed: bool, real_brake_pressed: bool,
|
||||
di_cruise_state: str = "OFF") -> list[structs.CarState.ButtonEvent]:
|
||||
self.preap_cc_cancel_needed = False
|
||||
self.preap_cc_engage_needed = False
|
||||
button_events: list[structs.CarState.ButtonEvent] = []
|
||||
|
||||
if cruise_buttons == CruiseButtons.MAIN and prev_cruise_buttons != CruiseButtons.MAIN:
|
||||
if self.enableDoublePull:
|
||||
self._handle_double_pull(curr_time_ms, v_ego, speed_units, use_pedal, pedal_long_allowed, long_control_allowed, di_cruise_state)
|
||||
else:
|
||||
self.cruiseEnabled = True
|
||||
self.enableLongControl = long_control_allowed
|
||||
self.enableJustCC = not long_control_allowed
|
||||
self.pedal_speed_kph = self._capture_target_speed(v_ego, speed_units) if pedal_long_allowed else 0.0
|
||||
if not use_pedal and di_cruise_state == "STANDBY":
|
||||
self.preap_cc_engage_needed = True
|
||||
self.preap_last_cc_spoof_ms = curr_time_ms
|
||||
|
||||
if cruise_buttons != prev_cruise_buttons:
|
||||
button_events.append(self._make_button_event(cruise_buttons, prev_cruise_buttons, curr_time_ms, v_ego, speed_units, use_pedal))
|
||||
|
||||
if self.pending_enable and (curr_time_ms - self.stalk_pull_time_ms > self.double_pull_window_ms):
|
||||
self.pending_enable = False
|
||||
|
||||
brake_rising_edge = real_brake_pressed and not self.preap_brake_pressed_prev
|
||||
if use_pedal and brake_rising_edge and self.cruiseEnabled and self.enableLongControl:
|
||||
self.enableLongControl = False
|
||||
self.enableJustCC = True
|
||||
self.pending_enable = False
|
||||
self.pedal_speed_kph = 0.0
|
||||
self.preap_brake_pressed_prev = real_brake_pressed
|
||||
|
||||
return button_events
|
||||
|
||||
def check_can_engage(self, door_open: bool, gear_shifter, seatbelt_unlatched: bool) -> bool:
|
||||
can_engage = not door_open and gear_shifter == structs.CarState.GearShifter.drive and not seatbelt_unlatched
|
||||
if not can_engage:
|
||||
self.cruiseEnabled = False
|
||||
self.enableLongControl = False
|
||||
self.enableJustCC = False
|
||||
self.pending_enable = False
|
||||
return can_engage
|
||||
|
||||
def _handle_double_pull(self, curr_time_ms: int, v_ego: float, speed_units: str, use_pedal: bool,
|
||||
pedal_long_allowed: bool, long_control_allowed: bool, di_cruise_state: str) -> None:
|
||||
self.prev_stalk_pull_time_ms = self.stalk_pull_time_ms
|
||||
self.stalk_pull_time_ms = curr_time_ms
|
||||
double_pull = (self.stalk_pull_time_ms - self.prev_stalk_pull_time_ms) < self.double_pull_window_ms
|
||||
|
||||
self.cruiseEnabled = True
|
||||
self.pending_enable = False
|
||||
self.enableLongControl = long_control_allowed if double_pull else False
|
||||
self.enableJustCC = not self.enableLongControl
|
||||
self.pedal_speed_kph = self._capture_target_speed(v_ego, speed_units) if pedal_long_allowed and double_pull else 0.0
|
||||
|
||||
if double_pull:
|
||||
if not use_pedal:
|
||||
self.preap_cc_engage_needed = True
|
||||
self.preap_last_cc_spoof_ms = curr_time_ms
|
||||
else:
|
||||
self.pending_enable = True
|
||||
if not use_pedal:
|
||||
self.preap_cc_cancel_needed = True
|
||||
self.preap_last_cc_spoof_ms = curr_time_ms
|
||||
|
||||
def _make_button_event(self, cruise_buttons: int, prev_cruise_buttons: int, curr_time_ms: int,
|
||||
v_ego: float, speed_units: str, use_pedal: bool) -> structs.CarState.ButtonEvent:
|
||||
be = structs.CarState.ButtonEvent()
|
||||
be.pressed = cruise_buttons != CruiseButtons.IDLE
|
||||
state = cruise_buttons if be.pressed else prev_cruise_buttons
|
||||
|
||||
if state == CruiseButtons.MAIN:
|
||||
be.type = ButtonType.setCruise
|
||||
if be.pressed:
|
||||
self.last_stalk_non_cancel_ms = curr_time_ms
|
||||
elif state == CruiseButtons.CANCEL:
|
||||
is_echo = (self.cruiseEnabled and (curr_time_ms - self.last_stalk_non_cancel_ms) < CANCEL_ECHO_WINDOW_MS) or \
|
||||
((curr_time_ms - self.preap_last_cc_spoof_ms) < SPOOF_ECHO_WINDOW_MS)
|
||||
be.type = ButtonType.unknown if is_echo else ButtonType.cancel
|
||||
if not is_echo:
|
||||
self.cruiseEnabled = False
|
||||
self.enableLongControl = False
|
||||
self.enableJustCC = False
|
||||
self.pending_enable = False
|
||||
self.pedal_speed_kph = 0.0
|
||||
self.stalk_pull_time_ms = 0
|
||||
self.prev_stalk_pull_time_ms = -1000
|
||||
elif CruiseButtons.is_accel(state):
|
||||
be.type = ButtonType.accelCruise
|
||||
if be.pressed and use_pedal and self.enableLongControl:
|
||||
speed_uom_kph = CV.MPH_TO_KPH if speed_units == "MPH" else 1.0
|
||||
actual_kph = int(v_ego * CV.MS_TO_KPH / speed_uom_kph + 0.5) * speed_uom_kph
|
||||
self.pedal_speed_kph = min(max(self.pedal_speed_kph, actual_kph) + (5 * speed_uom_kph if state == CruiseButtons.RES_ACCEL_2ND else speed_uom_kph), 270.0)
|
||||
elif CruiseButtons.is_decel(state):
|
||||
be.type = ButtonType.decelCruise
|
||||
if be.pressed and use_pedal and self.enableLongControl:
|
||||
speed_uom_kph = CV.MPH_TO_KPH if speed_units == "MPH" else 1.0
|
||||
self.pedal_speed_kph = max(self.pedal_speed_kph - (5 * speed_uom_kph if state == CruiseButtons.DECEL_2ND else speed_uom_kph), 0.0)
|
||||
else:
|
||||
be.type = ButtonType.unknown
|
||||
|
||||
return be
|
||||
|
||||
@staticmethod
|
||||
def _capture_target_speed(v_ego: float, speed_units: str) -> float:
|
||||
speed_uom_kph = CV.MPH_TO_KPH if speed_units == "MPH" else 1.0
|
||||
return max(int(v_ego * CV.MS_TO_KPH / speed_uom_kph + 0.5) * speed_uom_kph, 0.0)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
SPEED_BP = [0.0, 5.0, 12.0, 20.0, 30.0, 40.0]
|
||||
ACCEL_BP = [-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5]
|
||||
DEFAULT_TABLE = [
|
||||
[-5.00, -3.33, -1.67, 0.00, 10.00, 20.00, 30.00, 40.00, 50.00],
|
||||
[-5.00, -3.33, -1.67, 0.00, 11.60, 23.20, 34.80, 46.40, 58.00],
|
||||
[-5.00, -3.33, -1.67, 0.00, 13.20, 26.40, 39.60, 52.80, 66.00],
|
||||
[-5.00, -3.33, -1.67, 0.00, 14.80, 29.60, 44.40, 59.20, 74.00],
|
||||
[-5.00, -3.33, -1.67, 0.00, 16.40, 32.80, 49.20, 65.60, 82.00],
|
||||
[-5.00, -3.33, -1.67, 0.00, 18.00, 36.00, 54.00, 72.00, 90.00],
|
||||
]
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import numpy as np
|
||||
|
||||
from opendbc.car import get_safety_config, structs
|
||||
from opendbc.car.tesla.preap.constants import ACCEL_PREAP_BP, ACCEL_PREAP_PROFILES, PEDAL_LONG_K_BP, PEDAL_LONG_KI_V, PEDAL_LONG_KP_V
|
||||
from opendbc.car.tesla.preap.nap_conf import nap_conf
|
||||
|
||||
PREAP_FLAG_ENABLE_PEDAL = 1
|
||||
PREAP_FLAG_RADAR_EMULATION = 2
|
||||
PREAP_FLAG_RADAR_BEHIND_NOSECONE = 4
|
||||
SAFETY_TESLA_PREAP = 35
|
||||
|
||||
|
||||
def get_preap_accel_limits(current_speed: float) -> tuple[float, float]:
|
||||
try:
|
||||
from openpilot.common.params import Params
|
||||
personality = Params().get_int("LongitudinalPersonality")
|
||||
except Exception:
|
||||
personality = 1
|
||||
profile = ACCEL_PREAP_PROFILES.get(personality, ACCEL_PREAP_PROFILES[1])
|
||||
return -1.5, float(np.interp(current_speed, ACCEL_PREAP_BP, profile))
|
||||
|
||||
|
||||
def get_preap_params(ret: structs.CarParams) -> structs.CarParams:
|
||||
safety_flags = 0
|
||||
if nap_conf.use_pedal:
|
||||
safety_flags |= PREAP_FLAG_ENABLE_PEDAL
|
||||
if nap_conf.radar_enabled:
|
||||
safety_flags |= PREAP_FLAG_RADAR_EMULATION
|
||||
if nap_conf.radar_behind_nosecone:
|
||||
safety_flags |= PREAP_FLAG_RADAR_BEHIND_NOSECONE
|
||||
|
||||
use_pedal = nap_conf.use_pedal and nap_conf.pedal_calibrated
|
||||
ret.safetyConfigs = [get_safety_config(SAFETY_TESLA_PREAP, safety_flags)]
|
||||
ret.radarUnavailable = not nap_conf.radar_enabled
|
||||
ret.steerControlType = structs.CarParams.SteerControlType.angle
|
||||
ret.openpilotLongitudinalControl = use_pedal
|
||||
ret.pcmCruise = not use_pedal
|
||||
ret.alphaLongitudinalAvailable = False
|
||||
ret.steerLimitTimer = 0.4
|
||||
ret.steerActuatorDelay = 0.1
|
||||
ret.steerAtStandstill = True
|
||||
ret.vEgoStopping = 0.1
|
||||
ret.vEgoStarting = 0.1
|
||||
ret.stoppingDecelRate = 1.0
|
||||
|
||||
if use_pedal:
|
||||
ret.longitudinalTuning.kpBP = PEDAL_LONG_K_BP
|
||||
ret.longitudinalTuning.kpV = PEDAL_LONG_KP_V
|
||||
ret.longitudinalTuning.kiBP = PEDAL_LONG_K_BP
|
||||
ret.longitudinalTuning.kiV = PEDAL_LONG_KI_V
|
||||
try:
|
||||
ret.longitudinalTuning.kf = 1.0
|
||||
except AttributeError:
|
||||
pass
|
||||
ret.longitudinalActuatorDelay = 0.4
|
||||
|
||||
return ret
|
||||
@@ -0,0 +1,89 @@
|
||||
from openpilot.common.params import Params
|
||||
|
||||
from opendbc.car.tesla.preap.nap_params import NAPParamKeys
|
||||
|
||||
_params = Params()
|
||||
|
||||
PEDAL_DI_MIN = -5.0
|
||||
PEDAL_DI_ZERO = 0.0
|
||||
PEDAL_DI_PRESSED = 2.0
|
||||
ACCEL_MAX = 2.5
|
||||
REGEN_MAX = -1.5
|
||||
PEDAL_BP = [0.0, 5.0, 12.0, 20.0, 30.0, 40.0]
|
||||
PEDAL_MAX_VALUES = [50.0, 58.0, 66.0, 74.0, 82.0, 90.0]
|
||||
|
||||
|
||||
def transform_di_to_pedal(val: float, pedal_zero: float, pedal_factor: float) -> float:
|
||||
return pedal_zero + (val - PEDAL_DI_ZERO) / (pedal_factor or 1.0)
|
||||
|
||||
|
||||
def transform_pedal_to_di(val: float, pedal_zero: float, pedal_factor: float) -> float:
|
||||
return PEDAL_DI_ZERO + (val - pedal_zero) * (pedal_factor or 1.0)
|
||||
|
||||
|
||||
class NAPConf:
|
||||
@property
|
||||
def adaptive_accel(self) -> bool:
|
||||
return _params.get_bool(NAPParamKeys.ADAPTIVE_ACCEL)
|
||||
|
||||
@property
|
||||
def follow_distance(self) -> int:
|
||||
return max(1, min(7, _params.get_int(NAPParamKeys.FOLLOW_DISTANCE)))
|
||||
|
||||
@property
|
||||
def use_pedal(self) -> bool:
|
||||
return _params.get_bool(NAPParamKeys.PEDAL_ENABLED)
|
||||
|
||||
@property
|
||||
def pedal_calibrated(self) -> bool:
|
||||
if not _params.get_bool(NAPParamKeys.PEDAL_CALIB_DONE):
|
||||
return False
|
||||
return abs(self.pedal_factor) > 1e-6
|
||||
|
||||
@property
|
||||
def pedal_can_zero(self) -> bool:
|
||||
return _params.get_int(NAPParamKeys.PEDAL_CAN_BUS) == 0
|
||||
|
||||
@property
|
||||
def pedal_can_bus(self) -> int:
|
||||
return 0 if self.pedal_can_zero else 2
|
||||
|
||||
@property
|
||||
def pedal_factor(self) -> float:
|
||||
return _params.get_float(NAPParamKeys.PEDAL_CALIB_FACTOR)
|
||||
|
||||
@property
|
||||
def pedal_zero(self) -> float:
|
||||
return _params.get_float(NAPParamKeys.PEDAL_CALIB_ZERO)
|
||||
|
||||
@property
|
||||
def radar_enabled(self) -> bool:
|
||||
return _params.get_bool(NAPParamKeys.RADAR_ENABLED)
|
||||
|
||||
@property
|
||||
def radar_behind_nosecone(self) -> bool:
|
||||
return _params.get_bool(NAPParamKeys.RADAR_BEHIND_NOSECONE)
|
||||
|
||||
@property
|
||||
def radar_offset(self) -> float:
|
||||
return _params.get_float(NAPParamKeys.RADAR_OFFSET)
|
||||
|
||||
@property
|
||||
def double_pull_enabled(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def double_pull_window_ms(self) -> int:
|
||||
return 500
|
||||
|
||||
def get_pedal_profile_values(self) -> list[float]:
|
||||
return PEDAL_MAX_VALUES
|
||||
|
||||
def di_to_pedal(self, val: float) -> float:
|
||||
return transform_di_to_pedal(val, self.pedal_zero, self.pedal_factor)
|
||||
|
||||
def pedal_to_di(self, val: float) -> float:
|
||||
return transform_pedal_to_di(val, self.pedal_zero, self.pedal_factor)
|
||||
|
||||
|
||||
nap_conf = NAPConf()
|
||||
@@ -0,0 +1,15 @@
|
||||
class NAPParamKeys:
|
||||
ADAPTIVE_ACCEL = "NAPAdaptiveAccel"
|
||||
FOLLOW_DISTANCE = "NAPFollowDistance"
|
||||
FORCE_PRE_AP = "NAPForcePreAP"
|
||||
PEDAL_ENABLED = "NAPPedalEnabled"
|
||||
PEDAL_PROFILE = "NAPPedalProfile"
|
||||
PEDAL_CAN_BUS = "NAPPedalCanBus"
|
||||
PEDAL_CALIB_DONE = "NAPPedalCalibDone"
|
||||
PEDAL_CALIB_MIN = "NAPPedalCalibMin"
|
||||
PEDAL_CALIB_MAX = "NAPPedalCalibMax"
|
||||
PEDAL_CALIB_FACTOR = "NAPPedalCalibFactor"
|
||||
PEDAL_CALIB_ZERO = "NAPPedalCalibZero"
|
||||
RADAR_ENABLED = "NAPRadarEnabled"
|
||||
RADAR_BEHIND_NOSECONE = "NAPRadarBehindNosecone"
|
||||
RADAR_OFFSET = "NAPRadarOffset"
|
||||
@@ -0,0 +1,41 @@
|
||||
from opendbc.car.tesla.preap.nap_conf import PEDAL_DI_PRESSED, nap_conf
|
||||
|
||||
PEDAL_TIMEOUT_MS = 500
|
||||
|
||||
|
||||
class PedalFeedback:
|
||||
def __init__(self):
|
||||
self.interceptor_value = 0.0
|
||||
self.interceptor_value2 = 0.0
|
||||
self.interceptor_state = 0
|
||||
self.idx = 0
|
||||
self.prev_idx = 0
|
||||
self.last_seen_ms = 0
|
||||
self.available = False
|
||||
self.timeout = True
|
||||
self.torque_level = 0.0
|
||||
|
||||
def update(self, gas_sensor_msg, curr_time_ms: int) -> bool:
|
||||
if not gas_sensor_msg:
|
||||
return False
|
||||
|
||||
self.prev_idx = self.idx
|
||||
self.interceptor_value = float(nap_conf.pedal_to_di(float(gas_sensor_msg.get("INTERCEPTOR_GAS", 0.0))))
|
||||
self.interceptor_value2 = float(nap_conf.pedal_to_di(float(gas_sensor_msg.get("INTERCEPTOR_GAS2", 0.0))))
|
||||
self.interceptor_state = int(gas_sensor_msg.get("STATE", 0))
|
||||
self.idx = int(gas_sensor_msg.get("IDX", 0))
|
||||
|
||||
if self.idx != self.prev_idx:
|
||||
self.last_seen_ms = curr_time_ms
|
||||
|
||||
self.timeout = (curr_time_ms - self.last_seen_ms) > PEDAL_TIMEOUT_MS
|
||||
self.available = (not self.timeout) and (self.interceptor_state == 0)
|
||||
return True
|
||||
|
||||
def update_torque(self, di_torque1_msg) -> None:
|
||||
self.torque_level = di_torque1_msg.get("DI_torqueMotor", 0.0)
|
||||
|
||||
@property
|
||||
def gas_pressed(self) -> bool:
|
||||
return self.interceptor_value > PEDAL_DI_PRESSED
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
from opendbc.car.tesla.values import CruiseButtons
|
||||
|
||||
_PHASE_IDLE = 0
|
||||
_PHASE_ENGAGING = 1
|
||||
CANCEL_DELAY_FRAMES = 10
|
||||
CC_ENGAGE_TIMEOUT_FRAMES = 50
|
||||
|
||||
|
||||
class StockCCSpoofer:
|
||||
def __init__(self):
|
||||
self.cc_engage_phase = _PHASE_IDLE
|
||||
self.cc_engage_start_frame = 0
|
||||
self.cancel_pending = False
|
||||
self.cancel_frame = -1_000_000
|
||||
self.prev_di_cc_engaged = False
|
||||
self.pcc_event = None
|
||||
|
||||
def update(self, CS, frame: int, tesla_can, can_bus_party: int):
|
||||
can_sends = []
|
||||
|
||||
if getattr(CS, "preap_cc_cancel_needed", False):
|
||||
self.cancel_pending = True
|
||||
self.cancel_frame = frame
|
||||
self.cc_engage_phase = _PHASE_IDLE
|
||||
CS.preap_cc_cancel_needed = False
|
||||
|
||||
if getattr(CS, "preap_cc_engage_needed", False) and self.cc_engage_phase == _PHASE_IDLE:
|
||||
self.cc_engage_phase = _PHASE_ENGAGING
|
||||
self.cc_engage_start_frame = frame
|
||||
CS.preap_cc_engage_needed = False
|
||||
|
||||
if self.cancel_pending and (frame - self.cancel_frame) >= CANCEL_DELAY_FRAMES and frame % 10 == 0:
|
||||
sent = self._send(CS, tesla_can, can_bus_party, CruiseButtons.CANCEL)
|
||||
if sent is not None:
|
||||
can_sends.append(sent)
|
||||
self.cancel_pending = False
|
||||
elif self.cc_engage_phase == _PHASE_ENGAGING and frame % 10 == 0:
|
||||
if (frame - self.cc_engage_start_frame) >= CC_ENGAGE_TIMEOUT_FRAMES or getattr(CS, "di_cruise_state", "OFF") == "ENABLED":
|
||||
self.cc_engage_phase = _PHASE_IDLE
|
||||
else:
|
||||
sent = self._send(CS, tesla_can, can_bus_party, CruiseButtons.SET_ACCEL)
|
||||
if sent is not None:
|
||||
can_sends.append(sent)
|
||||
|
||||
di_cc_engaged = getattr(CS, "di_cruise_state", "OFF") == "ENABLED"
|
||||
if di_cc_engaged and not self.prev_di_cc_engaged:
|
||||
self.pcc_event = "teslaCCEngaged"
|
||||
elif not di_cc_engaged and self.prev_di_cc_engaged:
|
||||
self.pcc_event = "teslaCCDisengaged"
|
||||
else:
|
||||
self.pcc_event = None
|
||||
self.prev_di_cc_engaged = di_cc_engaged
|
||||
|
||||
return can_sends
|
||||
|
||||
@staticmethod
|
||||
def _send(CS, tesla_can, can_bus_party: int, button: int):
|
||||
msg_stw = getattr(CS, "msg_stw_actn_req", None)
|
||||
if msg_stw is None:
|
||||
return None
|
||||
counter = (int(msg_stw.get("MC_STW_ACTN_RQ", 0)) + 1) % 16
|
||||
return tesla_can.create_action_request(button, can_bus_party, counter, msg_stw)
|
||||
@@ -0,0 +1,96 @@
|
||||
import struct
|
||||
from ctypes import create_string_buffer
|
||||
|
||||
from opendbc.car.tesla.values import CANBUS
|
||||
|
||||
PEDAL_M1 = 0.050796813
|
||||
PEDAL_M2 = 0.101593626
|
||||
PEDAL_D = -22.85856576
|
||||
GAS_COMMAND_ID = 0x551
|
||||
_STW_CRC_POLY = 0x1D
|
||||
_STW_DEFAULTS = {
|
||||
"VSL_Enbl_Rq": 1, "DTR_Dist_Rq": 0, "TurnIndLvr_Stat": 0,
|
||||
"HiBmLvr_Stat": 0, "WprWashSw_Psd": 0, "WprWash_R_Sw_Posn_V2": 0,
|
||||
"StW_Lvr_Stat": 0, "StW_Cond_Flt": 0, "StW_Cond_Psd": 0,
|
||||
"HrnSw_Psd": 0, "StW_Sw00_Psd": 0, "StW_Sw01_Psd": 0,
|
||||
"StW_Sw02_Psd": 0, "StW_Sw03_Psd": 0, "StW_Sw04_Psd": 0,
|
||||
"StW_Sw05_Psd": 0, "StW_Sw06_Psd": 0,
|
||||
"WprSw6Posn": 0,
|
||||
}
|
||||
|
||||
|
||||
def _crc8_stw(data: bytes) -> int:
|
||||
crc = 0xFF
|
||||
for b in data:
|
||||
crc ^= b
|
||||
for _ in range(8):
|
||||
crc = ((crc << 1) ^ _STW_CRC_POLY) & 0xFF if (crc & 0x80) else (crc << 1) & 0xFF
|
||||
return crc ^ 0xFF
|
||||
|
||||
|
||||
class TeslaCANPreAP:
|
||||
def __init__(self, packer):
|
||||
self.packer = packer
|
||||
self.pedal_can_bus = 2
|
||||
self.pedal_idx = 0
|
||||
|
||||
@staticmethod
|
||||
def checksum(msg_id: int, dat: bytes) -> int:
|
||||
return ((msg_id & 0xFF) + ((msg_id >> 8) & 0xFF) + sum(dat)) & 0xFF
|
||||
|
||||
def create_steering_control(self, counter: int, angle: float, enabled: bool):
|
||||
values = {
|
||||
"DAS_steeringControlCounter": counter,
|
||||
"DAS_steeringAngleRequest": -angle,
|
||||
"DAS_steeringHapticRequest": 0,
|
||||
"DAS_steeringControlType": 1 if enabled else 0,
|
||||
}
|
||||
data = self.packer.make_can_msg("DAS_steeringControl", CANBUS.party, values)[1]
|
||||
values["DAS_steeringControlChecksum"] = self.checksum(0x488, data[:3])
|
||||
return self.packer.make_can_msg("DAS_steeringControl", CANBUS.party, values)
|
||||
|
||||
def create_epas_control(self, counter: int, mode: int):
|
||||
values = {
|
||||
"EPB_epasEACAllow": mode,
|
||||
"EPB_epasControlCounter": counter,
|
||||
"EPB_epasControlChecksum": 0,
|
||||
}
|
||||
data = self.packer.make_can_msg("EPB_epasControl", CANBUS.party, values)[1]
|
||||
values["EPB_epasControlChecksum"] = self.checksum(0x214, data)
|
||||
return self.packer.make_can_msg("EPB_epasControl", CANBUS.party, values)
|
||||
|
||||
def create_pedal_command(self, accel_command: float, enable: int = 1, pedal_can_bus: int | None = None):
|
||||
if pedal_can_bus is None:
|
||||
pedal_can_bus = self.pedal_can_bus
|
||||
|
||||
idx = self.pedal_idx
|
||||
self.pedal_idx = (self.pedal_idx + 1) % 16
|
||||
if enable == 1:
|
||||
int_cmd1 = max(0, min(65534, int((accel_command - PEDAL_D) / PEDAL_M1)))
|
||||
int_cmd2 = max(0, min(65534, int((accel_command - PEDAL_D) / PEDAL_M2)))
|
||||
else:
|
||||
int_cmd1 = 0
|
||||
int_cmd2 = 0
|
||||
|
||||
msg = create_string_buffer(6)
|
||||
struct.pack_into("BBBBB", msg, 0,
|
||||
(int_cmd1 >> 8) & 0xFF, int_cmd1 & 0xFF,
|
||||
(int_cmd2 >> 8) & 0xFF, int_cmd2 & 0xFF,
|
||||
((enable << 7) + idx) & 0xFF)
|
||||
struct.pack_into("B", msg, 5, self.checksum(GAS_COMMAND_ID, msg.raw))
|
||||
return GAS_COMMAND_ID, bytes(msg.raw), pedal_can_bus
|
||||
|
||||
def create_action_request(self, button_to_press: int, bus: int, counter: int, msg_stw=None):
|
||||
values = {"MC_STW_ACTN_RQ": counter, "CRC_STW_ACTN_RQ": 0, "SpdCtrlLvr_Stat": button_to_press}
|
||||
if msg_stw is not None:
|
||||
for key, default in _STW_DEFAULTS.items():
|
||||
values[key] = msg_stw.get(key, default)
|
||||
else:
|
||||
values.update(_STW_DEFAULTS)
|
||||
|
||||
# Preserve the live stalk layout, but force VSL enable on engage/resume spoofs.
|
||||
values["VSL_Enbl_Rq"] = 0 if button_to_press == 1 else 1
|
||||
|
||||
data = self.packer.make_can_msg("STW_ACTN_RQ", bus, values)[1]
|
||||
values["CRC_STW_ACTN_RQ"] = _crc8_stw(data[:7])
|
||||
return self.packer.make_can_msg("STW_ACTN_RQ", bus, values)
|
||||
@@ -0,0 +1,139 @@
|
||||
import math
|
||||
|
||||
from numpy import clip, interp
|
||||
|
||||
from opendbc.car.common.filter_simple import FirstOrderFilter, HighPassFilter
|
||||
from opendbc.car.common.pid import PIDController
|
||||
from opendbc.car.tesla.pedal.controller import PEDAL_RAMP_RATE_DOWN, PEDAL_RAMP_RATE_UP, get_zero_torque
|
||||
from opendbc.car.tesla.preap.constants import (
|
||||
VDAS_AEGO_FILTER_RC,
|
||||
VDAS_FUTURE_T_BP,
|
||||
VDAS_FUTURE_T_V,
|
||||
VDAS_INNER_K_BP,
|
||||
VDAS_INNER_KI_V,
|
||||
VDAS_INNER_KP_V,
|
||||
)
|
||||
from opendbc.car.tesla.preap.ff_table_default import ACCEL_BP, DEFAULT_TABLE, SPEED_BP
|
||||
from opendbc.car.tesla.preap.nap_conf import ACCEL_MAX, PEDAL_BP, PEDAL_DI_MIN, PEDAL_MAX_VALUES, REGEN_MAX
|
||||
|
||||
PID_ERROR_DEADBAND = 0.1
|
||||
GRAVITY = 9.81
|
||||
PITCH_LP_RC = 0.5
|
||||
PITCH_HP_RC1 = 0.1
|
||||
PITCH_HP_RC2 = 1.0
|
||||
MAX_PITCH_COMPENSATION = 1.5
|
||||
|
||||
|
||||
class GradeEstimator:
|
||||
def __init__(self, dt: float = 0.02):
|
||||
self.pitch_lp = FirstOrderFilter(0.0, PITCH_LP_RC, dt)
|
||||
self.pitch_hp = HighPassFilter(0.0, PITCH_HP_RC1, PITCH_HP_RC2, dt)
|
||||
|
||||
def update(self, orientation_ned: list[float]) -> tuple[float, float]:
|
||||
if len(orientation_ned) < 2:
|
||||
return 0.0, 0.0
|
||||
pitch = orientation_ned[1]
|
||||
self.pitch_lp.update(pitch)
|
||||
self.pitch_hp.update(pitch)
|
||||
grade_accel = math.sin(self.pitch_lp.x) * GRAVITY
|
||||
pitch_comp = float(clip(math.sin(self.pitch_hp.x) * GRAVITY, -MAX_PITCH_COMPENSATION, MAX_PITCH_COMPENSATION))
|
||||
return grade_accel, pitch_comp
|
||||
|
||||
def reset(self) -> None:
|
||||
self.pitch_lp.x = 0.0
|
||||
self.pitch_hp.x = 0.0
|
||||
self.pitch_hp._f1.x = 0.0
|
||||
self.pitch_hp._f2.x = 0.0
|
||||
|
||||
|
||||
class JerkLimiter:
|
||||
def __init__(self, j_max: float = 2.5, dt: float = 0.02):
|
||||
self.j_max = j_max
|
||||
self.dt = dt
|
||||
self.a_limited = 0.0
|
||||
|
||||
def update(self, a_cmd: float) -> float:
|
||||
da_max = self.j_max * self.dt
|
||||
self.a_limited += float(clip(a_cmd - self.a_limited, -da_max, da_max))
|
||||
return self.a_limited
|
||||
|
||||
def reset(self, a_init: float = 0.0) -> None:
|
||||
self.a_limited = a_init
|
||||
|
||||
|
||||
class FeedforwardModel:
|
||||
def __init__(self):
|
||||
self.speed_bp = list(SPEED_BP)
|
||||
self.accel_bp = list(ACCEL_BP)
|
||||
self.table = [list(row) for row in DEFAULT_TABLE]
|
||||
|
||||
def get(self, a_cmd: float, v_ego: float, zero_torque_di: float) -> float:
|
||||
si = float(interp(v_ego, self.speed_bp, range(len(self.speed_bp))))
|
||||
si_lo = int(si)
|
||||
si_hi = min(si_lo + 1, len(self.speed_bp) - 1)
|
||||
sf = si - si_lo
|
||||
di_lo = float(interp(a_cmd, self.accel_bp, self.table[si_lo]))
|
||||
di_hi = float(interp(a_cmd, self.accel_bp, self.table[si_hi]))
|
||||
base_di = di_lo + sf * (di_hi - di_lo)
|
||||
|
||||
if a_cmd < 0:
|
||||
blend = float(clip((a_cmd - REGEN_MAX) / (0.0 - REGEN_MAX), 0.0, 1.0))
|
||||
else:
|
||||
blend = float(1.0 - a_cmd / ACCEL_MAX)
|
||||
return base_di + zero_torque_di * blend
|
||||
|
||||
|
||||
class VirtualDAS:
|
||||
def __init__(self, dt: float = 0.02):
|
||||
self.dt = dt
|
||||
self.jerk_limiter = JerkLimiter(dt=dt)
|
||||
self.ff_model = FeedforwardModel()
|
||||
self.grade_estimator = GradeEstimator(dt=dt)
|
||||
self.inner_pid = PIDController(
|
||||
k_p=(VDAS_INNER_K_BP, VDAS_INNER_KP_V),
|
||||
k_i=(VDAS_INNER_K_BP, VDAS_INNER_KI_V),
|
||||
k_f=0.0,
|
||||
pos_limit=PEDAL_RAMP_RATE_UP,
|
||||
neg_limit=-PEDAL_RAMP_RATE_DOWN,
|
||||
rate=1.0 / dt,
|
||||
)
|
||||
self.a_ego_filter = FirstOrderFilter(0.0, VDAS_AEGO_FILTER_RC, dt)
|
||||
self.prev_a_ego_filtered = 0.0
|
||||
self.prev_pedal_di = 0.0
|
||||
|
||||
def update(self, a_cmd: float, v_ego: float, prev_pedal_di: float, a_ego: float = 0.0,
|
||||
freeze_integrator: bool = False, orientation_ned: list[float] | None = None) -> float:
|
||||
a_limited = self.jerk_limiter.update(a_cmd)
|
||||
grade_accel, pitch_comp = self.grade_estimator.update(orientation_ned or [])
|
||||
|
||||
ff_di = self._feedforward(a_limited, v_ego) + pitch_comp
|
||||
a_ego_corrected = a_ego - grade_accel
|
||||
a_ego_filtered = self.a_ego_filter.update(a_ego_corrected)
|
||||
j_ego = (a_ego_filtered - self.prev_a_ego_filtered) / self.dt
|
||||
self.prev_a_ego_filtered = a_ego_filtered
|
||||
|
||||
future_t = float(interp(v_ego, VDAS_FUTURE_T_BP, VDAS_FUTURE_T_V))
|
||||
error = a_limited - (a_ego_filtered + j_ego * future_t)
|
||||
if abs(error) < PID_ERROR_DEADBAND:
|
||||
error = 0.0
|
||||
|
||||
pedal_di = ff_di + float(self.inner_pid.update(error, speed=v_ego, freeze_integrator=freeze_integrator))
|
||||
max_pedal_value = float(interp(v_ego, PEDAL_BP, PEDAL_MAX_VALUES))
|
||||
pedal_di = float(clip(pedal_di, PEDAL_DI_MIN, max_pedal_value))
|
||||
pedal_di = float(clip(pedal_di, prev_pedal_di - PEDAL_RAMP_RATE_DOWN, prev_pedal_di + PEDAL_RAMP_RATE_UP))
|
||||
self.prev_pedal_di = pedal_di
|
||||
return pedal_di
|
||||
|
||||
def reset(self, a_init: float = 0.0, pedal_di_init: float = 0.0) -> None:
|
||||
self.jerk_limiter.reset(a_init)
|
||||
self.inner_pid.reset()
|
||||
self.grade_estimator.reset()
|
||||
self.a_ego_filter.x = 0.0
|
||||
self.prev_a_ego_filtered = 0.0
|
||||
self.prev_pedal_di = pedal_di_init
|
||||
|
||||
def _feedforward(self, a_cmd: float, v_ego: float) -> float:
|
||||
zero_torque_di = get_zero_torque().get(v_ego)
|
||||
max_pedal_value = float(interp(v_ego, PEDAL_BP, PEDAL_MAX_VALUES))
|
||||
return float(clip(self.ff_model.get(a_cmd, v_ego, zero_torque_di), PEDAL_DI_MIN, max_pedal_value))
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
from opendbc.can import CANParser
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.interfaces import RadarInterfaceBase
|
||||
from opendbc.car.tesla.preap.nap_conf import nap_conf
|
||||
from opendbc.car.tesla.values import CANBUS, DBC, CAR
|
||||
|
||||
_BOSCH_RADAR_STATUS_MSG = 769
|
||||
_BOSCH_RADAR_POINT_A_BASE = 784
|
||||
_BOSCH_RADAR_POINT_B_BASE = 785
|
||||
_BOSCH_RADAR_POINT_STRIDE = 3
|
||||
_BOSCH_RADAR_POINTS = 32
|
||||
_BOSCH_TRIGGER_MSG = _BOSCH_RADAR_POINT_B_BASE + ((_BOSCH_RADAR_POINTS - 1) * _BOSCH_RADAR_POINT_STRIDE)
|
||||
|
||||
|
||||
class RadarInterface(RadarInterfaceBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
|
||||
self.radar_off_can = CP.radarUnavailable or CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP
|
||||
self.updated_messages: set[int] = set()
|
||||
self.track_id = 0
|
||||
self.radar_offset = float(nap_conf.radar_offset) if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP else 0.0
|
||||
|
||||
if self.radar_off_can:
|
||||
self.rcp = None
|
||||
else:
|
||||
messages = [(_BOSCH_RADAR_STATUS_MSG, 8)]
|
||||
for i in range(_BOSCH_RADAR_POINTS):
|
||||
messages.append((_BOSCH_RADAR_POINT_A_BASE + (i * _BOSCH_RADAR_POINT_STRIDE), 8))
|
||||
messages.append((_BOSCH_RADAR_POINT_B_BASE + (i * _BOSCH_RADAR_POINT_STRIDE), 8))
|
||||
self.rcp = CANParser(DBC[CP.carFingerprint][Bus.radar], messages, CANBUS.radar)
|
||||
|
||||
self.trigger_msg = _BOSCH_TRIGGER_MSG
|
||||
|
||||
def update(self, can_strings):
|
||||
if self.radar_off_can or self.rcp is None:
|
||||
return super().update(None)
|
||||
|
||||
vls = self.rcp.update(can_strings)
|
||||
self.updated_messages.update(vls)
|
||||
|
||||
if self.trigger_msg not in self.updated_messages:
|
||||
return None
|
||||
|
||||
ret = structs.RadarData()
|
||||
if not self.rcp.can_valid:
|
||||
ret.errors.canError = True
|
||||
|
||||
radar_status = self.rcp.vl[_BOSCH_RADAR_STATUS_MSG]
|
||||
ret.errors.radarFault = bool(radar_status["RADC_HWFail"])
|
||||
ret.errors.radarUnavailableTemporary = False
|
||||
|
||||
current_points: set[int] = set()
|
||||
for i in range(_BOSCH_RADAR_POINTS):
|
||||
msg_a_id = _BOSCH_RADAR_POINT_A_BASE + (i * _BOSCH_RADAR_POINT_STRIDE)
|
||||
msg_b_id = _BOSCH_RADAR_POINT_B_BASE + (i * _BOSCH_RADAR_POINT_STRIDE)
|
||||
|
||||
msg_a = self.rcp.vl[msg_a_id]
|
||||
msg_b = self.rcp.vl[msg_b_id]
|
||||
if msg_a["Index"] != msg_b["Index2"]:
|
||||
continue
|
||||
|
||||
if not msg_a["Tracked"] or msg_a["LongDist"] <= 0.0 or msg_a["LongDist"] > 250.0 or msg_a["ProbExist"] < 50.0:
|
||||
self.pts.pop(i, None)
|
||||
continue
|
||||
|
||||
current_points.add(i)
|
||||
if i not in self.pts:
|
||||
self.pts[i] = structs.RadarData.RadarPoint()
|
||||
self.pts[i].trackId = self.track_id
|
||||
self.track_id += 1
|
||||
|
||||
point = self.pts[i]
|
||||
point.dRel = msg_a["LongDist"]
|
||||
point.yRel = msg_a["LatDist"] + self.radar_offset
|
||||
point.vRel = msg_a["LongSpeed"]
|
||||
point.aRel = msg_a["LongAccel"]
|
||||
point.yvRel = msg_b["LatSpeed"]
|
||||
point.measured = bool(msg_a["Meas"])
|
||||
|
||||
for point_id in list(self.pts.keys()):
|
||||
if point_id not in current_points:
|
||||
del self.pts[point_id]
|
||||
|
||||
ret.points = list(self.pts.values())
|
||||
self.updated_messages.clear()
|
||||
return ret
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user