mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-24 01:33:46 +08:00
Compare commits
1125 Commits
plexy
...
CodenameAdeeb
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fad2a9154 | |||
| 9b7f1222e8 | |||
| ff12e1fe8f | |||
| d7498e7a93 | |||
| 22f19ad578 | |||
| 225e98ed58 | |||
| 66af0216b9 | |||
| 779b42d8ea | |||
| f5f846f5b6 | |||
| d8b6e43a0d | |||
| b72042042f | |||
| bd6dced538 | |||
| 66245a5a2e | |||
| d4a3bc0f5c | |||
| fcc5cc359e | |||
| aa381d4d97 | |||
| bc3f215764 | |||
| daf0f84bc9 | |||
| b945d4c021 | |||
| 9f1066ce83 | |||
| 370654637a | |||
| fb854cecd3 | |||
| 713ce294cc | |||
| 7450b9de9a | |||
| 5b2b053afd | |||
| 88be0e6387 | |||
| cc5c95705d | |||
| d16d72afc6 | |||
| 6230d08c0f | |||
| 48af4d2f7f | |||
| d773ad9025 | |||
| 894eae7090 | |||
| 28fcba8471 | |||
| 0cff67bda8 | |||
| f0eeec955f | |||
| 9b28c7bd52 | |||
| ef400a9b3f | |||
| d4b3c3c3b0 | |||
| c57c05886c | |||
| 80f465616a | |||
| 4e70755895 | |||
| 5b41566e4e | |||
| 2141e5a5b1 | |||
| d51dd02d10 | |||
| bb3ed894b1 | |||
| d4c911f58c | |||
| fc87af4da1 | |||
| 7cd02e0c40 | |||
| db8d38e85f | |||
| 2c7e6e72c5 | |||
| 85dd1cbe7e | |||
| 665f538533 | |||
| 28a4de1c9e | |||
| 42eef3f21c | |||
| ce86d9d05a | |||
| b70d0941c9 | |||
| 8d7ba284d7 | |||
| 71b577cbc2 | |||
| cc40a28562 | |||
| 104ae74901 | |||
| 2e45e57287 | |||
| 10843b2f6e | |||
| eb3eb116eb | |||
| b34e968f91 | |||
| 3701079265 | |||
| 1a713fe3e2 | |||
| 6d92a8e5ad | |||
| 01df304f88 | |||
| 7a76325e67 | |||
| c3d1f727c0 | |||
| 94e60c5d70 | |||
| 43143e7a1f | |||
| f2b14c7ac0 | |||
| d5c0fea948 | |||
| a51b1fd78f | |||
| 8b7c2b5f36 | |||
| 0eab89c8d9 | |||
| 7b124faad2 | |||
| 4ac7fcaf5c | |||
| f265aa3f7b | |||
| 11044975c5 | |||
| 27f73a6f08 | |||
| 3e5bb853cb | |||
| 2f0864e215 | |||
| 8376ba7c97 | |||
| c928e27033 | |||
| 5f52120d7a | |||
| cab8457f47 | |||
| 8453f6b4eb | |||
| 03cf613e4c | |||
| ce44830b22 | |||
| 3ae3c5361e | |||
| d3dd2545a9 | |||
| 29cb892e1a | |||
| a106e222ca | |||
| 6ccc984dab | |||
| cce803e888 | |||
| 9427041200 | |||
| 6bae55ac8e | |||
| d8b9bf5529 | |||
| 4c07ea96e7 | |||
| fcf1bf2f3c | |||
| e3c377df34 | |||
| 28141e3074 | |||
| 25c5722372 | |||
| d699f26ded | |||
| 9ccd61e8cf | |||
| fccd46fef5 | |||
| 1863259a58 | |||
| c001d2b184 | |||
| 03ebfd2c69 | |||
| 94141aebb1 | |||
| 71ee4ab2a3 | |||
| dfb5acddf5 | |||
| 33d7e977b4 | |||
| c6708e716f | |||
| 5a7ec6b154 | |||
| d935514f78 | |||
| 8f3f9cf6c7 | |||
| 4013a70e9d | |||
| 3e363cf7a3 | |||
| 9f13e71a0a | |||
| 28ea8d0c09 | |||
| d0c877f591 | |||
| b5ebc6474e | |||
| cb151bb9aa | |||
| e943d2afd2 | |||
| 8556708cd6 | |||
| 6fa509cc88 | |||
| 8e7ce87be8 | |||
| 5d8137b30a | |||
| 368cb1a6f6 | |||
| ba460bbb7b | |||
| a6ffaaa781 | |||
| d4cfd34291 | |||
| b0334c2ae5 | |||
| 6a19e82bb2 | |||
| 5693de7d8d | |||
| 20fa52f5df | |||
| cb540160e4 | |||
| be1fda1504 | |||
| 0c3bd608d9 | |||
| 258b4c6048 | |||
| 8b23fd8c03 | |||
| eb7d4212fd | |||
| 73485ba76c | |||
| 1ea2143e01 | |||
| 20235f027d | |||
| 55ad1fd0fd | |||
| 5a2117892c | |||
| d8e4d75656 | |||
| 77db3e506c | |||
| acd15fcba7 | |||
| 00539e1dc2 | |||
| fb98b8cb29 | |||
| fc88f70ebd | |||
| 6233e7b2c0 | |||
| 0144a691b9 | |||
| 37a6c62e6e | |||
| ca09e50081 | |||
| 8098b9fa53 | |||
| 5564d133b3 | |||
| 6e3bf42c97 | |||
| f94babe509 | |||
| 45eead8dcd | |||
| fcd39be875 | |||
| a609716520 | |||
| a0ae06022c | |||
| c9cf8cfdf6 | |||
| 3d4e1f2d48 | |||
| ca74743eac | |||
| fb031a3c57 | |||
| 4bad6f6f79 | |||
| 7e1b3769d7 | |||
| 4a98785aa3 | |||
| d305071351 | |||
| 653009185e | |||
| 57bfc691bf | |||
| 7e4f8d4154 | |||
| 361f692d53 | |||
| f81099af79 | |||
| 1d982da86e | |||
| c306fc7cfd | |||
| b49c21e5aa | |||
| 44b2df59da | |||
| c8c3a814ea | |||
| 46c3592563 | |||
| 6534ce356a | |||
| e4dfbd1a60 | |||
| e9a2c925ed | |||
| 57292f09bf | |||
| e577502f4b | |||
| 7b0c0784ed | |||
| 96dcfa4287 | |||
| 6046a84e95 | |||
| 1884087590 | |||
| 637c3c820c | |||
| 4f47f8cb0a | |||
| e38668b3cb | |||
| 9dbf5db9ec | |||
| e47a836d2d | |||
| 6c0ee59a3e | |||
| a0f4028aaa | |||
| beb7e5bf6b | |||
| f88ef7ecc2 | |||
| 6cc8e9b86a | |||
| a999f28270 | |||
| 2a3d2744af | |||
| 804316e40c | |||
| bdde8907bf | |||
| 85ab7557e9 | |||
| 3d2b959db7 | |||
| 889cd3db1a | |||
| 4916143a5d | |||
| 1153010f85 | |||
| 0cbd8013b2 | |||
| a425a9c11a | |||
| 987de36f54 | |||
| f2fbd3f97c | |||
| 30245e7d34 | |||
| 3aa2801ce7 | |||
| bc3c245aa5 | |||
| cdb266cba2 | |||
| e4e416bfb3 | |||
| 39414ee2e4 | |||
| a08fe79cef | |||
| 33508d42ef | |||
| 110bd8b295 | |||
| 622720f730 | |||
| 4bdf55f6a0 | |||
| 9e2448b3d5 | |||
| 2ccd6e35b7 | |||
| 71b5cd8594 | |||
| 3bcd062eb7 | |||
| 6a4b508864 | |||
| 6e48d7dc12 | |||
| d849ac120b | |||
| 4243a5b055 | |||
| 8d9e97fdcd | |||
| 1457b902ac | |||
| 8ab1b723ae | |||
| 2fb5fbf3ad | |||
| cbe6eb077e | |||
| 831a8f27e5 | |||
| 46d19999ce | |||
| e27badccef | |||
| 07cf72d573 | |||
| 8bda3d5be1 | |||
| cdc2b7a3ed | |||
| c7bc46ec8e | |||
| dc1b9fc790 | |||
| 275f8966b2 | |||
| 5b1445bb5f | |||
| 0b1a94914b | |||
| 3317d545be | |||
| a630533b44 | |||
| cb82059d67 | |||
| e08745d519 | |||
| 7e76fa9b41 | |||
| b72570df40 | |||
| 95ee3a818b | |||
| 72992743a4 | |||
| 8eefbacc48 | |||
| 4531edad1e | |||
| 0af6ca7d37 | |||
| ac4ffb4a7e | |||
| 641f1dc238 | |||
| 650cb03b60 | |||
| cefcc41bca | |||
| 957fe8af75 | |||
| d98ae2b94b | |||
| 12a984b26d | |||
| 9cd81e89f6 | |||
| 677545cc23 | |||
| 2f02c93b3b | |||
| 7f314d9f28 | |||
| 6ea41a0329 | |||
| c33e8094e8 | |||
| f0b23a40fe | |||
| b65158cd97 | |||
| b6794d72d3 | |||
| ea2aa532af | |||
| 5bbe5f1daf | |||
| 1afb6b3978 | |||
| 9e03465520 | |||
| 06e267cef2 | |||
| b79527a217 | |||
| fa85c848f6 | |||
| 0e08829188 | |||
| 1363a5b044 | |||
| 60406cb982 | |||
| 152c39044f | |||
| 6c392185be | |||
| c7a5c880cc | |||
| 0510e87530 | |||
| c9734c9b1a | |||
| 66e04d4a45 | |||
| c1577d2536 | |||
| 23b354d47e | |||
| 5c9888a0d7 | |||
| 74cf048f94 | |||
| 0eb8d0cfdc | |||
| 9ddf5d31b4 | |||
| 7da4359dd0 | |||
| 032b2a1fde | |||
| 08df283511 | |||
| ae3792c683 | |||
| 9d2149d495 | |||
| 5234a121f9 | |||
| 706c65a70d | |||
| 04bee88dd2 | |||
| 072928ec96 | |||
| 03ec0b2c60 | |||
| 294e301000 | |||
| 74e4dd4c44 | |||
| f40881657c | |||
| 4bb15dded4 | |||
| 5eb7e8f02b | |||
| ef5ece17eb | |||
| 1339ca02c4 | |||
| d5e40d3bdf | |||
| 97c20744d3 | |||
| 30786472cb | |||
| af185f8491 | |||
| 2a5cb66b9a | |||
| 82eae95add | |||
| 85b33d8819 | |||
| e4fc9b6912 | |||
| a44cde63a5 | |||
| d93039698c | |||
| 3a1ca3fb81 | |||
| 28e51ebfea | |||
| 1c2627e1ae | |||
| b4dc28a45e | |||
| c8a9eb5464 | |||
| 11be793c3a | |||
| ce989294ab | |||
| a21520fa8f | |||
| e5751388d4 | |||
| 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"
|
||||
@@ -21,6 +21,7 @@ compiledmodels/
|
||||
/docs_site/
|
||||
|
||||
*.mp4
|
||||
!docs/assets/speed-limit-vision-demo*.mp4
|
||||
*.dylib
|
||||
*.DSYM
|
||||
*.d
|
||||
@@ -68,6 +69,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 = [
|
||||
|
||||
@@ -3,4 +3,110 @@
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
exec "${ROOT_DIR}/scripts/laptop_device_build.sh" build "$@"
|
||||
original_args=("$@")
|
||||
|
||||
shortcut_targets=()
|
||||
passthrough_args=()
|
||||
shortcut_includes_panda=0
|
||||
panda_generated_files=(
|
||||
"panda/board/obj/gitversion.h"
|
||||
"panda/board/obj/version"
|
||||
)
|
||||
|
||||
add_panda_targets() {
|
||||
local variants=(
|
||||
panda
|
||||
panda_h7
|
||||
panda_remote
|
||||
panda_h7_remote
|
||||
panda_hkg_remote
|
||||
panda_h7_hkg_remote
|
||||
panda_can_ignition_only
|
||||
panda_h7_can_ignition_only
|
||||
panda_remote_can_ignition_only
|
||||
panda_h7_remote_can_ignition_only
|
||||
panda_hkg_remote_can_ignition_only
|
||||
panda_h7_hkg_remote_can_ignition_only
|
||||
panda_jungle_h7
|
||||
body_h7
|
||||
)
|
||||
|
||||
local variant
|
||||
for variant in "${variants[@]}"; do
|
||||
shortcut_targets+=("panda/board/obj/${variant}.bin.signed")
|
||||
done
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--params|-params|-param)
|
||||
shortcut_targets+=("common/params_pyx.so")
|
||||
;;
|
||||
--panda|-panda)
|
||||
shortcut_includes_panda=1
|
||||
add_panda_targets
|
||||
;;
|
||||
--ui|-ui)
|
||||
shortcut_targets+=("selfdrive/ui/ui")
|
||||
;;
|
||||
--cereal|-cereal)
|
||||
shortcut_targets+=(
|
||||
"cereal/libcereal.a"
|
||||
"cereal/libsocketmaster.a"
|
||||
"cereal/messaging/bridge"
|
||||
"cereal/services.h"
|
||||
)
|
||||
;;
|
||||
*)
|
||||
if [[ "$1" =~ ^[0-9]+$ ]]; then
|
||||
passthrough_args+=("-j$1")
|
||||
else
|
||||
passthrough_args+=("$1")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ "${#shortcut_targets[@]}" -gt 0 ]]; then
|
||||
scons_args=("--no-scrub")
|
||||
scons_args+=("${shortcut_targets[@]}")
|
||||
if [[ "${#passthrough_args[@]}" -gt 0 ]]; then
|
||||
scons_args+=("${passthrough_args[@]}")
|
||||
fi
|
||||
|
||||
if [[ "${shortcut_includes_panda}" -eq 1 ]]; then
|
||||
exec "${ROOT_DIR}/scripts/laptop_device_build.sh" scons "${scons_args[@]}"
|
||||
fi
|
||||
|
||||
tmpdir="$(mktemp -d)"
|
||||
for path in "${panda_generated_files[@]}"; do
|
||||
if [[ -e "${ROOT_DIR}/${path}" ]]; then
|
||||
cp -p "${ROOT_DIR}/${path}" "${tmpdir}/${path//\//__}"
|
||||
else
|
||||
touch "${tmpdir}/${path//\//__}.missing"
|
||||
fi
|
||||
done
|
||||
|
||||
if "${ROOT_DIR}/scripts/laptop_device_build.sh" scons "${scons_args[@]}"; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
|
||||
for path in "${panda_generated_files[@]}"; do
|
||||
if [[ -e "${tmpdir}/${path//\//__}.missing" ]]; then
|
||||
rm -f "${ROOT_DIR}/${path}"
|
||||
else
|
||||
cp -p "${tmpdir}/${path//\//__}" "${ROOT_DIR}/${path}"
|
||||
fi
|
||||
done
|
||||
rm -rf "${tmpdir}"
|
||||
exit "${status}"
|
||||
fi
|
||||
|
||||
if [[ "${#original_args[@]}" -gt 0 ]]; then
|
||||
exec "${ROOT_DIR}/scripts/laptop_device_build.sh" build "${original_args[@]}"
|
||||
fi
|
||||
|
||||
exec "${ROOT_DIR}/scripts/laptop_device_build.sh" build
|
||||
|
||||
+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.
+125
-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,13 @@ 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;
|
||||
sleepProb @14 :Float32;
|
||||
notReadyProbDEPRECATED @12 :List(Float32);
|
||||
occludedProbDEPRECATED @10 :Float32;
|
||||
readyProbDEPRECATED @11 :List(Float32);
|
||||
@@ -2221,7 +2234,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 +2252,91 @@ 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;
|
||||
lockoutCount @15 :Int8;
|
||||
lockoutMinutesRemaining @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 +2657,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 +2706,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 +2736,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 +2799,6 @@ struct Event {
|
||||
gyroscope2DEPRECATED @100 :SensorEventData;
|
||||
accelerometer2DEPRECATED @101 :SensorEventData;
|
||||
temperatureSensor2DEPRECATED @123 :SensorEventData;
|
||||
driverMonitoringStateDEPRECATED @71 :DriverMonitoringStateDEPRECATED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +268,7 @@ class SubMaster:
|
||||
class PubMaster:
|
||||
def __init__(self, services: List[str]):
|
||||
self.sock = {}
|
||||
self._zmq_readers_settled = set()
|
||||
for s in services:
|
||||
self.sock[s] = pub_sock(s)
|
||||
|
||||
@@ -277,6 +278,13 @@ class PubMaster:
|
||||
self.sock[s].send(dat)
|
||||
|
||||
def wait_for_readers_to_update(self, s: str, timeout: int, dt: float = 0.05) -> bool:
|
||||
if "ZMQ" in os.environ:
|
||||
if s not in self._zmq_readers_settled:
|
||||
time.sleep(min(timeout, 1.0))
|
||||
self._zmq_readers_settled.add(s)
|
||||
elif dt >= 0.05:
|
||||
time.sleep(min(timeout, 1.0))
|
||||
return True
|
||||
for _ in range(int(timeout*(1./dt))):
|
||||
if self.sock[s].all_readers_updated():
|
||||
return True
|
||||
|
||||
Binary file not shown.
+69
-1
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import io
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
CHUNK_SIZE = 45 * 1024 * 1024 # 45MB, under GitHub's 50MB limit
|
||||
@@ -13,9 +16,16 @@ def get_manifest_path(name):
|
||||
return f"{name}.chunkmanifest"
|
||||
|
||||
|
||||
def _chunk_paths(path, num_chunks):
|
||||
return [get_manifest_path(path)] + [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)]
|
||||
|
||||
|
||||
def get_chunk_paths(path, file_size):
|
||||
num_chunks = math.ceil(file_size / CHUNK_SIZE)
|
||||
return [get_manifest_path(path)] + [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)]
|
||||
return _chunk_paths(path, num_chunks)
|
||||
|
||||
|
||||
get_chunk_targets = get_chunk_paths
|
||||
|
||||
|
||||
def chunk_file(path, targets):
|
||||
@@ -31,6 +41,59 @@ def chunk_file(path, targets):
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def get_existing_chunks(path):
|
||||
if os.path.isfile(path):
|
||||
return [path]
|
||||
manifest_path = get_manifest_path(path)
|
||||
if os.path.isfile(manifest_path):
|
||||
num_chunks = int(Path(manifest_path).read_text().strip())
|
||||
return _chunk_paths(path, num_chunks)
|
||||
raise FileNotFoundError(path)
|
||||
|
||||
|
||||
def file_chunked_exists(path) -> bool:
|
||||
return os.path.isfile(path) or os.path.isfile(get_manifest_path(path))
|
||||
|
||||
|
||||
class ChunkStream(io.RawIOBase):
|
||||
def __init__(self, paths):
|
||||
self._paths = iter(paths)
|
||||
self._file = None
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
def readinto(self, buffer):
|
||||
count = 0
|
||||
view = memoryview(buffer)
|
||||
while count < len(buffer):
|
||||
if self._file is None:
|
||||
path = next(self._paths, None)
|
||||
if path is None:
|
||||
break
|
||||
self._file = open(path, "rb")
|
||||
bytes_read = self._file.readinto(view[count:])
|
||||
if not bytes_read:
|
||||
self._file.close()
|
||||
self._file = None
|
||||
continue
|
||||
count += bytes_read
|
||||
return count
|
||||
|
||||
def close(self):
|
||||
if self._file is not None:
|
||||
self._file.close()
|
||||
self._file = None
|
||||
super().close()
|
||||
|
||||
|
||||
def open_file_chunked(path):
|
||||
chunks = get_existing_chunks(path)
|
||||
if chunks and chunks[0] == get_manifest_path(path):
|
||||
chunks = chunks[1:]
|
||||
return io.BufferedReader(ChunkStream(chunks))
|
||||
|
||||
|
||||
def read_file_chunked(path):
|
||||
manifest_path = get_manifest_path(path)
|
||||
if os.path.isfile(manifest_path):
|
||||
@@ -39,3 +102,8 @@ def read_file_chunked(path):
|
||||
if os.path.isfile(path):
|
||||
return Path(path).read_bytes()
|
||||
raise FileNotFoundError(path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
file_path = sys.argv[1]
|
||||
chunk_file(file_path, get_chunk_targets(file_path, os.path.getsize(file_path)))
|
||||
|
||||
Binary file not shown.
+5
-1
@@ -99,7 +99,7 @@ Params::Params(const std::string &path, bool memory) {
|
||||
if (memory) {
|
||||
params_folder = Path::shm_path() + "/params";
|
||||
} else {
|
||||
cache_path = "/cache/params" + params_prefix + "/";
|
||||
cache_path = Path::params_cache() + params_prefix + "/";
|
||||
params_folder = path;
|
||||
}
|
||||
params_path = ensure_params_path(params_prefix, params_folder);
|
||||
@@ -309,6 +309,10 @@ int Params::getTuningLevel(const std::string &key) {
|
||||
return keys[key].tuning_level;
|
||||
}
|
||||
|
||||
ParamSettingsTier Params::getSettingsTier(const std::string &key) {
|
||||
return keys[key].settings_tier;
|
||||
}
|
||||
|
||||
std::optional<std::string> Params::getStockValue(const std::string &key) {
|
||||
ParamKeyAttributes &attributes = keys[key];
|
||||
if (attributes.stock_value) {
|
||||
|
||||
@@ -33,6 +33,11 @@ enum ParamKeyType {
|
||||
BYTES = 6
|
||||
};
|
||||
|
||||
enum ParamSettingsTier {
|
||||
SETTINGS_SIMPLE = 0,
|
||||
SETTINGS_ADVANCED = 1,
|
||||
};
|
||||
|
||||
struct ParamKeyAttributes {
|
||||
uint32_t flags;
|
||||
ParamKeyType type;
|
||||
@@ -42,6 +47,9 @@ struct ParamKeyAttributes {
|
||||
std::optional<std::string> stock_value = std::nullopt;
|
||||
|
||||
int tuning_level = 0;
|
||||
|
||||
// Controls settings-page visibility only. It does not gate the param's runtime behavior.
|
||||
ParamSettingsTier settings_tier = SETTINGS_ADVANCED;
|
||||
};
|
||||
|
||||
class Params {
|
||||
@@ -112,6 +120,8 @@ public:
|
||||
|
||||
int getTuningLevel(const std::string &key);
|
||||
|
||||
ParamSettingsTier getSettingsTier(const std::string &key);
|
||||
|
||||
std::optional<std::string> getStockValue(const std::string &key);
|
||||
|
||||
private:
|
||||
|
||||
+273
-58
@@ -1,70 +1,285 @@
|
||||
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
|
||||
|
||||
SETTINGS_SIMPLE = 0
|
||||
SETTINGS_ADVANCED = 1
|
||||
|
||||
|
||||
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):
|
||||
def _load_settings_tiers() -> dict[str, int]:
|
||||
params_keys = Path(__file__).with_name("params_keys.h")
|
||||
if not params_keys.exists():
|
||||
return {}
|
||||
|
||||
tiers = {}
|
||||
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:
|
||||
tiers[parts[1]] = SETTINGS_SIMPLE if "SETTINGS_SIMPLE" in line else SETTINGS_ADVANCED
|
||||
return tiers
|
||||
|
||||
|
||||
_SETTINGS_TIERS = _load_settings_tiers()
|
||||
|
||||
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
|
||||
|
||||
def get_settings_tier(self, key):
|
||||
return _SETTINGS_TIERS.get(self.check_key(key), SETTINGS_ADVANCED)
|
||||
|
||||
else:
|
||||
assert _Params
|
||||
assert ParamKeyFlag
|
||||
assert ParamKeyType
|
||||
assert UnknownKeyName
|
||||
|
||||
class Params(_Params):
|
||||
def get_settings_tier(self, key):
|
||||
try:
|
||||
return super().get_settings_tier(key)
|
||||
except AttributeError:
|
||||
return _SETTINGS_TIERS.get(self.check_key(key), SETTINGS_ADVANCED)
|
||||
|
||||
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
|
||||
|
||||
+303
-194
@@ -8,10 +8,9 @@
|
||||
inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"AccessToken", {CLEAR_ON_MANAGER_START | DONT_LOG, STRING}},
|
||||
{"AdbEnabled", {PERSISTENT, BOOL}},
|
||||
{"AlwaysAllowUploads", {PERSISTENT, BOOL, "0"}},
|
||||
{"AlwaysAllowUploads", {PERSISTENT, BOOL, "0", std::nullopt, 0, SETTINGS_SIMPLE}},
|
||||
{"AlwaysOnDM", {PERSISTENT, BOOL}},
|
||||
{"ApiCache_Device", {PERSISTENT, STRING}},
|
||||
{"ApiCache_FirehoseStats", {PERSISTENT, JSON}},
|
||||
{"AssistNowToken", {PERSISTENT, STRING}},
|
||||
{"AthenadPid", {PERSISTENT, INT}},
|
||||
{"AthenadUploadQueue", {PERSISTENT, JSON}},
|
||||
@@ -37,13 +36,19 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DoReboot", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"DoShutdown", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"DoUninstall", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"DoUserReboot", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"DriverTooDistracted", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, BOOL}},
|
||||
{"DriverLockoutCount", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, INT, "0"}},
|
||||
{"EcuDisableFailed", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"AlphaLongitudinalEnabled", {PERSISTENT, BOOL}},
|
||||
{"ExperimentalLongitudinalEnabled", {PERSISTENT, BOOL}},
|
||||
{"ExperimentalMode", {PERSISTENT, BOOL}},
|
||||
{"ExperimentalModeConfirmed", {PERSISTENT, BOOL}},
|
||||
{"PersistExperimentalState", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"LongitudinalModelPreference", {PERSISTENT, INT, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"LongitudinalModelPreferenceOverride", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "-1", "-1"}},
|
||||
{"PersistChillState", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PersistExperimentalState", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"PersistedCCStatus", {PERSISTENT, INT, "0", "0"}},
|
||||
{"PersistedCEStatus", {PERSISTENT, INT, "0", "0"}},
|
||||
{"FirmwareQueryDone", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||
{"ForcePowerDown", {PERSISTENT, BOOL}},
|
||||
@@ -55,10 +60,14 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"GithubUsername", {PERSISTENT, STRING}},
|
||||
{"GitRemote", {PERSISTENT, STRING}},
|
||||
{"GsmApn", {PERSISTENT, STRING}},
|
||||
{"GsmMetered", {PERSISTENT, BOOL, "1"}},
|
||||
{"GsmMetered", {PERSISTENT, BOOL, "1", std::nullopt, 0, SETTINGS_SIMPLE}},
|
||||
{"GsmRoaming", {PERSISTENT, BOOL}},
|
||||
{"HardwareSerial", {PERSISTENT, STRING}},
|
||||
{"HasAcceptedTerms", {PERSISTENT, STRING, "0"}},
|
||||
{"HondaGasFactorParams", {PERSISTENT, FLOAT}},
|
||||
{"HondaLateralPidKiScale", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
|
||||
{"HondaLateralPidKpScale", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
|
||||
{"HondaWindFactorParams", {PERSISTENT, FLOAT}},
|
||||
{"InstallDate", {PERSISTENT, TIME}},
|
||||
{"IsDriverViewEnabled", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"IsEngaged", {PERSISTENT, BOOL}},
|
||||
@@ -66,7 +75,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, std::nullopt, std::nullopt, 0, SETTINGS_SIMPLE}},
|
||||
{"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 +122,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}},
|
||||
@@ -118,6 +130,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SecOCKey", {PERSISTENT | DONT_LOG, STRING}},
|
||||
{"ShowDebugInfo", {PERSISTENT, BOOL}},
|
||||
{"ShowAllToggles", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"TryRaylibUI", {PERSISTENT, BOOL, "1"}},
|
||||
{"UseOldUI", {PERSISTENT, BOOL, "0", std::nullopt, 0, SETTINGS_SIMPLE}},
|
||||
{"UsePrebuilt", {PERSISTENT, BOOL, "1"}},
|
||||
{"RouteCount", {PERSISTENT, INT, "0"}},
|
||||
{"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||
@@ -138,26 +152,30 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"UpdaterLastFetchTime", {PERSISTENT, TIME}},
|
||||
{"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}},
|
||||
{"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
|
||||
{"UsbGpuActive", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||
{"UsbGpuCompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||
{"UsbGpuPresent", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||
{"Version", {PERSISTENT, STRING}},
|
||||
|
||||
// StarPilot variables
|
||||
{"AccelerationPath", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"AccelerationProfile", {PERSISTENT, INT, "2", "0", 0}},
|
||||
{"AccelerationPath", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"AccelerationProfile", {PERSISTENT, INT, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"AdjacentLeadsUI", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"AdjacentPath", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"AdjacentPath", {PERSISTENT, BOOL, "0", "0", 3, SETTINGS_SIMPLE}},
|
||||
{"AdjacentPathMetrics", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"AdvancedCustomUI", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"AdvancedCustomUI", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"AlwaysOnLateral", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"AlertVolumeControl", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"AllowImpossibleAcceleration", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"AlwaysOnLateral", {PERSISTENT, BOOL, "1", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"AlwaysOnLateralLKAS", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ApiCache_DriveStats", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"AutomaticallyDownloadModels", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
@@ -165,45 +183,55 @@ 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}},
|
||||
{"BlindSpotMetrics", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"BlindSpotPath", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"BelowSteerSpeedVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"BlindSpotPath", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"BelowSteerSpeedVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"BorderMetrics", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"BorderWidth", {PERSISTENT, FLOAT, "100.0", "100.0", 2}},
|
||||
{"BorderWidth", {PERSISTENT, FLOAT, "100.0", "100.0", 2, SETTINGS_SIMPLE}},
|
||||
{"CalibratedLateralAcceleration", {PERSISTENT, FLOAT, "2.0", "2.0", 2}},
|
||||
{"CalibrationProgress", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"CameraView", {PERSISTENT, INT, "3", "0", 2}},
|
||||
{"CameraOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"CameraView", {PERSISTENT, INT, "3", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"CarModel", {PERSISTENT, STRING, "MOCK", "MOCK", 0}},
|
||||
{"CarMake", {PERSISTENT, STRING, "mock", "mock", 0, SETTINGS_SIMPLE}},
|
||||
{"CarModel", {PERSISTENT, STRING, "MOCK", "MOCK", 0, SETTINGS_SIMPLE}},
|
||||
{"CarModelName", {PERSISTENT, STRING, "", "", 0}},
|
||||
{"CECurves", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"CECurves", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"CECurvesLead", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"CELead", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"CEModelStopTime", {PERSISTENT, FLOAT, "7.0", "0.0", 2}},
|
||||
{"CELead", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"CEModelStopTime", {PERSISTENT, FLOAT, "7.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"CESignalLaneDetection", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"CESignalSpeed", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"CESlowerLead", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"CESpeed", {PERSISTENT, FLOAT, "0.0", "0.0", 1}},
|
||||
{"CESpeedLead", {PERSISTENT, FLOAT, "0.0", "0.0", 1}},
|
||||
{"CESignalSpeed", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"CESlowerLead", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"CESpeed", {PERSISTENT, FLOAT, "0.0", "0.0", 1, SETTINGS_SIMPLE}},
|
||||
{"CESpeedLead", {PERSISTENT, FLOAT, "0.0", "0.0", 1, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"ClusterOffset", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
|
||||
{"ColorScheme", {PERSISTENT, STRING, "frog", "stock", 0}},
|
||||
{"CEStopLights", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"CEStoppedLead", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"ClusterOffset", {PERSISTENT, FLOAT, "1.0", "1.0", 2, SETTINGS_SIMPLE}},
|
||||
{"ColorScheme", {PERSISTENT, STRING, "stock", "stock", 0}},
|
||||
{"ColorToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"BootLogoToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"Compass", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"Compass", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"CommunityFavorites", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"ConditionalExperimental", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"ConditionalChill", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"ConditionalExperimental", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"CurvatureData", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"CurveSpeedController", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"CustomAlerts", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"CurveSpeedController", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"CustomAlerts", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"CustomAccelProfile", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"CustomAccelProfileInitialized", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"CustomAccelProfile0MPH", {PERSISTENT, FLOAT, "3.0", "3.0", 3}},
|
||||
@@ -213,17 +241,20 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"CustomAccelProfile45MPH", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
|
||||
{"CustomAccelProfile56MPH", {PERSISTENT, FLOAT, "0.8", "0.8", 3}},
|
||||
{"CustomAccelProfile89MPH", {PERSISTENT, FLOAT, "0.6", "0.6", 3}},
|
||||
{"CustomCruise", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
|
||||
{"CustomCruiseLong", {PERSISTENT, FLOAT, "5.0", "5.0", 2}},
|
||||
{"CustomCruise", {PERSISTENT, FLOAT, "1.0", "1.0", 2, SETTINGS_SIMPLE}},
|
||||
{"CustomCruiseLong", {PERSISTENT, FLOAT, "5.0", "5.0", 2, SETTINGS_SIMPLE}},
|
||||
{"CustomPersonalities", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"CancelButtonControl", {PERSISTENT, INT, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"CustomUI", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"CustomThemes", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"CustomUI", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"DebugMode", {CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0", "0"}},
|
||||
{"DecelerationProfile", {PERSISTENT, INT, "1", "0", 2}},
|
||||
{"DecelerationProfile", {PERSISTENT, INT, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"DeveloperMetrics", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"DeveloperSidebar", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"DeveloperSidebarMetric1", {PERSISTENT, INT, "1", "0", 3}},
|
||||
@@ -234,14 +265,15 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DeveloperSidebarMetric6", {PERSISTENT, INT, "6", "0", 3}},
|
||||
{"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}},
|
||||
{"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"DeviceShutdown", {PERSISTENT, INT, "9", "33", 1}},
|
||||
{"DisableOnroadUploads", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"DisableOpenpilotLongitudinal", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"DeviceShutdown", {PERSISTENT, INT, "9", "33", 1, SETTINGS_SIMPLE}},
|
||||
{"DisableOnroadUploads", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"DisableOpenpilotLongitudinal", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"DiscordUsername", {PERSISTENT, STRING, "", "", 0}},
|
||||
{"DisengageVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"DistanceButtonControl", {PERSISTENT, INT, "1", "0", 2}},
|
||||
{"DisengageVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"DistanceButtonControl", {PERSISTENT, INT, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"DistanceIconPack", {PERSISTENT, STRING, "stock", "stock", 0}},
|
||||
{"DistanceIconToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"DownloadableBootLogos", {PERSISTENT, STRING, "", ""}},
|
||||
@@ -253,104 +285,147 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DownloadableWheels", {PERSISTENT, STRING, "", ""}},
|
||||
{"DownloadAllModels", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"DownloadMaps", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"DriverCamera", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"DriverCamera", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"Model", {PERSISTENT, STRING, "sc2", "sc2", 1}},
|
||||
{"ModelVersion", {PERSISTENT, STRING, "v11", "v11", 1}},
|
||||
{"DrivingModel", {PERSISTENT, STRING, "sc2", "sc2", 1}},
|
||||
{"DrivingModelName", {PERSISTENT, STRING, "South Carolina", "South Carolina", 1}},
|
||||
{"DrivingModelVersion", {PERSISTENT, STRING, "v11", "v11", 1}},
|
||||
{"DynamicPathWidth", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"DynamicPedalsOnUI", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"EngageVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"DynamicPathWidth", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"DynamicPedalsOnUI", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"EngageVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"EVTuning", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"Fahrenheit", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"FlashPanda", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"GMDashSpoofOffsets", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"GMPedalLongitudinal", {PERSISTENT, BOOL, "1", "1", 2}},
|
||||
{"LongPitch", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"RemoteStartBootsComma", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"RemapCancelToDistance", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"GMDashSpoofOffsets", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"GMPedalLongitudinal", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"HKGRemoteStartBootsComma", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"IgnoreIgnitionLine", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"LongPitch", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"RemoteStartBootsComma", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"RemapCancelToDistance", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPAdaptiveAccel", {PERSISTENT, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPFollowDistance", {PERSISTENT, INT, "4", "4"}},
|
||||
{"NAPForcePreAP", {PERSISTENT, BOOL, "0", "0"}},
|
||||
{"NAPPedalEnabled", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPPedalCanBus", {PERSISTENT, INT, "2", "2", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPPedalCalibDone", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPPedalCalibMin", {PERSISTENT, FLOAT, "-3.0", "-3.0"}},
|
||||
{"NAPPedalCalibMax", {PERSISTENT, FLOAT, "99.6", "99.6"}},
|
||||
{"NAPPedalCalibFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPPedalCalibZero", {PERSISTENT, FLOAT, "0.0", "0.0", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPPedalProfile", {PERSISTENT, INT, "4", "4"}},
|
||||
{"NAPRadarBehindNosecone", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPRadarEnabled", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"NAPRadarOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 0, SETTINGS_SIMPLE}},
|
||||
{"ForceAutoTune", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"ForceAutoTuneOff", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ForceFingerprint", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ForceFingerprint", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ForceOffroad", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"ForceOnroad", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"ForceStops", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ForceStandstill", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ForceStops", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ForceStopDistanceOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ForceStandstill", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"FPSCounter", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"FLMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}},
|
||||
{"FLMActiveProfileId", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"FLMTrialBaseline", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"FLMTrialApplied", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"FPSCounter", {PERSISTENT, BOOL, "0", "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}},
|
||||
{"GoatScream", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"GoatScreamCriticalAlerts", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"GreenLightAlert", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"HideAlerts", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"GoatScream", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"GoatScreamCriticalAlerts", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"GreenLightAlert", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"HideAlerts", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"HideChangingLanesBanner", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"HideDistanceProfileBanner", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"HideTurningBanner", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"HideDMIcon", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"HideLeadMarker", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideMaxSpeed", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideSpeed", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"HideSpeedLimit", {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}},
|
||||
{"HumanLaneChanges", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"IconPack", {PERSISTENT, STRING, "frog-animated", "stock", 0}},
|
||||
{"HideMaxSpeed", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"HideSpeed", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"HideSpeedLimit", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"HideSteeringWheel", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"HigherBitrate", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"HolidayThemes", {PERSISTENT, BOOL, "1", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"HumanLaneChanges", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"IncreaseFollowingLowVisibility", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"IncreaseFollowingRain", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"IncreaseFollowingRainStorm", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"IncreaseFollowingSnow", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"IncreaseThermalLimits", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"IncreasedStoppedDistance", {PERSISTENT, FLOAT, "0.0", "0.0", 1, SETTINGS_SIMPLE}},
|
||||
{"IncreasedStoppedDistanceLowVisibility", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreasedStoppedDistanceRain", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreasedStoppedDistanceRainStorm", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreasedStoppedDistanceSnow", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"RedneckCruise", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"IncreaseFollowingLowVisibility", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreaseFollowingRain", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreaseFollowingRainStorm", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreaseFollowingSnow", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreaseThermalLimits", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"IssueReported", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
|
||||
{"KonikDongleId", {PERSISTENT, STRING, "", "", 0}},
|
||||
{"KonikMinutes", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"LaneChanges", {PERSISTENT, BOOL, "1", "1", 0}},
|
||||
{"LaneChangeSmoothing", {PERSISTENT, INT, "10", "10", 1}},
|
||||
{"LaneChangeTime", {PERSISTENT, FLOAT, "1.0", "0.0", 1}},
|
||||
{"LaneDetectionWidth", {PERSISTENT, FLOAT, "0.0", "0.0", 1}},
|
||||
{"LaneLinesColor", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"LaneLinesWidth", {PERSISTENT, FLOAT, "4.0", "2.0", 2}},
|
||||
{"LaneCentering", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"LaneCenteringE2EAuthority", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
|
||||
{"LaneCenterOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"LaneChanges", {PERSISTENT, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
|
||||
{"LaneChangeSmoothing", {PERSISTENT, INT, "5", "10", 1, SETTINGS_SIMPLE}},
|
||||
{"LaneChangeTime", {PERSISTENT, FLOAT, "1.0", "0.0", 1, SETTINGS_SIMPLE}},
|
||||
{"LaneDetectionWidth", {PERSISTENT, FLOAT, "0.0", "0.0", 1, SETTINGS_SIMPLE}},
|
||||
{"LaneLinesColor", {PERSISTENT, STRING, "", "", 2, SETTINGS_SIMPLE}},
|
||||
{"LaneLinesWidth", {PERSISTENT, FLOAT, "4.0", "2.0", 2, SETTINGS_SIMPLE}},
|
||||
{"LastMapsUpdate", {PERSISTENT, STRING, "", ""}},
|
||||
{"LateralTune", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"LeadDepartingAlert", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"LeadDepartingAlert", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"LeadDetectionThreshold", {PERSISTENT, INT, "35", "50", 3}},
|
||||
{"LeadIndicator", {PERSISTENT, BOOL, "1", "1", 2}},
|
||||
{"LeadIndicator", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"LeadInfo", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"LKASButtonControl", {PERSISTENT, INT, "5", "0", 2}},
|
||||
{"LKASButtonControl", {PERSISTENT, INT, "5", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"LockDoorsTimer", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"LongDistanceButtonControl", {PERSISTENT, INT, "5", "0", 2}},
|
||||
{"LongModeButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"LongStarButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"LongCancelButtonControl", {PERSISTENT, INT, "5", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"LongDistanceButtonControl", {PERSISTENT, INT, "5", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"LongModeButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"LongStarButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"LongitudinalActuatorDelay", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"LongitudinalActuatorDelayStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"LateralManeuverStatus", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON, "{}", "{}"}},
|
||||
{"LongitudinalManeuverPaddleMode", {PERSISTENT, STRING, "auto", "auto"}},
|
||||
{"LongitudinalManeuverStatus", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON, "{}", "{}"}},
|
||||
{"LongitudinalTune", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"LoudBlindspotAlert", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"LowVoltageShutdown", {PERSISTENT, FLOAT, "11.8", "11.8", 3}},
|
||||
{"LongitudinalTune", {PERSISTENT, BOOL, "1", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"LoudBlindspotAlert", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"LoudBlindspotAlertWhenDisengaged", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"LowVoltageShutdown", {PERSISTENT, FLOAT, "11.8", "11.8", 3, SETTINGS_SIMPLE}},
|
||||
{"MainCruiseButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"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, "{}", "{}"}},
|
||||
{"MapboxSecretKey", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"MapDeceleration", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"MapdSettings", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"MapGears", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"MapGears", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"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, SETTINGS_SIMPLE}},
|
||||
{"ClearNavOnOffroadTimeoutMinutes", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"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"}},
|
||||
@@ -359,27 +434,31 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"VisionSpeedLimitLastEvent", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"VisionSpeedLimitStatus", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"VisionSpeedLimitStream", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"VisionSpeedLimitSupportCount", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"VisionSpeedLimitSupportSpeed", {CLEAR_ON_MANAGER_START, FLOAT, "0.0", "0.0"}},
|
||||
{"MaxDesiredAcceleration", {PERSISTENT, FLOAT, "4.0", "2.0", 2}},
|
||||
{"MinimumBackupSize", {PERSISTENT, INT, "0", "0"}},
|
||||
{"MinimumLaneChangeSpeed", {PERSISTENT, FLOAT, "20.0", "20.0", 2}},
|
||||
{"ModeButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"MinimumLaneChangeSpeed", {PERSISTENT, FLOAT, "20.0", "20.0", 2, SETTINGS_SIMPLE}},
|
||||
{"ModeButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ModelDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"ModelDrivesAndScores", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"ModelReleasedDates", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"ModelRandomizer", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ModelSortMode", {PERSISTENT, STRING, "alphabetical", "alphabetical", 1}},
|
||||
{"ModelToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"ModelUI", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ModelUI", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ModelVersions", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"NavigationUI", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"ModelManifestVersion", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"NavigationUI", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"NNFF", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"NNFFLite", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"NostalgiaMode", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"NostalgiaMode", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"NumericalTemp", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"NoLogging", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"NoUploads", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"NudgelessLaneChange", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"NudgelessLaneChangeOnlyWhenEngaged", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"NumericalTemp", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"Offset1", {PERSISTENT, FLOAT, "5.0", "0.0", 0}},
|
||||
{"Offset2", {PERSISTENT, FLOAT, "5.0", "0.0", 0}},
|
||||
{"Offset3", {PERSISTENT, FLOAT, "5.0", "0.0", 0}},
|
||||
@@ -387,102 +466,112 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"Offset5", {PERSISTENT, FLOAT, "10.0", "0.0", 0}},
|
||||
{"Offset6", {PERSISTENT, FLOAT, "10.0", "0.0", 0}},
|
||||
{"Offset7", {PERSISTENT, FLOAT, "10.0", "0.0", 0}},
|
||||
{"OneLaneChange", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"OnroadDistanceButton", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"OneLaneChange", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"OnroadDistanceButton", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"OnroadDistanceButtonPressed", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"FavoriteVirtualAccelCruiseCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"FavoriteVirtualDecelCruiseCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"WheelButtonBookmarkCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
|
||||
{"openpilotMinutes", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"OverpassRequests", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"PathColor", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"PathEdgesColor", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"PathEdgeWidth", {PERSISTENT, FLOAT, "20.0", "0.0", 2}},
|
||||
{"PathWidth", {PERSISTENT, FLOAT, "6.1", "5.9", 2}},
|
||||
{"PauseAOLOnBrake", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PauseLateralOnSignal", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PauseLateralSpeed", {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}},
|
||||
{"PathColor", {PERSISTENT, STRING, "", "", 2, SETTINGS_SIMPLE}},
|
||||
{"PathEdgesColor", {PERSISTENT, STRING, "", "", 2, SETTINGS_SIMPLE}},
|
||||
{"PathEdgeWidth", {PERSISTENT, FLOAT, "20.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"PathWidth", {PERSISTENT, FLOAT, "6.1", "5.9", 2, SETTINGS_SIMPLE}},
|
||||
{"PauseAOLOnBrake", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"PauseLateralOnSignal", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"PauseLateralSpeed", {PERSISTENT, FLOAT, "0.0", "0.0", 1, SETTINGS_SIMPLE}},
|
||||
{"LateralResumeDelay", {PERSISTENT, FLOAT, "0.0", "0.0", 1, SETTINGS_SIMPLE}},
|
||||
{"PedalsOnUI", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"PromptVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"QOLLateral", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"QOLLongitudinal", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"QOLVisuals", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"PromptDistractedVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"PromptVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"QOLLateral", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"QOLLongitudinal", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"QOLVisuals", {PERSISTENT, BOOL, "1", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"RadarTakeoffs", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"RadarTracksUI", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"RainbowPath", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"RainbowPath", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"RandomEvents", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"RandomThemes", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"RandomThemesHolidays", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"ReduceAccelerationLowVisibility", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"ReduceAccelerationRain", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"ReduceAccelerationRainStorm", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"ReduceAccelerationSnow", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"ReduceLateralAccelerationLowVisibility", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"ReduceLateralAccelerationRain", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"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}},
|
||||
{"ReduceAccelerationLowVisibility", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ReduceAccelerationRain", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ReduceAccelerationRainStorm", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ReduceAccelerationSnow", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ReduceLateralAccelerationLowVisibility", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ReduceLateralAccelerationRain", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ReduceLateralAccelerationRainStorm", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ReduceLateralAccelerationSnow", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"RefuseVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"RoadNameUI", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"RotatingWheel", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"ScreenBrightness", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"ScreenBrightnessOnroad", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"ScreenManagement", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"ScreenRecorder", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ScreenTimeout", {PERSISTENT, INT, "30", "30", 2}},
|
||||
{"ScreenTimeoutOnroad", {PERSISTENT, INT, "30", "10", 2}},
|
||||
{"RoadEdgesWidth", {PERSISTENT, FLOAT, "2.0", "2.0", 2, SETTINGS_SIMPLE}},
|
||||
{"RoadNameUI", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"RotatingWheel", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightness", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessOnroad", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"ScreenRecorder", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenTimeout", {PERSISTENT, INT, "30", "30", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenTimeoutOnroad", {PERSISTENT, INT, "30", "10", 2, SETTINGS_SIMPLE}},
|
||||
{"SecOCKeys", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"SafeMode", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"SafeModeBackup", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"SetSpeedLimit", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"SetSpeedOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 2}},
|
||||
{"ShowCEMStatus", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"ShowCPU", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"ShowCSCStatus", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"SetSpeedOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowCEMStatus", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowCCMStatus", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ShowCPU", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"ShowCSCStatus", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowGPU", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"ShowIP", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"ShowMemoryUsage", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"ShowMemoryUsage", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"ShowModeStatusBanner", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShownToggleDescriptions", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"ShowSLCOffset", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"ShowSpeedLimits", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"ShowSpeedLimits", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"ShowSteering", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"ShowStoppingPoint", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"ShowStoppingPointMetrics", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"ShowStorageLeft", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"ShowStorageUsed", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"SidebarMetrics", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"SidebarMetrics", {PERSISTENT, BOOL, "0", "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}},
|
||||
{"SimpleMode", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"SLCFallback", {PERSISTENT, INT, "2", "0", 1}},
|
||||
{"SLCLookaheadHigher", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"SLCLookaheadLower", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"SLCMapboxFiller", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"SLCMapboxFiller", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"SLCOverride", {PERSISTENT, INT, "1", "0", 1}},
|
||||
{"SLCPriority", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"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}},
|
||||
{"SLCPriority1", {PERSISTENT, STRING, "Vision", "Map Data", 2}},
|
||||
{"SLCPriority2", {PERSISTENT, STRING, "Map Data", "Dashboard", 2}},
|
||||
{"SNGHack", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"SpeedLimitChangedAlert", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"SpeedLimitController", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"SpeedLimitFiller", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"SpeedLimits", {PERSISTENT | DONT_LOG, JSON, "[]", "[]"}},
|
||||
@@ -490,22 +579,23 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SpeedLimitSources", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"VisionSpeedLimitAutoBookmark", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"VisionSpeedLimitAutoPreserveSegment", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"VisionSpeedLimitDetection", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"VisionSpeedLimitDetection", {PERSISTENT, BOOL, "1", "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}},
|
||||
{"StandbyMode", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"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, SETTINGS_SIMPLE}},
|
||||
{"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}},
|
||||
{"StaticPedalsOnUI", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"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, SETTINGS_SIMPLE}},
|
||||
{"SteerDelay", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"SteerDelayModeMigrated", {PERSISTENT, BOOL}},
|
||||
{"SteerDelayStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"SteerFriction", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"SteerFrictionStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
@@ -517,19 +607,21 @@ 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}},
|
||||
{"EnableTorqueBarWidget", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"StockConfidenceBallWidget", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"StockDongleId", {PERSISTENT, STRING, "", ""}},
|
||||
{"StopAccel", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StopAccelStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StoppedTimer", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"StopDistance", {PERSISTENT, FLOAT, "6.0", "6.0", 2}},
|
||||
{"StoppedTimer", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"StoppingDecelRate", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StoppingDecelRateStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StarButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"SwitchbackModeCooldown", {PERSISTENT, INT, "5", "0", 2}},
|
||||
{"StarButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SwitchbackModeCooldown", {PERSISTENT, INT, "5", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SwitchbackModeEnabled", {CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0", "0"}},
|
||||
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2}},
|
||||
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"TacoTuneHacks", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
{"TetheringEnabled", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"ThemeDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
@@ -537,12 +629,13 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"Timezone", {PERSISTENT, STRING, "", ""}},
|
||||
{"TinygradUpdateAvailable", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"ToyotaDoors", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"TrafficFollow", {PERSISTENT, FLOAT, "0.5", "0.5", 2}},
|
||||
{"TrafficJerkAcceleration", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"TrailerLoad", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"TrafficFollow", {PERSISTENT, FLOAT, "0.75", "0.75", 2}},
|
||||
{"TrafficJerkAcceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"TrafficJerkDanger", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"TrafficJerkDeceleration", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"TrafficJerkSpeed", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"TrafficJerkSpeedDecrease", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
|
||||
{"TrafficJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"TrafficJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"TrafficJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"TruckTuning", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"TuningLevel", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"TuningLevelConfirmed", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
@@ -554,24 +647,40 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"UpdateTinygrad", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"UpdateWheelImage", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"UseActiveTheme", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
|
||||
{"UseKonikServer", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"UseAutoSteerDelay", {PERSISTENT, BOOL, "1", "1", 3}},
|
||||
{"UseKonikServer", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"UseSI", {PERSISTENT, BOOL, "1", "1", 3}},
|
||||
{"UserFavorites", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"UseVienna", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"UseVienna", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"VASMAnnotationConfig", {PERSISTENT, JSON, "{}", "{}", 2}},
|
||||
{"VASMConfidenceThreshold", {PERSISTENT, FLOAT, "0.85", "0.85", 2}},
|
||||
{"VASMEnabled", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"VASMLeftActive", {CLEAR_ON_MANAGER_START, STRING, "0", "0", 2}},
|
||||
{"VASMLeftConfidence", {CLEAR_ON_MANAGER_START, STRING, "0.0", "0.0", 2}},
|
||||
{"VASMLastUpdateMonoTime", {CLEAR_ON_MANAGER_START, STRING, "0", "0", 2}},
|
||||
{"VASMRightActive", {CLEAR_ON_MANAGER_START, STRING, "0", "0", 2}},
|
||||
{"VASMRightConfidence", {CLEAR_ON_MANAGER_START, STRING, "0.0", "0.0", 2}},
|
||||
{"VASMSmoothSeconds", {PERSISTENT, FLOAT, "0.2", "0.2", 2}},
|
||||
{"VASMTimestampEof", {CLEAR_ON_MANAGER_START, STRING, "0", "0", 2}},
|
||||
{"VEgoStarting", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"VEgoStartingStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"VEgoStopping", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"VEgoStoppingStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"VeryLongDistanceButtonControl", {PERSISTENT, INT, "6", "0", 2}},
|
||||
{"VeryLongModeButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"VeryLongStarButtonControl", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"VoltSNG", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"WarningImmediateVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"WarningSoftVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
{"WeatherPresets", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"VeryLongCancelButtonControl", {PERSISTENT, INT, "6", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"VeryLongDistanceButtonControl", {PERSISTENT, INT, "6", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"VeryLongModeButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"VeryLongStarButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"VoltSNG", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"JeepBrakeHold", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"GMAutoHold", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"VoltOnePedalMode", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ToyotaAutoHold", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"WarningImmediateVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"WarningSoftVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"WeatherPresets", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"WeatherToken", {PERSISTENT | DONT_LOG, STRING, "", "", 2}},
|
||||
{"WheelControls", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"WheelIcon", {PERSISTENT, STRING, "frog", "stock", 0}},
|
||||
{"WheelSpeed", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"WheelIcon", {PERSISTENT, STRING, "stock", "stock", 0}},
|
||||
{"WheelSpeed", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"WheelToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
};
|
||||
|
||||
+1104
-855
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,10 @@ cdef extern from "common/params.h":
|
||||
JSON
|
||||
BYTES
|
||||
|
||||
cdef enum ParamSettingsTier:
|
||||
SETTINGS_SIMPLE
|
||||
SETTINGS_ADVANCED
|
||||
|
||||
cdef cppclass c_Params "Params":
|
||||
c_Params(string, bool) except + nogil
|
||||
string get(string, bool) nogil
|
||||
@@ -55,6 +59,8 @@ cdef extern from "common/params.h":
|
||||
|
||||
int getTuningLevel(string) nogil
|
||||
|
||||
ParamSettingsTier getSettingsTier(string) nogil
|
||||
|
||||
PYTHON_2_CPP = {
|
||||
(str, STRING): lambda v: v,
|
||||
(builtins.bool, BOOL): lambda v: "1" if v else "0",
|
||||
@@ -255,3 +261,6 @@ cdef class Params:
|
||||
cdef string k = self.check_key(key)
|
||||
cdef optional[int] level = self.p.getTuningLevel(k)
|
||||
return level.value() if level.has_value() else 0
|
||||
|
||||
def get_settings_tier(self, key):
|
||||
return self.p.getSettingsTier(self.check_key(key))
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
from pathlib import Path
|
||||
|
||||
from openpilot.common import file_chunker
|
||||
|
||||
|
||||
def test_chunked_stream_round_trip(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(file_chunker, "CHUNK_SIZE", 7)
|
||||
path = tmp_path / "artifact.pkl"
|
||||
payload = b"a model artifact spanning several chunks"
|
||||
path.write_bytes(payload)
|
||||
|
||||
targets = file_chunker.get_chunk_targets(path, len(payload))
|
||||
file_chunker.chunk_file(path, targets)
|
||||
|
||||
assert file_chunker.file_chunked_exists(path)
|
||||
assert file_chunker.read_file_chunked(path) == payload
|
||||
with file_chunker.open_file_chunked(path) as stream:
|
||||
assert stream.read(9) + stream.read() == payload
|
||||
|
||||
|
||||
def test_unchunked_stream_does_not_materialize_file(tmp_path, monkeypatch):
|
||||
path = tmp_path / "fat.onnx"
|
||||
payload = b"large model read through bounded buffers"
|
||||
path.write_bytes(payload)
|
||||
original_read_bytes = Path.read_bytes
|
||||
|
||||
def reject_whole_file_read(candidate):
|
||||
if candidate == path:
|
||||
raise AssertionError("streaming must not call Path.read_bytes()")
|
||||
return original_read_bytes(candidate)
|
||||
|
||||
monkeypatch.setattr(Path, "read_bytes", reject_whole_file_read)
|
||||
with file_chunker.open_file_chunked(path) as stream:
|
||||
assert stream.read() == payload
|
||||
@@ -25,3 +25,13 @@ TEST_CASE("params_nonblocking_put") {
|
||||
REQUIRE(p.get(name) == "1");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("settings_tier_is_independent_from_tuning_level") {
|
||||
Params params;
|
||||
|
||||
REQUIRE(params.getSettingsTier("AlwaysOnLateral") == SETTINGS_SIMPLE);
|
||||
REQUIRE(params.getTuningLevel("AlwaysOnLateral") == 0);
|
||||
REQUIRE(params.getSettingsTier("HumanLaneChanges") == SETTINGS_SIMPLE);
|
||||
REQUIRE(params.getTuningLevel("HumanLaneChanges") == 2);
|
||||
REQUIRE(params.getSettingsTier("AdvancedLateralTune") == SETTINGS_ADVANCED);
|
||||
}
|
||||
|
||||
@@ -50,11 +50,8 @@ class TestParams:
|
||||
assert self.params.get("CarParams", block=True) == b"test"
|
||||
|
||||
def test_params_unknown_key_fails(self):
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.get("swag")
|
||||
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.get_bool("swag")
|
||||
assert self.params.get("swag") is None
|
||||
assert not self.params.get_bool("swag")
|
||||
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.put("swag", "abc")
|
||||
@@ -133,8 +130,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)
|
||||
|
||||
+54
-2
@@ -1,8 +1,49 @@
|
||||
import contextlib
|
||||
import gc
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
try:
|
||||
int(os.environ.get("DEBUG", "0"))
|
||||
except ValueError:
|
||||
os.environ["DEBUG"] = "0"
|
||||
|
||||
if platform.system() == "Darwin":
|
||||
os.environ["SP_HEADLESS_TEST"] = "1"
|
||||
|
||||
|
||||
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
|
||||
@@ -57,8 +98,7 @@ def openpilot_function_fixture(request):
|
||||
# ensure the test doesn't change the prefix
|
||||
assert "OPENPILOT_PREFIX" in os.environ and prefix == os.environ["OPENPILOT_PREFIX"]
|
||||
|
||||
# cleanup any started processes
|
||||
manager.manager_cleanup()
|
||||
manager.manager_cleanup()
|
||||
|
||||
# some processes disable gc for performance, re-enable here
|
||||
if not gc.isenabled():
|
||||
@@ -86,6 +126,9 @@ def tici_setup_fixture(request, openpilot_function_fixture):
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
skipper = pytest.mark.skip(reason="Skipping tici test on PC")
|
||||
darwin_fake_event_skipper = pytest.mark.skip(reason="Fake socket events are unavailable on macOS")
|
||||
darwin_tmpfs_skipper = pytest.mark.skip(reason="tmpfs mount tests require Linux")
|
||||
darwin_proxy_skipper = pytest.mark.skip(reason="Athena local proxy socket test requires Linux select semantics")
|
||||
for item in items:
|
||||
if "tici" in item.keywords:
|
||||
if not TICI:
|
||||
@@ -98,6 +141,15 @@ def pytest_collection_modifyitems(config, items):
|
||||
class_property_value = getattr(item.cls, class_property_name)
|
||||
item.add_marker(pytest.mark.xdist_group(class_property_value))
|
||||
|
||||
if platform.system() == "Darwin" and item.path.name in ("test_fuzzy.py", "test_processes.py") and "process_replay" in item.path.parts:
|
||||
item.add_marker(darwin_fake_event_skipper)
|
||||
|
||||
if platform.system() == "Darwin" and item.path.name == "test_git.py" and "updated" in item.path.parts:
|
||||
item.add_marker(darwin_tmpfs_skipper)
|
||||
|
||||
if platform.system() == "Darwin" and item.name == "test_start_local_proxy" and "athena" in item.path.parts:
|
||||
item.add_marker(darwin_proxy_skipper)
|
||||
|
||||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_configure(config):
|
||||
|
||||
+6
-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>|||
|
||||
@@ -230,13 +231,17 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|
||||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|
||||
|Subaru|Ascent 2019-21|All[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<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=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Ascent 2023|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D connector<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=Subaru Ascent 2023">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<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=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<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=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Crosstrek 2025|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D connector<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=Subaru Crosstrek 2025">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Forester 2019-21|All[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<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=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Forester 2022-24|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru C connector<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=Subaru Forester 2022-24">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<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=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<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=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Legacy 2020-22|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B connector<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=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2020-22|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru B connector<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=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2023|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru D connector<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=Subaru Outback 2023">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<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=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru A connector<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=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Škoda|Fabia 2022-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# 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`.
|
||||
- Normal artifacts target QCOM. External-GPU artifacts must be compiled explicitly and tagged in the manifest.
|
||||
- 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
|
||||
```
|
||||
|
||||
For a model that cannot run on the device GPU, compile with the USB AMD GPU attached:
|
||||
|
||||
```bash
|
||||
./models --lebowski --gpu
|
||||
```
|
||||
|
||||
The ASM2464PD bridge must run the current tinygrad custom firmware from
|
||||
https://github.com/tinygrad/asm2464pd-firmware. Its USB product string starts
|
||||
with `custom`; the legacy `USB 3.2 PCIe TinyEnclosure` patch is not compatible
|
||||
with comma's current external-GPU runtime. Firmware flashing is a separate,
|
||||
explicit hardware setup step and StarPilot never performs it automatically.
|
||||
|
||||
The dynamic flag (`--lebowski` above) sets the output and manifest model ID;
|
||||
when only one source model is staged, its ONNX filename does not need to match
|
||||
that ID. Input format and behavior version are inferred. `--external-gpu`
|
||||
remains available as a compatibility alias for `--gpu`.
|
||||
|
||||
This emits a streaming out-of-band pickle and keeps QCOM available for camera warps. Its manifest entry must include:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "lebowski",
|
||||
"uses_external_gpu": true
|
||||
}
|
||||
```
|
||||
|
||||
Only tagged models activate the external GPU. If the GPU or artifact is unavailable, runtime falls back to the built-in model; all untagged models retain the existing QCOM path.
|
||||
|
||||
`--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.
|
||||
|
||||
`uses_external_gpu` is optional and defaults to `false`.
|
||||
|
||||
## 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.
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
@@ -0,0 +1,102 @@
|
||||
# Contribute to StarPilot
|
||||
|
||||
Contributions are welcome. StarPilot is driver-assistance software, so changes should be focused, testable, and safe for vehicles outside the change's intended scope.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Open all pull requests against the **`Dom` branch**. `Dom` is StarPilot's testing and integration branch; do not target the release branch directly.
|
||||
|
||||
A pull request should:
|
||||
|
||||
* have one clear purpose and contain only changes needed for that purpose;
|
||||
* explain what changed, why it changed, and how it was tested;
|
||||
* link the relevant issue or feedback item when one exists;
|
||||
* avoid unrelated cleanup, refactors, dependency changes, and formatting churn; and
|
||||
* pass the existing tests and checks.
|
||||
|
||||
Keep commits reviewable and update documentation when behavior or configuration changes. If a change needs a large refactor, separate that work from the behavior change when practical.
|
||||
|
||||
### Vehicle-specific changes
|
||||
|
||||
Do your best to ensure a vehicle-specific change affects only the intended vehicle, platform, or brand. Prefer the narrowest appropriate condition instead of changing shared behavior for every vehicle.
|
||||
|
||||
Tests should prove both sides of that boundary:
|
||||
|
||||
* the affected vehicle receives the new or corrected behavior; and
|
||||
* an unaffected vehicle, platform, brand, or configuration retains the existing behavior.
|
||||
|
||||
Include the vehicle and hardware used for on-road or bench testing in the pull request. Hardware testing is valuable, but it does not replace an automated regression test when the behavior can be tested in code.
|
||||
|
||||
## Development environment
|
||||
|
||||
StarPilot keeps host-native development tools separate from device-target builds. Run the setup and development commands from the repository root.
|
||||
|
||||
Install the Python dependencies before starting:
|
||||
|
||||
```bash
|
||||
tools/install_python_dependencies.sh
|
||||
```
|
||||
|
||||
On Ubuntu, `tools/ubuntu_setup.sh` installs both the system and Python dependencies. The development tools also require `uv`.
|
||||
|
||||
Use `./dev` for host-native tools. It creates and reuses an isolated environment under `.host_runtime/`, keeping host build artifacts out of the working tree:
|
||||
|
||||
```bash
|
||||
./dev replay
|
||||
./dev cabana
|
||||
./dev plotjuggler
|
||||
./dev shell
|
||||
```
|
||||
|
||||
For desktop UI work, use `./c3`, `./c4`, or `./raybig`. These commands use the same isolated host environment.
|
||||
|
||||
Use `./build` when you need comma-compatible device artifacts. This is the expected validation for changes that affect compiled device code or runtime behavior:
|
||||
|
||||
```bash
|
||||
./build
|
||||
```
|
||||
|
||||
Device builds require Docker Desktop or Podman with Linux/aarch64 support and a configured comma sysroot. See the [laptop device-build guide](../how-to/laptop-device-build.md) for setup instructions and the [complete StarPilot development workflow](https://github.com/firestar5683/StarPilot/blob/Dom/tools/STARPILOT_DEVELOPMENT.md) for all host commands and troubleshooting.
|
||||
|
||||
## Code formatting
|
||||
|
||||
Match the style of the code around your change and do not reformat unrelated files. Python formatting and lint rules are defined in `pyproject.toml`; the project uses two-space indentation and Ruff.
|
||||
|
||||
Run Ruff on changed Python files while developing:
|
||||
|
||||
```bash
|
||||
ruff check path/to/changed_file.py
|
||||
ruff format --check path/to/changed_file.py
|
||||
```
|
||||
|
||||
Before submitting, run the repository lint checks from the project root:
|
||||
|
||||
```bash
|
||||
./scripts/lint/lint.sh
|
||||
```
|
||||
|
||||
## Testing standards
|
||||
|
||||
Every behavior change or bug fix should include focused automated tests when practical. Recent StarPilot tests favor small regression cases that construct the relevant state, exercise one behavior, and assert the exact result.
|
||||
|
||||
At minimum:
|
||||
|
||||
1. Add or update a test that would fail without the change.
|
||||
2. Cover important boundaries, modes, and disabled states related to the change.
|
||||
3. For vehicle-specific behavior, add a negative case showing the change does not bleed into an unaffected vehicle or configuration.
|
||||
4. Run the directly affected test module and the existing tests for the affected subsystem.
|
||||
5. Ensure the pull request passes all existing CI checks before it is ready to merge.
|
||||
|
||||
Run a focused test module with pytest:
|
||||
|
||||
```bash
|
||||
pytest path/to/test_file.py
|
||||
```
|
||||
|
||||
Run the full test suite when your development environment supports it:
|
||||
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
|
||||
If a test requires special hardware or cannot run in your environment, say so in the pull request and document the closest validation you completed.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This flow builds **device-target (`larch64`) binaries on your laptop** using a Linux/aarch64 container and a synced comma sysroot.
|
||||
|
||||
For the full StarPilot branch workflow, including host-native shorthand tools such as `./dev`, `./c3`, `./c4`, and `./raybig`, see [tools/STARPILOT_DEVELOPMENT.md](../../tools/STARPILOT_DEVELOPMENT.md).
|
||||
For the full StarPilot branch workflow, including host-native shorthand tools such as `./dev`, `./c3`, `./c4`, and `./raybig`, see the [StarPilot development guide](https://github.com/firestar5683/StarPilot/blob/Dom/tools/STARPILOT_DEVELOPMENT.md).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
||||
@@ -302,3 +302,112 @@ For temporal behavior on a saved frame directory or route extract, replay the ru
|
||||
```bash
|
||||
.venv/bin/python scripts/replay_speed_limit_vision.py .tmp/vision_iter/seg10_5fps --frames-fps 5
|
||||
```
|
||||
|
||||
The detector/classifier runtime is model-only by default. Use `--crop-ocr` with
|
||||
`evaluate_runtime_manifest.py` or `replay_route_runtime.py` only for an explicit
|
||||
legacy comparison. A model-only release must match reviewed-manifest accuracy
|
||||
and pass representative route replays at measured on-device cadence. Evaluate
|
||||
candidate recognition and temporal publish behavior separately: a correct
|
||||
single-frame candidate can still be suppressed by the history and speed-change
|
||||
confirmation policy.
|
||||
|
||||
Ignored review rows label the proposed crop, not the entire camera frame.
|
||||
Consequently, negative-window candidate and publish counts from
|
||||
`evaluate_reviewed_route_events.py` are an upper bound until the full frame is
|
||||
audited; another valid sign can be present outside the rejected crop. Use the
|
||||
per-row output and frame image to audit any regression delta before treating it
|
||||
as a runtime false positive.
|
||||
|
||||
## Promotion Gate
|
||||
|
||||
Do not promote a checkpoint from classifier validation accuracy alone. Export it
|
||||
to an isolated model directory and run the complete runtime pipeline against the
|
||||
reviewed positive, hard-negative, and failed-drive manifests. A candidate must
|
||||
preserve exact-value recall, avoid new wrong-value reads, and remain within the
|
||||
accepted false-positive budget before route replay.
|
||||
|
||||
Mine detector proposals that fool an integrated-reject classifier into a new
|
||||
reject class before retraining:
|
||||
|
||||
```bash
|
||||
.venv/bin/python scripts/speed_limit_vision/mine_classifier_reject_crops.py \
|
||||
--models-dir /path/to/candidate/models \
|
||||
--dataset /path/to/versioned/classifier \
|
||||
--manifest /path/to/reviewed-negative-manifest.csv
|
||||
```
|
||||
|
||||
Keep the resulting dataset version separate from the current training set. If a
|
||||
hard-negative retrain lowers reviewed recall, reject the checkpoint even when it
|
||||
improves aggregate validation accuracy or removes a known false positive.
|
||||
|
||||
## Active-Learning Review Pass
|
||||
|
||||
Keep parallel miners in separate directories and merge them only when their
|
||||
model and mining fingerprints match:
|
||||
|
||||
```bash
|
||||
.venv/bin/python scripts/speed_limit_vision/merge_manual_review_queues.py \
|
||||
/path/to/shard0 /path/to/shard1 /path/to/shard2 /path/to/shard3 \
|
||||
--output-dir /path/to/merged
|
||||
```
|
||||
|
||||
When rescanning with a new model, compare the fingerprinted queues before
|
||||
selecting another batch. The optional review output retains the full queue
|
||||
schema so it can be passed directly to the selector and review server:
|
||||
|
||||
```bash
|
||||
.venv/bin/python scripts/speed_limit_vision/compare_manual_review_queues.py \
|
||||
--before /path/to/baseline/manual_review_queue.csv \
|
||||
--after /path/to/candidate/manual_review_queue.csv \
|
||||
--output-csv /path/to/comparison.csv \
|
||||
--review-output /path/to/disagreements/manual_review_queue.csv
|
||||
|
||||
.venv/bin/python scripts/speed_limit_vision/select_manual_review_queue.py \
|
||||
--input /path/to/disagreements/manual_review_queue.csv \
|
||||
--output /path/to/review/manual_review_queue.csv \
|
||||
--max-rows 1200 \
|
||||
--min-seconds-per-route-speed 3
|
||||
```
|
||||
|
||||
The selector prioritizes value changes and gained/lost reads, balances routes
|
||||
and speed classes, and removes adjacent same-speed frames from one scene. Start
|
||||
the reviewer and import its labels without moving route media off the training
|
||||
volume:
|
||||
|
||||
```bash
|
||||
.venv/bin/python scripts/speed_limit_vision/serve_manual_review_queue.py \
|
||||
--manifest /path/to/review/manual_review_queue.csv \
|
||||
--port 8765
|
||||
|
||||
.venv/bin/python scripts/speed_limit_vision/import_manual_review_queue.py \
|
||||
--queue /path/to/review/manual_review_queue.csv
|
||||
```
|
||||
|
||||
## Re-mine the Route Backlog
|
||||
|
||||
Re-run the backlog after a candidate passes the reviewed-manifest and route
|
||||
replay gates. Use a model fingerprinted run so new pseudo-labels are staged next
|
||||
to, rather than merged into, the original route-mining data:
|
||||
|
||||
```bash
|
||||
.venv/bin/python scripts/speed_limit_vision/mine_route_training_samples.py \
|
||||
--workspace /Volumes/T5/starpilot_speed_limit/workspace/speed_limit_training_clean \
|
||||
--models-dir /path/to/promoted/models \
|
||||
--model-only \
|
||||
--run-id auto \
|
||||
--sample-every 2.0 \
|
||||
--transition-step 0.5 \
|
||||
--max-frames-per-route 720 \
|
||||
--max-positives-per-route 120 \
|
||||
--max-negatives-per-route 200
|
||||
```
|
||||
|
||||
The output is written under
|
||||
`staging/route_mining/model_<model-fingerprint>_run_<mining-fingerprint>/` with
|
||||
its own detector images, classifier labels, review manifest, and per-route
|
||||
completion state. The mining fingerprint includes the model-only mode,
|
||||
thresholds, sampling configuration, and relevant source code. Review and
|
||||
deduplicate that staged run before merging it into a training dataset. Never
|
||||
overwrite the canonical route samples or automatically train on every mined
|
||||
positive; map agreement and human review remain required because a stronger
|
||||
model can still reproduce its own mistakes at larger scale.
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,278 @@
|
||||
# I Taught My Comma to Read Speed Limit Signs
|
||||
|
||||
<video controls playsinline preload="metadata" poster="assets/speed-limit-vision-demo-poster.jpg" style="width: 100%; max-width: 1280px;">
|
||||
<source src="assets/speed-limit-vision-demo.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
[Watch the speed-limit vision demo](assets/speed-limit-vision-demo.mp4)
|
||||
|
||||
This whole project started because I was lazy.
|
||||
|
||||
If you’ve driven an HKG or Toyota vehicle in recent years, you’ve probably seen the dashboard automatically pick up speed limits using the camera. Coming from a car that didn't have this functionality, I was very jealous. Those cars can feed those speed limits directly into openpilot forks speed limit controllers. Mine couldn't.
|
||||
|
||||
The “solution” has been to manually add speed limits to Mapbox or OpenStreetMaps. It worked, but it was tedious. I live in rural Kansas, where my commute is almost entirely devoid of nerds contributing open source map data. I began mapping my commute when I first joined the project and within an hour said, “I ain't doin this”
|
||||
|
||||
We have an AI model that's good enough to drive our cars running on a smartphone chipset from years ago. How hard could it be to build one that just reads speed limit signs? I wanted StarPilot to look out the windshield, see a speed limit sign, and just know what it said.
|
||||
|
||||
That sounded simple enough.
|
||||
|
||||
It wasn’t.
|
||||
|
||||
The first prototype was basically held together with duct tape: I used all the public speed limit datasets I could find (glare and Lisa), some OpenCV, a little OCR, and a lot of wishful thinking. It could occasionally read a sign, but it missed 90% of them and produced enough false positives that you definitely wouldn’t want to trust it.
|
||||
|
||||
The important part wasn’t that it sucked - it was that it worked just well enough to start collecting better training data.
|
||||
|
||||
I run a fork of openpilot called StarPilot. We're heavily focused on tuning and testing wild ideas, which made my users the perfect candidates for this project.
|
||||
|
||||
Instead of relying on public datasets that barely resembled comma camera footage, StarPilot started collecting its own. Community members submitted routes from all over the country using bookmarks that were generated automatically when the model said "I think this might've been a sign," and every promising detection went through manual review. Eventually that grew into hundreds of gigabytes of real driving footage and thousands of carefully labeled speed limit signs.
|
||||
|
||||
From there it became an endless cycle:
|
||||
|
||||
- Train a better model.
|
||||
- Mine more routes.
|
||||
- Find more missed signs.
|
||||
- Label them.
|
||||
- Repeat.
|
||||
|
||||
The funny part is that training the neural network wasn’t actually the hardest problem.
|
||||
|
||||
The hard part was everything around it.
|
||||
|
||||
One of the biggest challenges wasn’t accuracy, it was speed. Every millisecond spent processing was another frame the model didn't have time to see. The faster it could process frames, the more chances it had to catch a sharp, readable speed limit sign before it disappeared. This was particularly imperative at night, when naturally camera frame rates drop alongside your chances of picking up a clear sign.
|
||||
|
||||
It’s slowly evolved from a proof of concept into a model that’s trained primarily on real comma footage instead of generic traffic sign datasets. It now runs entirely on-device, publishes vision-based speed limits directly into StarPilot, and no longer depends on OCR or manually maintaining map data.
|
||||
|
||||
And best of all…
|
||||
|
||||
While I'm too lazy to hand write hundreds of speed limit signs into OpenStreetMaps, this tool can now be used to automate the entire process, giving back to the open source mapping community that helps those in larger areas so well.
|
||||
|
||||
Give my vision speed limits a try, or help continue to build a better one than mine! All info and training stack is available on the StarPilot repo.
|
||||
|
||||
---
|
||||
|
||||
*For a full LLM style, more technical breakdown for those interested in the process, please read below:*
|
||||
|
||||
|
||||
## Teaching StarPilot to Read Speed Limit Signs
|
||||
|
||||
In March 2026, this project started with a practical question: could StarPilot use the road camera to recognize speed-limit signs, attach them to GPS data, and help fill gaps in OpenStreetMap?
|
||||
|
||||
The first answer was "probably." A clean 40 mph sign in recorded comma footage was large enough to detect and read. StarPilot already had access to the live road-camera stream through VisionIPC, ONNX models could run through OpenCV DNN, and the existing Speed Limit Controller already knew how to combine several sources. The pieces existed.
|
||||
|
||||
What did not exist was a model trained for this camera, a trustworthy dataset, an honest replay benchmark, or a runtime that could analyze enough frames without interfering with openpilot. Building those became the real project.
|
||||
|
||||
This is the story of how we went from a weak imported model that was barely useful for automatic bookmarks to a custom detector and classifier trained primarily from real comma footage, how community routes changed the quality of the data, and why model accuracy on a laptop turned out to be only half of the problem.
|
||||
|
||||
### The first prototype
|
||||
|
||||
The first live implementation used an imported Ultralytics-style checkpoint called `ayoubsa_best`. Its provenance was thin: the ONNX metadata identified it as a Kaggle-trained detector, but there was no training notebook or source dataset in the repository. More importantly, its classes were a poor fit for American roads. It knew speed limits in 10 mph increments, such as 20, 30, and 40, but not the common 25, 35, 45, 55, or 65 mph signs.
|
||||
|
||||
That led to a hybrid design. The model proposed a sign region, then lightweight OpenCV and OCR-like digit logic tried to read the crop. It was enough to prove the complete path:
|
||||
|
||||
1. Read the live road-camera stream.
|
||||
2. Search the right side of the frame for likely signs.
|
||||
3. Produce a candidate speed and confidence.
|
||||
4. Confirm it over time.
|
||||
5. Publish it as a selectable `Vision` source for the Speed Limit Controller.
|
||||
6. Save debug frames and bookmarks for later training.
|
||||
|
||||
It was not yet a good speed-limit reader. It missed most signs, produced false positives, and struggled badly with 5 mph increments. But it could occasionally find real signs, which meant it could help us collect the data needed to replace itself.
|
||||
|
||||
That bootstrap capability mattered more than its initial accuracy.
|
||||
|
||||
### Public data gave us a starting point
|
||||
|
||||
We chose a two-stage architecture early: one model would answer "where is the speed-limit sign and what kind is it?" while another would answer "what number is printed on it?" Detection and reading are related, but they are not the same task. Keeping them separate let the detector learn the general shape and location of a sign without needing a separate object class for every posted speed.
|
||||
|
||||
Three public U.S. traffic-sign datasets formed the initial training base:
|
||||
|
||||
- [LISA](https://cvrr.ucsd.edu/lisa-traffic-signs-dataset) provided U.S. road scenes, 47 sign types, and 7,855 annotations across 6,610 frames. Its annotations also included useful information such as occlusion and whether a sign belonged to a side road.
|
||||
- [GLARE](https://arxiv.org/abs/2209.08716) provided 2,157 U.S. traffic-sign images taken from 33 dashcam videos under strong sun glare. The paper itself demonstrated why mixed normal and glare training was important.
|
||||
- [ARTS](https://swshah.w3.uvm.edu/vail/datasets.php), the Automotive Repository of Traffic Signs, provided additional U.S. signs in easy, challenging, and video-log configurations.
|
||||
|
||||
The clean first pass imported 3,078 ARTS images with 3,139 relevant boxes. When a working LISA archive was found, it added 1,577 images and 1,680 boxes, including 15, 25, 35, 45, 55, and 65 mph classes. GLARE was downloaded selectively to avoid pulling a large collection of unrelated checkpoints.
|
||||
|
||||
|
||||
### More data made the first retrain worse
|
||||
|
||||
The first clean public-data retrain looked reasonable in conventional validation metrics and failed badly on real comma routes.
|
||||
|
||||
The stronger older model found a candidate in 9 of 39 bookmarked route windows. The clean public-data model found only 1 of 39. It also regressed the saved-frame suite. We restored the older model instead of promoting a checkpoint merely because it was newer or had better training curves.
|
||||
|
||||
The domain gap was larger than image dimensions suggested. LISA, GLARE, ARTS, and comma footage were all forward-looking road imagery, but they differed in lens distortion, mounting position, exposure, dynamic range, compression, motion blur, sign position, and the number of pixels available when a sign first appeared. Public data often contained a cleaner or more centered sign than the live comma pipeline would see.
|
||||
|
||||
This established a rule that guided every later pass: public data was useful for bootstrapping and preservation, but real comma footage had to dominate final training and evaluation.
|
||||
|
||||
### Turning StarPilot into a data engine
|
||||
|
||||
The next large improvement did not come from a new backbone. It came from collecting better data.
|
||||
|
||||
StarPilot gained an automatic training collector, automatic bookmarks, manual bookmarks, debug snapshots, route metadata, and tools to preserve full-resolution video segments. A route-bundling script verified that a contributed route was public, contained the required qlogs, rlogs, and `fcamera.hevc` files, and had the vision collector enabled before packaging it.
|
||||
|
||||
That allowed StarPilot users to contribute real driving data from different cars, cameras, roads, states, lighting conditions, and sign styles. Eventually, nearly 200 GB of zipped comma routes landed on disk. This was enormously more useful than another generic sign dataset because it matched the exact production domain:
|
||||
|
||||
- the same road camera and encoding path;
|
||||
- the same wide-angle geometry;
|
||||
- the same night exposure and motion blur;
|
||||
- the same right-shoulder sign placement;
|
||||
- the same live crop errors;
|
||||
- and the same hard negatives seen by the runtime detector.
|
||||
|
||||
The model could then scan old routes, find likely signs and mistakes, and generate another review queue. A better model could rescan the same backlog and find candidates the weaker model had never seen. Each iteration improved not only the deployed model, but also the quality of the next dataset.
|
||||
|
||||
### Human review was the quality control
|
||||
|
||||
Automatic labels were never trusted blindly. A map transition did not prove that a sign was visible, a bookmark did not prove that the best frame had been selected, and a confident model prediction did not prove that the crop contained the correct sign.
|
||||
|
||||
The manual review UI evolved around throughput. Typing a number labeled a regulatory sign. Single-key shortcuts marked advisory (`a`), school-zone (`s`), regulatory (`r`), uncertain (`u`), or ignored/not-a-sign (`i` or `x`) samples. Enter or Space accepted a prediction. Reviewers could also redraw a bad bounding box.
|
||||
|
||||
Those details affected model quality directly:
|
||||
|
||||
- A readable but slightly imperfect crop was useful.
|
||||
- A crop that showed only the minimum-speed portion of a 65/40 sign was not a valid 40 mph regulatory label.
|
||||
- Stop signs and empty crops became hard negatives.
|
||||
- Advisory signs were labeled explicitly, even though overall sign recall remained the product priority.
|
||||
- Uncertain night crops were retained separately instead of being treated as equally strong labels.
|
||||
- Repeated frames from the same sign track were deduplicated so one scene could not dominate the dataset.
|
||||
- Incorrectly large or shifted boxes were redrawn once the review tool exposed that capability.
|
||||
|
||||
One labeling ambiguity forced a larger re-review: earlier sessions had not made regulatory versus advisory state clear enough. Rather than preserve potentially poisoned labels, affected advisory candidates were put back into the queue under the corrected UI contract.
|
||||
|
||||
This was active learning in a practical form: spend human time on disagreements, weak reads, false publishes, missed signs, bad boxes, and rare conditions, not on thousands of easy duplicate frames.
|
||||
|
||||
By one later OCR-free promotion audit, the backlog covered 80 routes and 366,946 sampled frames. The model under audit produced 34,260 candidates for rescoring, while the canonical review database contained 5,383 unique human-reviewed crops: 593 positive reads and 4,146 usable crop-level rejects, with the remainder reserved for uncertain or otherwise excluded decisions. The raw route collection was large, but those reviewed examples were the part that made it trustworthy.
|
||||
|
||||
### The architecture that survived
|
||||
|
||||
The current production path is model-only. OCR remains in the source tree for legacy compatibility and offline experiments, but both full-frame OCR and crop OCR are disabled for the active detector/classifier pipeline.
|
||||
|
||||
The deployed stack contains:
|
||||
|
||||
- A YOLO11n-derived detector exported as a fixed `256x256` ONNX model. It proposes regulatory, advisory, and school-zone speed-limit signs.
|
||||
- A YOLO11n-classifier-derived `128x128` ONNX model. It predicts 15 through 75 mph in 5 mph increments and includes a learned reject class for crops that are not valid speed-limit reads.
|
||||
- Runtime logic that evaluates several crop expansions, combines their support, applies geometry and sign-type checks, and confirms lower-confidence changes over multiple frames.
|
||||
- A right-side region of interest, which reduces wasted work while retaining the part of U.S. road scenes where most applicable signs appear.
|
||||
|
||||
The detector is about 9.9 MB and the classifier about 5.9 MB. They remain separate ONNX files intentionally. Joining the graphs into one file would make packaging look tidier, but it would not eliminate the detector or classifier computation. Separate models also let us change detector resolution, classifier training, reject behavior, and crop strategy independently.
|
||||
|
||||
A third standalone reject model was tested and discarded. It added another inference pass without measurably improving the reviewed positives or surviving false positive. Folding rejection into the value classifier produced a better cost/accuracy tradeoff.
|
||||
|
||||
OCR was removed only after the model-only path could pass the preservation suites on its own. In the decisive audit, model consensus improved the targeted 20 mph events from 1/14 to 11/14 and the broader targeted set from 21/36 to 27/36 without regressing the established legacy suites. At that point, OCR's occasional rescue was no longer worth the latency, extra failure modes, and frames lost while the device waited for it to finish.
|
||||
|
||||
Direct-value detectors, smaller input sizes, MobileNet-style classifiers, optical-flow tracking, and multiple crop strategies were also tested. Some looked attractive on desktop metrics and failed where it mattered. In particular, a 224-pixel detector was faster but dropped the hometown 20 mph suite from 14/14 to 9/14. The 256-pixel detector remained the better production choice.
|
||||
|
||||
### Frame rate is part of accuracy
|
||||
|
||||
The most important evaluation lesson came from a route with seven manually bookmarked signs.
|
||||
|
||||
An early offline replay sampled the video at 5 fps and found several signs. The actual comma found almost none. The model files matched and the vision process was running, so the discrepancy initially looked mysterious.
|
||||
|
||||
The replay had been landing on lucky frames.
|
||||
|
||||
The camera produced 25 frames per second, but the live process only attempted inference every 0.4 seconds. When we replayed all 25 source frames while enforcing that real inference gate, the local result fell to 0 of 7, matching the drive. Halving the interval to 0.2 seconds moved the same honest replay to 5 of 7 candidate windows and 3 of 7 actual publishes.
|
||||
|
||||
From then on, a model was not evaluated only as a collection of independent images. Route replay had to include:
|
||||
|
||||
- source-frame timing;
|
||||
- the measured detector and classifier cost;
|
||||
- steady and follow-up inference intervals;
|
||||
- temporal confirmation rules;
|
||||
- CPU backoff;
|
||||
- and the distinction between a candidate and a value actually published to the UI or controller.
|
||||
|
||||
This distinction explained many arguments over numbers. A candidate means the detector and reader noticed something in a sign window. A publish means the temporal and confidence gates accepted it as the live speed limit. A model can read a sign correctly once and still fail to publish if a weak read requires a second frame that the device never processes.
|
||||
|
||||
Later-frame mining showed why this mattered. One pass revisited 405 known sign events and extracted 939 clearer frames from later in their tracks. Combined with track-aware classifier training and a reduction from four classifier crops to three, the measured runtime suite improved from 247 to 265 correct publications, including a gain from 211 to 229 in the priority 30-65 mph range. Better frame selection and less per-frame work helped at the same time.
|
||||
|
||||
### Making it fit beside openpilot
|
||||
|
||||
The comma is already running camera processing, the main driving models, localization, controls, the UI, and vehicle communication. A speed-limit sidecar cannot assume an idle CPU.
|
||||
|
||||
The early hybrid pipeline could take roughly 1.5 seconds per frame when the detector, crop reader, and OCR paths all fired. At that cadence, signs could pass through their brief readable window without ever being analyzed. It also added enough CPU pressure to coincide with camera/model frame-sync errors and temporary `locationd` alerts on some night drives.
|
||||
|
||||
Changing process priority was not the solution. The daemon was already running at a very low scheduling priority. The useful changes were to remove OCR from production, restrict detection to the right-side ROI, shrink the models carefully, avoid the extra reject-model pass, add device-load backoff, and use a faster follow-up cadence after a candidate appeared.
|
||||
|
||||
The current runtime requests a 0.15-second steady interval and a 0.10-second follow-up interval, but inference cost is the real limiter. On two measured routes, the complete process sustained about 1.77 to 1.81 inferences per second. The detector alone took roughly 0.43 to 0.44 seconds, and each classifier crop could add about 0.066 seconds. A sign may receive only 4 to 13 analyzed frames in a seven-second approach window.
|
||||
|
||||
This is why "run it at 10 Hz" is not a configuration change. The process can ask for 10 Hz, but it cannot start the next inference until the current one finishes.
|
||||
|
||||
### Debugging the system, not just the model
|
||||
|
||||
Several apparent model regressions were actually system problems.
|
||||
|
||||
|
||||
These failures led to better observability: model hashes, inference counts, interval reasons, detector and classifier timing, CPU backoff state, candidates, publications, and debug captures all became part of route analysis. We stopped asking only "is the model accurate?" and started asking "which exact model and runtime processed which exact frames, and what did it publish?"
|
||||
|
||||
### How we decide whether a model is better
|
||||
|
||||
The promotion process now uses several independent gates:
|
||||
|
||||
- Reviewed crop accuracy checks whether the classifier can read localized signs.
|
||||
- Hard-negative sets contain parking signs, traffic signals, truck restrictions, taillights, and other shapes that previously caused false reads.
|
||||
- A 100-event main route suite tests full candidate and publication behavior at measured comma cadence.
|
||||
- A separate 34-event held-out suite guards against tuning directly to the main set.
|
||||
- A hometown 20 mph suite protects a class that earlier models repeatedly missed.
|
||||
- Legacy suites ensure that old wins do not disappear.
|
||||
- Targeted night and glare replays test the conditions most likely to break normal validation assumptions.
|
||||
- On-device timing and live routes remain the final authority.
|
||||
|
||||
Many experiments were rejected despite improving one number. A glare-focused model lost broader recall. A traffic-signal hard-negative detector fixed one false positive and lost legitimate 35 mph signs. A more aggressive reject-classifier update reduced errors and then changed enough timing to expose different false publishes. Conservative weight interpolation often produced the safest result because it taught a narrow correction without erasing the representation that already worked.
|
||||
|
||||
The drive currently contains 87 detector experiment directories and 72 classifier experiment directories. Those are not 159 successive improvements. They are the record of resolution bake-offs, failed hard-negative passes, direct-value experiments, night/glare runs, integrated-reject models, temporal-crop training, and interpolations that mapped the boundaries of the system.
|
||||
|
||||
### Where accuracy is now
|
||||
|
||||
The current classifier, internally called `distilled-moonstone v3`, improved the deployed route suite without changing model size or runtime cost:
|
||||
|
||||
| Runtime suite | Previous tree | Current model |
|
||||
| --- | ---: | ---: |
|
||||
| Main route publishes | 78/100 | **92/100** |
|
||||
| Main wrong publishes | 0 | **0** |
|
||||
| Held-out publishes | 24/34 | **26/34** |
|
||||
| Hometown 20 mph signs | 14/14 | **14/14** |
|
||||
| Hard sun-glare check | 70 mph published | **70 mph published** |
|
||||
|
||||
These denominators are manually confirmed sign events, not arbitrary video frames. `92/100` means the full runtime pipeline published the correct value in 92 of 100 known sign windows while running with measured comma timing. It does not mean the model is 92 percent accurate on every road or every possible sign.
|
||||
|
||||
In ordinary daylight scenes, current performance is very good. The model is now reliable when signs are reasonably exposed and readable, while extreme image quality and limited frame opportunities remain the dominant failures.
|
||||
|
||||
The current result is also substantially better than the first public-data prototype, which missed nearly every sign on several routes and sometimes produced only a single useful event in an entire drive.
|
||||
|
||||
### The next hard barriers
|
||||
|
||||
The remaining misses are unusually informative. Of the 18 main-suite events the current model does not publish:
|
||||
|
||||
- 6 never produce a correct candidate at measured cadence.
|
||||
- 4 produce exactly one weak correct read, but cannot satisfy the two-read confirmation rule.
|
||||
- At an idealized 6.7 Hz cadence, the same model recovers 7 of those 10 publishes, reaching a theoretical 97/100, but also introduces one wrong publish.
|
||||
- The three stubborn cases are all 45 mph scenes: two remain unreadable even at ideal cadence, and one first reads 40 before seeing 45 too late.
|
||||
|
||||
That theoretical 97/100 is not a production accuracy claim. It is a diagnostic result showing that most remaining failures already contain a readable frame that the device does not process.
|
||||
|
||||
The next large improvement is therefore likely to come from throughput and temporal reuse, not from simply lowering confidence thresholds:
|
||||
|
||||
1. Make the detector materially faster without giving up the small-sign and 20 mph recall lost by the 224- and 192-pixel experiments.
|
||||
2. After a detector finds a sign, track or cheaply reclassify that crop across nearby frames instead of paying for another full detector pass every time.
|
||||
3. Investigate a more efficient inference backend or model architecture on comma hardware. Direct model microbenchmarks are useful, but the complete process and onroad CPU contention must remain the acceptance metric.
|
||||
4. Add targeted, human-reviewed sequences for the remaining 45 mph failures, night ghosting, distant signs, and direct-sun approaches.
|
||||
5. Improve sign applicability for minimum-speed, truck-only, school-zone, advisory, side-road, and exit-ramp signs without sacrificing the product's primary goal: finding normal regulatory speed limits.
|
||||
6. Keep model-fingerprinted backlog mining so every promoted model can rescan old routes into an isolated review set without silently replacing canonical labels.
|
||||
|
||||
Night remains difficult because the camera often produces fewer sharp frames, motion blur can create double digits, and reflective signs may be detected while they are still too distant to read. Glare is difficult for the opposite reason: contrast collapses and sign edges disappear into the sky. Both problems require sequence-level examples, not just one selected crop.
|
||||
|
||||
### What we learned
|
||||
|
||||
This was never one long training run. It was the construction of a data and evaluation system.
|
||||
|
||||
Public datasets made the first detector possible. A weak model made automatic collection possible. StarPilot users supplied the camera-native diversity that public data could not. Human review turned model proposals into trustworthy labels. Full-cadence replay exposed timing mistakes that image metrics hid. Live device telemetry kept CPU and process failures from being mistaken for neural-network failures. Conservative promotion gates preserved hard-won behavior while narrow experiments improved specific weaknesses.
|
||||
|
||||
The result is already useful and, in ordinary scenes, often impressively accurate. The path to the next jump is also clearer than it was at the beginning. We do not mainly need another giant pile of generic sign images. We need to process more of the good frames the comma already sees, teach the remaining hard sequences carefully, and continue judging the complete on-device system rather than the model in isolation.
|
||||
|
||||
That is the difference between training a traffic-sign classifier and building a speed-limit reader that works in a car.
|
||||
|
||||
### Technical references
|
||||
|
||||
- Runtime implementation: [`starpilot/system/speed_limit_vision.py`](../starpilot/system/speed_limit_vision.py)
|
||||
- Training runbook: [`docs/how-to/train-speed-limit-vision.md`](how-to/train-speed-limit-vision.md)
|
||||
- Training, mining, review, and evaluation tools: [`scripts/speed_limit_vision`](../scripts/speed_limit_vision)
|
||||
- Deployed model assets: [`starpilot/assets/vision_models`](../starpilot/assets/vision_models)
|
||||
+70
-65
@@ -4,7 +4,25 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
|
||||
source "$DIR/launch_env.sh"
|
||||
|
||||
export SP_BOOT_TIMING_LOG="${SP_BOOT_TIMING_LOG:-/tmp/starpilot_boot_timing.log}"
|
||||
: > "$SP_BOOT_TIMING_LOG" 2>/dev/null || true
|
||||
SP_LAUNCH_LAST_SECONDS=$SECONDS
|
||||
|
||||
function sp_boot_timing_line {
|
||||
echo "$1"
|
||||
printf '%s\n' "$1" >> "$SP_BOOT_TIMING_LOG" 2>/dev/null || true
|
||||
}
|
||||
|
||||
function sp_launch_timing {
|
||||
local now=$SECONDS
|
||||
local delta=$((now - SP_LAUNCH_LAST_SECONDS))
|
||||
sp_boot_timing_line "SP_BOOT_TIMING launch $1 +${delta}s total=${now}s"
|
||||
SP_LAUNCH_LAST_SECONDS=$now
|
||||
}
|
||||
|
||||
function agnos_init {
|
||||
sp_launch_timing "agnos_init_start"
|
||||
|
||||
# TODO: move this to agnos
|
||||
sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta
|
||||
|
||||
@@ -33,67 +51,17 @@ 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
|
||||
|
||||
# Check if AGNOS update is required
|
||||
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
|
||||
AGNOS_CURRENT_VERSION="$(< /VERSION)"
|
||||
AGNOS_UPDATE_REQUIRED=1
|
||||
for accepted_version in $AGNOS_ACCEPTED_VERSIONS; do
|
||||
if [ "$AGNOS_CURRENT_VERSION" = "$accepted_version" ]; then
|
||||
AGNOS_UPDATE_REQUIRED=0
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$AGNOS_UPDATE_REQUIRED" = "1" ]; then
|
||||
AGNOS_PY="$DIR/system/hardware/tici/agnos.py"
|
||||
MANIFEST="$DIR/system/hardware/tici/agnos.json"
|
||||
if $AGNOS_PY --verify $MANIFEST; then
|
||||
@@ -101,9 +69,13 @@ function agnos_init {
|
||||
fi
|
||||
$DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST
|
||||
fi
|
||||
|
||||
sp_launch_timing "agnos_init_done"
|
||||
}
|
||||
|
||||
function launch {
|
||||
sp_launch_timing "launch_start"
|
||||
|
||||
# Remove orphaned git lock if it exists on boot
|
||||
[ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock
|
||||
|
||||
@@ -140,30 +112,37 @@ function launch {
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
sp_launch_timing "overlay_check_done"
|
||||
|
||||
# handle pythonpath
|
||||
ln -sfn $(pwd) /data/pythonpath
|
||||
export BASEDIR="$DIR"
|
||||
export PYTHONPATH="$DIR/starpilot/third_party:$PWD"
|
||||
sp_launch_timing "pythonpath_done"
|
||||
|
||||
# hardware specific init
|
||||
if [ -f /AGNOS ]; then
|
||||
agnos_init
|
||||
fi
|
||||
sp_launch_timing "hardware_init_done"
|
||||
|
||||
# write tmux scrollback to a file
|
||||
tmux capture-pane -pq -S-1000 > /tmp/launch_log
|
||||
sp_launch_timing "capture_launch_log_done"
|
||||
|
||||
# start manager
|
||||
cd system/manager
|
||||
|
||||
sp_launch_timing "launch_param_migrations_start"
|
||||
if ! python3 ./launch_param_migrations.py; then
|
||||
echo "Launch param migrations failed; continuing boot."
|
||||
fi
|
||||
sp_launch_timing "launch_param_migrations_done"
|
||||
|
||||
# Bootstrap runtime (e.g. /usr/comma after reset/uninstall) must go straight
|
||||
# to manager/setup flow. Do not run StarPilot prebuilt checks/builds here.
|
||||
if [ "$DIR" = "/usr/comma" ] || [ ! -d "$DIR/.git" ]; then
|
||||
sp_launch_timing "bootstrap_manager_start"
|
||||
./manager.py
|
||||
while true; do sleep 1; done
|
||||
fi
|
||||
@@ -171,15 +150,35 @@ function launch {
|
||||
function prebuilt_runtime_compatible {
|
||||
python3 - <<'PY'
|
||||
import importlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
|
||||
start = time.monotonic()
|
||||
last = start
|
||||
log_path = os.environ.get("SP_BOOT_TIMING_LOG")
|
||||
|
||||
def emit(line):
|
||||
print(line, flush=True)
|
||||
if log_path:
|
||||
try:
|
||||
with open(log_path, "a") as f:
|
||||
f.write(line + "\n")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def log_step(label):
|
||||
global last
|
||||
now = time.monotonic()
|
||||
emit(f"SP_BOOT_TIMING prebuilt_compat {label} +{now - last:.3f}s total={now - start:.3f}s")
|
||||
last = now
|
||||
|
||||
mods = [
|
||||
"openpilot.common.params_pyx",
|
||||
"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",
|
||||
@@ -191,15 +190,15 @@ for mod in mods:
|
||||
except Exception as e:
|
||||
print(f"Prebuilt compatibility failure in {mod}: {e}", file=sys.stderr)
|
||||
raise
|
||||
log_step(f"import:{mod}")
|
||||
|
||||
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",
|
||||
@@ -211,6 +210,7 @@ required_files = [
|
||||
for path in required_files:
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"Missing prebuilt runtime artifact: {path}")
|
||||
log_step("required_files")
|
||||
PY
|
||||
}
|
||||
|
||||
@@ -219,14 +219,19 @@ PY
|
||||
USE_PREBUILT=$(tr -d '\n' < /data/params/d/UsePrebuilt)
|
||||
fi
|
||||
|
||||
sp_launch_timing "prebuilt_decision_done"
|
||||
if [ "$USE_PREBUILT" = "1" ] && [ -f $DIR/prebuilt ] && ! prebuilt_runtime_compatible; then
|
||||
echo "Prebuilt runtime artifacts are incompatible on this device; rebuilding locally."
|
||||
USE_PREBUILT=0
|
||||
fi
|
||||
sp_launch_timing "prebuilt_compat_done"
|
||||
|
||||
if [ "$USE_PREBUILT" != "1" ] || [ ! -f $DIR/prebuilt ]; then
|
||||
sp_launch_timing "build_start"
|
||||
./build.py
|
||||
sp_launch_timing "build_done"
|
||||
fi
|
||||
sp_launch_timing "manager_start"
|
||||
./manager.py
|
||||
|
||||
# if broken, keep on screen error
|
||||
|
||||
+5
-1
@@ -21,7 +21,11 @@ fi
|
||||
export QCOM_PRIORITY=12
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="12.8.16"
|
||||
export AGNOS_VERSION="12.8.28"
|
||||
fi
|
||||
|
||||
if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then
|
||||
export AGNOS_ACCEPTED_VERSIONS="$AGNOS_VERSION"
|
||||
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())
|
||||
+2
-2
@@ -32,10 +32,10 @@ nav:
|
||||
- What is a car port?: car-porting/what-is-a-car-port.md
|
||||
- Porting a car brand: car-porting/brand-port.md
|
||||
- Porting a car model: car-porting/model-port.md
|
||||
- Contributing:
|
||||
- Contribute:
|
||||
- Contributing Guide: contributing/contribute.md
|
||||
- Roadmap: contributing/roadmap.md
|
||||
#- Architecture: contributing/architecture.md
|
||||
- Contributing Guide →: https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md
|
||||
- Links:
|
||||
- Blog →: https://blog.comma.ai
|
||||
- Bounties →: https://comma.ai/bounties
|
||||
|
||||
@@ -21,6 +21,9 @@ static size_t fnv1a_hash(const std::string &str) {
|
||||
|
||||
static std::string namespaced_endpoint(std::string endpoint) {
|
||||
const char *namespace_env = std::getenv("OPENPILOT_ZMQ_NAMESPACE");
|
||||
if (namespace_env == nullptr || namespace_env[0] == '\0') {
|
||||
namespace_env = std::getenv("OPENPILOT_PREFIX");
|
||||
}
|
||||
if (namespace_env != nullptr && namespace_env[0] != '\0') {
|
||||
endpoint = std::string(namespace_env) + ":" + endpoint;
|
||||
}
|
||||
|
||||
@@ -15,10 +15,6 @@ const bool MUST_USE_ZMQ = false;
|
||||
|
||||
bool messaging_use_zmq(){
|
||||
if (std::getenv("ZMQ") || MUST_USE_ZMQ) {
|
||||
if (std::getenv("OPENPILOT_PREFIX")) {
|
||||
std::cerr << "OPENPILOT_PREFIX not supported with ZMQ backend\n";
|
||||
assert(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
Binary file not shown.
@@ -61,6 +61,7 @@ class TestVisionIpc:
|
||||
recv_buf = self.client.recv()
|
||||
assert recv_buf is not None
|
||||
assert recv_buf.data.view('<i4')[0] == 1234
|
||||
assert recv_buf.frame_id == 1337
|
||||
assert self.client.frame_id == 1337
|
||||
del self.client
|
||||
del self.server
|
||||
|
||||
@@ -30,6 +30,7 @@ cdef extern from "msgq/visionipc/visionbuf.h":
|
||||
size_t idx
|
||||
cl_mem buf_cl
|
||||
void set_frame_id(uint64_t id)
|
||||
uint64_t get_frame_id()
|
||||
|
||||
cdef extern from "msgq/visionipc/visionipc.h":
|
||||
struct VisionIpcBufExtra:
|
||||
|
||||
@@ -67,6 +67,10 @@ cdef class VisionBuf:
|
||||
def fd(self):
|
||||
return self.buf.fd
|
||||
|
||||
@property
|
||||
def frame_id(self):
|
||||
return self.buf.get_frame_id()
|
||||
|
||||
|
||||
cdef class VisionIpcServer:
|
||||
cdef cppVisionIpcServer * server
|
||||
|
||||
Binary file not shown.
@@ -19,7 +19,7 @@ test:
|
||||
run: codespell {files} -L tge,stdio -S *.dbc
|
||||
files: git ls-tree -r HEAD --name-only
|
||||
cpplint:
|
||||
run: cpplint --exclude=opendbc/safety/tests/misra/cppcheck/ --exclude=opendbc/can/*_pyx.cpp --recursive --quiet --counting=detailed --linelength=240 --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces opendbc/
|
||||
run: cpplint --exclude=opendbc/safety/tests/misra/cppcheck/ --exclude=opendbc/safety/tests/.venv/ --exclude=opendbc/can/*_pyx.cpp --recursive --quiet --counting=detailed --linelength=240 --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces opendbc/
|
||||
misra:
|
||||
run: opendbc/safety/tests/misra/test_misra.sh
|
||||
|
||||
|
||||
@@ -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,9 +2,28 @@ 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 JEEPS, RAM_CARS, RAM_DT, CarControllerParams, ChryslerFlags, ChryslerSafetyFlags, ChryslerStarPilotFlags
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
|
||||
JEEP_BRAKE_HOLD_DEFAULT_DECEL = -2.0
|
||||
JEEP_BRAKE_HOLD_MIN_DECEL = -0.5
|
||||
JEEP_BRAKE_HOLD_MAX_DECEL = -3.0
|
||||
|
||||
|
||||
def clip_jeep_brake_hold_decel(decel: float) -> float:
|
||||
return max(JEEP_BRAKE_HOLD_MAX_DECEL, min(JEEP_BRAKE_HOLD_MIN_DECEL, float(decel)))
|
||||
|
||||
|
||||
def supports_jeep_brake_hold(CP, brake_hold_enabled: bool) -> bool:
|
||||
safety_cfg = getattr(CP, "safetyConfigs", ())
|
||||
safety_param = safety_cfg[0].safetyParam if safety_cfg else 0
|
||||
return (
|
||||
brake_hold_enabled and
|
||||
getattr(CP, "pcmCruise", False) and
|
||||
CP.carFingerprint in JEEPS and
|
||||
bool(safety_param & ChryslerSafetyFlags.JEEP_BRAKE_HOLD.value)
|
||||
)
|
||||
|
||||
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_names, CP):
|
||||
@@ -18,6 +37,8 @@ class CarController(CarControllerBase):
|
||||
|
||||
self.packer = CANPacker(dbc_names[Bus.pt])
|
||||
self.params = CarControllerParams(CP)
|
||||
self.jeep_brake_hold_decel = JEEP_BRAKE_HOLD_DEFAULT_DECEL
|
||||
self.last_das_3_counter = -1
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
can_sends = []
|
||||
@@ -50,7 +71,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):
|
||||
@@ -80,6 +103,11 @@ class CarController(CarControllerBase):
|
||||
|
||||
can_sends.append(chryslercan.create_lkas_command(self.packer, self.CP, int(apply_torque), lkas_control_bit))
|
||||
|
||||
if supports_jeep_brake_hold(self.CP, getattr(starpilot_toggles, "jeep_brake_hold", False)):
|
||||
self.update_jeep_brake_hold(CC, CS, can_sends)
|
||||
elif getattr(CS, "brake_hold", False):
|
||||
CS.brake_hold = False
|
||||
|
||||
self.frame += 1
|
||||
|
||||
new_actuators = CC.actuators.as_builder()
|
||||
@@ -87,3 +115,43 @@ class CarController(CarControllerBase):
|
||||
new_actuators.torqueOutputCan = self.apply_torque_last
|
||||
|
||||
return new_actuators, can_sends
|
||||
|
||||
def update_jeep_brake_hold(self, CC, CS, can_sends):
|
||||
if not getattr(CS, "das_3", None):
|
||||
CS.brake_hold = False
|
||||
return
|
||||
|
||||
counter_changed = CS.das_3.get("COUNTER") != self.last_das_3_counter
|
||||
self.last_das_3_counter = CS.das_3.get("COUNTER")
|
||||
|
||||
if not CS.brake_hold and CS.cruise_active_actual and CS.acc_decelerating and CS.out.standstill:
|
||||
CS.brake_hold = True
|
||||
self.jeep_brake_hold_decel = JEEP_BRAKE_HOLD_DEFAULT_DECEL
|
||||
|
||||
driver_intervened = (
|
||||
CC.cruiseControl.cancel or
|
||||
CS.out.gasPressed or
|
||||
CS.out.brakePressed or
|
||||
not CS.forward_gear or
|
||||
not CS.out.standstill
|
||||
)
|
||||
if CS.brake_hold and driver_intervened:
|
||||
CS.brake_hold = False
|
||||
return
|
||||
|
||||
if not CS.brake_hold:
|
||||
return
|
||||
|
||||
if CS.cruise_active_actual:
|
||||
self.jeep_brake_hold_decel = clip_jeep_brake_hold_decel(
|
||||
min(self.jeep_brake_hold_decel, CS.das_3.get("ACC_DECEL", JEEP_BRAKE_HOLD_DEFAULT_DECEL))
|
||||
)
|
||||
return
|
||||
|
||||
counter_offset = 2 if counter_changed else 3
|
||||
can_sends.append(chryslercan.create_das_3_command(self.packer, counter_offset, self.jeep_brake_hold_decel, CS.das_3))
|
||||
|
||||
if self.frame % 10 == 0:
|
||||
can_sends.append(chryslercan.create_cruise_buttons(
|
||||
self.packer, CS.button_counter + 1, 0, CS.button_message, resume=True
|
||||
))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from cereal import custom
|
||||
from opendbc.can import CANDefine, CANParser
|
||||
from opendbc.car import Bus, create_button_events, structs
|
||||
from opendbc.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS, ChryslerStarPilotFlags
|
||||
from opendbc.car.chrysler.values import DBC, JEEPS, STEER_THRESHOLD, RAM_CARS, ChryslerStarPilotFlags
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
|
||||
@@ -29,6 +29,11 @@ class CarState(CarStateBase):
|
||||
self.button_message = "CRUISE_BUTTONS_ALT" if FPCP.flags & ChryslerStarPilotFlags.RAM_HD_ALT_BUTTONS else "CRUISE_BUTTONS"
|
||||
|
||||
self.lkas_button = 0
|
||||
self.brake_hold = False
|
||||
self.cruise_active_actual = False
|
||||
self.forward_gear = False
|
||||
self.acc_decelerating = False
|
||||
self.das_3 = {}
|
||||
|
||||
@staticmethod
|
||||
def get_lkas_button(pt_signals, is_ram: bool) -> bool:
|
||||
@@ -93,6 +98,13 @@ class CarState(CarStateBase):
|
||||
ret.cruiseState.standstill = cp_cruise.vl["DAS_3"]["ACC_STANDSTILL"] == 1
|
||||
ret.accFaulted = cp_cruise.vl["DAS_3"]["ACC_FAULTED"] != 0
|
||||
|
||||
if self.CP.carFingerprint in JEEPS:
|
||||
self.forward_gear = ret.gearShifter == structs.CarState.GearShifter.drive
|
||||
self.cruise_active_actual = ret.cruiseState.enabled
|
||||
self.acc_decelerating = cp_cruise.vl["DAS_3"]["ACC_DECEL"] < -0.5
|
||||
self.das_3 = dict(cp_cruise.vl["DAS_3"])
|
||||
ret.brakeHoldActive = self.brake_hold
|
||||
|
||||
if self.CP.carFingerprint in RAM_CARS:
|
||||
# Auto High Beam isn't Located in this message on chrysler or jeep currently located in 729 message
|
||||
self.auto_high_beam = cp_cam.vl["DAS_6"]['AUTO_HIGH_BEAM_ON']
|
||||
|
||||
@@ -73,6 +73,21 @@ def create_cruise_buttons(packer, frame, bus, button_message, cancel=False, resu
|
||||
return packer.make_can_msg(button_message, bus, values)
|
||||
|
||||
|
||||
def create_das_3_command(packer, counter_offset, brake_decel, das_3):
|
||||
values = das_3.copy()
|
||||
values["ACC_AVAILABLE"] = 1
|
||||
values["ACC_ACTIVE"] = 1
|
||||
values["ACC_GO"] = 0
|
||||
values["ACC_STANDSTILL"] = 0
|
||||
values["ACC_DECEL_REQ"] = 1
|
||||
values["ACC_DECEL"] = brake_decel
|
||||
values["ACC_BRK_PREP"] = 0
|
||||
values["ENGINE_TORQUE_REQUEST_MAX"] = 0
|
||||
values["GR_MAX_REQ"] = 2
|
||||
values["COUNTER"] = (das_3["COUNTER"] + counter_offset) % 0x10
|
||||
return packer.make_can_msg("DAS_3", 0, values)
|
||||
|
||||
|
||||
def chrysler_checksum(address: int, sig, d: bytearray) -> int:
|
||||
checksum = 0xFF
|
||||
for j in range(len(d) - 1):
|
||||
|
||||
@@ -3,7 +3,7 @@ from opendbc.car import get_safety_config, structs
|
||||
from opendbc.car.chrysler.carcontroller import CarController
|
||||
from opendbc.car.chrysler.carstate import CarState
|
||||
from opendbc.car.chrysler.radar_interface import RadarInterface
|
||||
from opendbc.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags, ChryslerSafetyFlags
|
||||
from opendbc.car.chrysler.values import CAR, JEEPS, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags, ChryslerSafetyFlags
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= ChryslerSafetyFlags.RAM_HD.value
|
||||
elif candidate in RAM_DT:
|
||||
ret.safetyConfigs[0].safetyParam |= ChryslerSafetyFlags.RAM_DT.value
|
||||
elif candidate in JEEPS:
|
||||
ret.safetyConfigs[0].safetyParam |= ChryslerSafetyFlags.JEEP_BRAKE_HOLD.value
|
||||
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
if candidate not in RAM_CARS:
|
||||
|
||||
@@ -12,6 +12,7 @@ Ecu = CarParams.Ecu
|
||||
class ChryslerSafetyFlags(IntFlag):
|
||||
RAM_DT = 1
|
||||
RAM_HD = 2
|
||||
JEEP_BRAKE_HOLD = 4
|
||||
|
||||
|
||||
class ChryslerFlags(IntFlag):
|
||||
@@ -21,6 +22,7 @@ class ChryslerFlags(IntFlag):
|
||||
|
||||
class ChryslerStarPilotFlags(IntFlag):
|
||||
RAM_HD_ALT_BUTTONS = 1
|
||||
NO_MIN_STEERING_SPEED = 2
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -97,6 +99,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
|
||||
@@ -120,6 +141,7 @@ STEER_THRESHOLD = 120
|
||||
RAM_DT = {CAR.RAM_1500_5TH_GEN, }
|
||||
RAM_HD = {CAR.RAM_HD_5TH_GEN, }
|
||||
RAM_CARS = RAM_DT | RAM_HD
|
||||
JEEPS = {CAR.JEEP_GRAND_CHEROKEE, CAR.JEEP_GRAND_CHEROKEE_2019}
|
||||
|
||||
|
||||
CHRYSLER_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||||
|
||||
@@ -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,30 @@ 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 HYBRID 2026": HYUNDAI.KIA_SPORTAGE_HEV_2026,
|
||||
"KIA SPORTAGE 2026": HYUNDAI.KIA_SPORTAGE_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,
|
||||
"KIA CARNIVAL 2025": HYUNDAI.KIA_CARNIVAL_2025,
|
||||
"KIA CARNIVAL HYBRID 4TH GEN": HYUNDAI.KIA_CARNIVAL_HEV_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,
|
||||
|
||||
@@ -16,6 +16,11 @@ AVERAGE_ROAD_ROLL = 0.06 # ~3.4 degrees, 6% superelevation. higher actual roll
|
||||
MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL - (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~2.4 m/s^2
|
||||
|
||||
|
||||
def apply_ford_angle(desired_angle_deg: float, current_angle_deg: float) -> float:
|
||||
relative_angle = desired_angle_deg - current_angle_deg
|
||||
return float(np.clip(relative_angle, -5.8, 5.8))
|
||||
|
||||
|
||||
def anti_overshoot(apply_curvature, apply_curvature_last, v_ego):
|
||||
diff = 0.1
|
||||
tau = 5 # 5s smooths over the overshoot
|
||||
@@ -65,6 +70,7 @@ class CarController(CarControllerBase):
|
||||
self.CAN = fordcan.CanBus(CP)
|
||||
|
||||
self.apply_curvature_last = 0
|
||||
self.apply_angle_last = 0
|
||||
self.anti_overshoot_curvature_last = 0
|
||||
self.accel = 0.0
|
||||
self.gas = 0.0
|
||||
@@ -98,38 +104,62 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, tja_toggle=True))
|
||||
|
||||
### lateral control ###
|
||||
# send steer msg at 20Hz
|
||||
if (self.frame % CarControllerParams.STEER_STEP) == 0:
|
||||
# Bronco and some other cars consistently overshoot curv requests
|
||||
# Apply some deadzone + smoothing convergence to avoid oscillations
|
||||
if self.CP.carFingerprint in (CAR.FORD_BRONCO_SPORT_MK1, CAR.FORD_F_150_MK14):
|
||||
self.anti_overshoot_curvature_last = anti_overshoot(actuators.curvature, self.anti_overshoot_curvature_last, CS.out.vEgoRaw)
|
||||
apply_curvature = self.anti_overshoot_curvature_last
|
||||
if self.CP.flags & FordFlags.LKA_STEERING:
|
||||
lka_active = CC.latActive and CS.lkas_available
|
||||
if lka_active:
|
||||
self.apply_angle_last = apply_ford_angle(actuators.steeringAngleDeg, CS.out.steeringAngleDeg)
|
||||
current_curvature = -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
|
||||
self.apply_curvature_last = apply_ford_curvature_limits(actuators.curvature, self.apply_curvature_last, current_curvature,
|
||||
CS.out.vEgoRaw, 0., True, self.CP)
|
||||
else:
|
||||
apply_curvature = actuators.curvature
|
||||
self.apply_angle_last = 0.
|
||||
self.apply_curvature_last = 0.
|
||||
|
||||
# apply rate limits, curvature error limit, and clip to signal range
|
||||
current_curvature = -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
|
||||
# Keep the stock LMC heartbeat present while steering through Lane_Assist_Data1.
|
||||
if (self.frame % CarControllerParams.STEER_STEP) == 0:
|
||||
can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, False, 0., 0., 0., 0.,
|
||||
stock_lmc=CS.lateral_motion_control))
|
||||
|
||||
self.apply_curvature_last = apply_ford_curvature_limits(apply_curvature, self.apply_curvature_last, current_curvature,
|
||||
CS.out.vEgoRaw, 0., CC.latActive, self.CP)
|
||||
if (self.frame % CarControllerParams.LKA_STEP) == 0:
|
||||
direction = 0
|
||||
if lka_active:
|
||||
direction = 2 if CS.out.steeringAngleDeg > 0 else 4
|
||||
ramp_type = 1 if abs(self.apply_angle_last) >= 5 else 0
|
||||
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN, active=lka_active, apply_angle=self.apply_angle_last,
|
||||
direction=direction, ramp_type=ramp_type, curvature=-self.apply_curvature_last))
|
||||
else:
|
||||
# send steer msg at 20Hz
|
||||
if (self.frame % CarControllerParams.STEER_STEP) == 0:
|
||||
# Bronco and some other cars consistently overshoot curv requests
|
||||
# Apply some deadzone + smoothing convergence to avoid oscillations
|
||||
if self.CP.carFingerprint in (CAR.FORD_BRONCO_SPORT_MK1, CAR.FORD_F_150_MK14):
|
||||
self.anti_overshoot_curvature_last = anti_overshoot(actuators.curvature, self.anti_overshoot_curvature_last, CS.out.vEgoRaw)
|
||||
apply_curvature = self.anti_overshoot_curvature_last
|
||||
else:
|
||||
apply_curvature = actuators.curvature
|
||||
|
||||
if self.CP.flags & FordFlags.CANFD:
|
||||
# TODO: extended mode
|
||||
# Ford uses four individual signals to dictate how to drive to the car. Curvature alone (limited to 0.02m/s^2)
|
||||
# can actuate the steering for a large portion of any lateral movements. However, in order to get further control on
|
||||
# steer actuation, the other three signals are necessary. Ford controls vehicles differently than most other makes.
|
||||
# A detailed explanation on ford control can be found here:
|
||||
# https://www.f150gen14.com/forum/threads/introducing-bluepilot-a-ford-specific-fork-for-comma3x-openpilot.24241/#post-457706
|
||||
mode = 1 if CC.latActive else 0
|
||||
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
|
||||
can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -self.apply_curvature_last, 0., counter))
|
||||
else:
|
||||
can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -self.apply_curvature_last, 0.))
|
||||
# apply rate limits, curvature error limit, and clip to signal range
|
||||
current_curvature = -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
|
||||
|
||||
# send lka msg at 33Hz
|
||||
if (self.frame % CarControllerParams.LKA_STEP) == 0:
|
||||
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN))
|
||||
self.apply_curvature_last = apply_ford_curvature_limits(apply_curvature, self.apply_curvature_last, current_curvature,
|
||||
CS.out.vEgoRaw, 0., CC.latActive, self.CP)
|
||||
|
||||
if self.CP.flags & FordFlags.CANFD:
|
||||
# TODO: extended mode
|
||||
# Ford uses four individual signals to dictate how to drive to the car. Curvature alone (limited to 0.02m/s^2)
|
||||
# can actuate the steering for a large portion of any lateral movements. However, in order to get further control on
|
||||
# steer actuation, the other three signals are necessary. Ford controls vehicles differently than most other makes.
|
||||
# A detailed explanation on ford control can be found here:
|
||||
# https://www.f150gen14.com/forum/threads/introducing-bluepilot-a-ford-specific-fork-for-comma3x-openpilot.24241/#post-457706
|
||||
mode = 1 if CC.latActive else 0
|
||||
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
|
||||
can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -self.apply_curvature_last, 0., counter))
|
||||
else:
|
||||
can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -self.apply_curvature_last, 0.))
|
||||
|
||||
# send lka msg at 33Hz
|
||||
if (self.frame % CarControllerParams.LKA_STEP) == 0:
|
||||
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN))
|
||||
|
||||
### longitudinal control ###
|
||||
# send acc msg at 50Hz
|
||||
@@ -195,6 +225,8 @@ class CarController(CarControllerBase):
|
||||
self.lead_distance_bars_last = hud_control.leadDistanceBars
|
||||
|
||||
new_actuators = actuators.as_builder()
|
||||
if self.CP.flags & FordFlags.LKA_STEERING:
|
||||
new_actuators.steeringAngleDeg = self.apply_angle_last + CS.out.steeringAngleDeg
|
||||
new_actuators.curvature = self.apply_curvature_last
|
||||
new_actuators.accel = self.accel
|
||||
new_actuators.gas = self.gas
|
||||
|
||||
@@ -20,6 +20,8 @@ class CarState(CarStateBase):
|
||||
|
||||
self.distance_button = 0
|
||||
self.lc_button = 0
|
||||
self.lkas_available = False
|
||||
self.lateral_motion_control = None
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
cp = can_parsers[Bus.pt]
|
||||
@@ -108,6 +110,15 @@ class CarState(CarStateBase):
|
||||
# Stock values from IPMA so that we can retain some stock functionality
|
||||
self.acc_tja_status_stock_values = cp_cam.vl["ACCDATA_3"]
|
||||
self.lkas_status_stock_values = cp_cam.vl["IPMA_Data"]
|
||||
if self.CP.flags & FordFlags.LKA_STEERING:
|
||||
try:
|
||||
self.lkas_available = cp.vl["Lane_Assist_Data3_FD1"]["LaActAvail_D_Actl"] == 3
|
||||
except KeyError:
|
||||
self.lkas_available = False
|
||||
try:
|
||||
self.lateral_motion_control = cp_cam.vl["LateralMotionControl"]
|
||||
except KeyError:
|
||||
self.lateral_motion_control = None
|
||||
|
||||
ret.buttonEvents = [
|
||||
*create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}),
|
||||
|
||||
@@ -106,10 +106,12 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.FORD_F_150_MK14: {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'ML3V-14D003-BA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'ML3V-14D003-BC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'ML3V-14D003-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.abs, 0x760, None): [
|
||||
b'ML34-2D053-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'NL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PL34-2D053-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
@@ -117,6 +119,7 @@ FW_VERSIONS = {
|
||||
b'PL3V-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x764, None): [
|
||||
b'ML3T-14D049-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'ML3T-14D049-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
@@ -124,6 +127,7 @@ FW_VERSIONS = {
|
||||
b'ML3T-14H102-ABR\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'ML3T-14H102-ABT\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'ML3T-14H102-ACA\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PJ6T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'RJ6T-14H102-ACJ\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
@@ -211,6 +215,7 @@ FW_VERSIONS = {
|
||||
b'PB3C-2D053-ZD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PB3C-2D053-ZG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PB3C-2D053-ZJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'RB3C-2D053-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x764, None): [
|
||||
b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
@@ -220,4 +225,18 @@ FW_VERSIONS = {
|
||||
b'RJ6T-14H102-BBB\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.FORD_TRANSIT_MK5: {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'KK21-14D003-AM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.abs, 0x760, None): [
|
||||
b'NK41-2D053-DF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x764, None): [
|
||||
b'PC4T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x706, None): [
|
||||
b'NK3T-14F397-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import math
|
||||
|
||||
from opendbc.car import CanBusBase, structs
|
||||
|
||||
HUDControl = structs.CarControl.HUDControl
|
||||
@@ -33,20 +35,40 @@ def calculate_lat_ctl2_checksum(mode: int, counter: int, dat: bytearray) -> int:
|
||||
return 0xFF - (checksum & 0xFF)
|
||||
|
||||
|
||||
def create_lka_msg(packer, CAN: CanBus):
|
||||
def create_lka_msg(packer, CAN: CanBus, active: bool = False, apply_angle: float = 0.0,
|
||||
direction: int = 0, ramp_type: int = 0, curvature: float = 0.0):
|
||||
"""
|
||||
Creates an empty CAN message for the Ford LKA Command.
|
||||
Creates a CAN message for the Ford LKA Command.
|
||||
|
||||
This command can apply "Lane Keeping Aid" maneuvers, which are subject to the PSCM lockout.
|
||||
On LKA-steering platforms, this command applies Lane Keeping Aid maneuvers through the PSCM.
|
||||
|
||||
Frequency is 33Hz.
|
||||
"""
|
||||
|
||||
return packer.make_can_msg("Lane_Assist_Data1", CAN.main, {})
|
||||
if active:
|
||||
mrad = math.radians(max(-5.8, min(5.8, apply_angle))) * 1000.0
|
||||
mrad = max(-102.4, min(102.3, mrad))
|
||||
curvature = max(-0.01023, min(0.01023, curvature))
|
||||
else:
|
||||
mrad = 0.0
|
||||
direction = 0
|
||||
ramp_type = 0
|
||||
curvature = 0.0
|
||||
|
||||
values = {
|
||||
"LkaDrvOvrrd_D_Rq": 0,
|
||||
"LkaActvStats_D2_Req": direction if active else 0,
|
||||
"LaRefAng_No_Req": mrad,
|
||||
"LaRampType_B_Req": ramp_type,
|
||||
"LaCurvature_No_Calc": curvature,
|
||||
"LdwActvStats_D_Req": 0,
|
||||
"LdwActvIntns_D_Req": 3,
|
||||
}
|
||||
return packer.make_can_msg("Lane_Assist_Data1", CAN.main, values)
|
||||
|
||||
|
||||
def create_lat_ctl_msg(packer, CAN: CanBus, lat_active: bool, path_offset: float, path_angle: float, curvature: float,
|
||||
curvature_rate: float):
|
||||
curvature_rate: float, stock_lmc=None):
|
||||
"""
|
||||
Creates a CAN message for the Ford TJA/LCA Command.
|
||||
|
||||
@@ -68,20 +90,33 @@ def create_lat_ctl_msg(packer, CAN: CanBus, lat_active: bool, path_offset: float
|
||||
Frequency is 20Hz.
|
||||
"""
|
||||
|
||||
values = {
|
||||
"LatCtlRng_L_Max": 0, # Unknown [0|126] meter
|
||||
"HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1]
|
||||
"LatCtl_D_Rq": 1 if lat_active else 0, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft,
|
||||
# 3=InterventionRight, 4-7=NotUsed [0|7]
|
||||
"LatCtlRampType_D_Rq": 0, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3]
|
||||
# Makes no difference with curvature control
|
||||
"LatCtlPrecision_D_Rq": 1, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3]
|
||||
# The stock system always uses comfortable
|
||||
"LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter
|
||||
"LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians
|
||||
"LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2
|
||||
"LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter
|
||||
}
|
||||
if stock_lmc is not None:
|
||||
values = {
|
||||
"LatCtlRng_L_Max": stock_lmc["LatCtlRng_L_Max"],
|
||||
"HandsOffCnfm_B_Rq": stock_lmc["HandsOffCnfm_B_Rq"],
|
||||
"LatCtl_D_Rq": 0,
|
||||
"LatCtlRampType_D_Rq": stock_lmc["LatCtlRampType_D_Rq"],
|
||||
"LatCtlPrecision_D_Rq": stock_lmc["LatCtlPrecision_D_Rq"],
|
||||
"LatCtlPathOffst_L_Actl": stock_lmc["LatCtlPathOffst_L_Actl"],
|
||||
"LatCtlPath_An_Actl": stock_lmc["LatCtlPath_An_Actl"],
|
||||
"LatCtlCurv_NoRate_Actl": stock_lmc["LatCtlCurv_NoRate_Actl"],
|
||||
"LatCtlCurv_No_Actl": stock_lmc["LatCtlCurv_No_Actl"],
|
||||
}
|
||||
else:
|
||||
values = {
|
||||
"LatCtlRng_L_Max": 0, # Unknown [0|126] meter
|
||||
"HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1]
|
||||
"LatCtl_D_Rq": 1 if lat_active else 0, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft,
|
||||
# 3=InterventionRight, 4-7=NotUsed [0|7]
|
||||
"LatCtlRampType_D_Rq": 0, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3]
|
||||
# Makes no difference with curvature control
|
||||
"LatCtlPrecision_D_Rq": 1, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3]
|
||||
# The stock system always uses comfortable
|
||||
"LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter
|
||||
"LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians
|
||||
"LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2
|
||||
"LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter
|
||||
}
|
||||
return packer.make_can_msg("LateralMotionControl", CAN.main, values)
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
ret.radarUnavailable = Bus.radar not in DBC[candidate]
|
||||
ret.steerControlType = structs.CarParams.SteerControlType.angle
|
||||
ret.steerActuatorDelay = 0.2
|
||||
ret.steerActuatorDelay = 0.05 if ret.flags & FordFlags.LKA_STEERING else 0.2
|
||||
ret.steerLimitTimer = 1.0
|
||||
ret.steerAtStandstill = True
|
||||
|
||||
@@ -63,6 +63,8 @@ class CarInterface(CarInterfaceBase):
|
||||
if fingerprint[CAN.camera].get(0x3d6) != 8 or fingerprint[CAN.camera].get(0x186) != 8:
|
||||
carlog.error('dashcamOnly: SecOC is unsupported')
|
||||
ret.dashcamOnly = True
|
||||
elif ret.flags & FordFlags.LKA_STEERING:
|
||||
ret.safetyConfigs[-1].safetyParam |= FordSafetyFlags.LKA_STEERING.value
|
||||
else:
|
||||
# Lock out if the car does not have needed lateral and longitudinal control APIs.
|
||||
# Note that we also check CAN for adaptive cruise, but no known signal for LCA exists
|
||||
|
||||
@@ -46,11 +46,13 @@ class CarControllerParams:
|
||||
class FordSafetyFlags(IntFlag):
|
||||
LONG_CONTROL = 1
|
||||
CANFD = 2
|
||||
LKA_STEERING = 4
|
||||
|
||||
|
||||
class FordFlags(IntFlag):
|
||||
# Static flags
|
||||
CANFD = 1
|
||||
LKA_STEERING = 2
|
||||
|
||||
|
||||
class RADAR:
|
||||
@@ -111,6 +113,13 @@ class FordCANFDPlatformConfig(FordPlatformConfig):
|
||||
self.flags |= FordFlags.CANFD
|
||||
|
||||
|
||||
@dataclass
|
||||
class FordLKASteeringPlatformConfig(FordPlatformConfig):
|
||||
def init(self):
|
||||
super().init()
|
||||
self.flags |= FordFlags.LKA_STEERING
|
||||
|
||||
|
||||
@dataclass
|
||||
class FordF150LightningPlatform(FordCANFDPlatformConfig):
|
||||
def init(self):
|
||||
@@ -178,6 +187,10 @@ class CAR(Platforms):
|
||||
[FordCarDocs("Ford Ranger 2024", "Adaptive Cruise Control with Lane Centering", setup_video="https://www.youtube.com/watch?v=2oJlXCKYOy0")],
|
||||
CarSpecs(mass=2000, wheelbase=3.27, steerRatio=17.0),
|
||||
)
|
||||
FORD_TRANSIT_MK5 = FordLKASteeringPlatformConfig(
|
||||
[FordCarDocs("Ford Transit 2025", "Co-Pilot360 Assist+")],
|
||||
CarSpecs(mass=2068, wheelbase=3.302, steerRatio=16.7),
|
||||
)
|
||||
|
||||
|
||||
# FW response contains a combined software and part number
|
||||
|
||||
@@ -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
|
||||
@@ -6,21 +6,76 @@ from opendbc.car.lateral import apply_driver_steer_torque_limits
|
||||
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,
|
||||
ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, CC_REGEN_PADDLE_CAR, DBC, EV_CAR, SDGM_CAR, AccState, CanBus, CarControllerParams,
|
||||
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,
|
||||
}
|
||||
TRUCK_FRICTION_BRAKE_ENGAGE = 40
|
||||
TRUCK_FRICTION_BRAKE_RELEASE = 8
|
||||
TRUCK_FRICTION_BRAKE_IMMEDIATE_ACCEL = -0.85
|
||||
ACC_DASHBOARD_ZERO_RESERVED_CARS = {
|
||||
CAR.CHEVROLET_BLAZER,
|
||||
CAR.CHEVROLET_EQUINOX,
|
||||
CAR.CHEVROLET_SILVERADO,
|
||||
CAR.CHEVROLET_TRAILBLAZER,
|
||||
CAR.CHEVROLET_TRAX,
|
||||
}
|
||||
|
||||
|
||||
def get_stock_cc_active_for_cancel(CP, CS):
|
||||
@@ -36,7 +91,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 +123,32 @@ 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_always_one(CP):
|
||||
return 0 if CP.carFingerprint in ACC_DASHBOARD_ZERO_RESERVED_CARS else 1
|
||||
|
||||
|
||||
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 +184,299 @@ 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 should_send_acc_2cd(CP):
|
||||
return (
|
||||
CP.networkLocation == NetworkLocation.fwdCamera and
|
||||
CP.carFingerprint in CAMERA_ACC_CAR and
|
||||
CP.carFingerprint not in (CC_ONLY_CAR | SDGM_CAR) and
|
||||
not bool(getattr(CP, "flags", 0) & GMFlags.NO_CAMERA.value)
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
lead_visible: bool = False, set_speed_error: float = 0.0) -> 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.93, 0.84, 0.76, 0.70]))
|
||||
mid_scale = float(np.interp(v_ego, [12.0, 18.0, 25.0, 35.0], [0.97, 0.91, 0.85, 0.79]))
|
||||
|
||||
if lead_visible and set_speed_error > 0.0:
|
||||
follow_relief = float(np.interp(set_speed_error, [0.0, 1.0, 2.5, 4.0, 6.0], [0.0, 0.04, 0.10, 0.18, 0.30]))
|
||||
low_scale += (1.0 - low_scale) * follow_relief
|
||||
mid_scale += (1.0 - mid_scale) * follow_relief
|
||||
|
||||
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 shape_truck_pitch_accel(pitch_accel: float, v_ego: float, enabled: bool) -> float:
|
||||
if not enabled:
|
||||
return pitch_accel
|
||||
|
||||
scale = float(np.interp(v_ego, [8.0, 15.0, 25.0, 35.0], [0.60, 0.45, 0.30, 0.25]))
|
||||
return pitch_accel * scale
|
||||
|
||||
|
||||
def shape_truck_friction_brake(apply_brake: int, accel_cmd: float, stopping: bool, active: bool) -> tuple[int, bool]:
|
||||
if apply_brake <= 0:
|
||||
return 0, False
|
||||
|
||||
# Preserve full brake response for stop control and meaningful deceleration.
|
||||
if stopping or accel_cmd <= TRUCK_FRICTION_BRAKE_IMMEDIATE_ACCEL:
|
||||
return apply_brake, True
|
||||
|
||||
if active:
|
||||
if apply_brake <= TRUCK_FRICTION_BRAKE_RELEASE:
|
||||
return 0, False
|
||||
return apply_brake, True
|
||||
|
||||
if apply_brake >= TRUCK_FRICTION_BRAKE_ENGAGE:
|
||||
return apply_brake, True
|
||||
|
||||
# Keep tiny corrections in the continuous gas/regen torque path. Switching
|
||||
# to friction also forces max regen, which makes a small request perceptible.
|
||||
return 0, False
|
||||
|
||||
|
||||
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
|
||||
getattr(CP, "openpilotLongitudinalControl", False) 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
|
||||
getattr(CP, "openpilotLongitudinalControl", False) 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:
|
||||
# cam-long: 0x315 goes where the panda whitelist allows it and the EBCM hears it
|
||||
safety_cfg = getattr(CP, "safetyConfigs", ())
|
||||
safety_param = safety_cfg[0].safetyParam if safety_cfg else 0
|
||||
if safety_param & GMSafetyFlags.HW_CAM_LONG.value:
|
||||
# SASCM relays 0x315 to the EBCM off its camera-bus (bus2) leg; bare SDGM uses the pt bus
|
||||
if CP.flags & GMFlags.SASCM.value:
|
||||
return CanBus.CAMERA
|
||||
return CanBus.POWERTRAIN
|
||||
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)
|
||||
@@ -111,6 +487,15 @@ class CarController(CarControllerBase):
|
||||
self.last_steer_frame = 0
|
||||
self.last_button_frame = 0
|
||||
self.cancel_counter = 0
|
||||
self.xt4_cc_button_burst_remaining = 0
|
||||
self.xt4_cc_button_burst_button = CruiseButtons.INIT
|
||||
self.xt4_cc_button_burst_last_counter = -1
|
||||
self.xt4_cc_button_observed_counter = -1
|
||||
self.xt4_cc_button_counter_frame = 0
|
||||
self.gm_cc_last_direction_button = CruiseButtons.INIT
|
||||
self.gm_cc_last_direction_frame = 0
|
||||
self.gm_cc_pending_reverse_button = CruiseButtons.INIT
|
||||
self.gm_cc_pending_reverse_frame = 0
|
||||
|
||||
self.lka_steering_cmd_counter = 0
|
||||
self.lka_icon_status_last = (False, False)
|
||||
@@ -153,6 +538,61 @@ 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
|
||||
self.truck_friction_brake_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 +715,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 +745,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 +882,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
|
||||
@@ -425,6 +947,18 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(gmcan.create_ecm_cruise_control_command(
|
||||
self.packer_pt, CanBus.POWERTRAIN, True, hud_v_cruise * CV.MS_TO_KPH))
|
||||
|
||||
xt4_cc_button_spam = (
|
||||
self.CP.carFingerprint == CAR.CADILLAC_XT4_CC and
|
||||
should_send_cc_button_spam(self.CP, CC, CS)
|
||||
)
|
||||
if xt4_cc_button_spam:
|
||||
can_sends.extend(gmcan.create_gm_cc_spam_command(self.packer_pt, self, CS, actuators, starpilot_toggles))
|
||||
elif self.CP.carFingerprint == CAR.CADILLAC_XT4_CC:
|
||||
self.xt4_cc_button_burst_remaining = 0
|
||||
self.xt4_cc_button_burst_button = CruiseButtons.INIT
|
||||
self.xt4_cc_button_burst_last_counter = -1
|
||||
self.xt4_cc_button_observed_counter = -1
|
||||
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
# Gas/regen, brakes, and UI commands - all at 25Hz
|
||||
if self.frame % 4 == 0:
|
||||
@@ -436,6 +970,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 +978,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 +1022,24 @@ 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))
|
||||
truck_long_smoothing = (
|
||||
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_due_to_pitch = shape_truck_pitch_accel(accel_due_to_pitch, CS.out.vEgo, truck_long_smoothing)
|
||||
accel_input = actuators.accel + accel_due_to_pitch
|
||||
if truck_long_smoothing:
|
||||
accel_input = shape_truck_positive_accel(
|
||||
accel_input,
|
||||
CS.out.vEgo,
|
||||
True,
|
||||
lead_visible=CC.hudControl.leadVisible,
|
||||
set_speed_error=max(CC.hudControl.setSpeed - CS.out.vEgo, 0.0),
|
||||
)
|
||||
|
||||
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 +1047,33 @@ 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 truck_long_smoothing:
|
||||
self.apply_brake, self.truck_friction_brake_active = shape_truck_friction_brake(
|
||||
self.apply_brake, accel_cmd, stopping, self.truck_friction_brake_active,
|
||||
)
|
||||
else:
|
||||
self.truck_friction_brake_active = False
|
||||
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 +1084,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
|
||||
|
||||
@@ -537,28 +1117,50 @@ class CarController(CarControllerBase):
|
||||
|
||||
if self.CP.flags & GMFlags.CC_LONG.value:
|
||||
if should_send_cc_button_spam(self.CP, CC, CS):
|
||||
# Using extend instead of append since the message is only sent intermittently
|
||||
can_sends.extend(gmcan.create_gm_cc_spam_command(self.packer_pt, self, CS, actuators, starpilot_toggles))
|
||||
elif (CS.out.cruiseState.enabled and CC.enabled and self.frame % 52 == 0 and
|
||||
if self.CP.carFingerprint != CAR.CADILLAC_XT4_CC:
|
||||
# Using extend instead of append since the message is only sent intermittently
|
||||
can_sends.extend(gmcan.create_gm_cc_spam_command(self.packer_pt, self, CS, actuators, starpilot_toggles))
|
||||
else:
|
||||
if (CS.out.cruiseState.enabled and CC.enabled and self.frame % 52 == 0 and
|
||||
CS.cruise_buttons == CruiseButtons.UNPRESS and CS.out.gasPressed and CS.out.cruiseState.speed < CS.out.vEgo < hud_v_cruise):
|
||||
if self.CP.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC:
|
||||
can_sends.append(gmcan.create_buttons_malibu(
|
||||
self.packer_pt, CanBus.POWERTRAIN, CruiseButtons.DECEL_SET,
|
||||
self.malibu_button_phase, CS.steering_button_prefix))
|
||||
self.malibu_button_phase = (self.malibu_button_phase + 1) % 4
|
||||
else:
|
||||
can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.POWERTRAIN, (CS.buttons_counter + 1) % 4, CruiseButtons.DECEL_SET))
|
||||
if self.CP.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC:
|
||||
can_sends.append(gmcan.create_buttons_malibu(
|
||||
self.packer_pt, CanBus.POWERTRAIN, CruiseButtons.DECEL_SET,
|
||||
self.malibu_button_phase, CS.steering_button_prefix))
|
||||
self.malibu_button_phase = (self.malibu_button_phase + 1) % 4
|
||||
else:
|
||||
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 should_send_acc_2cd(self.CP):
|
||||
can_sends.append(gmcan.create_acc_2cd_command(CanBus.POWERTRAIN, idx))
|
||||
|
||||
if self.CP.autoResumeSng:
|
||||
resume = actuators.longControlState != LongCtrlState.starting or CC.cruiseControl.resume
|
||||
@@ -569,47 +1171,68 @@ 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)
|
||||
acc_dashboard_always_one = get_acc_dashboard_always_one(self.CP)
|
||||
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,
|
||||
acc_dashboard_always_one))
|
||||
|
||||
# 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 +1256,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,17 @@ 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.CADILLAC_XT4_CC: FINGERPRINTS[CAR.CADILLAC_XT4],
|
||||
CAR.BUICK_LACROSSE_ASCM: FINGERPRINTS[CAR.BUICK_LACROSSE],
|
||||
})
|
||||
|
||||
FW_VERSIONS: dict[str, dict[tuple, list[bytes]]] = {
|
||||
|
||||
@@ -17,6 +17,18 @@ MALIBU_BUTTON_MAP = {
|
||||
CruiseButtons.CANCEL: 5,
|
||||
}
|
||||
|
||||
ACC_CRUISE_STATE_ADAPTIVE = 2
|
||||
XT4_CC_BUTTON_BURST_FRAMES = 6
|
||||
XT4_CC_BUTTON_COUNTER_DELAY_FRAMES = 1
|
||||
BOLT_CC_BUTTON_CARS = {
|
||||
CAR.CHEVROLET_BOLT_CC_2017,
|
||||
CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
CAR.CHEVROLET_BOLT_CC_2022_2023,
|
||||
}
|
||||
BOLT_CC_TARGET_DEADBAND_MPH = 0.75
|
||||
BOLT_CC_REVERSE_CONFIRM_S = 0.6
|
||||
BOLT_CC_DIRECTION_MEMORY_S = 1.5
|
||||
|
||||
|
||||
def malibu_phase_map_for_button(button):
|
||||
key = MALIBU_BUTTON_MAP.get(button)
|
||||
@@ -179,22 +191,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,18 +227,26 @@ 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_2cd_command(bus, idx):
|
||||
dat = bytearray([0x00, 0x2c, 0x03, 0xd3, 0x00])
|
||||
dat[0] = (idx & 0x3) << 6
|
||||
dat[4] = (0xfd - (idx & 0x3)) & 0xff
|
||||
return CanData(0x2CD, bytes(dat), bus)
|
||||
|
||||
|
||||
def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, hud_control, fcw_alert, acc_always_one=1):
|
||||
target_speed = min(target_speed_kph, 255)
|
||||
|
||||
values = {
|
||||
"ACCAlwaysOne": 1,
|
||||
"ACCAlwaysOne": acc_always_one,
|
||||
"ACCCruiseState": ACC_CRUISE_STATE_ADAPTIVE,
|
||||
"ACCResumeButton": 0,
|
||||
"ACCSpeedSetpoint": target_speed,
|
||||
"ACCGapLevel": hud_control.leadDistanceBars * enabled, # 3 "far", 0 "inactive"
|
||||
"ACCCmdActive": enabled,
|
||||
"ACCAlwaysOne2": 1,
|
||||
"ACCAlwaysOne2": acc_always_one,
|
||||
"ACCLeadCar": hud_control.leadVisible,
|
||||
"FCWAlert": 0x3 if fcw else 0
|
||||
"FCWAlert": int(fcw_alert) & 0x3,
|
||||
}
|
||||
|
||||
return packer.make_can_msg("ASCMActiveCruiseControlStatus", bus, values)
|
||||
@@ -280,32 +306,110 @@ def create_lka_icon_command(bus, active, critical, steer):
|
||||
return CanData(0x104c006c, dat, bus)
|
||||
|
||||
|
||||
def stabilize_bolt_cc_button(controller, CP, requested_button):
|
||||
if CP.carFingerprint not in BOLT_CC_BUTTON_CARS:
|
||||
return requested_button
|
||||
|
||||
direction_buttons = (CruiseButtons.RES_ACCEL, CruiseButtons.DECEL_SET)
|
||||
if requested_button not in direction_buttons:
|
||||
controller.gm_cc_pending_reverse_button = CruiseButtons.INIT
|
||||
return requested_button
|
||||
|
||||
last_button = getattr(controller, "gm_cc_last_direction_button", CruiseButtons.INIT)
|
||||
last_frame = getattr(controller, "gm_cc_last_direction_frame", -int(BOLT_CC_DIRECTION_MEMORY_S / DT_CTRL) - 1)
|
||||
recently_sent = (controller.frame - last_frame) * DT_CTRL <= BOLT_CC_DIRECTION_MEMORY_S
|
||||
reversing_to_accel = (last_button == CruiseButtons.DECEL_SET and
|
||||
requested_button == CruiseButtons.RES_ACCEL and recently_sent)
|
||||
|
||||
if reversing_to_accel:
|
||||
pending_button = getattr(controller, "gm_cc_pending_reverse_button", CruiseButtons.INIT)
|
||||
if pending_button != requested_button:
|
||||
controller.gm_cc_pending_reverse_button = requested_button
|
||||
controller.gm_cc_pending_reverse_frame = controller.frame
|
||||
return CruiseButtons.INIT
|
||||
|
||||
pending_frame = getattr(controller, "gm_cc_pending_reverse_frame", controller.frame)
|
||||
if (controller.frame - pending_frame) * DT_CTRL < BOLT_CC_REVERSE_CONFIRM_S:
|
||||
return CruiseButtons.INIT
|
||||
|
||||
controller.gm_cc_pending_reverse_button = CruiseButtons.INIT
|
||||
return requested_button
|
||||
|
||||
|
||||
def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles):
|
||||
accel = actuators.accel
|
||||
v_ego = CS.out.vEgo
|
||||
cruise_btn = CruiseButtons.INIT
|
||||
rate = 1 if abs(accel) <= 0.15 else 0.2
|
||||
ms_convert = CV.MS_TO_KPH if getattr(starpilot_toggles, "is_metric", False) else CV.MS_TO_MPH
|
||||
is_metric = getattr(starpilot_toggles, "is_metric", False)
|
||||
ms_convert = CV.MS_TO_KPH if is_metric else CV.MS_TO_MPH
|
||||
speed_setpoint = int(round(CS.out.cruiseState.speed * ms_convert))
|
||||
desired_setpoint = int(round((v_ego * 1.01 + 3 * accel) * ms_convert))
|
||||
projected_setpoint = (v_ego * 1.01 + 3 * accel) * ms_convert
|
||||
desired_setpoint = int(round(projected_setpoint))
|
||||
bolt_cc = CS.CP.carFingerprint in BOLT_CC_BUTTON_CARS
|
||||
target_deadband = BOLT_CC_TARGET_DEADBAND_MPH * (CV.MPH_TO_KPH if is_metric else 1.0) if bolt_cc else 0.0
|
||||
comparison_setpoint = projected_setpoint if bolt_cc else desired_setpoint
|
||||
|
||||
if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25:
|
||||
cruise_btn = CruiseButtons.CANCEL
|
||||
controller.apply_speed = 0
|
||||
elif desired_setpoint < speed_setpoint and speed_setpoint > CS.CP.minEnableSpeed * ms_convert + 1:
|
||||
elif comparison_setpoint < speed_setpoint - target_deadband and speed_setpoint > CS.CP.minEnableSpeed * ms_convert + 1:
|
||||
cruise_btn = CruiseButtons.DECEL_SET
|
||||
controller.apply_speed = speed_setpoint - 1
|
||||
elif desired_setpoint > speed_setpoint:
|
||||
elif comparison_setpoint > speed_setpoint + target_deadband:
|
||||
cruise_btn = CruiseButtons.RES_ACCEL
|
||||
|
||||
cruise_btn = stabilize_bolt_cc_button(controller, CS.CP, cruise_btn)
|
||||
if cruise_btn == CruiseButtons.CANCEL:
|
||||
controller.apply_speed = 0
|
||||
elif cruise_btn == CruiseButtons.DECEL_SET:
|
||||
controller.apply_speed = speed_setpoint - 1
|
||||
elif cruise_btn == CruiseButtons.RES_ACCEL:
|
||||
controller.apply_speed = speed_setpoint + 1
|
||||
else:
|
||||
cruise_btn = CruiseButtons.INIT
|
||||
controller.apply_speed = speed_setpoint
|
||||
|
||||
if CS.CP.carFingerprint == CAR.CADILLAC_XT4_CC:
|
||||
if controller.xt4_cc_button_observed_counter != CS.buttons_counter:
|
||||
controller.xt4_cc_button_observed_counter = CS.buttons_counter
|
||||
controller.xt4_cc_button_counter_frame = controller.frame
|
||||
|
||||
if cruise_btn == CruiseButtons.INIT:
|
||||
controller.xt4_cc_button_burst_remaining = 0
|
||||
controller.xt4_cc_button_burst_button = CruiseButtons.INIT
|
||||
return []
|
||||
|
||||
if (controller.xt4_cc_button_burst_remaining > 0 and
|
||||
controller.xt4_cc_button_burst_button != cruise_btn):
|
||||
controller.xt4_cc_button_burst_remaining = 0
|
||||
|
||||
if controller.xt4_cc_button_burst_remaining == 0:
|
||||
if (controller.frame - controller.last_button_frame) * DT_CTRL <= rate:
|
||||
return []
|
||||
controller.last_button_frame = controller.frame
|
||||
controller.xt4_cc_button_burst_button = cruise_btn
|
||||
controller.xt4_cc_button_burst_remaining = XT4_CC_BUTTON_BURST_FRAMES
|
||||
controller.xt4_cc_button_burst_last_counter = -1
|
||||
|
||||
# XT4 physical taps hold the button for 5-7 consecutive 33 Hz frames.
|
||||
# Sending immediately after the stock frame is too early for the receiving ECU.
|
||||
if controller.frame - controller.xt4_cc_button_counter_frame < XT4_CC_BUTTON_COUNTER_DELAY_FRAMES:
|
||||
return []
|
||||
|
||||
# Send once per observed stock counter so the injected sequence has the same cadence.
|
||||
if controller.xt4_cc_button_burst_last_counter == CS.buttons_counter:
|
||||
return []
|
||||
|
||||
controller.xt4_cc_button_burst_last_counter = CS.buttons_counter
|
||||
controller.xt4_cc_button_burst_remaining -= 1
|
||||
idx = (CS.buttons_counter + 1) % 4
|
||||
return [create_buttons(packer, CanBus.POWERTRAIN, idx, controller.xt4_cc_button_burst_button)]
|
||||
|
||||
# Check rlogs closely - our message shouldn't show up on the pt bus for us
|
||||
# Or bus 2, since we're forwarding... but I think it does
|
||||
if (cruise_btn != CruiseButtons.INIT) and ((controller.frame - controller.last_button_frame) * DT_CTRL > rate):
|
||||
controller.last_button_frame = controller.frame
|
||||
if bolt_cc and cruise_btn in (CruiseButtons.RES_ACCEL, CruiseButtons.DECEL_SET):
|
||||
controller.gm_cc_last_direction_button = cruise_btn
|
||||
controller.gm_cc_last_direction_frame = controller.frame
|
||||
if CS.CP.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC:
|
||||
phase_map = malibu_phase_map_for_button(cruise_btn)
|
||||
if phase_map:
|
||||
|
||||
@@ -58,8 +58,32 @@ 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],
|
||||
},
|
||||
}
|
||||
|
||||
NON_LINEAR_TORQUE_PARAM_ALIASES = {
|
||||
CAR.CHEVROLET_VOLT_ASCM: CAR.CHEVROLET_VOLT,
|
||||
CAR.CHEVROLET_VOLT_CAMERA: CAR.CHEVROLET_VOLT,
|
||||
CAR.CHEVROLET_VOLT_CC: CAR.CHEVROLET_VOLT,
|
||||
CAR.CHEVROLET_VOLT_2019: CAR.CHEVROLET_VOLT,
|
||||
}
|
||||
|
||||
|
||||
def get_nonlinear_torque_params(car_fingerprint):
|
||||
source_fingerprint = NON_LINEAR_TORQUE_PARAM_ALIASES.get(car_fingerprint, car_fingerprint)
|
||||
return NON_LINEAR_TORQUE_PARAMS.get(source_fingerprint)
|
||||
|
||||
PEDAL_MSG = 0x201
|
||||
CAM_MSG = 0x320
|
||||
ACCELERATOR_POS_MSG = 0xBE
|
||||
@@ -85,6 +109,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 +125,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 +145,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],
|
||||
@@ -141,7 +181,7 @@ class CarInterface(CarInterfaceBase):
|
||||
# The "lat_accel vs torque" relationship is assumed to be the sum of "sigmoid + linear" curves
|
||||
# An important thing to consider is that the slope at 0 should be > 0 (ideally >1)
|
||||
# This has big effect on the stability about 0 (noise when going straight)
|
||||
non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS.get(self.CP.carFingerprint)
|
||||
non_linear_torque_params = get_nonlinear_torque_params(self.CP.carFingerprint)
|
||||
assert non_linear_torque_params, "The params are not defined"
|
||||
if isinstance(non_linear_torque_params, dict):
|
||||
side_key = "left" if lateral_acceleration >= 0 else "right"
|
||||
@@ -159,7 +199,7 @@ class CarInterface(CarInterfaceBase):
|
||||
return torque_values, lataccel_values
|
||||
|
||||
def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
|
||||
if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
|
||||
if get_nonlinear_torque_params(self.CP.carFingerprint) is not None:
|
||||
torque_values, lataccel_values = self.get_lataccel_torque_siglin()
|
||||
|
||||
def torque_from_lateral_accel_siglin(lateral_acceleration: float, torque_params: structs.CarParams.LateralTorqueTuning):
|
||||
@@ -169,7 +209,7 @@ class CarInterface(CarInterfaceBase):
|
||||
return self.torque_from_lateral_accel_linear
|
||||
|
||||
def lateral_accel_from_torque(self) -> LateralAccelFromTorqueCallbackType:
|
||||
if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
|
||||
if get_nonlinear_torque_params(self.CP.carFingerprint) is not None:
|
||||
torque_values, lataccel_values = self.get_lataccel_torque_siglin()
|
||||
|
||||
def lateral_accel_from_torque_siglin(torque: float, torque_params: structs.CarParams.LateralTorqueTuning):
|
||||
@@ -189,11 +229,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 +434,10 @@ 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, CAR.BUICK_LACROSSE_ASCM_19US):
|
||||
CarInterfaceBase.configure_torque_tune(CAR.BUICK_LACROSSE, ret.lateralTuning)
|
||||
if candidate == CAR.BUICK_LACROSSE_ASCM_19US:
|
||||
ret.minSteerSpeed = 27 * CV.MPH_TO_MS
|
||||
|
||||
elif candidate == CAR.CADILLAC_ESCALADE:
|
||||
ret.minEnableSpeed = -1. # engage speed is decided by pcm
|
||||
@@ -395,7 +446,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 +455,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 +496,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
|
||||
@@ -457,7 +509,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.CADILLAC_XT4:
|
||||
elif candidate in (CAR.CADILLAC_XT4, CAR.CADILLAC_XT4_CC):
|
||||
ret.steerActuatorDelay = 0.2
|
||||
if not ret.openpilotLongitudinalControl:
|
||||
ret.minEnableSpeed = -1.
|
||||
@@ -481,7 +533,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 +542,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:
|
||||
@@ -539,12 +603,16 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.longitudinalTuning.kpV = [0.095, 0.085, 0.065, 0.050]
|
||||
ret.longitudinalTuning.kiV = [0.07, 0.10, 0.15, 0.24]
|
||||
ret.longitudinalTuning.kfDEPRECATED = 0.20
|
||||
ret.longitudinalActuatorDelay = 0.6
|
||||
else:
|
||||
ret.longitudinalTuning.kfDEPRECATED = 0.25
|
||||
|
||||
if is_bolt_2022_2023_pedal:
|
||||
# Gen2 Bolt pedal-long should follow the no-ACC panda path.
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_ACC.value
|
||||
ret.startingState = True
|
||||
ret.startAccel = 0.55
|
||||
ret.vEgoStarting = max(ret.vEgoStarting, 0.35)
|
||||
|
||||
if candidate in (CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL, CAR.CHEVROLET_MALIBU_HYBRID_CC):
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_BOLT_2022_PEDAL.value
|
||||
@@ -584,6 +652,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.20, 0.18, 0.13, 0.08]
|
||||
|
||||
elif candidate in CC_ONLY_CAR and not ret.enableGasInterceptorDEPRECATED:
|
||||
ret.flags |= GMFlags.CC_LONG.value
|
||||
ret.alphaLongitudinalAvailable = False
|
||||
@@ -614,12 +688,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 +706,41 @@ 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 = (
|
||||
ret.openpilotLongitudinalControl and
|
||||
(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. 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. Do not expose
|
||||
# the path at all when OP long is disabled in CarParams.
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
|
||||
|
||||
volt_stock_one_pedal_safety = (
|
||||
ret.openpilotLongitudinalControl and
|
||||
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.
|
||||
# Do not expose the path at all when OP long is disabled in CarParams.
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_PANDA_3D1_SCHED.value
|
||||
|
||||
use_panda_3d1_sched = (
|
||||
ret.openpilotLongitudinalControl and
|
||||
ret.enableGasInterceptorDEPRECATED and
|
||||
|
||||
@@ -0,0 +1,993 @@
|
||||
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_friction_brake,
|
||||
shape_truck_pitch_accel,
|
||||
shape_truck_positive_accel,
|
||||
should_use_fixed_stopping_brake,
|
||||
should_activate_auto_hold,
|
||||
should_activate_volt_one_pedal,
|
||||
should_send_acc_2cd,
|
||||
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_acc_2cd_replacement_only_used_with_live_camera_path():
|
||||
assert should_send_acc_2cd(SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_TRAILBLAZER, networkLocation=CarParams.NetworkLocation.fwdCamera, flags=0))
|
||||
assert not should_send_acc_2cd(SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_TRAILBLAZER, networkLocation=CarParams.NetworkLocation.fwdCamera, flags=GMFlags.NO_CAMERA.value))
|
||||
assert not should_send_acc_2cd(SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_TRAILBLAZER, networkLocation=CarParams.NetworkLocation.gateway, flags=0))
|
||||
assert not should_send_acc_2cd(SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_TRAILBLAZER_CC, networkLocation=CarParams.NetworkLocation.fwdCamera, flags=0))
|
||||
assert not should_send_acc_2cd(SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_BLAZER, networkLocation=CarParams.NetworkLocation.fwdCamera, flags=0))
|
||||
|
||||
|
||||
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 not supports_volt_auto_hold(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT,
|
||||
openpilotLongitudinalControl=False,
|
||||
networkLocation=CarParams.NetworkLocation.gateway,
|
||||
safetyConfigs=stock_safety,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not 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,
|
||||
openpilotLongitudinalControl=True,
|
||||
safetyConfigs=stock_safety,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_one_pedal(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
openpilotLongitudinalControl=True,
|
||||
safetyConfigs=no_safety,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_one_pedal(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
openpilotLongitudinalControl=True,
|
||||
safetyConfigs=stock_safety,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_one_pedal(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
openpilotLongitudinalControl=True,
|
||||
safetyConfigs=stock_safety,
|
||||
transmissionType=structs.CarParams.TransmissionType.automatic,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_one_pedal(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
openpilotLongitudinalControl=False,
|
||||
safetyConfigs=stock_safety,
|
||||
transmissionType=structs.CarParams.TransmissionType.direct,
|
||||
),
|
||||
True,
|
||||
)
|
||||
assert not supports_volt_one_pedal(
|
||||
SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CAMERA,
|
||||
openpilotLongitudinalControl=True,
|
||||
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.08 < shaped < 0.095
|
||||
|
||||
|
||||
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_shape_truck_positive_accel_preserves_more_follow_authority_with_lead():
|
||||
base = shape_truck_positive_accel(0.28, 26.0, True)
|
||||
relieved = shape_truck_positive_accel(0.28, 26.0, True, lead_visible=True, set_speed_error=6.0)
|
||||
|
||||
assert relieved > base
|
||||
assert relieved < 0.28
|
||||
|
||||
|
||||
def test_shape_truck_positive_accel_does_not_relax_without_speed_error():
|
||||
base = shape_truck_positive_accel(0.28, 26.0, True)
|
||||
no_error = shape_truck_positive_accel(0.28, 26.0, True, lead_visible=True, set_speed_error=0.0)
|
||||
|
||||
assert no_error == base
|
||||
|
||||
|
||||
def test_shape_truck_pitch_accel_attenuates_highway_grade_feedforward():
|
||||
assert shape_truck_pitch_accel(-0.30, 30.0, True) == pytest.approx(-0.0825)
|
||||
assert shape_truck_pitch_accel(0.30, 30.0, True) == pytest.approx(0.0825)
|
||||
|
||||
|
||||
def test_shape_truck_pitch_accel_is_inactive_without_truck_tuning():
|
||||
assert shape_truck_pitch_accel(-0.30, 30.0, False) == pytest.approx(-0.30)
|
||||
|
||||
|
||||
def test_shape_truck_friction_brake_suppresses_boundary_chatter():
|
||||
assert shape_truck_friction_brake(14, -0.3, False, False) == (0, False)
|
||||
|
||||
|
||||
def test_shape_truck_friction_brake_uses_hysteresis_once_engaged():
|
||||
assert shape_truck_friction_brake(39, -0.3, False, False) == (0, False)
|
||||
assert shape_truck_friction_brake(40, -0.3, False, False) == (40, True)
|
||||
assert shape_truck_friction_brake(14, -0.3, False, True) == (14, True)
|
||||
assert shape_truck_friction_brake(8, -0.3, False, True) == (0, False)
|
||||
|
||||
|
||||
def test_shape_truck_friction_brake_never_delays_meaningful_braking():
|
||||
assert shape_truck_friction_brake(5, -0.85, False, False) == (5, True)
|
||||
assert shape_truck_friction_brake(5, -0.2, True, False) == (5, True)
|
||||
|
||||
|
||||
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,29 @@
|
||||
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_always_one,
|
||||
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 +45,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 +66,84 @@ class TestGMFingerprint:
|
||||
|
||||
|
||||
class TestGMInterface:
|
||||
@parameterized.expand([
|
||||
CAR.CHEVROLET_BOLT_CC_2017,
|
||||
CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
CAR.CHEVROLET_BOLT_CC_2022_2023,
|
||||
CAR.CHEVROLET_MALIBU_HYBRID_CC,
|
||||
])
|
||||
def test_bolt_pedal_long_uses_shared_planning_delay_without_retuning_pid(self, car_model):
|
||||
CarInterface = interfaces[car_model]
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0][0x201] = 8
|
||||
params = Params()
|
||||
|
||||
try:
|
||||
params.put_bool("GMPedalLongitudinal", True)
|
||||
car_params = CarInterface.get_params(
|
||||
car_model,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
finally:
|
||||
params.remove("GMPedalLongitudinal")
|
||||
|
||||
assert car_params.longitudinalActuatorDelay == pytest.approx(0.6)
|
||||
assert list(car_params.longitudinalTuning.kpV) == pytest.approx([0.095, 0.085, 0.065, 0.050])
|
||||
assert list(car_params.longitudinalTuning.kiV) == pytest.approx([0.07, 0.10, 0.15, 0.24])
|
||||
assert car_params.longitudinalTuning.kfDEPRECATED == pytest.approx(0.20)
|
||||
|
||||
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 +156,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 +201,57 @@ class TestGMInterface:
|
||||
assert car_params.flags & GMFlags.NO_CAMERA.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
def test_volt_ascm_sparse_fingerprint_without_camera_does_not_set_no_camera(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
|
||||
fingerprint = {
|
||||
0: FINGERPRINTS[CAR.CHEVROLET_VOLT][0].copy(),
|
||||
1: {},
|
||||
}
|
||||
fingerprint[0][0x2FF] = 8 # SASCM detected
|
||||
|
||||
car_params = CarInterface.get_params(CAR.CHEVROLET_VOLT_ASCM, fingerprint, [], alpha_long=False, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
|
||||
assert not (car_params.flags & GMFlags.NO_CAMERA.value)
|
||||
assert not (car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value)
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.HW_ASCM_INT.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.20, 0.18, 0.13, 0.08])
|
||||
|
||||
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 +261,223 @@ 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_auto_hold_does_not_set_stock_hold_safety_bit_with_op_long_disabled(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=False, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
finally:
|
||||
params.remove("GMAutoHold")
|
||||
|
||||
assert not car_params.openpilotLongitudinalControl
|
||||
assert not (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
|
||||
|
||||
def test_volt_one_pedal_does_not_set_stock_hold_safety_bits_with_op_long_disabled(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=False, is_release=False,
|
||||
docs=False, starpilot_toggles=_test_starpilot_toggles())
|
||||
finally:
|
||||
params.remove("GMAutoHold")
|
||||
params.remove("VoltOnePedalMode")
|
||||
|
||||
assert not car_params.openpilotLongitudinalControl
|
||||
assert not (car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value)
|
||||
assert not (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
|
||||
assert car_params.startingState
|
||||
assert car_params.startAccel == pytest.approx(0.55)
|
||||
assert car_params.vEgoStarting == pytest.approx(0.35)
|
||||
|
||||
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)
|
||||
|
||||
@parameterized.expand((CAR.CHEVROLET_VOLT_ASCM, CAR.CHEVROLET_VOLT_CAMERA, CAR.CHEVROLET_VOLT_CC, CAR.CHEVROLET_VOLT_2019))
|
||||
def test_volt_integration_variants_share_nonlinear_torque_curve(self, candidate):
|
||||
assert gm_interface.get_nonlinear_torque_params(candidate) == gm_interface.NON_LINEAR_TORQUE_PARAMS[CAR.CHEVROLET_VOLT]
|
||||
|
||||
CarInterface = interfaces[candidate]
|
||||
car_params = CarInterface.get_non_essential_params(candidate)
|
||||
ci = CarInterface(car_params, custom.StarPilotCarParams.new_message())
|
||||
torque_from_lataccel = ci.torque_from_lateral_accel()
|
||||
|
||||
left_torque = torque_from_lataccel(0.5, car_params.lateralTuning.torque)
|
||||
right_torque = torque_from_lataccel(-0.5, car_params.lateralTuning.torque)
|
||||
assert left_torque > abs(right_torque)
|
||||
|
||||
|
||||
class TestGMCarController:
|
||||
def test_dash_speed_spoof_respects_live_stock_acc_toggles(self):
|
||||
@@ -122,6 +492,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 +588,226 @@ 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]
|
||||
|
||||
@parameterized.expand([
|
||||
CAR.CHEVROLET_BOLT_CC_2017,
|
||||
CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
CAR.CHEVROLET_BOLT_CC_2022_2023,
|
||||
])
|
||||
def test_bolt_cc_redneck_ignores_small_setpoint_error(self, car_model):
|
||||
packer = CANPacker(DBC[car_model][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=car_model,
|
||||
minEnableSpeed=24 * CV.MPH_TO_MS,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=(60.6 * CV.MPH_TO_MS) / 1.01,
|
||||
cruiseState=SimpleNamespace(speed=60 * CV.MPH_TO_MS),
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(packer, controller, cs, SimpleNamespace(accel=0.0), SimpleNamespace(is_metric=False))
|
||||
|
||||
assert msgs == []
|
||||
assert controller.apply_speed == 60
|
||||
|
||||
def test_non_bolt_cc_redneck_keeps_existing_setpoint_selector(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_EQUINOX_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_EQUINOX_CC,
|
||||
flags=GMFlags.CC_LONG.value,
|
||||
minEnableSpeed=24 * CV.MPH_TO_MS,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=(60.6 * CV.MPH_TO_MS) / 1.01,
|
||||
cruiseState=SimpleNamespace(speed=60 * CV.MPH_TO_MS),
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(packer, controller, cs, SimpleNamespace(accel=0.0), SimpleNamespace(is_metric=False))
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 61
|
||||
|
||||
def test_bolt_cc_redneck_requires_persistent_acceleration_after_deceleration(self):
|
||||
cp = SimpleNamespace(carFingerprint=CAR.CHEVROLET_BOLT_CC_2018_2021)
|
||||
controller = SimpleNamespace(
|
||||
frame=100,
|
||||
gm_cc_last_direction_button=CruiseButtons.DECEL_SET,
|
||||
gm_cc_last_direction_frame=100,
|
||||
gm_cc_pending_reverse_button=CruiseButtons.INIT,
|
||||
gm_cc_pending_reverse_frame=0,
|
||||
)
|
||||
|
||||
assert gmcan.stabilize_bolt_cc_button(controller, cp, CruiseButtons.RES_ACCEL) == CruiseButtons.INIT
|
||||
controller.frame += int(0.5 / DT_CTRL)
|
||||
assert gmcan.stabilize_bolt_cc_button(controller, cp, CruiseButtons.RES_ACCEL) == CruiseButtons.INIT
|
||||
controller.frame += int(0.11 / DT_CTRL)
|
||||
assert gmcan.stabilize_bolt_cc_button(controller, cp, CruiseButtons.RES_ACCEL) == CruiseButtons.RES_ACCEL
|
||||
|
||||
def test_bolt_cc_redneck_deceleration_is_not_debounced(self):
|
||||
cp = SimpleNamespace(carFingerprint=CAR.CHEVROLET_BOLT_CC_2018_2021)
|
||||
controller = SimpleNamespace(
|
||||
frame=100,
|
||||
gm_cc_last_direction_button=CruiseButtons.RES_ACCEL,
|
||||
gm_cc_last_direction_frame=100,
|
||||
gm_cc_pending_reverse_button=CruiseButtons.INIT,
|
||||
gm_cc_pending_reverse_frame=0,
|
||||
)
|
||||
|
||||
assert gmcan.stabilize_bolt_cc_button(controller, cp, CruiseButtons.DECEL_SET) == CruiseButtons.DECEL_SET
|
||||
|
||||
def test_xt4_cc_redneck_spam_matches_physical_button_burst(self):
|
||||
packer = CANPacker(DBC[CAR.CADILLAC_XT4_CC][Bus.pt])
|
||||
controller = SimpleNamespace(
|
||||
frame=int(0.3 / DT_CTRL),
|
||||
last_button_frame=0,
|
||||
apply_speed=0,
|
||||
malibu_button_phase=0,
|
||||
xt4_cc_button_burst_remaining=0,
|
||||
xt4_cc_button_burst_button=CruiseButtons.INIT,
|
||||
xt4_cc_button_burst_last_counter=-1,
|
||||
xt4_cc_button_observed_counter=-1,
|
||||
xt4_cc_button_counter_frame=0,
|
||||
)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CADILLAC_XT4_CC,
|
||||
flags=GMFlags.CC_LONG.value,
|
||||
minEnableSpeed=24 * CV.MPH_TO_MS,
|
||||
),
|
||||
buttons_counter=0,
|
||||
out=SimpleNamespace(
|
||||
vEgo=25.0,
|
||||
cruiseState=SimpleNamespace(speed=20.0),
|
||||
),
|
||||
)
|
||||
actuators = SimpleNamespace(accel=1.0)
|
||||
|
||||
dats = []
|
||||
send_counts = []
|
||||
for counter in (0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 0, 0, 0, 1, 1, 1):
|
||||
cs.buttons_counter = counter
|
||||
msgs = gmcan.create_gm_cc_spam_command(packer, controller, cs, actuators, SimpleNamespace(is_metric=False))
|
||||
send_counts.append(len(msgs))
|
||||
dats.extend(bytes(msg[1]).hex() for msg in msgs)
|
||||
controller.frame += 1
|
||||
|
||||
assert send_counts == [0, 1, 0] * gmcan.XT4_CC_BUTTON_BURST_FRAMES
|
||||
assert dats == [
|
||||
"000000010125de",
|
||||
"00000001022acd",
|
||||
"00000001032fbc",
|
||||
"000000010020ef",
|
||||
"000000010125de",
|
||||
"00000001022acd",
|
||||
]
|
||||
assert controller.xt4_cc_button_burst_remaining == 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]])
|
||||
|
||||
values = parser.vl["ASCMActiveCruiseControlStatus"]
|
||||
|
||||
assert values["ACCAlwaysOne"] == 1
|
||||
assert values["ACCAlwaysOne2"] == 1
|
||||
assert values["ACCCruiseState"] == 2
|
||||
assert values["ACCCmdActive"] == 1
|
||||
assert values["FCWAlert"] == 2
|
||||
|
||||
def test_acc_dashboard_command_allows_camera_acc_zero_reserved_bits(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_TRAILBLAZER][Bus.pt])
|
||||
parser = CANParser(DBC[CAR.CHEVROLET_TRAILBLAZER][Bus.pt], [("ASCMActiveCruiseControlStatus", 0)], 0)
|
||||
msg = gmcan.create_acc_dashboard_command(
|
||||
packer,
|
||||
0,
|
||||
True,
|
||||
67.1875,
|
||||
SimpleNamespace(leadDistanceBars=1, leadVisible=False),
|
||||
0,
|
||||
acc_always_one=0,
|
||||
)
|
||||
|
||||
parser.update([0, [msg]])
|
||||
values = parser.vl["ASCMActiveCruiseControlStatus"]
|
||||
|
||||
assert msg[1] == b"\x00\x02\x94\x33\x00\x00"
|
||||
assert values["ACCAlwaysOne"] == 0
|
||||
assert values["ACCAlwaysOne2"] == 0
|
||||
assert values["ACCCruiseState"] == 2
|
||||
assert values["ACCCmdActive"] == 1
|
||||
|
||||
def test_acc_dashboard_always_one_matches_camera_acc_platforms(self):
|
||||
assert get_acc_dashboard_always_one(SimpleNamespace(carFingerprint=CAR.CHEVROLET_TRAILBLAZER)) == 0
|
||||
assert get_acc_dashboard_always_one(SimpleNamespace(carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023)) == 1
|
||||
|
||||
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["ACCCruiseState"] == 2
|
||||
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,86 @@
|
||||
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_acc_2cd_command_matches_stock_camera_counter_layout(self):
|
||||
assert [gmcan.create_acc_2cd_command(0, idx)[1].hex() for idx in range(4)] == [
|
||||
"002c03d3fd",
|
||||
"402c03d3fc",
|
||||
"802c03d3fb",
|
||||
"c02c03d3fa",
|
||||
]
|
||||
|
||||
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",
|
||||
@@ -241,17 +241,17 @@ class CAR(Platforms):
|
||||
},
|
||||
)
|
||||
CHEVROLET_VOLT_ASCM = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Volt 2017-18 ASCM Harness", min_enable_speed=0, video="https://youtu.be/QeMCN_4TFfQ")],
|
||||
[GMCarDocs("Chevrolet Volt ASCM Harness 2017-18", min_enable_speed=0, video="https://youtu.be/QeMCN_4TFfQ")],
|
||||
CHEVROLET_VOLT.specs,
|
||||
dbc_dict=CHEVROLET_VOLT.dbc_dict,
|
||||
)
|
||||
CHEVROLET_VOLT_CAMERA = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Volt 2017-18 Camera Harness", "Flashed camera-forward integration with ACC")],
|
||||
[GMCarDocs("Chevrolet Volt Camera Harness 2017-18", "Flashed camera-forward integration with ACC")],
|
||||
CHEVROLET_VOLT.specs,
|
||||
dbc_dict=CHEVROLET_VOLT.dbc_dict,
|
||||
)
|
||||
CHEVROLET_VOLT_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Volt 2017-18 - No-ACC", min_enable_speed=0)],
|
||||
[GMCarDocs("Chevrolet Volt No-ACC 2017-18", min_enable_speed=0)],
|
||||
CHEVROLET_VOLT.specs,
|
||||
dbc_dict=CHEVROLET_VOLT.dbc_dict,
|
||||
)
|
||||
@@ -264,7 +264,7 @@ class CAR(Platforms):
|
||||
GMCarSpecs(mass=1496, wheelbase=2.83, steerRatio=15.8, centerToFrontRatio=0.4),
|
||||
)
|
||||
CHEVROLET_MALIBU_ASCM = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Malibu 2017-19 ASCM Harness")],
|
||||
[GMCarDocs("Chevrolet Malibu ASCM Harness 2017-19")],
|
||||
CHEVROLET_MALIBU.specs,
|
||||
)
|
||||
GMC_ACADIA = GMASCMPlatformConfig(
|
||||
@@ -272,13 +272,21 @@ class CAR(Platforms):
|
||||
GMCarSpecs(mass=1975, wheelbase=2.86, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
)
|
||||
GMC_ACADIA_ASCM = GMPlatformConfig(
|
||||
[GMCarDocs("GMC Acadia 2018 ASCM Harness", video="https://www.youtube.com/watch?v=0ZN6DdsBUZo")],
|
||||
[GMCarDocs("GMC Acadia ASCM Harness 2018", video="https://www.youtube.com/watch?v=0ZN6DdsBUZo")],
|
||||
GMC_ACADIA.specs,
|
||||
)
|
||||
BUICK_LACROSSE = GMASCMPlatformConfig(
|
||||
[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 ASCM Harness 2017-19")],
|
||||
BUICK_LACROSSE.specs,
|
||||
)
|
||||
BUICK_LACROSSE_ASCM_19US = GMPlatformConfig(
|
||||
[GMCarDocs("Buick LaCrosse US ASCM Harness 2019")],
|
||||
BUICK_LACROSSE.specs,
|
||||
)
|
||||
BUICK_REGAL = GMASCMPlatformConfig(
|
||||
[GMCarDocs("Buick Regal Essence 2018")],
|
||||
GMCarSpecs(mass=1714, wheelbase=2.83, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
@@ -288,7 +296,7 @@ class CAR(Platforms):
|
||||
GMCarSpecs(mass=2564, wheelbase=2.95, steerRatio=17.3),
|
||||
)
|
||||
CADILLAC_ESCALADE_ASCM = GMPlatformConfig(
|
||||
[GMCarDocs("Cadillac Escalade 2018 ASCM Harness", "Driver Assist Package")],
|
||||
[GMCarDocs("Cadillac Escalade ASCM Harness 2018", "Driver Assist Package")],
|
||||
CADILLAC_ESCALADE.specs,
|
||||
)
|
||||
CADILLAC_ESCALADE_ESV = GMASCMPlatformConfig(
|
||||
@@ -299,6 +307,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 ASCM Harness 2019", "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"),
|
||||
@@ -306,19 +318,19 @@ class CAR(Platforms):
|
||||
GMCarSpecs(mass=1669, wheelbase=2.63779, steerRatio=16.8, centerToFrontRatio=0.4, tireStiffnessFactor=1.0),
|
||||
)
|
||||
CHEVROLET_BOLT_ACC_2022_2023_PEDAL = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Bolt EV 2022-23 ACC w Pedal")],
|
||||
[GMCarDocs("Chevrolet Bolt EV ACC w Pedal 2022-23")],
|
||||
CHEVROLET_BOLT_ACC_2022_2023.specs,
|
||||
)
|
||||
CHEVROLET_BOLT_CC_2022_2023 = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Bolt EV 2022-23 - No-ACC")],
|
||||
[GMCarDocs("Chevrolet Bolt EV No-ACC 2022-23")],
|
||||
CHEVROLET_BOLT_ACC_2022_2023.specs,
|
||||
)
|
||||
CHEVROLET_BOLT_CC_2018_2021 = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Bolt EV 2018-21 - No-ACC")],
|
||||
[GMCarDocs("Chevrolet Bolt EV No-ACC 2018-21")],
|
||||
CHEVROLET_BOLT_ACC_2022_2023.specs,
|
||||
)
|
||||
CHEVROLET_BOLT_CC_2017 = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Bolt EV 2017 - No-ACC")],
|
||||
[GMCarDocs("Chevrolet Bolt EV No-ACC 2017")],
|
||||
CHEVROLET_BOLT_ACC_2022_2023.specs,
|
||||
)
|
||||
|
||||
@@ -329,6 +341,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 2020-21"),
|
||||
GMCarDocs("GMC Sierra 1500 No-ACC 2020-21"),
|
||||
],
|
||||
CHEVROLET_SILVERADO.specs,
|
||||
)
|
||||
CHEVROLET_EQUINOX = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Equinox 2019-22")],
|
||||
GMCarSpecs(mass=1588, wheelbase=2.72, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
@@ -342,7 +361,7 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=2731, wheelbase=3.302, steerRatio=17.3, centerToFrontRatio=0.49),
|
||||
)
|
||||
GMC_YUKON_CC = GMPlatformConfig(
|
||||
[GMCarDocs("GMC Yukon - No-ACC")],
|
||||
[GMCarDocs("GMC Yukon No-ACC 2019-20")],
|
||||
CarSpecs(mass=2541, wheelbase=2.95, steerRatio=16.3, centerToFrontRatio=0.4),
|
||||
)
|
||||
GMC_YUKON = GMPlatformConfig(
|
||||
@@ -353,6 +372,14 @@ class CAR(Platforms):
|
||||
[GMCarDocs("Cadillac XT4 2023", "Driver Assist Package")],
|
||||
GMCarSpecs(mass=1660, wheelbase=2.78, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
)
|
||||
CADILLAC_XT4_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Cadillac XT4 No-ACC 2023")],
|
||||
CADILLAC_XT4.specs,
|
||||
)
|
||||
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),
|
||||
@@ -378,31 +405,31 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=2050, wheelbase=2.86, steerRatio=16.0, centerToFrontRatio=0.5),
|
||||
)
|
||||
CADILLAC_CT6_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Cadillac CT6 - No-ACC")],
|
||||
[GMCarDocs("Cadillac CT6 No-ACC 2016-20")],
|
||||
CarSpecs(mass=2358, wheelbase=3.11, steerRatio=17.7, centerToFrontRatio=0.4),
|
||||
)
|
||||
CADILLAC_XT5_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Cadillac XT5 - No-ACC")],
|
||||
CarSpecs(mass=1810, wheelbase=2.86, steerRatio=16.34, centerToFrontRatio=0.5),
|
||||
[GMCarDocs("Cadillac XT5 No-ACC 2022")],
|
||||
CADILLAC_XT5.specs,
|
||||
)
|
||||
CHEVROLET_EQUINOX_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Equinox 2019-22 - No-ACC")],
|
||||
[GMCarDocs("Chevrolet Equinox No-ACC 2019-22")],
|
||||
CHEVROLET_EQUINOX.specs,
|
||||
)
|
||||
CHEVROLET_MALIBU_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Malibu 2023 - No-ACC")],
|
||||
[GMCarDocs("Chevrolet Malibu No-ACC 2023")],
|
||||
CarSpecs(mass=1450, wheelbase=2.8, steerRatio=18.25, centerToFrontRatio=0.4, tireStiffnessFactor=0.997),
|
||||
)
|
||||
CHEVROLET_MALIBU_HYBRID_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Malibu Hybrid 2017 - No-ACC")],
|
||||
[GMCarDocs("Chevrolet Malibu Hybrid No-ACC 2017")],
|
||||
CarSpecs(mass=1450, wheelbase=2.8, steerRatio=15.8, centerToFrontRatio=0.4),
|
||||
)
|
||||
CHEVROLET_SUBURBAN_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Suburban Premier 2016-20 - No-ACC")],
|
||||
[GMCarDocs("Chevrolet Suburban Premier No-ACC 2016-20")],
|
||||
CHEVROLET_SUBURBAN.specs,
|
||||
)
|
||||
CHEVROLET_TRAILBLAZER_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Trailblazer 2021-22 - No-ACC")],
|
||||
[GMCarDocs("Chevrolet Trailblazer No-ACC 2021-22")],
|
||||
CHEVROLET_TRAILBLAZER.specs,
|
||||
)
|
||||
CHEVROLET_TRAX = GMPlatformConfig(
|
||||
@@ -527,6 +554,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 +577,8 @@ CC_ONLY_CAR = {
|
||||
CAR.CADILLAC_XT5_CC,
|
||||
CAR.CHEVROLET_MALIBU_CC,
|
||||
CAR.CHEVROLET_MALIBU_HYBRID_CC,
|
||||
CAR.CHEVROLET_SILVERADO_CC,
|
||||
CAR.CADILLAC_XT4_CC,
|
||||
}
|
||||
CC_REGEN_PADDLE_CAR = {
|
||||
CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
@@ -559,7 +589,15 @@ 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,
|
||||
CAR.BUICK_LACROSSE_ASCM_19US,
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -184,22 +201,33 @@ class CarState(CarStateBase):
|
||||
if self.CP.flags & HondaFlags.BOSCH_ALT_BRAKE:
|
||||
ret.brakePressed = cp.vl["BRAKE_MODULE"]["BRAKE_PRESSED"] != 0
|
||||
else:
|
||||
powertrain_data = cp.vl["POWERTRAIN_DATA"]
|
||||
# brake switch has shown some single time step noise, so only considered when
|
||||
# switch is on for at least 2 consecutive CAN samples
|
||||
# brake switch rises earlier than brake pressed but is never 1 when in park
|
||||
brake_switch_vals = cp.vl_all["POWERTRAIN_DATA"]["BRAKE_SWITCH"]
|
||||
if len(brake_switch_vals):
|
||||
brake_switch = cp.vl["POWERTRAIN_DATA"]["BRAKE_SWITCH"] != 0
|
||||
brake_switch = powertrain_data["BRAKE_SWITCH"] != 0
|
||||
if len(brake_switch_vals) > 1:
|
||||
self.brake_switch_prev = brake_switch_vals[-2] != 0
|
||||
self.brake_switch_active = brake_switch and self.brake_switch_prev
|
||||
self.brake_switch_prev = brake_switch
|
||||
ret.brakePressed = (cp.vl["POWERTRAIN_DATA"]["BRAKE_PRESSED"] != 0) or self.brake_switch_active
|
||||
ret.brakePressed = (powertrain_data["BRAKE_PRESSED"] != 0) or self.brake_switch_active
|
||||
|
||||
ret.brake = cp.vl["VSA_STATUS"]["USER_BRAKE"]
|
||||
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 +246,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,12 +263,21 @@ 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
|
||||
|
||||
def get_can_parsers(self, CP):
|
||||
pt_messages = [("GAS_SENSOR", 0)] if CP.enableGasInterceptorDEPRECATED else []
|
||||
pt_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).pt)
|
||||
if CP.enableGasInterceptorDEPRECATED:
|
||||
pt_parser.message_states[0x201].ignore_checksum = True
|
||||
pt_parser.message_states[0x201].ignore_counter = True
|
||||
|
||||
parsers = {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).pt),
|
||||
Bus.pt: pt_parser,
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).camera),
|
||||
}
|
||||
if CP.enableBsm:
|
||||
|
||||
@@ -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,13 @@ FW_VERSIONS = {
|
||||
b'57114-TGL-G330\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TBA-C020\x00\x00',
|
||||
b'39990-TBA-C120\x00\x00',
|
||||
b'39990-TBA-C020\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 +395,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),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user