mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-08 18:13:45 +08:00
Compare commits
862 Commits
sync+tg
...
hiimisaac-dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 72e9d94f62 | |||
| 7e63449749 | |||
| 01f8d51c82 | |||
| 744a97d9bc | |||
| 5fc16abc76 | |||
| ea1ed70c71 | |||
| 7ca3c6e3b3 | |||
| c4b3c55c82 | |||
| b3bc05acd4 | |||
| 6135084c94 | |||
| dfcfddb91c | |||
| 61dac4977b | |||
| 09acf8ec2f | |||
| 79a4caa1f6 | |||
| 0ace0b0510 | |||
| 047ae41c0d | |||
| 7eb457f6c4 | |||
| 98662df401 | |||
| 302f3ad892 | |||
| 10e354d668 | |||
| 7d558c0650 | |||
| daeb966d05 | |||
| 727c26ce8c | |||
| 614022defb | |||
| 5e67122e64 | |||
| 25d095177e | |||
| 3eb7938aad | |||
| a525905708 | |||
| e298864a50 | |||
| 8639bdcca4 | |||
| 132b31f4cf | |||
| 752c07f9e4 | |||
| 458a3015cd | |||
| e87dbbaba7 | |||
| 15efdb392f | |||
| f5bb855477 | |||
| 336ce75f3d | |||
| 517c15f9c2 | |||
| 47db84ebfb | |||
| aa73207ab8 | |||
| 68be777395 | |||
| ab389498a8 | |||
| 184b73d8de | |||
| 6249f4d5b0 | |||
| 8b88f7dd6e | |||
| 6a1b697ed3 | |||
| 7eb7e93deb | |||
| 79658800ce | |||
| 36561258fa | |||
| 693daf9866 | |||
| cb85ac1f0e | |||
| afcc2b9455 | |||
| 3fdab7e8f0 | |||
| c9f1602040 | |||
| f488bfc806 | |||
| fd62fed669 | |||
| 3a665737c2 | |||
| 06af2abe67 | |||
| 7d5596d5c3 | |||
| b6a87b8958 | |||
| a2e422eee0 | |||
| 51987a62d0 | |||
| a1dcec490f | |||
| e10c0fd960 | |||
| 98ed8111f6 | |||
| 0729ce7c08 | |||
| 916fb1d522 | |||
| af5e7f5327 | |||
| bf2e9ca318 | |||
| 41b433c619 | |||
| da8ce858ec | |||
| 9fa7ef3d17 | |||
| ed56f3ff7c | |||
| 5865ad108c | |||
| 8ed82eae6f | |||
| 88f6f66032 | |||
| 33e70080ad | |||
| 4adbb85742 | |||
| b7f0e3fbdc | |||
| 70df7f227b | |||
| de197ba6fa | |||
| 7f371b8acd | |||
| 0e32059484 | |||
| 24c858e618 | |||
| 405407c252 | |||
| d49b56bff5 | |||
| f8d8b8ee56 | |||
| 8774a462ac | |||
| 1b41e9637f | |||
| 27a220677a | |||
| 7cf55c3b7a | |||
| 26e4889fcb | |||
| 682b6a20df | |||
| a67cdf9a51 | |||
| 70fa5d0fca | |||
| e571e21d14 | |||
| 839d3f5004 | |||
| 5645370f84 | |||
| 633d17cd12 | |||
| 5419f57b3a | |||
| 1dd5a7c91d | |||
| 46f612224c | |||
| 6e0f4f4630 | |||
| 0f9c753e6e | |||
| acb784d207 | |||
| 131e473f37 | |||
| 2d700cc0d0 | |||
| 30f358eb59 | |||
| 9b9e3ea604 | |||
| cc9ae66b22 | |||
| 505270420f | |||
| bb1a17d2a0 | |||
| 7cc48b5bc9 | |||
| cbf750de20 | |||
| 6db807b5a0 | |||
| 42e1414bc4 | |||
| 3e020e321f | |||
| 7e2000e909 | |||
| e96055846c | |||
| d47646b28f | |||
| e75bc83424 | |||
| 318257fa3b | |||
| 08e48958b6 | |||
| 4cdc16031f | |||
| 31ea1850f7 | |||
| 25d0d0f1ff | |||
| 4075befc5e | |||
| 9f43d2477d | |||
| 2d6cc4c065 | |||
| 4a13639cfd | |||
| fa75fdd852 | |||
| 5cfdb2f4da | |||
| 63548ce10d | |||
| 980fb79c1a | |||
| d40df6f829 | |||
| da28afca91 | |||
| 15f201caed | |||
| 1d4558c067 | |||
| 78a766eb61 | |||
| b742b96c44 | |||
| 25c25047b8 | |||
| cefe5737b9 | |||
| 760c19d3f9 | |||
| 45814e3313 | |||
| 2ba91d2be5 | |||
| 19f83b274f | |||
| d14d0b1dd0 | |||
| 6cc5f3aad8 | |||
| 8e16c9babb | |||
| 2bcfed5c71 | |||
| 66cf334067 | |||
| 94ed0608e6 | |||
| 0fbca979df | |||
| dcddb2a0bd | |||
| 699eaf7957 | |||
| c246e6318a | |||
| 718db8c62e | |||
| c2214d4c32 | |||
| 0de7fbf33d | |||
| 211f990f6b | |||
| 97468e4fa4 | |||
| 6c6fba9a14 | |||
| 34621cf816 | |||
| 086530b7c6 | |||
| 4f46433e2b | |||
| 5a8567e3e7 | |||
| 07558166c8 | |||
| ca9338812e | |||
| 4667241fe7 | |||
| 084747c75d | |||
| a49c260927 | |||
| 5ad2bfdb75 | |||
| b742557d62 | |||
| 5ecd05aedf | |||
| 5ae100aa1d | |||
| be76a88b80 | |||
| 049d225d5a | |||
| c783f2225a | |||
| 53e13a7bc0 | |||
| 555f48c5d2 | |||
| dcf9d25bf3 | |||
| a8d1a280c6 | |||
| 5b36799eec | |||
| ba29a38507 | |||
| 20fdc3d824 | |||
| ed35a82129 | |||
| 7bd6cad821 | |||
| 8edce0da44 | |||
| 3c90b66b65 | |||
| 08c83149b0 | |||
| 03711a13b0 | |||
| 9f1709a7e1 | |||
| 2b576c5fce | |||
| 20ba774eaa | |||
| 59833c500a | |||
| 3d09a47a47 | |||
| 2f4744d39b | |||
| 6dd3457f4f | |||
| b7657f6553 | |||
| b8e14d85fb | |||
| 0b1ed0d047 | |||
| 9fb45b315a | |||
| 94a32493e3 | |||
| 053d9c4468 | |||
| 85d364d4de | |||
| dfbe0ee7c1 | |||
| 03e6c81821 | |||
| 047be14df9 | |||
| 351701689f | |||
| ec86732af8 | |||
| 0f40ca1d88 | |||
| 91d0f3309c | |||
| 73fc740831 | |||
| 97542f838f | |||
| dcbd66ad81 | |||
| 391132465d | |||
| a996f8ef90 | |||
| 6ad3532113 | |||
| 48b7f171a7 | |||
| 5e3d17c72c | |||
| fb555fdefd | |||
| 28560d6cf1 | |||
| 748c725e3e | |||
| 3447ec1789 | |||
| 76b69af59a | |||
| d9c4120f89 | |||
| df4566ef2f | |||
| d48cfa730b | |||
| 516ec1e682 | |||
| 6e69da4037 | |||
| 6405f15d5a | |||
| ac4ab9a9b8 | |||
| 7461f70fdb | |||
| c988e78893 | |||
| bdc8e4b02c | |||
| b7c333cf3f | |||
| fe47e752f6 | |||
| 10502adf95 | |||
| 7a76a50c4f | |||
| 927e822b5a | |||
| f1746f2e2d | |||
| 473ab17876 | |||
| ebc77e4c90 | |||
| ad5afe222d | |||
| 32b518d51d | |||
| 2d859a8ca6 | |||
| 062cb672e8 | |||
| 122a8ca00a | |||
| b1cdf387b5 | |||
| b5b156825d | |||
| eecff73850 | |||
| 6d5d1f6917 | |||
| 434fbad5fc | |||
| 65c007a9a2 | |||
| 2a16b0fbba | |||
| ea4dbc8936 | |||
| c6595fd99b | |||
| 617fcd4084 | |||
| 855c99bf92 | |||
| 91a53aa161 | |||
| fc4699a747 | |||
| 9e32dee281 | |||
| f8d50e062d | |||
| f531952be3 | |||
| fedea9fe8d | |||
| 9738db0e39 | |||
| df0bd5e6e3 | |||
| cf790746cc | |||
| 6f2a1d573c | |||
| 5701fcad05 | |||
| 59f01d212a | |||
| e32b1a344d | |||
| d02355b1a5 | |||
| b755d32276 | |||
| cbda3f7994 | |||
| a75cd2d277 | |||
| 8a88608800 | |||
| 4eb515b32d | |||
| 612d97cfdb | |||
| edd938105e | |||
| 5fadc71a3a | |||
| 0819f5c0fd | |||
| 66b3590b63 | |||
| b77da00697 | |||
| e6939dbede | |||
| 96ca1f8ed5 | |||
| 6f94f217ff | |||
| 03803d0c82 | |||
| be191eb8ac | |||
| 4e9e9190a5 | |||
| ff9c703289 | |||
| 2f4981045b | |||
| faf966adb2 | |||
| bd7b419a4e | |||
| 9bf834e64e | |||
| 4adf52fc6f | |||
| 76e900d3ad | |||
| 155d206b2b | |||
| dd2214a78c | |||
| d252ab5b42 | |||
| 2002a64d3b | |||
| 9e0d89968d | |||
| fec5a97a9e | |||
| 438035ec17 | |||
| 5406aba1a2 | |||
| 17150b6db0 | |||
| e7b1ee3a5b | |||
| 099143ad9d | |||
| 6909aa95ff | |||
| 5bdc0c23a9 | |||
| d86686fffd | |||
| a7f32be2fd | |||
| 4db21dd4f8 | |||
| aa0ac919ba | |||
| 2e4de38eb8 | |||
| ed777cf5e2 | |||
| f7bd4002d6 | |||
| 38ef0d2398 | |||
| b361e952c9 | |||
| d4211790da | |||
| b614253eeb | |||
| 6f88b59fdd | |||
| e04313cc91 | |||
| 7ef4304e2e | |||
| afa2b390ae | |||
| 04847f3805 | |||
| 9002016ac5 | |||
| 54a7e8926f | |||
| 20eb0d733a | |||
| f09211fcc5 | |||
| 7dbdff832f | |||
| 88c8f5a520 | |||
| 1a07e47228 | |||
| 5037016901 | |||
| 50b860c928 | |||
| bf74ce5447 | |||
| 978ec800fe | |||
| 3643dfbef0 | |||
| cc83d8615d | |||
| d3058de5c1 | |||
| a2ee4dfffc | |||
| b72f6e9f7d | |||
| d4e29ec945 | |||
| e3ba6492f9 | |||
| caee482fe7 | |||
| 1c54ff853a | |||
| c9f0d296b1 | |||
| 6ac27d491b | |||
| 47a7524595 | |||
| 0088cb7ef0 | |||
| 3ec980477d | |||
| e4d152e1a7 | |||
| 4cfdcea1d1 | |||
| 952d2cab8a | |||
| 799551c2d6 | |||
| 0b7bd2561d | |||
| 83000c571c | |||
| 294635beb5 | |||
| 3040f55382 | |||
| b4c9403211 | |||
| e04bb46b68 | |||
| e43de73311 | |||
| 02b6cef15c | |||
| 93f5aa469a | |||
| 74009870ab | |||
| 1d7b6f0f9f | |||
| 98e7c4f987 | |||
| 4dab10e98b | |||
| df90fc2526 | |||
| f29eb5d25c | |||
| 0bf99879d1 | |||
| e1db0f59f1 | |||
| 5bcff3f87d | |||
| 9a39ccc66f | |||
| 3a05c03079 | |||
| 27122bbd28 | |||
| fd22de1c9a | |||
| ee3583df33 | |||
| 7801bdf0cc | |||
| 0265ae5f76 | |||
| 9163d1cb74 | |||
| c37855d113 | |||
| 9225dac433 | |||
| a0cc313fdc | |||
| 93bb596a2e | |||
| 41b14fa595 | |||
| ee6f374621 | |||
| 0fdfaf737f | |||
| 71e29583cb | |||
| 171f271d11 | |||
| 1a753ad9ae | |||
| 9c312e91e7 | |||
| 4d4d6803e5 | |||
| 9c21b92c4d | |||
| ab7284fc8a | |||
| 554d8d2114 | |||
| b392328c35 | |||
| ddf782f08a | |||
| e04fd3c724 | |||
| 6b47a5b6b7 | |||
| 3601b850c2 | |||
| cfa8e5bf03 | |||
| e1c69719c6 | |||
| a8bb6b5aee | |||
| 87fde8a058 | |||
| 66300306f4 | |||
| 2895346746 | |||
| 49c3b8fc34 | |||
| 0bac3c9039 | |||
| 6335db69bd | |||
| 05f42f752b | |||
| 1791057369 | |||
| 736f3b1a00 | |||
| 2a1c44c904 | |||
| 74ac5ef9a0 | |||
| aac9d9ecf4 | |||
| 08fd571272 | |||
| 75d590fb90 | |||
| 576de9c7ed | |||
| 86753ea93d | |||
| c9b9fd56ea | |||
| 765d6fff9b | |||
| 9e02936717 | |||
| f1977eaa93 | |||
| eb6c9c7f0d | |||
| 1262e26899 | |||
| 911f07ee87 | |||
| ebb5cc12f5 | |||
| fdd1df79fb | |||
| 031b1ad0a3 | |||
| b1f2e638de | |||
| c20263d985 | |||
| 5472e69e35 | |||
| 2c334ede44 | |||
| 78909dac73 | |||
| 24a9b6dae5 | |||
| bf1b93f757 | |||
| bae7e49626 | |||
| 560cde61ae | |||
| d9596fa998 | |||
| e124d6df9b | |||
| 3dbf02f803 | |||
| fef29ad225 | |||
| 157c7080ce | |||
| caa9e770cc | |||
| 24893ebadb | |||
| 39e12c8bb1 | |||
| f0d93eb32d | |||
| 19ecc37de8 | |||
| ecac2d386b | |||
| e475d10adc | |||
| 7d74c3c99b | |||
| cefcf10ec3 | |||
| 1c07e00759 | |||
| f0841b827a | |||
| 3f49e2d33c | |||
| 3f93b00120 | |||
| c21b0821da | |||
| b9f25f8a43 | |||
| c8786d930d | |||
| 3a55f31dc5 | |||
| a04c045cd7 | |||
| 9aa5c3d2c6 | |||
| 5d23a78c77 | |||
| 06a73f538e | |||
| 60716edc37 | |||
| 14318d2f09 | |||
| 61608db786 | |||
| 70f2c3ac79 | |||
| a43fc920b5 | |||
| 91b067cca0 | |||
| 41886a90da | |||
| 680dd7cf3b | |||
| db99c8e5a6 | |||
| 48a5bdc8e4 | |||
| 79b79edd2f | |||
| 2b1fd89064 | |||
| ec72ee096d | |||
| eee13472ce | |||
| d275c65f75 | |||
| a9ffebe96e | |||
| 45661ee8c0 | |||
| c16039e0bc | |||
| d1e143ac98 | |||
| 45b53cf66a | |||
| cf8371c2c5 | |||
| f42dbbb01c | |||
| 4fd4ddb438 | |||
| 503531ab38 | |||
| a52da0d3df | |||
| a26254304f | |||
| c91731b13a | |||
| 60924556f6 | |||
| ee11c24cf4 | |||
| 6392096de5 | |||
| 39117a5879 | |||
| 1478d0c476 | |||
| f283f6703c | |||
| b827c0f55e | |||
| bf5540c364 | |||
| 3b3f5967ec | |||
| 1e49eac4d1 | |||
| 9877f6ac0e | |||
| 1ab1ed7453 | |||
| cb82722dd5 | |||
| 960c988355 | |||
| 05cf8023a9 | |||
| 1b6e127e68 | |||
| c02cf706a6 | |||
| 0d93f0e820 | |||
| 6de5d2c23a | |||
| 92f3d24c8a | |||
| d606014ce2 | |||
| 2aa934e7c2 | |||
| 70a6efb8fc | |||
| 07ec389f4c | |||
| 9d03e737dd | |||
| 4532320fb3 | |||
| 4c5bef5840 | |||
| 8e0ba7a91e | |||
| fa0c6876d3 | |||
| a2c554805b | |||
| e1e9efb965 | |||
| 8196b743af | |||
| 724971afc3 | |||
| 4fc5e308f3 | |||
| 8a3bbcdd4e | |||
| 8c927d0fc2 | |||
| 97215b17d7 | |||
| 31dc4d8e52 | |||
| 61b6d52b01 | |||
| da6313dbe9 | |||
| e9e5548edc | |||
| f9c555a3fe | |||
| 069506aa36 | |||
| 5629b5a83d | |||
| addca46f68 | |||
| 7d325d6650 | |||
| 288aa60bf4 | |||
| cddb6103eb | |||
| 1474d5474d | |||
| 2e9fa514bb | |||
| 8a9ca15464 | |||
| 5edc0bd89d | |||
| 20e0f21b58 | |||
| 37eda06c95 | |||
| df1663c58d | |||
| bc168bb33a | |||
| d79267fa22 | |||
| cea5273f1f | |||
| d8bd6c784e | |||
| ad5151b38b | |||
| fddb9fb31c | |||
| b871364e1f | |||
| f0831e549b | |||
| 7cee534b72 | |||
| e0e9f1a4d3 | |||
| 160942dfde | |||
| 52e25ebe4e | |||
| 3b4077d31b | |||
| f0f7b877c3 | |||
| cb6e422c91 | |||
| f48e99b335 | |||
| b2a7084906 | |||
| 62b97fabf7 | |||
| f257544f1b | |||
| 45d8bcd7f3 | |||
| 0038d84e13 | |||
| cd2c590d50 | |||
| ddc8d37136 | |||
| d4db600d4b | |||
| 5ab7e48479 | |||
| e2e3703ae4 | |||
| 3939f1e92c | |||
| 82a945d145 | |||
| 93224b3c05 | |||
| 8a80bd70e7 | |||
| 29e7f362ed | |||
| 3c4790c089 | |||
| c0456590f6 | |||
| f3b1f97afa | |||
| 4cefe7239d | |||
| 586dd4c611 | |||
| 291315ab25 | |||
| 37390743c1 | |||
| 15fb1a809d | |||
| 0240a62fd9 | |||
| fafcee04f4 | |||
| 2feca929ab | |||
| a40fa3a0b0 | |||
| f9cc67896c | |||
| cd052f124e | |||
| 14cff000e9 | |||
| 9ef3cfdf9a | |||
| 4024d13dda | |||
| b2bb71b4b7 | |||
| 98e5f547ea | |||
| 5a7b710d90 | |||
| 7661e03d1e | |||
| bdac9efa1e | |||
| 433c52f623 | |||
| 24d373062d | |||
| 41390a95b8 | |||
| 57b5eb3113 | |||
| 01a843e0ac | |||
| 097dd9b5f2 | |||
| 122ac986de | |||
| bc27262a92 | |||
| 7abe0205fd | |||
| ef3ec30fe2 | |||
| 066ba92e77 | |||
| dfb21bd53e | |||
| b0600a4225 | |||
| cc110d1eb1 | |||
| bfd8d4868d | |||
| 14a00e0cc8 | |||
| 46b9253729 | |||
| 40875ef72f | |||
| 268126f376 | |||
| 900a896c63 | |||
| c01b8be968 | |||
| 094591793a | |||
| f02d134f40 | |||
| 36a9c02b8f | |||
| 3137a32db4 | |||
| 5b3d5f74ed | |||
| 866cd01f32 | |||
| d09a411cb4 | |||
| 69e2c321e4 | |||
| c87f613659 | |||
| 249cafe897 | |||
| 0f17a98793 | |||
| 04e2351246 | |||
| bd148abbc4 | |||
| c76f481ddc | |||
| ce08f5290b | |||
| bc2b0d8272 | |||
| 965f47efe3 | |||
| 5bbcc32b5d | |||
| 517b9a2d5f | |||
| 5063c2055f | |||
| 9f3448f662 | |||
| 609a5c3cfa | |||
| d937401511 | |||
| 8499de6afe | |||
| 3452e878a7 | |||
| 804d9016ae | |||
| ad522f8444 | |||
| 9f59a4a5ca | |||
| 417efb3c05 | |||
| 4df40d2c19 | |||
| 069b912056 | |||
| a8772eb0af | |||
| a81617d8ee | |||
| 5408c86b7b | |||
| 4cd93f3eee | |||
| dfc3c98b22 | |||
| 4585e93066 | |||
| 5af72796f2 | |||
| ccac1e28de | |||
| bdca897248 | |||
| b93d166ff8 | |||
| d9553eeec7 | |||
| 9d74412fec | |||
| 95f562c298 | |||
| d489dd8909 | |||
| 1f227bbe20 | |||
| 9844075bb2 | |||
| a3cc9c7ac3 | |||
| aff9f9ffae | |||
| 38faa7c2cb | |||
| 52e182611d | |||
| 82338fd8c5 | |||
| 12aaacdffc | |||
| 6941a913a3 | |||
| 27e37f9d95 | |||
| 43d61f043c | |||
| 77017a913a | |||
| 24ff455bc0 | |||
| 2920503f94 | |||
| 46e3c907f6 | |||
| 2ed88a1dff | |||
| 107a6f4c00 | |||
| 059d0b6c4c | |||
| 65405bafa6 | |||
| d4a83deb7d | |||
| dd58eb68f0 | |||
| 4cfd774855 | |||
| e9cf5d67cf | |||
| 74554a523f | |||
| 2d4ac33ed7 | |||
| c9d77fb3fb | |||
| a432afd173 | |||
| c51ffe3808 | |||
| ef94b134c3 | |||
| f24ad7e27a | |||
| b88adeb4ad | |||
| a15aed1a79 | |||
| aa26dded8b | |||
| a06f31b472 | |||
| b3ed39525d | |||
| 57c44831da | |||
| 8ebc51a8aa | |||
| 4ecbdb0d7a | |||
| 4b945a1b79 | |||
| e8a03f7f32 | |||
| 69d3066d82 | |||
| 9574eee0e3 | |||
| 492ed73127 | |||
| af92603d17 | |||
| ecb661fe85 | |||
| dff6a80faf | |||
| 3a764c0ae3 | |||
| 11c14a138f | |||
| bdee8731b7 | |||
| 4b81dda1b5 | |||
| edc3ce89fa | |||
| 02f66e6e84 | |||
| 15267e4082 | |||
| 628e230b63 | |||
| 98512fc62b | |||
| 38ffb324f8 | |||
| 78007e82e0 | |||
| 534fb19714 | |||
| 294cb687f6 | |||
| 2a86d0cea5 | |||
| bbe5b38643 | |||
| 2691aa8e39 | |||
| da62722d07 | |||
| f6e2dd280d | |||
| 8583826166 | |||
| 93ed08ba20 | |||
| 6b6b7f0f33 | |||
| bea893820e | |||
| e624da2eed | |||
| 656de3f17b | |||
| 63508d0481 | |||
| b1a6223b14 | |||
| e771dfa007 | |||
| d7c562e130 | |||
| f87bc52405 | |||
| 1268227ce5 | |||
| 76f1f189db | |||
| 9fdcbae8de | |||
| f18aa113a5 | |||
| a3d3d0fea6 | |||
| 5745909e9b | |||
| fd37cd1d03 | |||
| ab1a962803 | |||
| 32671d1c3f | |||
| 01e7606b70 | |||
| bd1c7f39ec | |||
| c28eb95874 | |||
| a544cd7d39 | |||
| b7725c5cbb | |||
| 7ed960f713 | |||
| 6420e8d92a | |||
| 695a2d783f | |||
| 2596de8543 | |||
| 7a6dc19104 | |||
| 7e2b8430c5 | |||
| 521fa09b0d | |||
| 5adcff1221 | |||
| b9aa1962ca | |||
| 6b1b6aca05 | |||
| 41a8bc3fc4 | |||
| 540f4f5933 | |||
| 0e58ac33ad | |||
| 53e5ae0578 | |||
| dd0690da6f | |||
| 57d0a58855 | |||
| f64f3944a6 | |||
| 65b2bfe5d8 | |||
| 1caae26cfc | |||
| 94cf600bfe | |||
| 4a1b721636 | |||
| 55f033ced0 | |||
| 2182be05ea | |||
| 3e44c90c68 | |||
| 2d35bd895f | |||
| 855d5022ad | |||
| 6a363365ab | |||
| d238a1ccc4 | |||
| 1eeba86ec1 | |||
| 96d55a3283 | |||
| 5752095bc2 | |||
| 8560f2732a | |||
| b8bcf32457 | |||
| 1a93104bfd | |||
| ab43fd1369 | |||
| 605dfaa1a9 | |||
| b279aa6015 | |||
| 0f542bef38 | |||
| f364110a36 | |||
| 635a3bc7ab | |||
| 56b3b4d245 | |||
| 168b9831b2 | |||
| d20794f900 | |||
| 7b79305d99 | |||
| dd7da3153a | |||
| 39849def72 | |||
| 4db23ed4c6 | |||
| a4e016c142 | |||
| 61f7c7ea03 | |||
| 82a959ef8e | |||
| b3f369612d | |||
| ae0962cd0c | |||
| f00ff77c55 | |||
| dab51a864f | |||
| 360fd1555f | |||
| 813d794f05 | |||
| ddb9039493 | |||
| 0b7df7df10 | |||
| dd3feac854 | |||
| f7644c913e | |||
| 13ea74bcb4 | |||
| dc6e644359 | |||
| 85f00ff80a | |||
| 3af3c7e748 | |||
| 545ad018e0 | |||
| e86d4e86a6 | |||
| 7002d24213 | |||
| d1e069210f | |||
| ee54e82090 | |||
| 79cd8420eb | |||
| 8c533b14c0 | |||
| 494eba5961 | |||
| ad875632ac | |||
| 63068481d7 | |||
| 275206c14d | |||
| c3b0f0d11a | |||
| 1c69770c53 | |||
| 551e2f77bf | |||
| ad04c6a038 | |||
| bb4b96e05d | |||
| 7d71354fd0 | |||
| 49685fc2bc | |||
| 0eacf34e15 | |||
| 0be0d7fa94 | |||
| 736cf6d9df | |||
| df6d34e52b | |||
| 39d1eec575 | |||
| 2266a9dd9c | |||
| f8372ccc4d | |||
| f8c45d307c | |||
| ca04b70d0a | |||
| 571a547671 | |||
| b29d0a17af | |||
| 859bd215bf | |||
| 4988a62b31 | |||
| e202bbe4aa | |||
| 4286a64083 | |||
| 341786acb5 | |||
| 04b23ff849 | |||
| 6996e87f8d | |||
| b6432e705d | |||
| 5d7155fdda | |||
| b9986cae06 | |||
| 2c0903e45e | |||
| 389b639ef2 | |||
| 5624a4ccd6 | |||
| d81d66193f |
@@ -1,18 +0,0 @@
|
|||||||
**/.git
|
|
||||||
.DS_Store
|
|
||||||
*.dylib
|
|
||||||
*.DSYM
|
|
||||||
*.d
|
|
||||||
*.pyc
|
|
||||||
*.pyo
|
|
||||||
.*.swp
|
|
||||||
.*.swo
|
|
||||||
.*.un~
|
|
||||||
*.tmp
|
|
||||||
*.o
|
|
||||||
*.o-*
|
|
||||||
*.os
|
|
||||||
*.os-*
|
|
||||||
|
|
||||||
venv/
|
|
||||||
.venv/
|
|
||||||
+3
-11
@@ -9,15 +9,7 @@
|
|||||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
*.otf filter=lfs diff=lfs merge=lfs -text
|
*.otf filter=lfs diff=lfs merge=lfs -text
|
||||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||||
|
openpilot/selfdrive/assets/sounds/milestone.wav -filter -diff -merge -text
|
||||||
|
|
||||||
selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
|
openpilot/selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
|
||||||
system/hardware/tici/updater_weston filter=lfs diff=lfs merge=lfs -text
|
openpilot/common/hardware/comma/updater filter=lfs diff=lfs merge=lfs -text
|
||||||
system/hardware/tici/updater_magic filter=lfs diff=lfs merge=lfs -text
|
|
||||||
third_party/**/*.a filter=lfs diff=lfs merge=lfs -text
|
|
||||||
third_party/**/*.so filter=lfs diff=lfs merge=lfs -text
|
|
||||||
third_party/**/*.so.* filter=lfs diff=lfs merge=lfs -text
|
|
||||||
third_party/**/*.dylib filter=lfs diff=lfs merge=lfs -text
|
|
||||||
third_party/acados/*/t_renderer filter=lfs diff=lfs merge=lfs -text
|
|
||||||
third_party/qt5/larch64/bin/lrelease filter=lfs diff=lfs merge=lfs -text
|
|
||||||
third_party/qt5/larch64/bin/lupdate filter=lfs diff=lfs merge=lfs -text
|
|
||||||
third_party/catch2/include/catch2/catch.hpp filter=lfs diff=lfs merge=lfs -text
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
* @sunnypilot/dev-internal
|
|
||||||
/.github/ @devtekve @sunnyhaibin
|
|
||||||
/release/ci/ @devtekve @sunnyhaibin
|
|
||||||
/tinygrad_repo @devtekve @Discountchubbs
|
|
||||||
/tinygrad/ @devtekve @Discountchubbs
|
|
||||||
/selfdrive/controls/lib/longitudinal_planner.py @devtekve @Discountchubbs
|
|
||||||
/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @devtekve @Discountchubbs
|
|
||||||
/selfdrive/modeld/ @devtekve @Discountchubbs
|
|
||||||
/sunnypilot/model* @devtekve @Discountchubbs
|
|
||||||
/sunnypilot/sunnylink/ @devtekve
|
|
||||||
/system/athena/ @devtekve
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
name: Enhancement
|
|
||||||
about: For openpilot enhancement suggestions
|
|
||||||
title: ''
|
|
||||||
labels: 'enhancement'
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
@@ -8,24 +8,24 @@ chore:
|
|||||||
|
|
||||||
car:
|
car:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: '{selfdrive/car/**,opendbc_repo}'
|
- any-glob-to-all-files: '{openpilot/selfdrive/car/**,opendbc_repo}'
|
||||||
|
|
||||||
simulation:
|
simulation:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: 'tools/sim/**'
|
- any-glob-to-all-files: 'openpilot/tools/sim/**'
|
||||||
|
|
||||||
ui:
|
ui:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: '{selfdrive/assets/**,selfdrive/ui/**,system/ui/**}'
|
- any-glob-to-all-files: '{openpilot/selfdrive/assets/**,openpilot/selfdrive/ui/**,openpilot/system/ui/**}'
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: 'tools/**'
|
- any-glob-to-all-files: 'openpilot/tools/**'
|
||||||
|
|
||||||
multilanguage:
|
multilanguage:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: 'selfdrive/ui/translations/**'
|
- any-glob-to-all-files: 'openpilot/selfdrive/ui/translations/**'
|
||||||
|
|
||||||
autonomy:
|
autonomy:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: "{selfdrive/modeld/models/**,selfdrive/test/process_replay/model_replay_ref_commit,sunnypilot/modeld*/models/**}"
|
- any-glob-to-all-files: "{openpilot/selfdrive/modeld/models/**,openpilot/selfdrive/test/process_replay/model_replay_ref_commit,openpilot/sunnypilot/modeld*/models/**}"
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
<!-- Please copy and paste the relevant template -->
|
|
||||||
|
|
||||||
<!--- ***** Template: Fingerprint *****
|
|
||||||
|
|
||||||
**Car**
|
|
||||||
Which car (make, model, year) this fingerprint is for
|
|
||||||
|
|
||||||
**Route**
|
|
||||||
A route with the fingerprint
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--- ***** Template: Car Bugfix *****
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
A description of the bug and the fix. Also link the issue if it exists.
|
|
||||||
|
|
||||||
**Verification**
|
|
||||||
|
|
||||||
Explain how you tested this bug fix.
|
|
||||||
|
|
||||||
**Route**
|
|
||||||
|
|
||||||
Route: [a route with the bug fix]
|
|
||||||
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--- ***** Template: Bugfix *****
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
A description of the bug and the fix. Also link the issue if it exists.
|
|
||||||
|
|
||||||
**Verification**
|
|
||||||
|
|
||||||
Explain how you tested this bug fix.
|
|
||||||
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--- ***** Template: Car Port *****
|
|
||||||
|
|
||||||
**Checklist**
|
|
||||||
|
|
||||||
- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs
|
|
||||||
- [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/selfdrive/car/tests/routes.py)
|
|
||||||
- [ ] route with openpilot:
|
|
||||||
- [ ] route with stock system:
|
|
||||||
- [ ] car harness used (if comma doesn't sell it, put N/A):
|
|
||||||
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--- ***** Template: Refactor *****
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
A description of the refactor, including the goals it accomplishes.
|
|
||||||
|
|
||||||
**Verification**
|
|
||||||
|
|
||||||
Explain how you tested the refactor for regressions.
|
|
||||||
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: false
|
submodules: false
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
|
|
||||||
# Check PR target branch
|
# Check PR target branch
|
||||||
- name: check branch
|
- name: check branch
|
||||||
uses: Vankka/pr-target-branch-action@def32ec9d93514138d6ac0132ee62e120a72aed5
|
uses: Vankka/pr-target-branch-action@5da68a42bcb7b43d39104295a876a6f3f8d7908b
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
name: badges
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 * * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
PYTHONPATH: ${{ github.workspace }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
badges:
|
|
||||||
name: create badges
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Push badges
|
|
||||||
run: |
|
|
||||||
python3 selfdrive/ui/translations/create_badges.py
|
|
||||||
|
|
||||||
rm .gitattributes
|
|
||||||
|
|
||||||
git checkout --orphan badges
|
|
||||||
git rm -rf --cached .
|
|
||||||
git config user.email "badge-researcher@sunnypilot.ai"
|
|
||||||
git config user.name "Badge Researcher"
|
|
||||||
|
|
||||||
git add translation_badge.svg
|
|
||||||
git commit -m "Add/Update badges"
|
|
||||||
git push -f origin HEAD
|
|
||||||
@@ -7,6 +7,19 @@ on:
|
|||||||
description: 'Minimum selector version required for the models (see helpers.py or readme.md)'
|
description: 'Minimum selector version required for the models (see helpers.py or readme.md)'
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for (qcom or chestnut)'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
default: 'qcom'
|
||||||
|
options:
|
||||||
|
- qcom
|
||||||
|
- chestnut
|
||||||
|
hf_repo:
|
||||||
|
description: 'Hugging Face dataset repository'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'sunnypilot/sunnypilot_models_v1'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
setup:
|
||||||
@@ -30,7 +43,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd sunnypilot
|
cd sunnypilot
|
||||||
export PYTHONPATH=$(pwd)
|
export PYTHONPATH=$(pwd)
|
||||||
ref=$(python3 sunnypilot/models/tinygrad_ref.py)
|
ref=$(python3 openpilot/sunnypilot/models/tinygrad_ref.py)
|
||||||
echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT
|
echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT
|
||||||
echo "tinygrad_ref is $ref"
|
echo "tinygrad_ref is $ref"
|
||||||
|
|
||||||
@@ -46,13 +59,14 @@ jobs:
|
|||||||
id: get-json
|
id: get-json
|
||||||
run: |
|
run: |
|
||||||
cd docs/docs
|
cd docs/docs
|
||||||
latest=$(ls driving_models_v*.json | sed -E 's/.*_v([0-9]+)\.json/\1/' | sort -n | tail -1)
|
PREFIX="driving_models_${{ inputs.target_hardware == 'chestnut' && 'chestnut_' || '' }}v"
|
||||||
|
latest=$(ls ${PREFIX}*.json | sed -E "s/${PREFIX}([0-9]+)\.json/\1/" | sort -n | tail -1)
|
||||||
next=$((latest+1))
|
next=$((latest+1))
|
||||||
json_file="driving_models_v${next}.json"
|
json_file="${PREFIX}${next}.json"
|
||||||
cp "driving_models_v${latest}.json" "$json_file"
|
cp "${PREFIX}${latest}.json" "$json_file"
|
||||||
echo "json_file=docs/docs/$json_file" >> $GITHUB_OUTPUT
|
echo "json_file=docs/docs/$json_file" >> $GITHUB_OUTPUT
|
||||||
echo "json_version=$((next+0))" >> $GITHUB_OUTPUT
|
echo "json_version=$((next+0))" >> $GITHUB_OUTPUT
|
||||||
echo "SRC_JSON_FILE=docs/docs/driving_models_v${latest}.json" >> $GITHUB_ENV
|
echo "SRC_JSON_FILE=docs/docs/${PREFIX}${latest}.json" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Extract tinygrad models
|
- name: Extract tinygrad models
|
||||||
id: set-matrix
|
id: set-matrix
|
||||||
@@ -61,45 +75,24 @@ jobs:
|
|||||||
jq -c '[.bundles[] | select(.runner=="tinygrad") | {ref, display_name: (.display_name | gsub(" \\([^)]*\\)"; "")), is_20hz}]' "$(basename "${SRC_JSON_FILE}")" > matrix.json
|
jq -c '[.bundles[] | select(.runner=="tinygrad") | {ref, display_name: (.display_name | gsub(" \\([^)]*\\)"; "")), is_20hz}]' "$(basename "${SRC_JSON_FILE}")" > matrix.json
|
||||||
echo "model_matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT
|
echo "model_matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Set up SSH
|
- name: Get next recompiled dir number
|
||||||
uses: webfactory/ssh-agent@v0.9.0
|
|
||||||
with:
|
|
||||||
ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
|
|
||||||
- run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
- name: Clone GitLab docs repo and create new recompiled dir
|
|
||||||
id: create-recompiled-dir
|
id: create-recompiled-dir
|
||||||
env:
|
env:
|
||||||
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||||
|
HF_REPO: ${{ github.event.inputs.hf_repo }}
|
||||||
run: |
|
run: |
|
||||||
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
|
pip install huggingface_hub
|
||||||
cd gitlab_docs
|
recompiled_dir=$(python3 -c "
|
||||||
git checkout main
|
from huggingface_hub import HfApi
|
||||||
git sparse-checkout set --no-cone models/
|
import re, sys
|
||||||
cd models
|
api = HfApi()
|
||||||
latest_dir=$(ls -d recompiled* 2>/dev/null | sed -E 's/recompiled([0-9]+)/\1/' | sort -n | tail -1)
|
files = api.list_repo_files(repo_id=sys.argv[1], repo_type='dataset')
|
||||||
if [[ -z "$latest_dir" ]]; then
|
dirs = [re.search(r'models/recompiled([0-9]+)', f) for f in files]
|
||||||
next_dir=1
|
nums = [int(m.group(1)) for m in dirs if m]
|
||||||
else
|
print(max(nums) + 1)
|
||||||
next_dir=$((latest_dir+1))
|
" "$HF_REPO")
|
||||||
fi
|
|
||||||
recompiled_dir="${next_dir}"
|
|
||||||
mkdir -p "recompiled${recompiled_dir}"
|
|
||||||
touch "recompiled${recompiled_dir}/.gitkeep"
|
|
||||||
cd ../..
|
|
||||||
echo "recompiled_dir=$recompiled_dir" >> $GITHUB_OUTPUT
|
echo "recompiled_dir=$recompiled_dir" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Push empty recompiled dir to GitLab
|
|
||||||
run: |
|
|
||||||
cd gitlab_docs
|
|
||||||
git add models/recompiled${{ steps.create-recompiled-dir.outputs.recompiled_dir }}
|
|
||||||
git config --global user.name "GitHub Action"
|
|
||||||
git config --global user.email "action@github.com"
|
|
||||||
git commit -m "Add recompiled${{ steps.create-recompiled-dir.outputs.recompiled_dir }} for build-all" || echo "No changes to commit"
|
|
||||||
git push origin main
|
|
||||||
|
|
||||||
- name: Push new JSON to GitHub docs repo
|
- name: Push new JSON to GitHub docs repo
|
||||||
run: |
|
run: |
|
||||||
cd docs
|
cd docs
|
||||||
@@ -120,27 +113,33 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
upstream_branch: ${{ matrix.model.ref }}
|
upstream_branch: ${{ matrix.model.ref }}
|
||||||
custom_name: ${{ matrix.model.display_name }}
|
custom_name: ${{ matrix.model.display_name }}
|
||||||
|
is_20hz: ${{ matrix.model.is_20hz }}
|
||||||
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
|
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
|
||||||
json_version: ${{ needs.setup.outputs.json_version }}
|
json_version: ${{ needs.setup.outputs.json_version }}
|
||||||
|
target_hardware: ${{ github.event.inputs.target_hardware }}
|
||||||
|
hf_repo: ${{ github.event.inputs.hf_repo }}
|
||||||
|
set_min_version: ${{ github.event.inputs.set_min_version }}
|
||||||
|
tinygrad_ref: ${{ needs.setup.outputs.tinygrad_ref }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
retry_failed_models:
|
retry_failed_models:
|
||||||
needs: [setup, get_and_build]
|
needs: [setup, get_and_build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ needs.setup.result != 'failure' && !cancelled() }}
|
if: ${{ !cancelled() && needs.setup.result == 'success' && (needs.get_and_build.result == 'success' || needs.get_and_build.result == 'failure') }}
|
||||||
outputs:
|
outputs:
|
||||||
retry_matrix: ${{ steps.set-retry-matrix.outputs.retry_matrix }}
|
retry_matrix: ${{ steps.set-retry-matrix.outputs.retry_matrix }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
pattern: model-*
|
pattern: artifact-name-*
|
||||||
path: output
|
path: output
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
- id: set-retry-matrix
|
- id: set-retry-matrix
|
||||||
run: |
|
run: |
|
||||||
echo '${{ needs.setup.outputs.model_matrix }}' > matrix.json
|
echo '${{ needs.setup.outputs.model_matrix }}' > matrix.json
|
||||||
built=(); while IFS= read -r line; do built+=("$line"); done < <(
|
built=(); while IFS= read -r line; do [ -n "$line" ] && built+=("$line"); done < <(
|
||||||
find output -maxdepth 1 -name 'model-*' -printf "%f\n" | sed -E 's/^model-//' | sed -E 's/-[0-9]+$//' | sed -E 's/ \([^)]*\)//' | awk '{gsub(/^ +| +$/, ""); print}'
|
find output -maxdepth 1 -name 'artifact-name-*' -printf "%f\n" 2>/dev/null | sed -E 's/^artifact-name-//' | awk '{gsub(/^ +| +$/, ""); print}'
|
||||||
)
|
)
|
||||||
jq -c --argjson built "$(printf '%s\n' "${built[@]}" | jq -R . | jq -s .)" \
|
jq -c --argjson built "$(printf '%s\n' "${built[@]}" | jq -R . | jq -s .)" \
|
||||||
'map(select(.display_name as $n | ($built | index($n | gsub("^ +| +$"; "")) | not)))' matrix.json > retry_matrix.json
|
'map(select(.display_name as $n | ($built | index($n | gsub("^ +| +$"; "")) | not)))' matrix.json > retry_matrix.json
|
||||||
@@ -148,7 +147,7 @@ jobs:
|
|||||||
|
|
||||||
retry_get_and_build:
|
retry_get_and_build:
|
||||||
needs: [setup, get_and_build, retry_failed_models]
|
needs: [setup, get_and_build, retry_failed_models]
|
||||||
if: ${{ needs.get_and_build.result == 'failure' || (needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '') }}
|
if: ${{ !cancelled() && needs.retry_failed_models.result == 'success' && needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '' }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
model: ${{ fromJson(needs.retry_failed_models.outputs.retry_matrix) }}
|
model: ${{ fromJson(needs.retry_failed_models.outputs.retry_matrix) }}
|
||||||
@@ -157,148 +156,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
upstream_branch: ${{ matrix.model.ref }}
|
upstream_branch: ${{ matrix.model.ref }}
|
||||||
custom_name: ${{ matrix.model.display_name }}
|
custom_name: ${{ matrix.model.display_name }}
|
||||||
|
is_20hz: ${{ matrix.model.is_20hz }}
|
||||||
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
|
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
|
||||||
json_version: ${{ needs.setup.outputs.json_version }}
|
json_version: ${{ needs.setup.outputs.json_version }}
|
||||||
|
target_hardware: ${{ github.event.inputs.target_hardware }}
|
||||||
artifact_suffix: -retry
|
artifact_suffix: -retry
|
||||||
|
hf_repo: ${{ github.event.inputs.hf_repo }}
|
||||||
|
set_min_version: ${{ github.event.inputs.set_min_version }}
|
||||||
|
tinygrad_ref: ${{ needs.setup.outputs.tinygrad_ref }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
publish_models:
|
|
||||||
name: Publish models sequentially
|
|
||||||
needs: [setup, get_and_build, retry_failed_models, retry_get_and_build]
|
|
||||||
if: ${{ !cancelled() && (needs.get_and_build.result != 'failure' || needs.retry_get_and_build.result == 'success' || (needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '')) }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
max-parallel: 1
|
|
||||||
matrix:
|
|
||||||
model: ${{ fromJson(needs.setup.outputs.model_matrix) }}
|
|
||||||
env:
|
|
||||||
RECOMPILED_DIR: recompiled${{ needs.setup.outputs.recompiled_dir }}
|
|
||||||
JSON_FILE: ${{ needs.setup.outputs.json_file }}
|
|
||||||
ARTIFACT_NAME_INPUT: ${{ matrix.model.display_name }}
|
|
||||||
steps:
|
|
||||||
- name: Set up SSH
|
|
||||||
uses: webfactory/ssh-agent@v0.9.0
|
|
||||||
with:
|
|
||||||
ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
|
|
||||||
|
|
||||||
- name: Add GitLab.com SSH key to known_hosts
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
- name: Clone GitLab docs repo
|
|
||||||
env:
|
|
||||||
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
|
||||||
run: |
|
|
||||||
echo "Cloning GitLab"
|
|
||||||
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
|
|
||||||
cd gitlab_docs
|
|
||||||
echo "checkout models/${RECOMPILED_DIR}"
|
|
||||||
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
|
|
||||||
git checkout main
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
- name: Checkout docs repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: sunnypilot/sunnypilot-models
|
|
||||||
ref: gh-pages
|
|
||||||
path: docs
|
|
||||||
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
|
||||||
|
|
||||||
- name: Validate recompiled dir and JSON version
|
|
||||||
run: |
|
|
||||||
if [ ! -d "gitlab_docs/models/$RECOMPILED_DIR" ]; then
|
|
||||||
echo "Recompiled dir $RECOMPILED_DIR does not exist in GitLab repo"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ ! -f "$JSON_FILE" ]; then
|
|
||||||
echo "JSON file $JSON_FILE does not exist!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Download artifact name file
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: artifact-name-${{ env.ARTIFACT_NAME_INPUT }}
|
|
||||||
path: artifact_name
|
|
||||||
|
|
||||||
- name: Read artifact name
|
|
||||||
id: read-artifact-name
|
|
||||||
run: |
|
|
||||||
ARTIFACT_NAME=$(cat artifact_name/artifact_name.txt)
|
|
||||||
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Download model artifact
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
|
||||||
path: output
|
|
||||||
|
|
||||||
- name: Remove onnx files bc not needed for recompiled dir since they already exist from single build
|
|
||||||
run: |
|
|
||||||
find output -type f -name '*.onnx' -delete
|
|
||||||
find output -type f -name 'big_*.pkl' -delete
|
|
||||||
find output -type f -name 'dmonitoring_model_tinygrad.pkl' -delete
|
|
||||||
|
|
||||||
- name: Copy model artifacts to gitlab
|
|
||||||
env:
|
|
||||||
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
|
||||||
run: |
|
|
||||||
ARTIFACT_DIR="gitlab_docs/models/${RECOMPILED_DIR}/${ARTIFACT_NAME}"
|
|
||||||
mkdir -p "$ARTIFACT_DIR"
|
|
||||||
for path in output/*; do
|
|
||||||
if [ "$(basename "$path")" = "artifact_name.txt" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
name="$(basename "$path")"
|
|
||||||
if [ -d "$path" ]; then
|
|
||||||
mkdir -p "$ARTIFACT_DIR/$name"
|
|
||||||
cp -r "$path"/* "$ARTIFACT_DIR/$name/"
|
|
||||||
echo "Copied dir $name -> $ARTIFACT_DIR/$name"
|
|
||||||
else
|
|
||||||
cp "$path" "$ARTIFACT_DIR/"
|
|
||||||
echo "Copied file $name -> $ARTIFACT_DIR/"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Push recompiled dir to GitLab
|
|
||||||
env:
|
|
||||||
GITLAB_SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
|
|
||||||
run: |
|
|
||||||
cd gitlab_docs
|
|
||||||
git checkout main
|
|
||||||
git pull origin main
|
|
||||||
for d in models/"$RECOMPILED_DIR"/*/; do
|
|
||||||
git sparse-checkout add "$d"
|
|
||||||
done
|
|
||||||
git add models/"$RECOMPILED_DIR"
|
|
||||||
git config --global user.name "GitHub Action"
|
|
||||||
git config --global user.email "action@github.com"
|
|
||||||
git commit -m "Update $RECOMPILED_DIR with model from build-all-tinygrad-models" || echo "No changes to commit"
|
|
||||||
git push origin main
|
|
||||||
- run: |
|
|
||||||
cd docs
|
|
||||||
git pull origin gh-pages
|
|
||||||
|
|
||||||
- name: update json
|
|
||||||
run: |
|
|
||||||
ARGS=""
|
|
||||||
[ -n "${{ inputs.set_min_version }}" ] && ARGS="$ARGS --set-min-version \"${{ inputs.set_min_version }}\""
|
|
||||||
ARGS="$ARGS --sort-by-date"
|
|
||||||
ARGS="$ARGS --tinygrad-ref \"${{ needs.setup.outputs.tinygrad_ref }}\""
|
|
||||||
eval python3 docs/json_parser.py \
|
|
||||||
--json-path "$JSON_FILE" \
|
|
||||||
--recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR" \
|
|
||||||
$ARGS
|
|
||||||
|
|
||||||
- name: Push updated json to GitHub
|
|
||||||
run: |
|
|
||||||
cd docs
|
|
||||||
git config --global user.name "GitHub Action"
|
|
||||||
git config --global user.email "action@github.com"
|
|
||||||
git checkout gh-pages
|
|
||||||
git add docs/"$(basename $JSON_FILE)"
|
|
||||||
git commit -m "Update $(basename $JSON_FILE) after recompiling model" || echo "No changes to commit"
|
|
||||||
git push origin gh-pages
|
|
||||||
|
|||||||
@@ -0,0 +1,522 @@
|
|||||||
|
name: Build default models
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
target:
|
||||||
|
description: 'Model target to build'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- small
|
||||||
|
- big
|
||||||
|
- dm
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
target:
|
||||||
|
description: 'Model target to build (small, big, or dm)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: build-default-models-${{ inputs.target }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
HF_REPO: sunnypilot/sunnypilot_models_v1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
resolve:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
outputs:
|
||||||
|
model_name: ${{ steps.resolve.outputs.model_name }}
|
||||||
|
safe_model_name: ${{ steps.resolve.outputs.safe_model_name }}
|
||||||
|
onnx_ref: ${{ steps.resolve.outputs.onnx_ref }}
|
||||||
|
onnx_path: ${{ steps.resolve.outputs.onnx_path }}
|
||||||
|
hf_defaults_path: ${{ steps.resolve.outputs.hf_defaults_path }}
|
||||||
|
tinygrad_ref: ${{ steps.resolve.outputs.tinygrad_ref }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- id: resolve
|
||||||
|
run: |
|
||||||
|
export PYTHONPATH=${{ github.workspace }}
|
||||||
|
|
||||||
|
if [ "${{ inputs.target }}" = "big" ]; then
|
||||||
|
NAME=$(python3 -c "from openpilot.sunnypilot.models.model_name import DEFAULT_BIG_MODEL; print(DEFAULT_BIG_MODEL)")
|
||||||
|
ONNX_PATH="openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx"
|
||||||
|
HF_DEFAULTS_PATH="models/defaults/big"
|
||||||
|
elif [ "${{ inputs.target }}" = "dm" ]; then
|
||||||
|
ONNX_PATH="openpilot/selfdrive/modeld/models/dmonitoring_model.onnx"
|
||||||
|
HF_DEFAULTS_PATH="models/defaults/dm"
|
||||||
|
NAME="dmonitoring_model ($(git log -1 --format=%cd --date=format:'%B %d, %Y' -- "$ONNX_PATH"))"
|
||||||
|
else
|
||||||
|
NAME=$(python3 -c "from openpilot.sunnypilot.models.model_name import DEFAULT_MODEL; print(DEFAULT_MODEL)")
|
||||||
|
ONNX_PATH="openpilot/selfdrive/modeld/models/driving_supercombo.onnx"
|
||||||
|
HF_DEFAULTS_PATH="models/defaults/small"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ONNX_REF=$(git log -1 --format='%H' -- "$ONNX_PATH")
|
||||||
|
TINYGRAD_REF=$(python3 openpilot/sunnypilot/models/tinygrad_ref.py)
|
||||||
|
if [ -z "$TINYGRAD_REF" ]; then
|
||||||
|
echo "::error::Failed to resolve tinygrad ref"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SAFE_NAME="${NAME// /-}"
|
||||||
|
echo "model_name=${NAME}" >> $GITHUB_OUTPUT
|
||||||
|
echo "safe_model_name=${SAFE_NAME}" >> $GITHUB_OUTPUT
|
||||||
|
echo "onnx_ref=${ONNX_REF}" >> $GITHUB_OUTPUT
|
||||||
|
echo "onnx_path=${ONNX_PATH}" >> $GITHUB_OUTPUT
|
||||||
|
echo "hf_defaults_path=${HF_DEFAULTS_PATH}" >> $GITHUB_OUTPUT
|
||||||
|
echo "tinygrad_ref=${TINYGRAD_REF}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
build_small_model:
|
||||||
|
needs: resolve
|
||||||
|
if: ${{ inputs.target == 'small' }}
|
||||||
|
runs-on: [self-hosted, tici]
|
||||||
|
env:
|
||||||
|
SMALL_ONNX: openpilot/selfdrive/modeld/models/driving_supercombo.onnx
|
||||||
|
SMALL_PKL: openpilot/selfdrive/modeld/models/driving_tinygrad.pkl
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Pull ONNX via LFS
|
||||||
|
run: git lfs pull -I "${{ env.SMALL_ONNX }}"
|
||||||
|
|
||||||
|
- name: Set environment variables
|
||||||
|
run: |
|
||||||
|
source /etc/profile
|
||||||
|
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||||
|
export UV_PYTHON_PREFERENCE=managed
|
||||||
|
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
|
||||||
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
|
uv sync --frozen
|
||||||
|
printenv >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Disable powersave
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||||
|
|
||||||
|
- name: Compile small model with stock compiler
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
|
||||||
|
|
||||||
|
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
||||||
|
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
||||||
|
FRAME_SKIP=$(python3 -c "from openpilot.selfdrive.modeld.constants import ModelConstants as MC; print(MC.MODEL_RUN_FREQ // MC.MODEL_CONTEXT_FREQ)")
|
||||||
|
|
||||||
|
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
||||||
|
|
||||||
|
env ${TG_FLAGS} python3 \
|
||||||
|
${{ github.workspace }}/openpilot/selfdrive/modeld/compile_modeld.py \
|
||||||
|
--onnx ${{ github.workspace }}/${{ env.SMALL_ONNX }} \
|
||||||
|
--model-size $MODEL_SIZE \
|
||||||
|
--camera-resolutions $CAMERA_RES \
|
||||||
|
--frame-skip $FRAME_SKIP \
|
||||||
|
--output ${{ github.workspace }}/${{ env.SMALL_PKL }}
|
||||||
|
|
||||||
|
- name: Chunk small pkl
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH=${{ github.workspace }}
|
||||||
|
python3 -c "
|
||||||
|
from openpilot.common.file_chunker import chunk_file, get_chunk_targets
|
||||||
|
import os
|
||||||
|
pkl = '${{ github.workspace }}/${{ env.SMALL_PKL }}'
|
||||||
|
size = os.path.getsize(pkl)
|
||||||
|
targets = get_chunk_targets(pkl, size)
|
||||||
|
chunk_file(pkl, targets)
|
||||||
|
print(f'Chunked into {len(targets)} files')
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Prepare output
|
||||||
|
env:
|
||||||
|
MODEL_NAME: ${{ needs.resolve.outputs.safe_model_name }}
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH=${{ github.workspace }}
|
||||||
|
MODELS_DIR="${{ github.workspace }}/openpilot/selfdrive/modeld/models"
|
||||||
|
OUTPUT_DIR="${{ github.workspace }}/small_output"
|
||||||
|
PKL_BASE="driving_tinygrad.pkl"
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
cp "$MODELS_DIR/${PKL_BASE}".chunk* "$OUTPUT_DIR/"
|
||||||
|
cp "$MODELS_DIR/${PKL_BASE}.chunkmanifest" "$OUTPUT_DIR/"
|
||||||
|
|
||||||
|
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
|
||||||
|
--model-dir "$MODELS_DIR" \
|
||||||
|
--output-dir "$OUTPUT_DIR" \
|
||||||
|
--custom-name "$MODEL_NAME" \
|
||||||
|
--upstream-branch "${{ needs.resolve.outputs.onnx_ref }}"
|
||||||
|
|
||||||
|
echo "model-${MODEL_NAME}-${{ github.run_number }}" > "$OUTPUT_DIR/artifact_name.txt"
|
||||||
|
|
||||||
|
- name: Upload small model artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: model-${{ needs.resolve.outputs.safe_model_name }}-${{ github.run_number }}
|
||||||
|
path: ${{ github.workspace }}/small_output/
|
||||||
|
|
||||||
|
- name: Upload artifact name file
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: artifact-name-${{ needs.resolve.outputs.safe_model_name }}
|
||||||
|
path: ${{ github.workspace }}/small_output/artifact_name.txt
|
||||||
|
|
||||||
|
- name: Re-enable powersave
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||||
|
|
||||||
|
build_big_model:
|
||||||
|
needs: resolve
|
||||||
|
if: ${{ inputs.target == 'big' }}
|
||||||
|
runs-on: [self-hosted, chestnut]
|
||||||
|
env:
|
||||||
|
BIG_ONNX: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
|
||||||
|
BIG_PKL: openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Pull big ONNX via LFS
|
||||||
|
run: git lfs pull -I "${{ env.BIG_ONNX }}"
|
||||||
|
|
||||||
|
- name: Set environment variables
|
||||||
|
run: |
|
||||||
|
source /etc/profile
|
||||||
|
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||||
|
export UV_PYTHON_PREFERENCE=managed
|
||||||
|
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
|
||||||
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
|
uv sync --frozen
|
||||||
|
printenv >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Disable powersave
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||||
|
|
||||||
|
- name: Wait for chestnut PCIe link
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
|
||||||
|
python3 -c "
|
||||||
|
import time
|
||||||
|
from openpilot.system.hardware.chestnut.flash import link_up
|
||||||
|
for i in range(10):
|
||||||
|
if link_up():
|
||||||
|
print(f'PCIe link up after {i+1} attempt(s)')
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
raise RuntimeError('Chestnut PCIe link not ready after 10 attempts')
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Compile big model with stock compiler
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
|
||||||
|
|
||||||
|
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
||||||
|
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
||||||
|
FRAME_SKIP=$(python3 -c "from openpilot.selfdrive.modeld.constants import ModelConstants as MC; print(MC.MODEL_RUN_FREQ // MC.MODEL_CONTEXT_FREQ)")
|
||||||
|
|
||||||
|
TG_FLAGS="DEBUG=2 DEV=USB+AMD:LLVM WARP_DEV=QCOM FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0 TC_OPT=2"
|
||||||
|
|
||||||
|
env ${TG_FLAGS} python3 \
|
||||||
|
${{ github.workspace }}/openpilot/selfdrive/modeld/compile_modeld.py \
|
||||||
|
--onnx ${{ github.workspace }}/${{ env.BIG_ONNX }} \
|
||||||
|
--model-size $MODEL_SIZE \
|
||||||
|
--camera-resolutions $CAMERA_RES \
|
||||||
|
--frame-skip $FRAME_SKIP \
|
||||||
|
--output ${{ github.workspace }}/${{ env.BIG_PKL }}
|
||||||
|
|
||||||
|
- name: Chunk big pkl
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH=${{ github.workspace }}
|
||||||
|
python3 -c "
|
||||||
|
from openpilot.common.file_chunker import chunk_file, get_chunk_targets
|
||||||
|
import os
|
||||||
|
pkl = '${{ github.workspace }}/${{ env.BIG_PKL }}'
|
||||||
|
size = os.path.getsize(pkl)
|
||||||
|
targets = get_chunk_targets(pkl, size)
|
||||||
|
chunk_file(pkl, targets)
|
||||||
|
print(f'Chunked into {len(targets)} files')
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Prepare output
|
||||||
|
env:
|
||||||
|
MODEL_NAME: ${{ needs.resolve.outputs.safe_model_name }}
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH=${{ github.workspace }}
|
||||||
|
MODELS_DIR="${{ github.workspace }}/openpilot/selfdrive/modeld/models"
|
||||||
|
OUTPUT_DIR="${{ github.workspace }}/big_output"
|
||||||
|
PKL_BASE="big_driving_tinygrad.pkl"
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
cp "$MODELS_DIR/${PKL_BASE}".chunk* "$OUTPUT_DIR/"
|
||||||
|
cp "$MODELS_DIR/${PKL_BASE}.chunkmanifest" "$OUTPUT_DIR/"
|
||||||
|
|
||||||
|
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
|
||||||
|
--model-dir "$MODELS_DIR" \
|
||||||
|
--output-dir "$OUTPUT_DIR" \
|
||||||
|
--custom-name "$MODEL_NAME" \
|
||||||
|
--upstream-branch "${{ needs.resolve.outputs.onnx_ref }}"
|
||||||
|
|
||||||
|
echo "model-${MODEL_NAME}-${{ github.run_number }}" > "$OUTPUT_DIR/artifact_name.txt"
|
||||||
|
|
||||||
|
- name: Upload big model artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: model-${{ needs.resolve.outputs.safe_model_name }}-${{ github.run_number }}
|
||||||
|
path: ${{ github.workspace }}/big_output/
|
||||||
|
|
||||||
|
- name: Upload artifact name file
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: artifact-name-${{ needs.resolve.outputs.safe_model_name }}
|
||||||
|
path: ${{ github.workspace }}/big_output/artifact_name.txt
|
||||||
|
|
||||||
|
- name: Re-enable powersave
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||||
|
|
||||||
|
upload_defaults:
|
||||||
|
needs: [ resolve, build_small_model, build_big_model, build_dm_model ]
|
||||||
|
if: |
|
||||||
|
${{
|
||||||
|
!cancelled() &&
|
||||||
|
(inputs.target == 'big' && needs.build_big_model.result == 'success' ||
|
||||||
|
inputs.target == 'small' && needs.build_small_model.result == 'success' ||
|
||||||
|
inputs.target == 'dm' && needs.build_dm_model.result == 'success')
|
||||||
|
}}
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Pull ONNX via LFS
|
||||||
|
run: git lfs pull -I "${{ needs.resolve.outputs.onnx_path }}"
|
||||||
|
|
||||||
|
- name: Install huggingface_hub
|
||||||
|
run: pip install --upgrade "huggingface_hub>=0.22.0"
|
||||||
|
|
||||||
|
- name: Download artifact name
|
||||||
|
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: artifact-name-${{ needs.resolve.outputs.safe_model_name }}
|
||||||
|
path: artifact_name
|
||||||
|
|
||||||
|
- name: Read artifact name
|
||||||
|
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
|
||||||
|
id: artifact
|
||||||
|
run: |
|
||||||
|
ARTIFACT_NAME=$(cat artifact_name/artifact_name.txt)
|
||||||
|
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Download model artifact
|
||||||
|
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ steps.artifact.outputs.artifact_name }}
|
||||||
|
path: output
|
||||||
|
|
||||||
|
- name: Upload model to HF
|
||||||
|
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
|
||||||
|
env:
|
||||||
|
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||||
|
ARTIFACT_NAME: ${{ steps.artifact.outputs.artifact_name }}
|
||||||
|
run: |
|
||||||
|
rm -f output/artifact_name.txt
|
||||||
|
export PYTHONPATH=$(pwd)
|
||||||
|
python3 release/ci/upload_default_model.py \
|
||||||
|
--hf-repo "${{ env.HF_REPO }}" \
|
||||||
|
--hf-defaults-path "${{ needs.resolve.outputs.hf_defaults_path }}" \
|
||||||
|
--artifact-name "$ARTIFACT_NAME" \
|
||||||
|
--model-dir output \
|
||||||
|
--onnx-path "${{ needs.resolve.outputs.onnx_path }}" \
|
||||||
|
--onnx-ref "${{ needs.resolve.outputs.onnx_ref }}" \
|
||||||
|
--model-name "${{ needs.resolve.outputs.model_name }}" \
|
||||||
|
--tinygrad-ref "${{ needs.resolve.outputs.tinygrad_ref }}" \
|
||||||
|
--run-number "${{ github.run_number }}"
|
||||||
|
|
||||||
|
- name: Download DM artifact
|
||||||
|
if: ${{ inputs.target == 'dm' }}
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dm-model-${{ github.run_number }}
|
||||||
|
path: dm_output
|
||||||
|
|
||||||
|
- name: Generate DM metadata and upload to HF
|
||||||
|
if: ${{ inputs.target == 'dm' }}
|
||||||
|
env:
|
||||||
|
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||||
|
run: |
|
||||||
|
export PYTHONPATH=$(pwd)
|
||||||
|
python3 -c "
|
||||||
|
import json, hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime, UTC
|
||||||
|
|
||||||
|
dm_dir = Path('dm_output')
|
||||||
|
manifest = list(dm_dir.glob('*.chunkmanifest'))
|
||||||
|
assert manifest, 'No chunkmanifest found'
|
||||||
|
pkl_name = manifest[0].name.removesuffix('.chunkmanifest')
|
||||||
|
num_chunks = int(manifest[0].read_text().strip())
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
for i in range(num_chunks):
|
||||||
|
chunk = dm_dir / f'{pkl_name}.chunk{i+1:02d}of{num_chunks:02d}'
|
||||||
|
chunks.append({
|
||||||
|
'file_name': chunk.name,
|
||||||
|
'sha256': hashlib.sha256(chunk.read_bytes()).hexdigest()
|
||||||
|
})
|
||||||
|
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
for c in chunks:
|
||||||
|
with open(dm_dir / c['file_name'], 'rb') as f:
|
||||||
|
while block := f.read(1024*1024):
|
||||||
|
digest.update(block)
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
'bundles': [{
|
||||||
|
'short_name': 'DMMODEL',
|
||||||
|
'display_name': '${{ needs.resolve.outputs.model_name }}',
|
||||||
|
'ref': '${{ needs.resolve.outputs.onnx_ref }}',
|
||||||
|
'runner': 'tinygrad',
|
||||||
|
'build_time': datetime.now(UTC).strftime('%Y-%m-%dT%H:%M:%SZ'),
|
||||||
|
'models': [{
|
||||||
|
'type': 'chunked',
|
||||||
|
'artifact': {
|
||||||
|
'file_name': pkl_name,
|
||||||
|
'download_uri': {'url': '', 'sha256': digest.hexdigest()},
|
||||||
|
'chunks': chunks
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
with open(dm_dir / 'metadata.json', 'w') as f:
|
||||||
|
json.dump(metadata, f, indent=2)
|
||||||
|
print('Generated DM metadata.json')
|
||||||
|
"
|
||||||
|
|
||||||
|
python3 release/ci/upload_default_model.py \
|
||||||
|
--hf-repo "${{ env.HF_REPO }}" \
|
||||||
|
--hf-defaults-path "${{ needs.resolve.outputs.hf_defaults_path }}" \
|
||||||
|
--artifact-name "dm-model-${{ github.run_number }}" \
|
||||||
|
--model-dir dm_output \
|
||||||
|
--onnx-path "${{ needs.resolve.outputs.onnx_path }}" \
|
||||||
|
--onnx-ref "${{ needs.resolve.outputs.onnx_ref }}" \
|
||||||
|
--model-name "${{ needs.resolve.outputs.model_name }}" \
|
||||||
|
--tinygrad-ref "${{ needs.resolve.outputs.tinygrad_ref }}" \
|
||||||
|
--run-number "${{ github.run_number }}"
|
||||||
|
|
||||||
|
build_dm_model:
|
||||||
|
needs: resolve
|
||||||
|
if: ${{ inputs.target == 'dm' }}
|
||||||
|
runs-on: [self-hosted, tici]
|
||||||
|
env:
|
||||||
|
DM_ONNX: openpilot/selfdrive/modeld/models/dmonitoring_model.onnx
|
||||||
|
DM_PKL: openpilot/selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Pull DM ONNX via LFS
|
||||||
|
run: git lfs pull -I "${{ env.DM_ONNX }}"
|
||||||
|
|
||||||
|
- name: Set environment variables
|
||||||
|
run: |
|
||||||
|
source /etc/profile
|
||||||
|
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||||
|
export UV_PYTHON_PREFERENCE=managed
|
||||||
|
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
|
||||||
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
|
uv sync --frozen
|
||||||
|
printenv >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Disable powersave
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||||
|
|
||||||
|
- name: Compile DM model
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
|
||||||
|
|
||||||
|
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
||||||
|
|
||||||
|
taskset -c 7 env ${TG_FLAGS} python3 \
|
||||||
|
${{ github.workspace }}/tinygrad_repo/examples/openpilot/compile3.py \
|
||||||
|
${{ github.workspace }}/${{ env.DM_ONNX }} \
|
||||||
|
${{ github.workspace }}/${{ env.DM_PKL }}
|
||||||
|
|
||||||
|
- name: Chunk DM pkl
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH=${{ github.workspace }}
|
||||||
|
python3 -c "
|
||||||
|
from openpilot.common.file_chunker import chunk_file, get_chunk_targets
|
||||||
|
import os
|
||||||
|
pkl = '${{ github.workspace }}/${{ env.DM_PKL }}'
|
||||||
|
size = os.path.getsize(pkl)
|
||||||
|
targets = get_chunk_targets(pkl, size)
|
||||||
|
chunk_file(pkl, targets)
|
||||||
|
print(f'Chunked {pkl} into {len(targets)} chunks')
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Compile DM warp
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
|
||||||
|
|
||||||
|
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
||||||
|
MODEL_DIR="${{ github.workspace }}/openpilot/selfdrive/modeld"
|
||||||
|
DM_SIZE=$(python3 -c "from openpilot.common.transformations.model import DM_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
||||||
|
|
||||||
|
for res in $(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')"); do
|
||||||
|
WARP_PKL="${MODEL_DIR}/models/dm_warp_${res}_tinygrad.pkl"
|
||||||
|
taskset -c 7 env ${TG_FLAGS} python3 ${MODEL_DIR}/compile_dm_warp.py \
|
||||||
|
--camera-resolution ${res} \
|
||||||
|
--warp-to ${DM_SIZE} \
|
||||||
|
--output ${WARP_PKL}
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Prepare DM output
|
||||||
|
run: |
|
||||||
|
mkdir -p dm_output
|
||||||
|
cp ${{ github.workspace }}/${{ env.DM_PKL }}.chunk* dm_output/
|
||||||
|
cp ${{ github.workspace }}/${{ env.DM_PKL }}.chunkmanifest dm_output/
|
||||||
|
cp ${{ github.workspace }}/openpilot/selfdrive/modeld/models/dm_warp_* dm_output/
|
||||||
|
|
||||||
|
- name: Upload DM artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dm-model-${{ github.run_number }}
|
||||||
|
path: dm_output/
|
||||||
|
|
||||||
|
- name: Re-enable powersave
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||||
|
|
||||||
@@ -12,11 +12,11 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
recompiled_dir:
|
recompiled_dir:
|
||||||
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
|
description: 'Existing recompiled directory number (e.g. 1 for recompiled1)'
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
json_version:
|
json_version:
|
||||||
description: 'driving_models version number to update (e.g. 5 for driving_models_v5.json)'
|
description: 'driving_models version number to update (e.g. 18 for driving_models_v18.json)'
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
artifact_suffix:
|
artifact_suffix:
|
||||||
@@ -24,11 +24,29 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: ''
|
default: ''
|
||||||
bypass_push:
|
is_20hz:
|
||||||
description: 'Bypass pushing to GitLab for build-all'
|
description: 'Is this a 20Hz model'
|
||||||
required: false
|
required: false
|
||||||
default: true
|
|
||||||
type: boolean
|
type: boolean
|
||||||
|
default: true
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for (qcom or chestnut)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'qcom'
|
||||||
|
hf_repo:
|
||||||
|
description: 'Hugging Face dataset repository (e.g. sunnypilot/sunnypilot_models_v1)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'sunnypilot/sunnypilot_models_v1'
|
||||||
|
set_min_version:
|
||||||
|
description: 'Minimum selector version'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
tinygrad_ref:
|
||||||
|
description: 'Tinygrad reference'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
upstream_branch:
|
upstream_branch:
|
||||||
@@ -39,6 +57,11 @@ on:
|
|||||||
description: 'Custom name for the model (no date, only name)'
|
description: 'Custom name for the model (no date, only name)'
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
is_20hz:
|
||||||
|
description: 'Is this a 20Hz model'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
recompiled_dir:
|
recompiled_dir:
|
||||||
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
|
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
|
||||||
required: true
|
required: true
|
||||||
@@ -53,12 +76,11 @@ on:
|
|||||||
default: 'None'
|
default: 'None'
|
||||||
options:
|
options:
|
||||||
- None
|
- None
|
||||||
- Simple Plan Models
|
- Master Models
|
||||||
- Space Lab Models
|
- Release Models
|
||||||
- TR Models
|
- 2026 World Models
|
||||||
- DTR Models
|
- 2026 Deep RL Models
|
||||||
- Custom Merge Models
|
- Custom Merge Models
|
||||||
- FOF series models
|
|
||||||
- Other
|
- Other
|
||||||
custom_model_folder:
|
custom_model_folder:
|
||||||
description: 'Custom model folder name (if "Other" selected)'
|
description: 'Custom model folder name (if "Other" selected)'
|
||||||
@@ -72,9 +94,22 @@ on:
|
|||||||
description: 'Minimum selector version'
|
description: 'Minimum selector version'
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for'
|
||||||
|
required: false
|
||||||
|
type: choice
|
||||||
|
default: 'qcom'
|
||||||
|
options:
|
||||||
|
- qcom
|
||||||
|
- chestnut
|
||||||
|
hf_repo:
|
||||||
|
description: 'Hugging Face dataset repository'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'sunnypilot/sunnypilot_models_v1'
|
||||||
env:
|
env:
|
||||||
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
||||||
JSON_FILE: docs/docs/driving_models_v${{ inputs.json_version }}.json
|
JSON_FILE: docs/docs/driving_models_${{ inputs.target_hardware == 'chestnut' && 'chestnut_v' || 'v' }}${{ inputs.json_version }}.json
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_model:
|
build_model:
|
||||||
@@ -82,40 +117,22 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
upstream_branch: ${{ inputs.upstream_branch }}
|
upstream_branch: ${{ inputs.upstream_branch }}
|
||||||
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
||||||
is_20hz: true
|
is_20hz: ${{ inputs.is_20hz }}
|
||||||
artifact_suffix: ${{ inputs.artifact_suffix }}
|
artifact_suffix: ${{ inputs.artifact_suffix }}
|
||||||
|
target_hardware: ${{ inputs.target_hardware }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
publish_model:
|
publish_model:
|
||||||
if: ${{ !inputs.bypass_push && !cancelled() }}
|
if: ${{ !cancelled() && needs.build_model.result == 'success' }}
|
||||||
concurrency:
|
concurrency:
|
||||||
group: gitlab-push-${{ inputs.recompiled_dir }}
|
group: hf-push-${{ inputs.recompiled_dir }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
needs: build_model
|
needs: build_model
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Set up SSH
|
|
||||||
uses: webfactory/ssh-agent@v0.9.0
|
|
||||||
with:
|
|
||||||
ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
|
|
||||||
|
|
||||||
- name: Add GitLab.com SSH key to known_hosts
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
- name: Clone GitLab docs repo
|
|
||||||
env:
|
|
||||||
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
|
||||||
run: |
|
|
||||||
echo "Cloning GitLab"
|
|
||||||
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
|
|
||||||
cd gitlab_docs
|
|
||||||
echo "checkout models/${RECOMPILED_DIR}"
|
|
||||||
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
|
|
||||||
git checkout main
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
- name: Checkout docs repo
|
- name: Checkout docs repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -124,16 +141,23 @@ jobs:
|
|||||||
path: docs
|
path: docs
|
||||||
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Validate recompiled dir and JSON version
|
- name: Install huggingface_hub
|
||||||
|
run: pip install --upgrade "huggingface_hub>=0.22.0"
|
||||||
|
|
||||||
|
- name: Validate hf_repo and JSON version
|
||||||
|
env:
|
||||||
|
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
if [ ! -d "gitlab_docs/models/$RECOMPILED_DIR" ]; then
|
|
||||||
echo "Recompiled dir $RECOMPILED_DIR does not exist in GitLab repo"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ ! -f "$JSON_FILE" ]; then
|
if [ ! -f "$JSON_FILE" ]; then
|
||||||
echo "JSON file $JSON_FILE does not exist!"
|
echo "JSON file $JSON_FILE does not exist!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
python3 -c "
|
||||||
|
import sys
|
||||||
|
from huggingface_hub import HfApi
|
||||||
|
HfApi().repo_info(repo_id=sys.argv[1], repo_type='dataset')
|
||||||
|
print(f'Success: Repo {sys.argv[1]} exists.')
|
||||||
|
" "${{ inputs.hf_repo }}"
|
||||||
|
|
||||||
- name: Download artifact name file
|
- name: Download artifact name file
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
@@ -153,49 +177,26 @@ jobs:
|
|||||||
name: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
name: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
||||||
path: output
|
path: output
|
||||||
|
|
||||||
- name: Remove unwanted files
|
- name: Create models folder
|
||||||
run: |
|
|
||||||
find output -type f -name 'dmonitoring_model_tinygrad.pkl' -delete
|
|
||||||
find output -type f -name 'dmonitoring_model.onnx' -delete
|
|
||||||
|
|
||||||
- name: Copy model artifact(s) to GitLab recompiled dir
|
|
||||||
env:
|
env:
|
||||||
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
||||||
run: |
|
run: |
|
||||||
ARTIFACT_DIR="gitlab_docs/models/${RECOMPILED_DIR}/${ARTIFACT_NAME}"
|
mkdir -p "local_models/${RECOMPILED_DIR}/${ARTIFACT_NAME}"
|
||||||
mkdir -p "$ARTIFACT_DIR"
|
cp -r output/* "local_models/${RECOMPILED_DIR}/${ARTIFACT_NAME}/"
|
||||||
for path in output/*; do
|
rm -f "local_models/${RECOMPILED_DIR}/${ARTIFACT_NAME}/artifact_name.txt"
|
||||||
if [ "$(basename "$path")" = "artifact_name.txt" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
name="$(basename "$path")"
|
|
||||||
if [ -d "$path" ]; then
|
|
||||||
mkdir -p "$ARTIFACT_DIR/$name"
|
|
||||||
cp -r "$path"/* "$ARTIFACT_DIR/$name/"
|
|
||||||
echo "Copied dir $name -> $ARTIFACT_DIR/$name"
|
|
||||||
else
|
|
||||||
cp "$path" "$ARTIFACT_DIR/"
|
|
||||||
echo "Copied file $name -> $ARTIFACT_DIR/"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Push recompiled dir to GitLab
|
- name: Upload to Hugging Face
|
||||||
env:
|
env:
|
||||||
GITLAB_SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
|
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||||
|
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
||||||
run: |
|
run: |
|
||||||
cd gitlab_docs
|
hf upload ${{ inputs.hf_repo }} \
|
||||||
git checkout main
|
output/ \
|
||||||
git pull origin main
|
"models/${RECOMPILED_DIR}/${ARTIFACT_NAME}/" \
|
||||||
for d in models/"$RECOMPILED_DIR"/*/; do
|
--repo-type=dataset
|
||||||
git sparse-checkout add "$d"
|
|
||||||
done
|
|
||||||
git add models/"$RECOMPILED_DIR"
|
|
||||||
git config --global user.name "GitHub Action"
|
|
||||||
git config --global user.email "action@github.com"
|
|
||||||
git commit -m "Create/Update $RECOMPILED_DIR with new/updated model from build-single-tinygrad-model" || echo "No changes to commit"
|
|
||||||
git push origin main
|
|
||||||
|
|
||||||
- run: |
|
- name: Pull gh-pages
|
||||||
|
run: |
|
||||||
cd docs
|
cd docs
|
||||||
git pull origin gh-pages
|
git pull origin gh-pages
|
||||||
|
|
||||||
@@ -211,9 +212,11 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
[ -n "${{ inputs.generation }}" ] && ARGS="$ARGS --generation \"${{ inputs.generation }}\""
|
[ -n "${{ inputs.generation }}" ] && ARGS="$ARGS --generation \"${{ inputs.generation }}\""
|
||||||
[ -n "${{ inputs.version }}" ] && ARGS="$ARGS --version \"${{ inputs.version }}\""
|
[ -n "${{ inputs.version }}" ] && ARGS="$ARGS --version \"${{ inputs.version }}\""
|
||||||
|
[ -n "${{ inputs.set_min_version }}" ] && ARGS="$ARGS --set-min-version \"${{ inputs.set_min_version }}\""
|
||||||
|
[ -n "${{ inputs.tinygrad_ref }}" ] && ARGS="$ARGS --tinygrad-ref \"${{ inputs.tinygrad_ref }}\""
|
||||||
eval python3 docs/json_parser.py \
|
eval python3 docs/json_parser.py \
|
||||||
--json-path "$JSON_FILE" \
|
--json-path "$JSON_FILE" \
|
||||||
--recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR" \
|
--recompiled-dir "local_models/$RECOMPILED_DIR" \
|
||||||
--sort-by-date \
|
--sort-by-date \
|
||||||
$ARGS
|
$ARGS
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'cereal/**'
|
- 'openpilot/cereal/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
@@ -23,56 +23,75 @@ env:
|
|||||||
CI: 1
|
CI: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
generate_cereal_artifact:
|
|
||||||
name: Generate cereal validation artifacts
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Build openpilot
|
|
||||||
run: scons -j$(nproc) cereal
|
|
||||||
- name: Dump sunnypilot schema
|
|
||||||
run: |
|
|
||||||
export PYTHONPATH=${{ github.workspace }}
|
|
||||||
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py -g -f schema.json
|
|
||||||
- name: 'Prepare artifact'
|
|
||||||
run: |
|
|
||||||
mkdir -p "cereal/messaging/tests/cereal_validations"
|
|
||||||
cp cereal/messaging/tests/validate_sp_cereal_upstream.py "cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py"
|
|
||||||
cp schema.json "cereal/messaging/tests/cereal_validations/schema.json"
|
|
||||||
- name: 'Upload Artifact'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: cereal_validations
|
|
||||||
path: cereal/messaging/tests/cereal_validations
|
|
||||||
|
|
||||||
validate_cereal_with_upstream:
|
validate_cereal_with_upstream:
|
||||||
name: Validate cereal with Upstream
|
name: Validate cereal with Upstream
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: generate_cereal_artifact
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sunnypilot
|
- name: Checkout sunnypilot cereal
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
sparse-checkout: |
|
||||||
|
openpilot/cereal
|
||||||
|
uv.lock
|
||||||
|
submodules: false
|
||||||
|
|
||||||
|
- name: Init sunnypilot opendbc submodule
|
||||||
|
run: git submodule update --init --depth 1 opendbc_repo
|
||||||
|
|
||||||
|
- name: Locate sunnypilot capnp import paths
|
||||||
|
id: locate-sp-capnp
|
||||||
|
run: |
|
||||||
|
SP_IMPORT_ARGS=""
|
||||||
|
SP_CAR_CAPNP=$(find opendbc_repo -maxdepth 4 -name car.capnp -path '*/opendbc/car/car.capnp' -printf '%h\n' -quit)
|
||||||
|
if [ -n "$SP_CAR_CAPNP" ]; then
|
||||||
|
SP_IMPORT_ARGS="-I $SP_CAR_CAPNP"
|
||||||
|
echo "Found sunnypilot car.capnp at: $SP_CAR_CAPNP"
|
||||||
|
fi
|
||||||
|
echo "import_args=$SP_IMPORT_ARGS" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Checkout upstream openpilot
|
- name: Checkout upstream openpilot
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
repository: 'commaai/openpilot'
|
repository: 'commaai/openpilot'
|
||||||
path: openpilot
|
path: upstream_openpilot
|
||||||
submodules: true
|
|
||||||
ref: "refs/heads/master"
|
ref: "refs/heads/master"
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Build openpilot
|
- name: Init upstream opendbc submodule
|
||||||
working-directory: openpilot
|
working-directory: upstream_openpilot
|
||||||
run: scons -j$(nproc) cereal
|
run: git submodule update --init --depth 1 opendbc_repo
|
||||||
- name: Download build artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
- name: Locate upstream capnp paths
|
||||||
with:
|
id: locate-capnp
|
||||||
name: cereal_validations
|
|
||||||
path: openpilot/cereal/messaging/tests/cereal_validations
|
|
||||||
- name: 'Validate sunnypilot schema against upstream'
|
|
||||||
run: |
|
run: |
|
||||||
export PYTHONPATH=${{ github.workspace }}/openpilot
|
CEREAL_DIR=$(find upstream_openpilot -maxdepth 4 -name log.capnp -path '*/cereal/log.capnp' -printf '%h\n' -quit)
|
||||||
chmod +x openpilot/cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py
|
if [ -z "$CEREAL_DIR" ]; then
|
||||||
python3 openpilot/cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py -r -f openpilot/cereal/messaging/tests/cereal_validations/schema.json
|
echo "::error::Could not locate cereal/log.capnp in upstream openpilot"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "cereal_dir=$CEREAL_DIR" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Found upstream cereal at: $CEREAL_DIR"
|
||||||
|
|
||||||
|
IMPORT_ARGS=""
|
||||||
|
CAR_CAPNP=$(find upstream_openpilot -maxdepth 5 -name car.capnp -path '*/opendbc/car/car.capnp' -printf '%h\n' -quit)
|
||||||
|
if [ -n "$CAR_CAPNP" ]; then
|
||||||
|
IMPORT_ARGS="-I $CAR_CAPNP"
|
||||||
|
echo "Found car.capnp at: $CAR_CAPNP"
|
||||||
|
fi
|
||||||
|
echo "import_args=$IMPORT_ARGS" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Install pycapnp
|
||||||
|
run: |
|
||||||
|
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
|
||||||
|
pip install "pycapnp==${PYCAPNP_VER}"
|
||||||
|
|
||||||
|
- name: Generate sunnypilot schema
|
||||||
|
run: |
|
||||||
|
python3 openpilot/cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
||||||
|
-g -f /tmp/sp_schema.json --cereal-dir openpilot/cereal \
|
||||||
|
${{ steps.locate-sp-capnp.outputs.import_args }}
|
||||||
|
|
||||||
|
- name: Validate against upstream
|
||||||
|
run: |
|
||||||
|
python3 openpilot/cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
||||||
|
-r -f /tmp/sp_schema.json --cereal-dir ${{ steps.locate-capnp.outputs.cereal_dir }} \
|
||||||
|
${{ steps.locate-capnp.outputs.import_args }}
|
||||||
|
|||||||
@@ -17,29 +17,42 @@ jobs:
|
|||||||
- name: Wait for process replay
|
- name: Wait for process replay
|
||||||
id: wait
|
id: wait
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: lewagon/wait-on-check-action@v1.3.4
|
uses: lewagon/wait-on-check-action@1d57e2c51a58d812d2765e036a028b6bdb5a6154
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
check-name: process replay
|
check-name: process replay
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
allowed-conclusions: success,failure
|
allowed-conclusions: success,failure
|
||||||
wait-interval: 20
|
wait-interval: 20
|
||||||
|
- name: Get tests run ID
|
||||||
|
if: steps.wait.outcome == 'success'
|
||||||
|
id: tests-run
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
run_id=$(gh api \
|
||||||
|
"repos/${{ github.repository }}/actions/workflows/tests.yaml/runs?event=pull_request&head_sha=${{ github.event.pull_request.head.sha }}&per_page=1" \
|
||||||
|
--jq '.workflow_runs[0].id // empty')
|
||||||
|
if [ -z "$run_id" ]; then
|
||||||
|
echo "No tests.yaml run found for ${{ github.event.pull_request.head.sha }}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "run-id=$run_id" >> "$GITHUB_OUTPUT"
|
||||||
- name: Download diff
|
- name: Download diff
|
||||||
if: steps.wait.outcome == 'success'
|
if: steps.wait.outcome == 'success'
|
||||||
uses: dawidd6/action-download-artifact@v6
|
continue-on-error: true
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
workflow: tests.yaml
|
repository: ${{ github.repository }}
|
||||||
workflow_conclusion: ''
|
run-id: ${{ steps.tests-run.outputs.run-id }}
|
||||||
pr: ${{ github.event.number }}
|
|
||||||
name: diff_report_${{ github.event.number }}
|
name: diff_report_${{ github.event.number }}
|
||||||
path: .
|
path: .
|
||||||
allow_forks: true
|
|
||||||
- name: Comment on PR
|
- name: Comment on PR
|
||||||
if: steps.wait.outcome == 'success'
|
if: steps.wait.outcome == 'success' && hashFiles('diff_report.txt') != ''
|
||||||
uses: thollander/actions-comment-pull-request@v2
|
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b
|
||||||
with:
|
with:
|
||||||
filePath: diff_report.txt
|
file-path: diff_report.txt
|
||||||
comment_tag: diff_report
|
comment-tag: diff_report
|
||||||
pr_number: ${{ github.event.number }}
|
pr-number: ${{ github.event.number }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ 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 }}
|
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
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
GIT_CONFIG_COUNT: 1
|
||||||
|
GIT_CONFIG_KEY_0: lfs.fetchexclude
|
||||||
|
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docs:
|
docs:
|
||||||
name: build docs
|
name: build docs
|
||||||
@@ -22,7 +27,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: commaai/timeout@v1
|
- uses: commaai/timeout@v1
|
||||||
|
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -30,11 +35,10 @@ jobs:
|
|||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: |
|
run: |
|
||||||
git lfs pull
|
git lfs pull
|
||||||
pip install zensical
|
python docs/serve.py --build
|
||||||
python scripts/docs.py build
|
|
||||||
|
|
||||||
# Push to docs.comma.ai
|
# Push to docs.comma.ai
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
|
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
|
||||||
with:
|
with:
|
||||||
path: openpilot-docs
|
path: openpilot-docs
|
||||||
@@ -45,14 +49,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
source release/identity.sh
|
source tools/release/identity.sh
|
||||||
|
|
||||||
cd openpilot-docs
|
cd openpilot-docs
|
||||||
git checkout --orphan tmp
|
git checkout --orphan tmp
|
||||||
git rm -rf .
|
git rm -rf .
|
||||||
|
|
||||||
# copy over docs
|
# copy over docs
|
||||||
cp -r ../docs_site/ docs/
|
cp -r ../docs/_site/ docs/
|
||||||
|
|
||||||
# GitHub pages config
|
# GitHub pages config
|
||||||
touch docs/.nojekyll
|
touch docs/.nojekyll
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
name: Download HF model chunks
|
||||||
|
description: Resolve and download model chunks from HuggingFace in parallel
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
hf_repo:
|
||||||
|
description: HuggingFace dataset repo
|
||||||
|
required: true
|
||||||
|
models:
|
||||||
|
description: 'JSON array of {hf_path, onnx_hash, canonical} objects'
|
||||||
|
required: true
|
||||||
|
dest_dir:
|
||||||
|
description: Destination directory for downloaded chunks
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Download model chunks
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
HF_REPO: ${{ inputs.hf_repo }}
|
||||||
|
MODELS_JSON: ${{ inputs.models }}
|
||||||
|
DEST_DIR: ${{ inputs.dest_dir }}
|
||||||
|
run: |
|
||||||
|
set -eo pipefail
|
||||||
|
DOWNLOAD_LIST=$(mktemp)
|
||||||
|
|
||||||
|
resolve_chunks() {
|
||||||
|
local HF_PATH="$1" ONNX_HASH="$2" CANONICAL="$3" DEST_DIR="$4"
|
||||||
|
local JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_PATH}/default_models.json"
|
||||||
|
local DEFAULTS BUNDLE ARTIFACT BASE_URL NUM_CHUNKS
|
||||||
|
DEFAULTS=$(curl -fsSL "$JSON_URL")
|
||||||
|
BUNDLE=$(echo "$DEFAULTS" | jq --arg hash "$ONNX_HASH" '.bundles[] | select(.onnx_sha256 == $hash)')
|
||||||
|
ARTIFACT=$(echo "$BUNDLE" | jq -r '.models[0].artifact')
|
||||||
|
BASE_URL=$(echo "$ARTIFACT" | jq -r '.download_uri.url' | sed 's|/[^/]*$||')
|
||||||
|
NUM_CHUNKS=$(echo "$ARTIFACT" | jq -r '.chunks | length')
|
||||||
|
|
||||||
|
mkdir -p "$DEST_DIR"
|
||||||
|
while IFS= read -r CHUNK_NAME; do
|
||||||
|
CHUNK_IDX=$(echo "$CHUNK_NAME" | grep -oP 'chunk\K[0-9]+of[0-9]+' || true)
|
||||||
|
if [ -z "$CHUNK_IDX" ]; then
|
||||||
|
echo "::error::Failed to parse chunk index from: $CHUNK_NAME"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
ENCODED_URL=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${BASE_URL}/${CHUNK_NAME}', safe=':/'))")
|
||||||
|
printf '%s\t%s\n' "$ENCODED_URL" "${DEST_DIR}/${CANONICAL}.chunk${CHUNK_IDX}" >> "$DOWNLOAD_LIST"
|
||||||
|
done < <(echo "$ARTIFACT" | jq -r '.chunks[].file_name')
|
||||||
|
echo "$NUM_CHUNKS" > "${DEST_DIR}/${CANONICAL}.chunkmanifest"
|
||||||
|
|
||||||
|
if [ "$CANONICAL" = "dmonitoring_model_tinygrad.pkl" ]; then
|
||||||
|
for warp in dm_warp_1928x1208_tinygrad.pkl dm_warp_1344x760_tinygrad.pkl; do
|
||||||
|
ENCODED_URL=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${BASE_URL}/${warp}', safe=':/'))")
|
||||||
|
printf '%s\t%s\n' "$ENCODED_URL" "${DEST_DIR}/${warp}" >> "$DOWNLOAD_LIST"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "$MODELS_JSON" | jq -c '.[]' | while IFS= read -r model; do
|
||||||
|
HF_PATH=$(echo "$model" | jq -r '.hf_path')
|
||||||
|
ONNX_HASH=$(echo "$model" | jq -r '.onnx_hash')
|
||||||
|
CANONICAL=$(echo "$model" | jq -r '.canonical')
|
||||||
|
resolve_chunks "$HF_PATH" "$ONNX_HASH" "$CANONICAL" "$DEST_DIR"
|
||||||
|
done
|
||||||
|
|
||||||
|
TOTAL=$(wc -l < "$DOWNLOAD_LIST")
|
||||||
|
echo "Downloading $TOTAL chunks with 8 parallel connections..."
|
||||||
|
xargs -P8 -d'\n' -I{} bash -c '
|
||||||
|
URL="${1%% *}"
|
||||||
|
DEST="${1#* }"
|
||||||
|
echo "Downloading $(basename "$DEST")"
|
||||||
|
curl -fsSL --retry 3 --retry-delay 5 -o "$DEST" "$URL"
|
||||||
|
' _ {} < "$DOWNLOAD_LIST"
|
||||||
|
rm -f "$DOWNLOAD_LIST"
|
||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Delete stale Jenkins branches
|
- name: Delete stale Jenkins branches
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check for trigger phrase
|
- name: Check for trigger phrase
|
||||||
id: check_comment
|
id: check_comment
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const triggerPhrase = "trigger-jenkins";
|
const triggerPhrase = "trigger-jenkins";
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
if: steps.check_comment.outputs.result == 'true'
|
if: steps.check_comment.outputs.result == 'true'
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
ref: refs/pull/${{ github.event.issue.number }}/head
|
ref: refs/pull/${{ github.event.issue.number }}/head
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Delete trigger comment
|
- name: Delete trigger comment
|
||||||
if: steps.check_comment.outputs.result == 'true' && always()
|
if: steps.check_comment.outputs.result == 'true' && always()
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
await github.rest.issues.deleteComment({
|
await github.rest.issues.deleteComment({
|
||||||
|
|||||||
@@ -65,8 +65,21 @@ jobs:
|
|||||||
echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig
|
echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig
|
||||||
echo ' locksverify = false' >> .lfsconfig
|
echo ' locksverify = false' >> .lfsconfig
|
||||||
|
|
||||||
|
- name: Configure LFS transfer settings
|
||||||
|
run: |
|
||||||
|
git config lfs.activitytimeout 300
|
||||||
|
git config lfs.transfer.maxretries 5
|
||||||
|
git config lfs.concurrenttransfers 4
|
||||||
|
|
||||||
- name: Push LFS
|
- name: Push LFS
|
||||||
id: sync-and-commit
|
id: sync-and-commit
|
||||||
run: |
|
run: |
|
||||||
git lfs ls-files -l
|
git lfs ls-files -l
|
||||||
git lfs push --all origin
|
for attempt in 1 2 3; do
|
||||||
|
echo "Push attempt $attempt..."
|
||||||
|
git lfs push --all origin && exit 0
|
||||||
|
echo "Attempt $attempt failed, retrying in 30s..."
|
||||||
|
sleep 30
|
||||||
|
done
|
||||||
|
echo "All push attempts failed"
|
||||||
|
exit 1
|
||||||
|
|||||||
@@ -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@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Checkout master
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
ref: master
|
|
||||||
path: base
|
|
||||||
- run: git lfs pull
|
|
||||||
- run: cd base && git lfs pull
|
|
||||||
|
|
||||||
- name: scripts/reporter.py
|
|
||||||
id: report
|
|
||||||
run: |
|
|
||||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
|
||||||
echo "## Model Review" >> $GITHUB_OUTPUT
|
|
||||||
PYTHONPATH=${{ github.workspace }} 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: release/ci/docker_build_sp.sh
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_prebuilt:
|
|
||||||
name: build prebuilt
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
|
||||||
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|create badges).*).)*$
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: git lfs pull
|
|
||||||
- name: Build and Push docker image
|
|
||||||
run: |
|
|
||||||
$DOCKER_LOGIN
|
|
||||||
eval "$BUILD"
|
|
||||||
@@ -4,6 +4,11 @@ on:
|
|||||||
- cron: '0 9 * * *'
|
- cron: '0 9 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
GIT_CONFIG_COUNT: 1
|
||||||
|
GIT_CONFIG_KEY_0: lfs.fetchexclude
|
||||||
|
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build___nightly:
|
build___nightly:
|
||||||
name: build __nightly
|
name: build __nightly
|
||||||
@@ -15,17 +20,17 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Wait for green check mark
|
- name: Wait for green check mark
|
||||||
if: ${{ github.event_name == 'schedule' }}
|
if: ${{ github.event_name == 'schedule' }}
|
||||||
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
|
uses: lewagon/wait-on-check-action@1d57e2c51a58d812d2765e036a028b6bdb5a6154
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
wait-interval: 30
|
wait-interval: 30
|
||||||
running-workflow-name: 'build __nightly'
|
running-workflow-name: 'build __nightly'
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$
|
check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4 # checkout version > v4 breaks nightly release script
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Push __nightly
|
- name: Push __nightly
|
||||||
run: BRANCH=__nightly release/build_stripped.sh
|
run: BRANCH=__nightly tools/release/build_stripped.sh
|
||||||
|
|||||||
@@ -3,18 +3,25 @@ name: repo maintenance
|
|||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 14 * * 1" # every Monday at 2am UTC (6am PST)
|
- cron: "0 14 * * 1" # every Monday at 2am UTC (6am PST)
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHONPATH: ${{ github.workspace }}
|
PYTHONPATH: ${{ github.workspace }}
|
||||||
|
GIT_CONFIG_COUNT: 1
|
||||||
|
GIT_CONFIG_KEY_0: lfs.fetchexclude
|
||||||
|
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
package_updates:
|
package_updates:
|
||||||
name: package_updates
|
name: package_updates
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: >-
|
||||||
|
github.repository == 'sunnypilot/sunnypilot' &&
|
||||||
|
(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
@@ -50,10 +57,10 @@ jobs:
|
|||||||
git add .
|
git add .
|
||||||
- name: update car docs
|
- name: update car docs
|
||||||
run: |
|
run: |
|
||||||
python selfdrive/car/docs.py
|
python openpilot/selfdrive/car/docs.py
|
||||||
git add docs/CARS.md
|
git add docs/CARS.md
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0
|
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1
|
||||||
with:
|
with:
|
||||||
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||||
token: ${{ github.repository == 'commaai/openpilot' && secrets.ACTIONS_CREATE_PR_PAT || secrets.GITHUB_TOKEN }}
|
token: ${{ github.repository == 'commaai/openpilot' && secrets.ACTIONS_CREATE_PR_PAT || secrets.GITHUB_TOKEN }}
|
||||||
@@ -75,3 +82,51 @@ jobs:
|
|||||||
${{ steps.pip_tree.outputs.PIP_TREE }}
|
${{ steps.pip_tree.outputs.PIP_TREE }}
|
||||||
```
|
```
|
||||||
labels: bot
|
labels: bot
|
||||||
|
|
||||||
|
cleanup_closed_branches:
|
||||||
|
if: github.repository == 'commaai/openpilot' && github.event_name == 'pull_request'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v9
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const upstream = `${owner}/${repo}`;
|
||||||
|
|
||||||
|
const closed = context.payload.pull_request;
|
||||||
|
if (closed) {
|
||||||
|
if (closed.head.repo?.full_name === upstream) {
|
||||||
|
await github.rest.git.deleteRef({ owner, repo, ref: `heads/${closed.head.ref}` }).catch(console.log);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const response of github.paginate.iterator(github.rest.pulls.list, {
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
state: 'closed',
|
||||||
|
per_page: 100,
|
||||||
|
})) {
|
||||||
|
for (const pr of response.data) {
|
||||||
|
if (pr.head.repo?.full_name !== upstream) continue;
|
||||||
|
|
||||||
|
const branch = pr.head.ref;
|
||||||
|
try {
|
||||||
|
await github.rest.git.deleteRef({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
ref: `heads/${branch}`,
|
||||||
|
});
|
||||||
|
console.log(`Deleted branch ${branch} (PR #${pr.number})`);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status === 422 || error.status === 403) {
|
||||||
|
console.log(`Skipping branch ${branch} (PR #${pr.number}): ${error.message}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ env:
|
|||||||
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
|
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
|
||||||
UPSTREAM_REPO: "commaai/openpilot"
|
UPSTREAM_REPO: "commaai/openpilot"
|
||||||
TINYGRAD_PATH: ${{ github.workspace }}/tinygrad_repo
|
TINYGRAD_PATH: ${{ github.workspace }}/tinygrad_repo
|
||||||
MODELS_DIR: ${{ github.workspace }}/selfdrive/modeld/models
|
MODELS_DIR: ${{ github.workspace }}/openpilot/selfdrive/modeld/models
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
@@ -30,6 +30,11 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: ''
|
default: ''
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for (qcom or chestnut)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'qcom'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
upstream_branch:
|
upstream_branch:
|
||||||
@@ -46,6 +51,14 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- qcom
|
||||||
|
- chestnut
|
||||||
|
default: 'qcom'
|
||||||
|
|
||||||
|
|
||||||
run-name: Build model [${{ inputs.custom_name || inputs.upstream_branch }}] from ref [${{ inputs.upstream_branch }}]
|
run-name: Build model [${{ inputs.custom_name || inputs.upstream_branch }}] from ref [${{ inputs.upstream_branch }}]
|
||||||
@@ -67,6 +80,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repository: commaai/openpilot
|
repository: commaai/openpilot
|
||||||
ref: ${{ inputs.upstream_branch }}
|
ref: ${{ inputs.upstream_branch }}
|
||||||
|
fetch-depth: 1
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: openpilot
|
path: openpilot
|
||||||
|
|
||||||
@@ -76,26 +90,38 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repository: sunnypilot/sunnypilot
|
repository: sunnypilot/sunnypilot
|
||||||
ref: ${{ inputs.upstream_branch }}
|
ref: ${{ inputs.upstream_branch }}
|
||||||
|
fetch-depth: 1
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: openpilot
|
path: openpilot
|
||||||
- name: Get commit date
|
- name: Get commit date
|
||||||
id: commit-date
|
id: commit-date
|
||||||
run: |
|
run: |
|
||||||
cd ${{ github.workspace }}/openpilot
|
cd ${{ github.workspace }}/openpilot/openpilot
|
||||||
commit_date=$(git log -1 --format=%cd --date=format:'%B %d, %Y')
|
commit_date=$(git log -1 --format=%cd --date=format:'%B %d, %Y')
|
||||||
echo "model_date=${commit_date}" >> $GITHUB_OUTPUT
|
echo "model_date=${commit_date}" >> $GITHUB_OUTPUT
|
||||||
cat $GITHUB_OUTPUT
|
cat $GITHUB_OUTPUT
|
||||||
- run: |
|
- run: |
|
||||||
cd ${{ github.workspace }}/openpilot
|
cd ${{ github.workspace }}/openpilot/openpilot
|
||||||
git lfs pull
|
if [ "${{ inputs.target_hardware }}" != "chestnut" ]; then
|
||||||
|
git lfs pull -X "**/selfdrive/modeld/models/big_*.onnx,**/selfdrive/modeld/models/dmonitoring_*.onnx"
|
||||||
|
rm -f selfdrive/modeld/models/big_*.onnx selfdrive/modeld/models/dmonitoring_*.onnx
|
||||||
|
else
|
||||||
|
git lfs pull -I "**/selfdrive/modeld/models/big_*.onnx" -X ""
|
||||||
|
find selfdrive/modeld/models -name "*.onnx" ! -name "big_*.onnx" -delete
|
||||||
|
fi
|
||||||
|
if grep -lIF "version https://git-lfs.github.com/spec/v1" selfdrive/modeld/models/*.onnx; then
|
||||||
|
echo "::error::the ONNX files above are still LFS pointers, not real models"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
- name: 'Upload Artifact'
|
- name: 'Upload Artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
||||||
path: ${{ github.workspace }}/openpilot/selfdrive/modeld/models/*.onnx
|
path: ${{ github.workspace }}/openpilot/openpilot/selfdrive/modeld/models/*.onnx
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
build_model:
|
build_model:
|
||||||
runs-on: [self-hosted, tici]
|
runs-on: [self-hosted, "${{ inputs.target_hardware == 'chestnut' && 'chestnut' || 'tici' }}"]
|
||||||
needs: get_model
|
needs: get_model
|
||||||
env:
|
env:
|
||||||
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
|
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
|
||||||
@@ -103,24 +129,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- run: git lfs pull
|
|
||||||
- name: Cache SCons
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ${{env.SCONS_CACHE_DIR}}
|
|
||||||
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}-model-${{ github.sha }}
|
|
||||||
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
|
|
||||||
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
|
||||||
restore-keys: |
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}-model
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_NEW_BRANCH }}-model
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_BRANCH }}-model
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_NEW_BRANCH }}
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_BRANCH }}
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}
|
|
||||||
|
|
||||||
- name: Set environment variables
|
- name: Set environment variables
|
||||||
id: set-env
|
id: set-env
|
||||||
@@ -128,7 +139,10 @@ jobs:
|
|||||||
# Set up common environment
|
# Set up common environment
|
||||||
source /etc/profile;
|
source /etc/profile;
|
||||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||||
|
export UV_PYTHON_PREFERENCE=managed
|
||||||
|
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
|
||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
|
uv sync --frozen
|
||||||
printenv >> $GITHUB_ENV
|
printenv >> $GITHUB_ENV
|
||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||||
cat $GITHUB_OUTPUT
|
cat $GITHUB_OUTPUT
|
||||||
@@ -148,42 +162,99 @@ jobs:
|
|||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||||
printenv
|
printenv
|
||||||
fi
|
fi
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||||
rm -rf ${{ env.MODELS_DIR }}/*.onnx
|
rm -rf ${{ env.MODELS_DIR }}/*.onnx*
|
||||||
|
|
||||||
- name: Download model artifacts
|
- name: Download model artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
||||||
path: ${{ github.workspace }}/selfdrive/modeld/models
|
path: ${{ env.MODELS_DIR }}
|
||||||
- run: |
|
|
||||||
rm -f ${{ github.workspace }}/selfdrive/modeld/models/{dmonitoring_model,big_driving_policy,big_driving_vision}.onnx
|
|
||||||
|
|
||||||
- name: Build Model
|
- name: Build Model
|
||||||
run: |
|
run: |
|
||||||
source /etc/profile
|
source /etc/profile
|
||||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}"
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
|
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}:${{ github.workspace }}"
|
||||||
|
|
||||||
# Loop through all .onnx files
|
COMPILE_MODELD="${{ github.workspace }}/openpilot/sunnypilot/modeld_v2/compile_modeld.py"
|
||||||
|
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
||||||
|
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
||||||
|
|
||||||
|
TG_FLAGS_QCOM="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
||||||
|
if [ "${{ inputs.target_hardware }}" == "chestnut" ]; then
|
||||||
|
echo "CHESTNUT build"
|
||||||
|
export CHESTNUT=1
|
||||||
|
TG_FLAGS="DEBUG=1 DEV=USB+AMD:LLVM FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0 TC_OPT=2 TC_OCCUPANCY_OPT=1"
|
||||||
|
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
||||||
|
else
|
||||||
|
echo "QCOM build"
|
||||||
|
TG_FLAGS="$TG_FLAGS_QCOM"
|
||||||
|
OUTPUT_PKL="${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate metadata for all ONNX files
|
||||||
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
||||||
base_name=$(basename "$onnx_file" .onnx)
|
echo "Generating metadata: $onnx_file"
|
||||||
output_file="${{ env.MODELS_DIR }}/${base_name}_tinygrad.pkl"
|
env ${TG_FLAGS_QCOM} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
||||||
|
|
||||||
echo "Compiling: $onnx_file -> $output_file"
|
|
||||||
QCOM=1 python3 "${{ env.TINYGRAD_PATH }}/examples/openpilot/compile3.py" "$onnx_file" "$output_file"
|
|
||||||
DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Validate Model Outputs
|
# Detect model type and build compile args
|
||||||
run: |
|
VISION_ONNX=""
|
||||||
source /etc/profile
|
for f in "${{ env.MODELS_DIR }}/driving_vision.onnx" "${{ env.MODELS_DIR }}/big_driving_vision.onnx"; do
|
||||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
[ -f "$f" ] && VISION_ONNX="$f" && break
|
||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
done
|
||||||
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
|
|
||||||
--validate-only \
|
POLICY_ONNX=""
|
||||||
--model-dir "${{ env.MODELS_DIR }}"
|
for f in "${{ env.MODELS_DIR }}/driving_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_policy.onnx"; do
|
||||||
|
[ -f "$f" ] && POLICY_ONNX="$f" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
OFF_POLICY_ONNX=""
|
||||||
|
for f in "${{ env.MODELS_DIR }}/driving_off_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_off_policy.onnx"; do
|
||||||
|
[ -f "$f" ] && OFF_POLICY_ONNX="$f" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
ON_POLICY_ONNX=""
|
||||||
|
for f in "${{ env.MODELS_DIR }}/driving_on_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_on_policy.onnx"; do
|
||||||
|
[ -f "$f" ] && ON_POLICY_ONNX="$f" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
SUPERCOMBO_ONNX=""
|
||||||
|
for f in "${{ env.MODELS_DIR }}/supercombo.onnx" "${{ env.MODELS_DIR }}/driving_supercombo.onnx" "${{ env.MODELS_DIR }}/big_supercombo.onnx" "${{ env.MODELS_DIR }}/big_driving_supercombo.onnx"; do
|
||||||
|
[ -f "$f" ] && SUPERCOMBO_ONNX="$f" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
|
||||||
|
if [ -f "$VISION_ONNX" ]; then
|
||||||
|
ONNX_ARGS="--vision-onnx $VISION_ONNX"
|
||||||
|
if [ -f "$ON_POLICY_ONNX" ] && [ -f "$OFF_POLICY_ONNX" ]; then
|
||||||
|
MODEL_TYPE=vision_multi_policy
|
||||||
|
ONNX_ARGS="$ONNX_ARGS --off-policy-onnx $OFF_POLICY_ONNX --on-policy-onnx $ON_POLICY_ONNX"
|
||||||
|
elif [ -f "$OFF_POLICY_ONNX" ] && [ -f "$POLICY_ONNX" ]; then
|
||||||
|
MODEL_TYPE=vision_multi_policy
|
||||||
|
ONNX_ARGS="$ONNX_ARGS --policy-onnx $POLICY_ONNX --off-policy-onnx $OFF_POLICY_ONNX"
|
||||||
|
elif [ -f "$POLICY_ONNX" ]; then
|
||||||
|
MODEL_TYPE=vision_policy
|
||||||
|
ONNX_ARGS="$ONNX_ARGS --policy-onnx $POLICY_ONNX"
|
||||||
|
fi
|
||||||
|
elif [ -f "$SUPERCOMBO_ONNX" ]; then
|
||||||
|
MODEL_TYPE=supercombo
|
||||||
|
ONNX_ARGS="--supercombo-onnx $SUPERCOMBO_ONNX"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$MODEL_TYPE" ]; then
|
||||||
|
echo "Detected: $MODEL_TYPE -> $OUTPUT_PKL"
|
||||||
|
env ${TG_FLAGS} python3 "$COMPILE_MODELD" \
|
||||||
|
--model-type $MODEL_TYPE \
|
||||||
|
--model-size $MODEL_SIZE \
|
||||||
|
--camera-resolutions $CAMERA_RES \
|
||||||
|
$ONNX_ARGS \
|
||||||
|
--output "$OUTPUT_PKL"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Prepare Output
|
- name: Prepare Output
|
||||||
run: |
|
run: |
|
||||||
@@ -193,8 +264,8 @@ jobs:
|
|||||||
# Copy the model files
|
# Copy the model files
|
||||||
rsync -avm \
|
rsync -avm \
|
||||||
--include='*.dlc' \
|
--include='*.dlc' \
|
||||||
--include='*.pkl' \
|
--include='*.chunk*' \
|
||||||
--include='*.onnx' \
|
--include='*.chunkmanifest' \
|
||||||
--exclude='*' \
|
--exclude='*' \
|
||||||
--delete-excluded \
|
--delete-excluded \
|
||||||
--chown=comma:comma \
|
--chown=comma:comma \
|
||||||
@@ -226,4 +297,5 @@ jobs:
|
|||||||
- name: Re-enable powersave
|
- name: Re-enable powersave
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ env:
|
|||||||
BUILD_DIR: "/data/openpilot"
|
BUILD_DIR: "/data/openpilot"
|
||||||
OUTPUT_DIR: ${{ github.workspace }}/output
|
OUTPUT_DIR: ${{ github.workspace }}/output
|
||||||
CI_DIR: ${{ github.workspace }}/release/ci
|
CI_DIR: ${{ github.workspace }}/release/ci
|
||||||
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
|
|
||||||
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
||||||
|
|
||||||
# Branch configurations
|
|
||||||
STAGING_SOURCE_BRANCH: 'master'
|
|
||||||
|
|
||||||
# Runtime configuration
|
# Runtime configuration
|
||||||
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
@@ -40,8 +36,11 @@ jobs:
|
|||||||
publish_concurrency_group: ${{ steps.strategy.outputs.publish_concurrency_group }}
|
publish_concurrency_group: ${{ steps.strategy.outputs.publish_concurrency_group }}
|
||||||
is_stable_branch: ${{ steps.strategy.outputs.is_stable_branch }}
|
is_stable_branch: ${{ steps.strategy.outputs.is_stable_branch }}
|
||||||
build: ${{ steps.strategy.outputs.build }}
|
build: ${{ steps.strategy.outputs.build }}
|
||||||
|
include_big_model: ${{ steps.strategy.outputs.include_big_model }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
- name: Extract deploy strategy
|
- name: Extract deploy strategy
|
||||||
id: strategy
|
id: strategy
|
||||||
run: |
|
run: |
|
||||||
@@ -79,9 +78,12 @@ jobs:
|
|||||||
is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')";
|
is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')";
|
||||||
echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT
|
echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
stable_version=$(cat sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g');
|
stable_version=$(cat openpilot/sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g');
|
||||||
echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT
|
echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT
|
||||||
echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT
|
echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
include_big_model="$(echo "$CONFIG" | jq -r '.include_big_model // false')";
|
||||||
|
echo "include_big_model=$include_big_model" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
echo "build=$BUILD" >> $GITHUB_OUTPUT
|
echo "build=$BUILD" >> $GITHUB_OUTPUT
|
||||||
cat $GITHUB_OUTPUT
|
cat $GITHUB_OUTPUT
|
||||||
@@ -96,6 +98,8 @@ jobs:
|
|||||||
}}
|
}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
- name: Wait for Tests
|
- name: Wait for Tests
|
||||||
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
||||||
with:
|
with:
|
||||||
@@ -109,11 +113,6 @@ jobs:
|
|||||||
group: build-${{ github.head_ref || github.ref_name }}
|
group: build-${{ github.head_ref || github.ref_name }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
runs-on: [self-hosted, tici]
|
runs-on: [self-hosted, tici]
|
||||||
outputs:
|
|
||||||
new_branch: ${{ needs.prepare_strategy.outputs.new_branch }}
|
|
||||||
version: ${{ needs.prepare_strategy.outputs.version }}
|
|
||||||
extra_version_identifier: ${{ needs.prepare_strategy.outputs.extra_version_identifier }}
|
|
||||||
commit_sha: ${{ github.sha }}
|
|
||||||
if: ${{
|
if: ${{
|
||||||
(always() && !cancelled() && !failure()) &&
|
(always() && !cancelled() && !failure()) &&
|
||||||
needs.prepare_strategy.result == 'success' &&
|
needs.prepare_strategy.result == 'success' &&
|
||||||
@@ -124,39 +123,22 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{ env.SOURCE_BRANCH }}
|
ref: ${{ env.SOURCE_BRANCH }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.fork && github.event.pull_request.head.repo.full_name || github.repository }}
|
repository: ${{ github.event.pull_request.head.repo.fork && github.event.pull_request.head.repo.full_name || github.repository }}
|
||||||
- run: git lfs pull
|
- run: git lfs pull
|
||||||
|
|
||||||
- name: Cache SCons
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ${{env.SCONS_CACHE_DIR}}
|
|
||||||
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}-${{ github.sha }}
|
|
||||||
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
|
|
||||||
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
|
||||||
restore-keys: |
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_SOURCE_BRANCH }}
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}
|
|
||||||
|
|
||||||
- name: Set environment variables
|
- name: Set environment variables
|
||||||
id: set-env
|
|
||||||
run: |
|
run: |
|
||||||
echo "new_branch=${{ needs.prepare_strategy.outputs.new_branch }}" >> $GITHUB_OUTPUT
|
|
||||||
echo "version=${{ needs.prepare_strategy.outputs.version }}" >> $GITHUB_OUTPUT
|
|
||||||
echo "extra_version_identifier=${{ needs.prepare_strategy.outputs.extra_version_identifier }}" >> $GITHUB_OUTPUT
|
|
||||||
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Set up common environment
|
# Set up common environment
|
||||||
source /etc/profile;
|
source /etc/profile;
|
||||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||||
|
export UV_PYTHON_PREFERENCE=managed
|
||||||
|
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
|
||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
|
uv sync
|
||||||
printenv >> $GITHUB_ENV
|
printenv >> $GITHUB_ENV
|
||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
|
||||||
cat $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup build environment
|
- name: Setup build environment
|
||||||
run: |
|
run: |
|
||||||
@@ -165,67 +147,56 @@ jobs:
|
|||||||
echo "Starting build stage..."
|
echo "Starting build stage..."
|
||||||
echo "BUILD_DIR: ${BUILD_DIR}"
|
echo "BUILD_DIR: ${BUILD_DIR}"
|
||||||
echo "CI_DIR: ${CI_DIR}"
|
echo "CI_DIR: ${CI_DIR}"
|
||||||
echo "VERSION: ${{ steps.set-env.outputs.version }}"
|
echo "VERSION: ${{ needs.prepare_strategy.outputs.version }}"
|
||||||
echo "UV_PROJECT_ENVIRONMENT: ${UV_PROJECT_ENVIRONMENT}"
|
echo "UV_PROJECT_ENVIRONMENT: ${UV_PROJECT_ENVIRONMENT}"
|
||||||
echo "VIRTUAL_ENV: ${VIRTUAL_ENV}"
|
echo "VIRTUAL_ENV: ${VIRTUAL_ENV}"
|
||||||
echo "-------"
|
echo "-------"
|
||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||||
printenv
|
printenv
|
||||||
fi
|
fi
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||||
|
|
||||||
- name: Build Main Project
|
- name: Build Main Project
|
||||||
run: |
|
run: |
|
||||||
export PYTHONPATH="$BUILD_DIR"
|
export PYTHONPATH="$BUILD_DIR:$BUILD_DIR/msgq_repo:$BUILD_DIR/opendbc_repo:$BUILD_DIR/rednose_repo:$BUILD_DIR/teleoprtc_repo:$BUILD_DIR/tinygrad_repo"
|
||||||
./release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/
|
./tools/release/release_files.py | xargs -0 cp -pR --parents -t "$BUILD_DIR" --
|
||||||
|
# outside the checkout, which is wiped each run. /data/scons_cache is the device's, not ours.
|
||||||
|
SCONS_CACHE="$RUNNER_WORKSPACE/scons_cache"
|
||||||
|
mkdir -p "$SCONS_CACHE"
|
||||||
cd $BUILD_DIR
|
cd $BUILD_DIR
|
||||||
sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py
|
echo "Building locationd..."
|
||||||
echo "Building sunnypilot's modeld_v2..."
|
# -j1: parallel rednose generators OOM the device
|
||||||
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld_v2
|
scons -j1 cache_dir="$SCONS_CACHE" --minimal \
|
||||||
echo "Building sunnypilot's locationd..."
|
openpilot/selfdrive/locationd openpilot/sunnypilot/selfdrive/locationd
|
||||||
scons -j2 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/selfdrive/locationd
|
|
||||||
echo "Building openpilot's locationd..."
|
|
||||||
scons -j1 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal selfdrive/locationd
|
|
||||||
echo "Building rest of sunnypilot"
|
echo "Building rest of sunnypilot"
|
||||||
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal
|
SKIP_TINYGRAD_COMPILE=1 /usr/bin/time -v scons -j$(nproc) cache_dir="$SCONS_CACHE" --minimal
|
||||||
touch ${BUILD_DIR}/prebuilt
|
touch ${BUILD_DIR}/prebuilt
|
||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||||
ls -la ${BUILD_DIR}
|
ls -la ${BUILD_DIR}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Prepare Output
|
- name: Strip release tree
|
||||||
run: |
|
run: |
|
||||||
sudo rm -rf ${OUTPUT_DIR}
|
cd $BUILD_DIR
|
||||||
mkdir -p ${OUTPUT_DIR}
|
find . -name '*.a' -delete
|
||||||
rsync -am${RUNNER_DEBUG:+v} \
|
find . -name '*.o' -delete
|
||||||
--exclude='.sconsign.dblite' \
|
find . -name '*.os' -delete
|
||||||
--exclude='*.a' \
|
find . -name '*.pyc' -delete
|
||||||
--exclude='*.o' \
|
find . -name 'moc_*' -delete
|
||||||
--exclude='*.os' \
|
find . -name '__pycache__' -type d -exec rm -rf {} +
|
||||||
--exclude='*.pyc' \
|
find . -name 'SConstruct' -delete
|
||||||
--exclude='moc_*' \
|
find . -name 'SConscript' -delete
|
||||||
--exclude='__pycache__' \
|
rm -rf .sconsign.dblite Jenkinsfile tools/release/ release/
|
||||||
--exclude='Jenkinsfile' \
|
rm -f openpilot/selfdrive/modeld/models/*.onnx*
|
||||||
--exclude='**/release/' \
|
rm -f openpilot/sunnypilot/modeld*/models/*.onnx*
|
||||||
--exclude='**/.github/' \
|
find openpilot/third_party/ -name '*x86*' -exec rm -r {} +
|
||||||
--exclude='**/selfdrive/ui/replay/' \
|
find openpilot/third_party/ -name '*Darwin*' -exec rm -r {} +
|
||||||
--exclude='**/__pycache__/' \
|
cd -
|
||||||
--exclude='${{env.SCONS_CACHE_DIR}}' \
|
|
||||||
--exclude='**/.git/' \
|
|
||||||
--exclude='**/SConstruct' \
|
|
||||||
--exclude='**/SConscript' \
|
|
||||||
--exclude='**/.venv/' \
|
|
||||||
--exclude='selfdrive/modeld/models/driving_vision.onnx' \
|
|
||||||
--exclude='selfdrive/modeld/models/driving_policy.onnx' \
|
|
||||||
--exclude='third_party/*x86*' \
|
|
||||||
--exclude='third_party/*Darwin*' \
|
|
||||||
--delete-excluded \
|
|
||||||
--chown=comma:comma \
|
|
||||||
${BUILD_DIR}/ ${OUTPUT_DIR}/
|
|
||||||
|
|
||||||
- name: 'Tar.gz files'
|
- name: 'Tar.gz files'
|
||||||
run: |
|
run: |
|
||||||
tar czf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }} .
|
tar czf prebuilt.tar.gz -C ${{ env.BUILD_DIR }} .
|
||||||
ls -la prebuilt.tar.gz
|
ls -la prebuilt.tar.gz
|
||||||
|
|
||||||
- name: 'Upload Artifact'
|
- name: 'Upload Artifact'
|
||||||
@@ -233,28 +204,286 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: prebuilt
|
name: prebuilt
|
||||||
path: prebuilt.tar.gz
|
path: prebuilt.tar.gz
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
- name: Re-enable powersave
|
- name: Re-enable powersave
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||||
|
|
||||||
|
prepare_chestnut:
|
||||||
|
needs: [ prepare_strategy ]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
if: ${{ needs.prepare_strategy.outputs.include_big_model == 'true' }}
|
||||||
|
concurrency:
|
||||||
|
group: prepare-chestnut
|
||||||
|
cancel-in-progress: false
|
||||||
|
outputs:
|
||||||
|
onnx_sha256: ${{ steps.resolve.outputs.onnx_sha256 }}
|
||||||
|
env:
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
HF_REPO: sunnypilot/sunnypilot_models_v1
|
||||||
|
HF_DEFAULTS_PATH: models/defaults/big
|
||||||
|
steps:
|
||||||
|
- name: Resolve ONNX hash and tinygrad ref via API
|
||||||
|
id: resolve
|
||||||
|
run: |
|
||||||
|
REF="${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
|
BLOB_SHA=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx?ref=${REF}" --jq '.sha')
|
||||||
|
ONNX_HASH=$(gh api "repos/${GH_REPO}/git/blobs/${BLOB_SHA}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
|
||||||
|
echo "ONNX hash: $ONNX_HASH"
|
||||||
|
[ -n "$ONNX_HASH" ] || { echo "::error::Failed to extract ONNX hash"; exit 1; }
|
||||||
|
echo "onnx_sha256=$ONNX_HASH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
|
||||||
|
echo "tinygrad ref: $TINYGRAD_REF"
|
||||||
|
|
||||||
|
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
|
||||||
|
|
||||||
|
check_defaults() {
|
||||||
|
DEFAULTS=$(curl -fsSL "${JSON_URL}?t=$(date +%s)" 2>/dev/null) || return 1
|
||||||
|
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
|
||||||
|
[ "$TINYGRAD_MATCH" = "true" ] || return 1
|
||||||
|
BUNDLE=$(echo "$DEFAULTS" | jq --arg hash "$ONNX_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
|
||||||
|
[ -n "$BUNDLE" ] && [ "$BUNDLE" != "null" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
if check_defaults; then
|
||||||
|
echo "HF defaults match repo ONNX hash and tinygrad ref"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "No matching model on HF — dispatching build"
|
||||||
|
gh workflow run build-default-models.yaml --ref "$REF" -f target=big
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
BUILD_RUN_ID=$(gh run list --workflow build-default-models.yaml --branch "$REF" --limit 1 --json databaseId --jq '.[0].databaseId')
|
||||||
|
echo "Dispatched build run: $BUILD_RUN_ID"
|
||||||
|
|
||||||
|
echo "Waiting for build run to complete..."
|
||||||
|
for i in $(seq 1 90); do
|
||||||
|
sleep 30
|
||||||
|
STATUS=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.status')
|
||||||
|
CONCLUSION=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.conclusion')
|
||||||
|
echo "Poll $i/90: status=$STATUS conclusion=$CONCLUSION"
|
||||||
|
if [ "$STATUS" = "completed" ]; then
|
||||||
|
if [ "$CONCLUSION" = "success" ]; then
|
||||||
|
echo "Build run succeeded, verifying HF..."
|
||||||
|
sleep 10
|
||||||
|
if check_defaults; then
|
||||||
|
echo "Big model verified on HF"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "::error::Build succeeded but model not found on HF"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "::error::Build run failed with conclusion=$CONCLUSION"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "::error::Build run did not complete within 45 minutes"
|
||||||
|
exit 1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Cancel run on failure
|
||||||
|
if: failure()
|
||||||
|
run: gh run cancel ${{ github.run_id }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
prepare_small_model:
|
||||||
|
needs: [ prepare_strategy ]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
concurrency:
|
||||||
|
group: prepare-small-model
|
||||||
|
cancel-in-progress: false
|
||||||
|
outputs:
|
||||||
|
driving_onnx_sha256: ${{ steps.resolve.outputs.driving_onnx_sha256 }}
|
||||||
|
env:
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
HF_REPO: sunnypilot/sunnypilot_models_v1
|
||||||
|
HF_DEFAULTS_PATH: models/defaults/small
|
||||||
|
steps:
|
||||||
|
- name: Resolve ONNX hash and tinygrad ref via API
|
||||||
|
id: resolve
|
||||||
|
run: |
|
||||||
|
REF="${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
|
BLOB_SHA=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/driving_supercombo.onnx?ref=${REF}" --jq '.sha')
|
||||||
|
DRIVING_HASH=$(gh api "repos/${GH_REPO}/git/blobs/${BLOB_SHA}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
|
||||||
|
echo "Driving ONNX hash: $DRIVING_HASH"
|
||||||
|
[ -n "$DRIVING_HASH" ] || { echo "::error::Failed to extract driving ONNX hash"; exit 1; }
|
||||||
|
echo "driving_onnx_sha256=$DRIVING_HASH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
|
||||||
|
echo "tinygrad ref: $TINYGRAD_REF"
|
||||||
|
|
||||||
|
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
|
||||||
|
|
||||||
|
check_defaults() {
|
||||||
|
DEFAULTS=$(curl -fsSL "${JSON_URL}?t=$(date +%s)" 2>/dev/null) || return 1
|
||||||
|
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
|
||||||
|
[ "$TINYGRAD_MATCH" = "true" ] || return 1
|
||||||
|
DRIVING=$(echo "$DEFAULTS" | jq --arg hash "$DRIVING_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
|
||||||
|
[ -n "$DRIVING" ] && [ "$DRIVING" != "null" ] || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if check_defaults; then
|
||||||
|
echo "HF defaults match repo ONNX hash and tinygrad ref"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "No matching model on HF — dispatching build"
|
||||||
|
gh workflow run build-default-models.yaml --ref "$REF" -f target=small
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
BUILD_RUN_ID=$(gh run list --workflow build-default-models.yaml --branch "$REF" --limit 1 --json databaseId --jq '.[0].databaseId')
|
||||||
|
echo "Dispatched build run: $BUILD_RUN_ID"
|
||||||
|
|
||||||
|
echo "Waiting for build run to complete..."
|
||||||
|
for i in $(seq 1 60); do
|
||||||
|
sleep 30
|
||||||
|
STATUS=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.status')
|
||||||
|
CONCLUSION=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.conclusion')
|
||||||
|
echo "Poll $i/60: status=$STATUS conclusion=$CONCLUSION"
|
||||||
|
if [ "$STATUS" = "completed" ]; then
|
||||||
|
if [ "$CONCLUSION" = "success" ]; then
|
||||||
|
echo "Build run succeeded, verifying HF..."
|
||||||
|
sleep 10
|
||||||
|
if check_defaults; then
|
||||||
|
echo "Small model verified on HF"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "::error::Build succeeded but model not found on HF"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "::error::Build run failed with conclusion=$CONCLUSION"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "::error::Small model build did not complete within 30 minutes"
|
||||||
|
exit 1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Cancel run on failure
|
||||||
|
if: failure()
|
||||||
|
run: gh run cancel ${{ github.run_id }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
prepare_dm_model:
|
||||||
|
needs: [ prepare_strategy ]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
concurrency:
|
||||||
|
group: prepare-dm-model
|
||||||
|
cancel-in-progress: false
|
||||||
|
outputs:
|
||||||
|
dm_onnx_sha256: ${{ steps.resolve.outputs.dm_onnx_sha256 }}
|
||||||
|
env:
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
HF_REPO: sunnypilot/sunnypilot_models_v1
|
||||||
|
HF_DEFAULTS_PATH: models/defaults/dm
|
||||||
|
steps:
|
||||||
|
- name: Resolve ONNX hash and tinygrad ref via API
|
||||||
|
id: resolve
|
||||||
|
run: |
|
||||||
|
REF="${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
|
BLOB_SHA=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx?ref=${REF}" --jq '.sha')
|
||||||
|
DM_HASH=$(gh api "repos/${GH_REPO}/git/blobs/${BLOB_SHA}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
|
||||||
|
echo "DM ONNX hash: $DM_HASH"
|
||||||
|
[ -n "$DM_HASH" ] || { echo "::error::Failed to extract DM ONNX hash"; exit 1; }
|
||||||
|
echo "dm_onnx_sha256=$DM_HASH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
|
||||||
|
echo "tinygrad ref: $TINYGRAD_REF"
|
||||||
|
|
||||||
|
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
|
||||||
|
|
||||||
|
check_defaults() {
|
||||||
|
DEFAULTS=$(curl -fsSL "${JSON_URL}?t=$(date +%s)" 2>/dev/null) || return 1
|
||||||
|
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
|
||||||
|
[ "$TINYGRAD_MATCH" = "true" ] || return 1
|
||||||
|
DM=$(echo "$DEFAULTS" | jq --arg hash "$DM_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
|
||||||
|
[ -n "$DM" ] && [ "$DM" != "null" ] || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if check_defaults; then
|
||||||
|
echo "HF defaults match DM ONNX hash and tinygrad ref"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "No matching DM model on HF — dispatching build"
|
||||||
|
gh workflow run build-default-models.yaml --ref "$REF" -f target=dm
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
BUILD_RUN_ID=$(gh run list --workflow build-default-models.yaml --branch "$REF" --limit 1 --json databaseId --jq '.[0].databaseId')
|
||||||
|
echo "Dispatched build run: $BUILD_RUN_ID"
|
||||||
|
|
||||||
|
echo "Waiting for build run to complete..."
|
||||||
|
for i in $(seq 1 60); do
|
||||||
|
sleep 30
|
||||||
|
STATUS=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.status')
|
||||||
|
CONCLUSION=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.conclusion')
|
||||||
|
echo "Poll $i/60: status=$STATUS conclusion=$CONCLUSION"
|
||||||
|
if [ "$STATUS" = "completed" ]; then
|
||||||
|
if [ "$CONCLUSION" = "success" ]; then
|
||||||
|
echo "Build run succeeded, verifying HF..."
|
||||||
|
sleep 10
|
||||||
|
if check_defaults; then
|
||||||
|
echo "DM model verified on HF"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "::error::Build succeeded but DM model not found on HF"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "::error::Build run failed with conclusion=$CONCLUSION"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "::error::DM model build did not complete within 30 minutes"
|
||||||
|
exit 1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Cancel run on failure
|
||||||
|
if: failure()
|
||||||
|
run: gh run cancel ${{ github.run_id }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
concurrency:
|
concurrency:
|
||||||
# We do a bit of a hack here to avoid canceling the publishing job if a new commit comes in while we're publishing by adding the sha to the group name.
|
|
||||||
# This means that if multiple commits come in while we're publishing, they will be queued up and publish one after the other.
|
|
||||||
# Otherwise, if a job is waiting to be published due to environment wait time, it would be canceled by a new commit and restart the wait time.
|
|
||||||
group: ${{ needs.prepare_strategy.outputs.publish_concurrency_group }}
|
group: ${{ needs.prepare_strategy.outputs.publish_concurrency_group }}
|
||||||
cancel-in-progress: ${{ needs.prepare_strategy.outputs.cancel_publish_in_progress == 'true' }}
|
cancel-in-progress: ${{ needs.prepare_strategy.outputs.cancel_publish_in_progress == 'true' }}
|
||||||
if: ${{ (always() && !cancelled() && !failure()) && needs.build.result == 'success' && needs.prepare_strategy.result == 'success' && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
if: ${{
|
||||||
needs: [ build, prepare_strategy ]
|
always() && !cancelled() &&
|
||||||
|
needs.build.result == 'success' &&
|
||||||
|
needs.prepare_strategy.result == 'success' &&
|
||||||
|
needs.prepare_small_model.result == 'success' &&
|
||||||
|
needs.prepare_dm_model.result == 'success' &&
|
||||||
|
(!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) &&
|
||||||
|
(needs.prepare_strategy.outputs.include_big_model != 'true' || needs.prepare_chestnut.result == 'success')
|
||||||
|
}}
|
||||||
|
needs: [ build, prepare_strategy, prepare_chestnut, prepare_small_model, prepare_dm_model ]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
environment: ${{ needs.prepare_strategy.outputs.environment }}
|
environment: ${{ needs.prepare_strategy.outputs.environment }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Download build artifacts
|
- name: Download prebuilt artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: prebuilt
|
name: prebuilt
|
||||||
@@ -264,6 +493,17 @@ jobs:
|
|||||||
mkdir -p ${{ env.OUTPUT_DIR }}
|
mkdir -p ${{ env.OUTPUT_DIR }}
|
||||||
tar xzf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }}
|
tar xzf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }}
|
||||||
|
|
||||||
|
- name: Download model chunks from HF
|
||||||
|
uses: ./.github/workflows/download-hf-model-chunks
|
||||||
|
with:
|
||||||
|
hf_repo: sunnypilot/sunnypilot_models_v1
|
||||||
|
dest_dir: ${{ env.OUTPUT_DIR }}/openpilot/selfdrive/modeld/models
|
||||||
|
models: |
|
||||||
|
[
|
||||||
|
{"hf_path": "models/defaults/small", "onnx_hash": "${{ needs.prepare_small_model.outputs.driving_onnx_sha256 }}", "canonical": "driving_tinygrad.pkl"},
|
||||||
|
{"hf_path": "models/defaults/dm", "onnx_hash": "${{ needs.prepare_dm_model.outputs.dm_onnx_sha256 }}", "canonical": "dmonitoring_model_tinygrad.pkl"}
|
||||||
|
]
|
||||||
|
|
||||||
- name: Configure Git
|
- name: Configure Git
|
||||||
run: |
|
run: |
|
||||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
@@ -273,36 +513,95 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo '${{ toJSON(needs.build.outputs) }}'
|
echo '${{ toJSON(needs.prepare_strategy.outputs) }}'
|
||||||
ls -la ${{ env.OUTPUT_DIR }}
|
ls -la ${{ env.OUTPUT_DIR }}
|
||||||
|
|
||||||
${{ env.CI_DIR }}/publish.sh \
|
${{ env.CI_DIR }}/publish.sh \
|
||||||
"${{ github.workspace }}" \
|
"${{ github.workspace }}" \
|
||||||
"${{ env.OUTPUT_DIR }}" \
|
"${{ env.OUTPUT_DIR }}" \
|
||||||
"${{ needs.build.outputs.new_branch }}" \
|
"${{ needs.prepare_strategy.outputs.new_branch }}" \
|
||||||
"${{ needs.build.outputs.version }}" \
|
"${{ needs.prepare_strategy.outputs.version }}" \
|
||||||
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
|
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
|
||||||
"${{ needs.build.outputs.extra_version_identifier }}"
|
"${{ needs.prepare_strategy.outputs.extra_version_identifier }}"
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "---- ℹ️ To update the list of branches that auto deploy prebuilts -----"
|
|
||||||
echo ""
|
|
||||||
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES"
|
|
||||||
echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}"
|
|
||||||
echo "3. Update as needed (JSON array with no spaces)"
|
|
||||||
|
|
||||||
- name: Tag ${{ needs.prepare_strategy.outputs.environment }}
|
- name: Tag ${{ needs.prepare_strategy.outputs.environment }}
|
||||||
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
|
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ needs.prepare_strategy.outputs.environment }}/${{ needs.prepare_strategy.outputs.version }}/${{ needs.prepare_strategy.outputs.build }}"
|
TAG="${{ needs.prepare_strategy.outputs.environment }}/${{ needs.prepare_strategy.outputs.version }}/${{ needs.prepare_strategy.outputs.build }}"
|
||||||
git tag -f -a ${TAG} -m "${{ needs.prepare_strategy.outputs.environment }} @ ${{ needs.prepare_strategy.outputs.version }} of build ${{ needs.build.outputs.build }}."
|
git tag -f -a ${TAG} -m "${{ needs.prepare_strategy.outputs.environment }} @ ${{ needs.prepare_strategy.outputs.version }} of build ${{ needs.prepare_strategy.outputs.build }}."
|
||||||
git push -f origin ${TAG}
|
git push -f origin ${TAG}
|
||||||
|
|
||||||
|
publish_chestnut:
|
||||||
|
concurrency:
|
||||||
|
group: ${{ needs.prepare_strategy.outputs.publish_concurrency_group }}-chestnut
|
||||||
|
cancel-in-progress: ${{ needs.prepare_strategy.outputs.cancel_publish_in_progress == 'true' }}
|
||||||
|
if: ${{
|
||||||
|
always() && !cancelled() &&
|
||||||
|
needs.build.result == 'success' &&
|
||||||
|
needs.prepare_strategy.result == 'success' &&
|
||||||
|
needs.prepare_small_model.result == 'success' &&
|
||||||
|
needs.prepare_dm_model.result == 'success' &&
|
||||||
|
needs.prepare_chestnut.result == 'success' &&
|
||||||
|
(!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
|
||||||
|
}}
|
||||||
|
needs: [ build, prepare_strategy, prepare_chestnut, prepare_small_model, prepare_dm_model ]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
environment: ${{ needs.prepare_strategy.outputs.environment }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Download prebuilt artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: prebuilt
|
||||||
|
|
||||||
|
- name: Untar prebuilt
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ env.OUTPUT_DIR }}
|
||||||
|
tar xzf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }}
|
||||||
|
|
||||||
|
- name: Download model chunks from HF
|
||||||
|
uses: ./.github/workflows/download-hf-model-chunks
|
||||||
|
with:
|
||||||
|
hf_repo: sunnypilot/sunnypilot_models_v1
|
||||||
|
dest_dir: ${{ env.OUTPUT_DIR }}/openpilot/selfdrive/modeld/models
|
||||||
|
models: |
|
||||||
|
[
|
||||||
|
{"hf_path": "models/defaults/small", "onnx_hash": "${{ needs.prepare_small_model.outputs.driving_onnx_sha256 }}", "canonical": "driving_tinygrad.pkl"},
|
||||||
|
{"hf_path": "models/defaults/dm", "onnx_hash": "${{ needs.prepare_dm_model.outputs.dm_onnx_sha256 }}", "canonical": "dmonitoring_model_tinygrad.pkl"},
|
||||||
|
{"hf_path": "models/defaults/big", "onnx_hash": "${{ needs.prepare_chestnut.outputs.onnx_sha256 }}", "canonical": "big_driving_tinygrad.pkl"}
|
||||||
|
]
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --global user.name "github-actions[bot]"
|
||||||
|
|
||||||
|
- name: Publish chestnut branch
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
CHESTNUT_BRANCH="${{ needs.prepare_strategy.outputs.new_branch }}-chestnut"
|
||||||
|
|
||||||
|
${{ env.CI_DIR }}/publish.sh \
|
||||||
|
"${{ github.workspace }}" \
|
||||||
|
"${{ env.OUTPUT_DIR }}" \
|
||||||
|
"$CHESTNUT_BRANCH" \
|
||||||
|
"${{ needs.prepare_strategy.outputs.version }}" \
|
||||||
|
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
|
||||||
|
"${{ needs.prepare_strategy.outputs.extra_version_identifier }}"
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
needs:
|
needs:
|
||||||
- prepare_strategy
|
- prepare_strategy
|
||||||
- build
|
- build
|
||||||
- publish
|
- publish
|
||||||
|
- publish_chestnut
|
||||||
|
- prepare_chestnut
|
||||||
|
- prepare_small_model
|
||||||
|
- prepare_dm_model
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
if: ${{ (always() && !cancelled() && !failure())
|
if: ${{ (always() && !cancelled() && !failure())
|
||||||
&& needs.publish.result == 'success'
|
&& needs.publish.result == 'success'
|
||||||
@@ -310,11 +609,12 @@ jobs:
|
|||||||
&& (fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name] != null) }}
|
&& (fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name] != null) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Prepare notification message
|
- name: Prepare notification message
|
||||||
id: message
|
id: message
|
||||||
run: |
|
run: |
|
||||||
TEMPLATE='${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}'
|
|
||||||
export VERSION="${{ needs.prepare_strategy.outputs.version }}"
|
export VERSION="${{ needs.prepare_strategy.outputs.version }}"
|
||||||
export branch_name="${{ env.SOURCE_BRANCH }}"
|
export branch_name="${{ env.SOURCE_BRANCH }}"
|
||||||
export new_branch="${{ needs.prepare_strategy.outputs.new_branch }}"
|
export new_branch="${{ needs.prepare_strategy.outputs.new_branch }}"
|
||||||
@@ -323,6 +623,7 @@ jobs:
|
|||||||
export commit_short_sha="${commit_short_sha:0:7}"
|
export commit_short_sha="${commit_short_sha:0:7}"
|
||||||
export extra_version_identifier="${{ needs.prepare_strategy.outputs.extra_version_identifier || github.run_number }}"
|
export extra_version_identifier="${{ needs.prepare_strategy.outputs.extra_version_identifier || github.run_number }}"
|
||||||
export PUBLIC_REPO_URL="${{ env.PUBLIC_REPO_URL }}"
|
export PUBLIC_REPO_URL="${{ env.PUBLIC_REPO_URL }}"
|
||||||
|
export chestnut_branch="${{ needs.prepare_chestnut.result == 'success' && format('{0}-chestnut', needs.prepare_strategy.outputs.new_branch) || '' }}"
|
||||||
|
|
||||||
MESSAGE=$(cat << 'EOF' | envsubst
|
MESSAGE=$(cat << 'EOF' | envsubst
|
||||||
${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}
|
${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ concurrency:
|
|||||||
env:
|
env:
|
||||||
CI: 1
|
CI: 1
|
||||||
PYTHONPATH: ${{ github.workspace }}
|
PYTHONPATH: ${{ github.workspace }}
|
||||||
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical
|
GIT_CONFIG_COUNT: 1
|
||||||
|
GIT_CONFIG_KEY_0: lfs.fetchexclude
|
||||||
|
GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_release:
|
build_release:
|
||||||
@@ -35,27 +37,27 @@ jobs:
|
|||||||
STRIPPED_DIR: /tmp/releasepilot
|
STRIPPED_DIR: /tmp/releasepilot
|
||||||
PYTHONPATH: /tmp/releasepilot
|
PYTHONPATH: /tmp/releasepilot
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Getting LFS files
|
- name: Getting LFS files
|
||||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e
|
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 2
|
timeout_minutes: 2
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
command: git lfs pull
|
command: git lfs pull
|
||||||
- name: Build devel
|
- name: Build devel
|
||||||
timeout-minutes: 1
|
timeout-minutes: 3
|
||||||
run: TARGET_DIR=$STRIPPED_DIR release/build_stripped.sh
|
run: TARGET_DIR=$STRIPPED_DIR tools/release/build_stripped.sh
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Build openpilot and run checks
|
- name: Build openpilot and run checks
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
working-directory: ${{ env.STRIPPED_DIR }}
|
working-directory: ${{ env.STRIPPED_DIR }}
|
||||||
run: python3 system/manager/build.py
|
run: python3 openpilot/system/manager/build.py
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
timeout-minutes: 1
|
timeout-minutes: 1
|
||||||
working-directory: ${{ env.STRIPPED_DIR }}
|
working-directory: ${{ env.STRIPPED_DIR }}
|
||||||
run: release/check-dirty.sh
|
run: tools/release/check-dirty.sh
|
||||||
- name: Check submodules
|
- name: Check submodules
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
@@ -75,15 +77,13 @@ jobs:
|
|||||||
export CHECK_PR_REFS=true
|
export CHECK_PR_REFS=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
release/check-submodules.sh
|
tools/release/check-submodules.sh
|
||||||
|
|
||||||
build_mac:
|
build_mac:
|
||||||
name: build macOS
|
name: build macOS
|
||||||
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' }}
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Remove Homebrew from environment
|
- name: Remove Homebrew from environment
|
||||||
run: |
|
run: |
|
||||||
FILTERED=$(echo "$PATH" | tr ':' '\n' | grep -v '/opt/homebrew' | tr '\n' ':')
|
FILTERED=$(echo "$PATH" | tr ':' '\n' | grep -v '/opt/homebrew' | tr '\n' ':')
|
||||||
@@ -101,9 +101,7 @@ jobs:
|
|||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Static analysis
|
- name: Static analysis
|
||||||
timeout-minutes: 1
|
timeout-minutes: 1
|
||||||
@@ -118,23 +116,21 @@ jobs:
|
|||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
|
- name: Install raylib headless dependencies
|
||||||
|
if: ${{ !contains(runner.name, 'nsc') }}
|
||||||
|
run: sudo apt-get install -y --no-install-recommends libgles2 libegl1
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons -j$(nproc)
|
run: scons
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 999 }}
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 999 }}
|
||||||
run: |
|
env:
|
||||||
source selfdrive/test/setup_xvfb.sh
|
RAYLIB_BACKEND: headless
|
||||||
# Pre-compile Python bytecode so each pytest worker doesn't need to
|
run: tools/op.sh test
|
||||||
$PYTEST --collect-only -m 'not slow' -qq
|
|
||||||
MAX_EXAMPLES=1 $PYTEST -m 'not slow'
|
|
||||||
|
|
||||||
process_replay:
|
process_replay:
|
||||||
name: process replay
|
name: process replay
|
||||||
if: false # disable process_replay for forks
|
|
||||||
runs-on: ${{
|
runs-on: ${{
|
||||||
(github.repository == 'commaai/openpilot') &&
|
(github.repository == 'commaai/openpilot') &&
|
||||||
((github.event_name != 'pull_request') ||
|
((github.event_name != 'pull_request') ||
|
||||||
@@ -142,62 +138,60 @@ jobs:
|
|||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons -j$(nproc)
|
run: scons
|
||||||
- name: Run replay
|
- name: Run replay
|
||||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }}
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }}
|
||||||
continue-on-error: ${{ github.ref == 'refs/heads/master' }}
|
continue-on-error: ${{ github.ref == 'refs/heads/master' }}
|
||||||
run: selfdrive/test/process_replay/test_processes.py -j$(nproc)
|
run: openpilot/selfdrive/test/process_replay/test_processes.py -j$(nproc)
|
||||||
- name: Print diff
|
- name: Print diff
|
||||||
id: print-diff
|
id: print-diff
|
||||||
if: always()
|
if: always()
|
||||||
run: cat selfdrive/test/process_replay/diff.txt
|
run: cat openpilot/selfdrive/test/process_replay/diff.txt
|
||||||
- name: Print diff report
|
- name: Print diff report
|
||||||
if: always()
|
if: always()
|
||||||
run: cat selfdrive/test/process_replay/diff_report.txt
|
run: cat openpilot/selfdrive/test/process_replay/diff_report.txt
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
if: always()
|
if: always()
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
name: process_replay_diff.txt
|
name: process_replay_diff.txt
|
||||||
path: selfdrive/test/process_replay/diff.txt
|
path: openpilot/selfdrive/test/process_replay/diff.txt
|
||||||
- name: Upload diff report
|
- name: Upload diff report
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
if: always() && github.event_name == 'pull_request'
|
if: always() && github.event_name == 'pull_request'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
name: diff_report_${{ github.event.number }}
|
name: diff_report_${{ github.event.number }}
|
||||||
path: selfdrive/test/process_replay/diff_report.txt
|
path: openpilot/selfdrive/test/process_replay/diff_report.txt
|
||||||
- name: Checkout ci-artifacts
|
- name: Checkout ci-artifacts
|
||||||
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: commaai/ci-artifacts
|
repository: sunnypilot/ci-artifacts
|
||||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||||
path: ${{ github.workspace }}/ci-artifacts
|
path: ${{ github.workspace }}/ci-artifacts
|
||||||
- name: Push refs
|
- name: Prepare refs
|
||||||
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master'
|
||||||
working-directory: ${{ github.workspace }}/ci-artifacts
|
working-directory: ${{ github.workspace }}/ci-artifacts
|
||||||
run: |
|
run: |
|
||||||
git config user.name "GitHub Actions Bot"
|
git config user.name "GitHub Actions Bot"
|
||||||
git config user.email "<>"
|
git config user.email "<>"
|
||||||
git fetch origin process-replay || true
|
git fetch origin process-replay || true
|
||||||
git checkout process-replay 2>/dev/null || git checkout --orphan process-replay
|
git checkout process-replay 2>/dev/null || git checkout --orphan process-replay
|
||||||
cp ${{ github.workspace }}/selfdrive/test/process_replay/fakedata/*.zst .
|
cp ${{ github.workspace }}/openpilot/selfdrive/test/process_replay/fakedata/*.zst .
|
||||||
echo "${{ github.sha }}" > ref_commit
|
echo "${{ github.sha }}" > ref_commit
|
||||||
git add .
|
git add .
|
||||||
git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit"
|
git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit"
|
||||||
git push origin process-replay --force
|
- name: Push refs
|
||||||
- name: Run regen
|
if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master'
|
||||||
if: false
|
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60
|
||||||
timeout-minutes: 4
|
with:
|
||||||
env:
|
timeout_minutes: 2
|
||||||
ONNXCPU: 1
|
max_attempts: 3
|
||||||
run: $PYTEST selfdrive/test/process_replay/test_regen.py
|
command: cd ${{ github.workspace }}/ci-artifacts && git push origin process-replay --force
|
||||||
|
|
||||||
simulator_driving:
|
simulator_driving:
|
||||||
name: simulator driving
|
name: simulator driving
|
||||||
@@ -209,17 +203,16 @@ jobs:
|
|||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
if: false # FIXME: Started to timeout recently
|
if: false # FIXME: Started to timeout recently
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons -j$(nproc)
|
run: scons
|
||||||
- name: Driving test
|
- name: Driving test
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
run: |
|
env:
|
||||||
source selfdrive/test/setup_xvfb.sh
|
# MetaDrive renders offscreen through panda3d's EGL pipe on llvmpipe
|
||||||
pytest -s tools/sim/tests/test_metadrive_bridge.py
|
EGL_PLATFORM: surfaceless
|
||||||
|
run: python openpilot/tools/sim/tests/test_metadrive_bridge.py
|
||||||
|
|
||||||
create_ui_report:
|
create_ui_report:
|
||||||
name: Create UI Report
|
name: Create UI Report
|
||||||
@@ -230,19 +223,21 @@ jobs:
|
|||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
|
- name: Install raylib headless dependencies
|
||||||
|
if: ${{ !contains(runner.name, 'nsc') }}
|
||||||
|
run: sudo apt-get install -y --no-install-recommends libgles2 libegl1
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons -j$(nproc)
|
run: scons
|
||||||
- name: Create UI Report
|
- name: Create UI Report
|
||||||
|
env:
|
||||||
|
RAYLIB_BACKEND: headless
|
||||||
run: |
|
run: |
|
||||||
source selfdrive/test/setup_xvfb.sh
|
python3 openpilot/selfdrive/ui/tests/diff/replay.py
|
||||||
python3 selfdrive/ui/tests/diff/replay.py
|
python3 openpilot/selfdrive/ui/tests/diff/replay.py --big
|
||||||
python3 selfdrive/ui/tests/diff/replay.py --big
|
|
||||||
- name: Upload UI Report
|
- name: Upload UI Report
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ui-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
name: ui-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||||
path: selfdrive/ui/tests/diff/report
|
path: openpilot/selfdrive/ui/tests/diff/report
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
paths:
|
paths:
|
||||||
- 'selfdrive/assets/**'
|
- 'openpilot/selfdrive/assets/**'
|
||||||
- 'selfdrive/ui/**'
|
- 'openpilot/selfdrive/ui/**'
|
||||||
- 'system/ui/**'
|
- 'openpilot/system/ui/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -25,7 +25,8 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
preview:
|
preview:
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: false # tmp disable due to GH API rate limiting flakiness
|
||||||
|
#if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
name: preview
|
name: preview
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
@@ -34,12 +35,12 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
actions: read
|
actions: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Waiting for ui generation to end
|
- name: Waiting for ui generation to end
|
||||||
uses: lewagon/wait-on-check-action@v1.3.4
|
uses: lewagon/wait-on-check-action@1d57e2c51a58d812d2765e036a028b6bdb5a6154
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.SHA }}
|
ref: ${{ env.SHA }}
|
||||||
check-name: ${{ env.UI_JOB_NAME }}
|
check-name: ${{ env.UI_JOB_NAME }}
|
||||||
@@ -53,7 +54,7 @@ jobs:
|
|||||||
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
|
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
|
- name: Getting proposed ui
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run_id: ${{ steps.get_run_id.outputs.run_id }}
|
run_id: ${{ steps.get_run_id.outputs.run_id }}
|
||||||
@@ -62,7 +63,7 @@ jobs:
|
|||||||
path: ${{ github.workspace }}/pr_ui
|
path: ${{ github.workspace }}/pr_ui
|
||||||
|
|
||||||
- name: Getting mici master ui
|
- name: Getting mici master ui
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: sunnypilot/ci-artifacts
|
repository: sunnypilot/ci-artifacts
|
||||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||||
@@ -70,7 +71,7 @@ jobs:
|
|||||||
ref: openpilot_master_ui_mici_raylib
|
ref: openpilot_master_ui_mici_raylib
|
||||||
|
|
||||||
- name: Getting big master ui
|
- name: Getting big master ui
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: sunnypilot/ci-artifacts
|
repository: sunnypilot/ci-artifacts
|
||||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||||
@@ -97,7 +98,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Setup FFmpeg
|
- name: Setup FFmpeg
|
||||||
uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae
|
uses: AnimMouse/setup-ffmpeg@178e0b4a408f06ce40d896c3cd79f50fa6f8b0c3
|
||||||
|
|
||||||
- name: Finding diffs
|
- name: Finding diffs
|
||||||
if: github.event_name == 'pull_request_target'
|
if: github.event_name == 'pull_request_target'
|
||||||
@@ -115,13 +116,13 @@ jobs:
|
|||||||
cp "${{ github.workspace }}/master_${name}/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_master.mp4"
|
cp "${{ github.workspace }}/master_${name}/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_master.mp4"
|
||||||
|
|
||||||
diff_exit_code=0
|
diff_exit_code=0
|
||||||
python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py \
|
python3 ${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/diff.py \
|
||||||
"${{ github.workspace }}/pr_ui/${video}_master.mp4" \
|
"${{ github.workspace }}/pr_ui/${video}_master.mp4" \
|
||||||
"${{ github.workspace }}/pr_ui/${video}_proposed.mp4" \
|
"${{ github.workspace }}/pr_ui/${video}_proposed.mp4" \
|
||||||
"${diff_name}.html" --basedir "$baseurl" --no-open || diff_exit_code=$?
|
"${diff_name}.html" --basedir "$baseurl" --no-open || diff_exit_code=$?
|
||||||
|
|
||||||
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${{ github.workspace }}/pr_ui/"
|
cp "${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/report/${diff_name}.html" "${{ github.workspace }}/pr_ui/"
|
||||||
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.mp4" "${{ github.workspace }}/pr_ui/"
|
cp "${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/report/${diff_name}.mp4" "${{ github.workspace }}/pr_ui/"
|
||||||
|
|
||||||
REPORT_URL="https://sunnypilot.github.io/ci-artifacts/${diff_name}_pr_${{ github.event.number }}.html"
|
REPORT_URL="https://sunnypilot.github.io/ci-artifacts/${diff_name}_pr_${{ github.event.number }}.html"
|
||||||
if [ $diff_exit_code -eq 0 ]; then
|
if [ $diff_exit_code -eq 0 ]; then
|
||||||
@@ -156,7 +157,7 @@ jobs:
|
|||||||
for variant in $VARIANTS; do
|
for variant in $VARIANTS; do
|
||||||
IFS=':' read -r name _ _ <<< "$variant"
|
IFS=':' read -r name _ _ <<< "$variant"
|
||||||
diff_name="${name}_diff"
|
diff_name="${name}_diff"
|
||||||
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${diff_name}_pr_${{ github.event.number }}.html"
|
cp "${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/report/${diff_name}.html" "${diff_name}_pr_${{ github.event.number }}.html"
|
||||||
git add "${diff_name}_pr_${{ github.event.number }}.html"
|
git add "${diff_name}_pr_${{ github.event.number }}.html"
|
||||||
done
|
done
|
||||||
git commit -m "ui diff reports for PR #${{ github.event.number }}" || echo "No changes to commit"
|
git commit -m "ui diff reports for PR #${{ github.event.number }}" || echo "No changes to commit"
|
||||||
@@ -164,12 +165,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Comment on PR
|
- name: Comment on PR
|
||||||
if: github.event_name == 'pull_request_target'
|
if: github.event_name == 'pull_request_target'
|
||||||
uses: thollander/actions-comment-pull-request@v2
|
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b
|
||||||
with:
|
with:
|
||||||
message: |
|
message: |
|
||||||
<!-- _(run_id_ui_preview **${{ github.run_id }}**)_ -->
|
<!-- _(run_id_ui_preview **${{ github.run_id }}**)_ -->
|
||||||
## UI Preview
|
## UI Preview
|
||||||
${{ steps.find_diff.outputs.COMMENT }}
|
${{ steps.find_diff.outputs.COMMENT }}
|
||||||
comment_tag: run_id_ui_preview
|
comment-tag: run_id_ui_preview
|
||||||
pr_number: ${{ github.event.number }}
|
pr-number: ${{ github.event.number }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
+19
-10
@@ -15,6 +15,13 @@ a.out
|
|||||||
.cache/
|
.cache/
|
||||||
bin/
|
bin/
|
||||||
|
|
||||||
|
# created at launch for comma hardware PYTHONPATH (PC uses editable installs via pyproject.toml)
|
||||||
|
/msgq
|
||||||
|
/opendbc
|
||||||
|
/rednose
|
||||||
|
/teleoprtc
|
||||||
|
/tinygrad
|
||||||
|
|
||||||
*.mp4
|
*.mp4
|
||||||
*.dylib
|
*.dylib
|
||||||
*.DSYM
|
*.DSYM
|
||||||
@@ -32,6 +39,8 @@ bin/
|
|||||||
*.os-*
|
*.os-*
|
||||||
*.so
|
*.so
|
||||||
*.a
|
*.a
|
||||||
|
st[0-9A-Za-z][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z][0-9A-Za-z]
|
||||||
|
*.unchunked
|
||||||
*.clb
|
*.clb
|
||||||
*.class
|
*.class
|
||||||
*.pyxbldc
|
*.pyxbldc
|
||||||
@@ -44,18 +53,18 @@ bin/
|
|||||||
config.json
|
config.json
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
compare_runtime*.html
|
compare_runtime*.html
|
||||||
selfdrive/modeld/models/tg_compiled_flags.json
|
openpilot/selfdrive/modeld/models/tg_input_devices.json
|
||||||
|
|
||||||
# build artifacts
|
# build artifacts
|
||||||
docs_site/
|
docs/_site/
|
||||||
selfdrive/pandad/pandad
|
openpilot/selfdrive/pandad/pandad
|
||||||
cereal/services.h
|
openpilot/cereal/services.h
|
||||||
cereal/gen
|
openpilot/cereal/gen
|
||||||
cereal/messaging/bridge
|
openpilot/cereal/messaging/bridge
|
||||||
selfdrive/ui/translations/tmp
|
openpilot/selfdrive/ui/translations/tmp
|
||||||
selfdrive/car/tests/cars_dump
|
openpilot/selfdrive/car/tests/cars_dump
|
||||||
system/camerad/camerad
|
openpilot/system/camerad/camerad
|
||||||
system/camerad/test/ae_gray_test
|
openpilot/system/camerad/test/ae_gray_test
|
||||||
|
|
||||||
.coverage*
|
.coverage*
|
||||||
coverage.xml
|
coverage.xml
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
url = https://github.com/sunnypilot/opendbc.git
|
url = https://github.com/sunnypilot/opendbc.git
|
||||||
[submodule "msgq"]
|
[submodule "msgq"]
|
||||||
path = msgq_repo
|
path = msgq_repo
|
||||||
url = https://github.com/commaai/msgq.git
|
url = https://github.com/sunnypilot/msgq.git
|
||||||
[submodule "rednose_repo"]
|
[submodule "rednose_repo"]
|
||||||
path = rednose_repo
|
path = rednose_repo
|
||||||
url = https://github.com/commaai/rednose.git
|
url = https://github.com/commaai/rednose.git
|
||||||
@@ -17,5 +17,5 @@
|
|||||||
path = tinygrad_repo
|
path = tinygrad_repo
|
||||||
url = https://github.com/sunnypilot/tinygrad.git
|
url = https://github.com/sunnypilot/tinygrad.git
|
||||||
[submodule "sunnypilot/neural_network_data"]
|
[submodule "sunnypilot/neural_network_data"]
|
||||||
path = sunnypilot/neural_network_data
|
path = openpilot/sunnypilot/neural_network_data
|
||||||
url = https://github.com/sunnypilot/neural-network-data.git
|
url = https://github.com/sunnypilot/neural-network-data.git
|
||||||
|
|||||||
Vendored
+4
-4
@@ -6,9 +6,9 @@
|
|||||||
"type": "pickString",
|
"type": "pickString",
|
||||||
"description": "Select the process to debug",
|
"description": "Select the process to debug",
|
||||||
"options": [
|
"options": [
|
||||||
"selfdrive/controls/controlsd.py",
|
"openpilot/selfdrive/controls/controlsd.py",
|
||||||
"system/timed/timed.py",
|
"openpilot/system/timed/timed.py",
|
||||||
"tools/sim/run_bridge.py"
|
"openpilot/tools/sim/run_bridge.py"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"type": "pickString",
|
"type": "pickString",
|
||||||
"description": "Select the process to debug",
|
"description": "Select the process to debug",
|
||||||
"options": [
|
"options": [
|
||||||
"selfdrive/ui/ui"
|
"openpilot/selfdrive/ui/ui"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Vendored
-6
@@ -17,11 +17,5 @@
|
|||||||
"**/.git",
|
"**/.git",
|
||||||
"**/.venv",
|
"**/.venv",
|
||||||
"**/__pycache__",
|
"**/__pycache__",
|
||||||
// exclude directories that should be using the symlinked version
|
|
||||||
"common/**",
|
|
||||||
"selfdrive/**",
|
|
||||||
"system/**",
|
|
||||||
"third_party/**",
|
|
||||||
"tools/**",
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+118
-1
@@ -1,4 +1,36 @@
|
|||||||
sunnypilot Version 2026.001.000 (2026-03-xx)
|
sunnypilot Version 2026.002.000 (2026-06-28)
|
||||||
|
========================
|
||||||
|
* What's Changed (sunnypilot/sunnypilot)
|
||||||
|
* ui: update gates for certain toggles by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1830
|
||||||
|
* release: ignore upstream IsReleaseBranch by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1831
|
||||||
|
* manager: disable DEVELOPMENT_ONLY reset by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1833
|
||||||
|
* sunnylink: fix max time offroad values by @nayan8teen in https://github.com/sunnypilot/sunnypilot/pull/1835
|
||||||
|
* ui: show default model name by @nayan8teen in https://github.com/sunnypilot/sunnypilot/pull/1837
|
||||||
|
* sunnylink: add CarParams fallback for brand-specific capabilities by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1839
|
||||||
|
* sunnylink SDUI: tweak DisableUpdate param for clarity by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1842
|
||||||
|
* Revert "DM: Lancia Delta HF Integrale model" by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1849
|
||||||
|
* modeld_v2: safe model validation by @Discountchubbs in https://github.com/sunnypilot/sunnypilot/pull/1855
|
||||||
|
* Revert "deprecate `carState.brake`" for Honda Gas Interceptor by @mvl-boston in https://github.com/sunnypilot/sunnypilot/pull/1860
|
||||||
|
* sunnylink: deprecate legacy params metadata by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1862
|
||||||
|
* ui: reset Enforce Torque Control and NNLC if both are enabled by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1863
|
||||||
|
* What's Changed (sunnypilot/opendbc)
|
||||||
|
* Rivian: suppress ACM hold-the-wheel warning during MADS-only lateral by @lukasloetkolben in https://github.com/sunnypilot/opendbc/pull/465
|
||||||
|
* Sync: `commaai/opendbc:master` → `sunnypilot/opendbc:master` by @sunnyhaibin in https://github.com/sunnypilot/opendbc/pull/479
|
||||||
|
* safety: add option to ignore frequency check for RX checks by @sunnyhaibin in https://github.com/sunnypilot/opendbc/pull/480
|
||||||
|
* Revert "deprecate carState.brake" for Honda Gas Interceptor by @mvl-boston in https://github.com/sunnypilot/opendbc/pull/481
|
||||||
|
* New Contributors (sunnypilot/sunnypilot)
|
||||||
|
* @mvl-boston made their first contribution in https://github.com/sunnypilot/sunnypilot/pull/1860
|
||||||
|
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2026.001.007...v2026.002.000
|
||||||
|
************************
|
||||||
|
* Synced with commaai's openpilot (v0.11.1)
|
||||||
|
* master commit 69e2c321e49760e52f7983eaa0a5f77cb95de637 (June 02, 2026)
|
||||||
|
* New driver monitoring model
|
||||||
|
* Improved image processing pipeline for driver camera
|
||||||
|
* Improved thermal policy for comma four
|
||||||
|
* Acura MDX 2022-24 support thanks to mvl-boston!
|
||||||
|
* Rivian R1S and R1T 2025 support thanks to lukasloetkolben!
|
||||||
|
|
||||||
|
sunnypilot Version 2026.001.000 (2026-05-06)
|
||||||
========================
|
========================
|
||||||
* What's Changed (sunnypilot/sunnypilot)
|
* What's Changed (sunnypilot/sunnypilot)
|
||||||
* Complete rewrite of the user interface from Qt C++ to Raylib Python
|
* Complete rewrite of the user interface from Qt C++ to Raylib Python
|
||||||
@@ -66,6 +98,64 @@ sunnypilot Version 2026.001.000 (2026-03-xx)
|
|||||||
* Pause Lateral Control with Blinker: Post-Blinker Delay by @CHaucke89
|
* Pause Lateral Control with Blinker: Post-Blinker Delay by @CHaucke89
|
||||||
* SCC-V: Use p97 for predicted lateral accel by @yasu-oh
|
* SCC-V: Use p97 for predicted lateral accel by @yasu-oh
|
||||||
* Controls: Support for Torque Lateral Control v0 Tune by @sunnyhaibin
|
* Controls: Support for Torque Lateral Control v0 Tune by @sunnyhaibin
|
||||||
|
* [TIZI/TICI] ui: ensure null checks for `CarParams` and `CarParamsSP` by @sunnyhaibin
|
||||||
|
* [TIZI/TICI] ui: use `vCruiseCluster` and `vEgoCluster` for SLA `preActive` by @sunnyhaibin
|
||||||
|
* Fix display of values when using use_float_scaling by @CHaucke89
|
||||||
|
* models: fix default & index "0" by @nayan8teen
|
||||||
|
* [TIZI/TICI] visuals: Improved speed limit by @angaz
|
||||||
|
* ICBM: ensure button timers update on disable to clear stale presses by @jamesmikesell
|
||||||
|
* [TIZI/TICI] ui: simplify Smart Cruise Control text rendering by @sunnyhaibin
|
||||||
|
* controlsd: fix steer_limited_by_safety not updating under MADS by @zephleggett
|
||||||
|
* soundd: trigger timeout warning during MADS lateral-only by @zephleggett
|
||||||
|
* pandad: flasher for Rivian long upgrade module by @lukasloetkolben
|
||||||
|
* modeld_v2: tinygrad transformation warp by @Discountchubbs
|
||||||
|
* tools: block `manage_sunnylinkd` in sim startup script by @sunnyhaibin
|
||||||
|
* [MICI] ui: need superclass `_render` in `HudRendererSP` by @sunnyhaibin
|
||||||
|
* [TIZI/TICI] ui: Speed Limit Assist active status by @sunnyhaibin
|
||||||
|
* ui: reimplement "Screen Off" option to Onroad Brightness by @sunnyhaibin
|
||||||
|
* ui: don't hide steering wheel when blindspot disabled by @royjr
|
||||||
|
* ui: Speed Limit Assist `preActive` improvements by @sunnyhaibin
|
||||||
|
* ui: consolidate Speed Limit Assist `preActive` status rendering by @sunnyhaibin
|
||||||
|
* [MICI] ui: Speed Limit Assist `preActive` status by @sunnyhaibin
|
||||||
|
* sunnypilot modeld: remove thneed modeld by @Discountchubbs
|
||||||
|
* modeld_v2: decouple planplus scaling from accel by @Discountchubbs
|
||||||
|
* sunnylink: Handle exceptions in `getParamsAllKeysV1` to log crashes by @devtekve
|
||||||
|
* [TIZI/TICI] ui: Developer UI cleanup by @sunnyhaibin
|
||||||
|
* [TIZI/TICI] ui: dynamic alert size by @nayan8teen
|
||||||
|
* i18n(fr): Add French translations by @didlawowo
|
||||||
|
* Toyota: Stop and Go Hack (Alpha) by @sunnyhaibin
|
||||||
|
* ui: `AlertFadeAnimator` for longitudinal-related statuses by @sunnyhaibin
|
||||||
|
* pandad: gate unsupported pandas before flashing by @sunnyhaibin
|
||||||
|
* Rivian: Flash xnor's Longitudinal Upgrade Kit prior supported panda check by @lukasloetkolben
|
||||||
|
* [TIZI/TICI] ui: add back gate steering arc behind toggle by @sunnyhaibin
|
||||||
|
* ui: gate Onroad Brightness Delay on readiness by @sunnyhaibin
|
||||||
|
* ui: add new timer options for Onroad Brightness Delay by @sunnyhaibin
|
||||||
|
* [TIZI/TICI] ui: branch switcher is always available by @sunnyhaibin
|
||||||
|
* pandad: always prioritize internal panda by @sunnyhaibin
|
||||||
|
* sunnylinkd: fetch compressed params schema by @sunnyhaibin
|
||||||
|
* sunnypilot locationd: remove unused car_ekf filter by @sunnyhaibin
|
||||||
|
* modeld_v2: update deprecated temporalPose ref by @sunnyhaibin
|
||||||
|
* NNLC: restore pre-v1 PID gains in torque extension by @mmmorks
|
||||||
|
* MADS safety: enable heartbeat and lateral controls mismatch checks by @sunnyhaibin
|
||||||
|
* [MICI] ui: models panel enhancements by @nayan8teen
|
||||||
|
* [TIZI/TICI] ui: fix unintended selection while scrolling in TreeOptionDialog by @TheSecurityDev
|
||||||
|
* tools: script for video concatenation by @Discountchubbs
|
||||||
|
* tools: profile memory usage by @Discountchubbs
|
||||||
|
* [TIZI/TICI] ui: remove per-frame param sync by @sunnyhaibin
|
||||||
|
* [MICI] ui: always offroad by @nayan8teen
|
||||||
|
* controls: always default Torque Lateral Control to v0 Tune by @sunnyhaibin
|
||||||
|
* Revert "controls: always default Torque Lateral Control to v0 Tune" by @sunnyhaibin
|
||||||
|
* Reapply "controls: always default Torque Lateral Control to v0 Tune" (#1806) by @sunnyhaibin
|
||||||
|
* [MICI] ui: add sunnylink info & connectivity check by @nayan8teen
|
||||||
|
* sunnylink: Remove unused API endpoint by @devtekve
|
||||||
|
* DM: wheel touch enforcement in MADS by @sunnyhaibin
|
||||||
|
* torque: show static override values in Dev UI & gate `useParams` on custom torque tune by @sunnyhaibin
|
||||||
|
* MADS: suppress espActive event when long is not engaged by @sunnyhaibin
|
||||||
|
* sunnylink: SDUI by @sunnyhaibin
|
||||||
|
* [MICI] ui: align upstream changes with sunnypilot settings buttons by @nayan8teen
|
||||||
|
* ui: fix cellular toggles by @AmyJeanes
|
||||||
|
* sunnylink: switch athena domain by @devtekve
|
||||||
|
* Platform List: dynamically migrate CarPlatformBundle by @sunnyhaibin
|
||||||
* What's Changed (sunnypilot/opendbc)
|
* What's Changed (sunnypilot/opendbc)
|
||||||
* Honda: DBC for Accord 9th Generation by @mvl-boston
|
* Honda: DBC for Accord 9th Generation by @mvl-boston
|
||||||
* FCA: update tire stiffness values for `RAM_HD` by @dparring
|
* FCA: update tire stiffness values for `RAM_HD` by @dparring
|
||||||
@@ -84,12 +174,25 @@ sunnypilot Version 2026.001.000 (2026-03-xx)
|
|||||||
* Honda: add missing `GasInterceptor` messages to Taiwan Odyssey DBC by @mvl-boston
|
* Honda: add missing `GasInterceptor` messages to Taiwan Odyssey DBC by @mvl-boston
|
||||||
* GM: remove `CHEVROLET_EQUINOX_NON_ACC_3RD_GEN` from `dashcamOnly` by @sunnyhaibin
|
* GM: remove `CHEVROLET_EQUINOX_NON_ACC_3RD_GEN` from `dashcamOnly` by @sunnyhaibin
|
||||||
* GM: remove `CHEVROLET_BOLT_NON_ACC_2ND_GEN` from `dashcamOnly` by @sunnyhaibin
|
* GM: remove `CHEVROLET_BOLT_NON_ACC_2ND_GEN` from `dashcamOnly` by @sunnyhaibin
|
||||||
|
* Hyundai Longitudinal: deprecate ramp update for dynamic tune by @Discountchubbs
|
||||||
|
* Rivian: long upgrade messages on bus 1 by @lukasloetkolben
|
||||||
|
* Toyota: Stop and Go Hack (Alpha) by @sunnyhaibin
|
||||||
|
* Toyota: gate Smart DSU behind Alpha Longitudinal by @sunnyhaibin
|
||||||
|
* Toyota: Gas Interceptor always set `standstill_req` by @sunnyhaibin
|
||||||
|
* MADS safety: dedicated `controls_allowed_lateral` by @sunnyhaibin
|
||||||
|
* Platform List: include community supported platforms by @sunnyhaibin
|
||||||
* New Contributors (sunnypilot/sunnypilot)
|
* New Contributors (sunnypilot/sunnypilot)
|
||||||
* @TheSecurityDev made their first contribution in "ui: fix sidebar scroll in UI screenshots"
|
* @TheSecurityDev made their first contribution in "ui: fix sidebar scroll in UI screenshots"
|
||||||
* @zikeji made their first contribution in "sunnylink: block remote modification of SSH key parameters"
|
* @zikeji made their first contribution in "sunnylink: block remote modification of SSH key parameters"
|
||||||
* @Candy0707 made their first contribution in "[TIZI/TICI] ui: Fix misaligned turn signals and blindspot indicators with sidebar"
|
* @Candy0707 made their first contribution in "[TIZI/TICI] ui: Fix misaligned turn signals and blindspot indicators with sidebar"
|
||||||
* @CHaucke89 made their first contribution in "Pause Lateral Control with Blinker: Post-Blinker Delay"
|
* @CHaucke89 made their first contribution in "Pause Lateral Control with Blinker: Post-Blinker Delay"
|
||||||
* @yasu-oh made their first contribution in "SCC-V: Use p97 for predicted lateral accel"
|
* @yasu-oh made their first contribution in "SCC-V: Use p97 for predicted lateral accel"
|
||||||
|
* @angaz made their first contribution in "[TIZI/TICI] visuals: Improved speed limit"
|
||||||
|
* @jamesmikesell made their first contribution in "ICBM: ensure button timers update on disable to clear stale presses"
|
||||||
|
* @zephleggett made their first contribution in "controlsd: fix steer_limited_by_safety not updating under MADS"
|
||||||
|
* @lukasloetkolben made their first contribution in "pandad: flasher for Rivian long upgrade module"
|
||||||
|
* @didlawowo made their first contribution in "i18n(fr): Add French translations"
|
||||||
|
* @mmmorks made their first contribution in "NNLC: restore pre-v1 PID gains in torque extension"
|
||||||
* New Contributors (sunnypilot/opendbc)
|
* New Contributors (sunnypilot/opendbc)
|
||||||
* @AmyJeanes made their first contribution in "Tesla: Fix stock LKAS being blocked when MADS is enabled"
|
* @AmyJeanes made their first contribution in "Tesla: Fix stock LKAS being blocked when MADS is enabled"
|
||||||
* @mvl-boston made their first contribution in "Honda: Update Clarity brake to renamed DBC message name"
|
* @mvl-boston made their first contribution in "Honda: Update Clarity brake to renamed DBC message name"
|
||||||
@@ -99,6 +202,20 @@ sunnypilot Version 2026.001.000 (2026-03-xx)
|
|||||||
* @royjr made their first contribution in "HKG: add KIA_FORTE_2019_NON_SCC fingerprint"
|
* @royjr made their first contribution in "HKG: add KIA_FORTE_2019_NON_SCC fingerprint"
|
||||||
* @ssysm made their first contribution in "Tesla: remove `TESLA_MODEL_X` from `dashcamOnly`"
|
* @ssysm made their first contribution in "Tesla: remove `TESLA_MODEL_X` from `dashcamOnly`"
|
||||||
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2025.002.000...v2026.001.000
|
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2025.002.000...v2026.001.000
|
||||||
|
************************
|
||||||
|
* Synced with commaai's openpilot (v0.11.1)
|
||||||
|
* master commit c001f3c9b490a80e69539f0af6022f6e07ceb721 (April 16, 2026)
|
||||||
|
* New driver monitoring model
|
||||||
|
* Improved image processing pipeline for driver camera
|
||||||
|
* Rivian R1S and R1T 2025 support thanks to lukasloetkolben!
|
||||||
|
* New driving model #36798
|
||||||
|
* Fully trained using a learned simulator
|
||||||
|
* Improved longitudinal performance in Experimental mode
|
||||||
|
* Reduce comma four standby power usage by 77% to 52 mW
|
||||||
|
* Kia K7 2017 support thanks to royjr!
|
||||||
|
* Lexus LS 2018 support thanks to Hacheoy!
|
||||||
|
* Improved inter-process communication memory efficiency
|
||||||
|
* comma four support
|
||||||
|
|
||||||
sunnypilot Version 2025.002.000 (2025-11-06)
|
sunnypilot Version 2025.002.000 (2025-11-06)
|
||||||
========================
|
========================
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
FROM ubuntu:24.04
|
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends sudo tzdata locales && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
|
||||||
ENV LANG=en_US.UTF-8
|
|
||||||
ENV LANGUAGE=en_US:en
|
|
||||||
ENV LC_ALL=en_US.UTF-8
|
|
||||||
|
|
||||||
ENV NVIDIA_VISIBLE_DEVICES=all
|
|
||||||
ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute
|
|
||||||
|
|
||||||
ARG USER=batman
|
|
||||||
ARG USER_UID=1001
|
|
||||||
RUN useradd -m -s /bin/bash -u $USER_UID $USER
|
|
||||||
RUN usermod -aG sudo $USER
|
|
||||||
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
|
||||||
USER $USER
|
|
||||||
|
|
||||||
ENV OPENPILOT_PATH=/home/$USER/openpilot
|
|
||||||
RUN mkdir -p ${OPENPILOT_PATH}
|
|
||||||
WORKDIR ${OPENPILOT_PATH}
|
|
||||||
|
|
||||||
COPY --chown=$USER . ${OPENPILOT_PATH}/
|
|
||||||
|
|
||||||
ENV UV_BIN="/home/$USER/.local/bin/"
|
|
||||||
ENV VIRTUAL_ENV=${OPENPILOT_PATH}/.venv
|
|
||||||
ENV PATH="$UV_BIN:$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
RUN tools/setup_dependencies.sh && \
|
|
||||||
sudo rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
USER root
|
|
||||||
RUN git config --global --add safe.directory '*'
|
|
||||||
Vendored
+33
-31
@@ -12,7 +12,7 @@ def retryWithDelay(int maxRetries, int delay, Closure body) {
|
|||||||
def device(String ip, String step_label, String cmd) {
|
def device(String ip, String step_label, String cmd) {
|
||||||
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
||||||
def ssh_cmd = """
|
def ssh_cmd = """
|
||||||
ssh -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec /usr/bin/bash <<'END'
|
ssh -o ControlMaster=auto -o ControlPath=/tmp/ssh_control_%C -o ControlPersist=yes -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec /usr/bin/bash <<'END'
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ shopt -s huponexit # kill all child processes when the shell exits
|
|||||||
|
|
||||||
export CI=1
|
export CI=1
|
||||||
export PYTHONWARNINGS=error
|
export PYTHONWARNINGS=error
|
||||||
|
export COMMA_CACHE=/data/tmp/comma_download_cache
|
||||||
#export LOGPRINT=debug # this has gotten too spammy...
|
#export LOGPRINT=debug # this has gotten too spammy...
|
||||||
export TEST_DIR=${env.TEST_DIR}
|
export TEST_DIR=${env.TEST_DIR}
|
||||||
export SOURCE_DIR=${env.SOURCE_DIR}
|
export SOURCE_DIR=${env.SOURCE_DIR}
|
||||||
@@ -30,14 +31,14 @@ export GIT_COMMIT=${env.GIT_COMMIT}
|
|||||||
export CI_ARTIFACTS_TOKEN=${env.CI_ARTIFACTS_TOKEN}
|
export CI_ARTIFACTS_TOKEN=${env.CI_ARTIFACTS_TOKEN}
|
||||||
export GITHUB_COMMENTS_TOKEN=${env.GITHUB_COMMENTS_TOKEN}
|
export GITHUB_COMMENTS_TOKEN=${env.GITHUB_COMMENTS_TOKEN}
|
||||||
export AZURE_TOKEN='${env.AZURE_TOKEN}'
|
export AZURE_TOKEN='${env.AZURE_TOKEN}'
|
||||||
# only use 1 thread for tici tests since most require HIL
|
# only use 1 thread since most require real hardware that can't be shared
|
||||||
export PYTEST_ADDOPTS="-n0 -s"
|
export PYTEST_ADDOPTS="-n0 -s"
|
||||||
|
|
||||||
|
|
||||||
export GIT_SSH_COMMAND="ssh -i /data/gitkey"
|
export GIT_SSH_COMMAND="ssh -i /data/gitkey"
|
||||||
|
|
||||||
source ~/.bash_profile
|
source ~/.bash_profile
|
||||||
if [ -f /TICI ]; then
|
if [ -f /AGNOS ]; then
|
||||||
source /etc/profile
|
source /etc/profile
|
||||||
|
|
||||||
rm -rf /tmp/tmp*
|
rm -rf /tmp/tmp*
|
||||||
@@ -63,6 +64,8 @@ if [ -f /data/openpilot/launch_env.sh ]; then
|
|||||||
source /data/openpilot/launch_env.sh
|
source /data/openpilot/launch_env.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH="\$(python -c 'import ffmpeg; print(ffmpeg.LIB_DIR)'):/usr/local/lib:\${LD_LIBRARY_PATH:-}"
|
||||||
|
|
||||||
ln -snf ${env.TEST_DIR} /data/pythonpath
|
ln -snf ${env.TEST_DIR} /data/pythonpath
|
||||||
|
|
||||||
cd ${env.TEST_DIR} || true
|
cd ${env.TEST_DIR} || true
|
||||||
@@ -93,7 +96,7 @@ def deviceStage(String stageName, String deviceType, List extra_env, def steps)
|
|||||||
retry (3) {
|
retry (3) {
|
||||||
def date = sh(script: 'date', returnStdout: true).trim();
|
def date = sh(script: 'date', returnStdout: true).trim();
|
||||||
device(device_ip, "set time", "date -s '" + date + "'")
|
device(device_ip, "set time", "date -s '" + date + "'")
|
||||||
device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
|
device(device_ip, "git checkout", extra + "\n" + readFile("openpilot/selfdrive/test/setup_device_ci.sh"))
|
||||||
}
|
}
|
||||||
steps.each { item ->
|
steps.each { item ->
|
||||||
def name = item[0]
|
def name = item[0]
|
||||||
@@ -166,8 +169,8 @@ node {
|
|||||||
env.GIT_BRANCH = checkout(scm).GIT_BRANCH
|
env.GIT_BRANCH = checkout(scm).GIT_BRANCH
|
||||||
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
||||||
|
|
||||||
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
|
def excludeBranches = ['__nightly', 'devel', 'devel-staging',
|
||||||
'release-tici', 'release-tizi', 'release-tizi-staging', 'release-mici-staging', 'testing-closet*', 'hotfix-*']
|
'release-tizi', 'release-tizi-staging', 'release-mici', 'release-mici-staging', 'testing-closet*', 'hotfix-*']
|
||||||
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
||||||
|
|
||||||
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
||||||
@@ -179,7 +182,7 @@ node {
|
|||||||
try {
|
try {
|
||||||
if (env.BRANCH_NAME == 'devel-staging') {
|
if (env.BRANCH_NAME == 'devel-staging') {
|
||||||
deviceStage("build release-tizi-staging", "tizi-needs-can", [], [
|
deviceStage("build release-tizi-staging", "tizi-needs-can", [], [
|
||||||
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh && git push -f origin release-tizi-staging:release-mici-staging"),
|
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging,release-mici-staging $SOURCE_DIR/tools/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,12 +190,12 @@ node {
|
|||||||
parallel (
|
parallel (
|
||||||
'nightly': {
|
'nightly': {
|
||||||
deviceStage("build nightly", "tizi-needs-can", [], [
|
deviceStage("build nightly", "tizi-needs-can", [], [
|
||||||
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/tools/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'nightly-dev': {
|
'nightly-dev': {
|
||||||
deviceStage("build nightly-dev", "tizi-needs-can", [], [
|
deviceStage("build nightly-dev", "tizi-needs-can", [], [
|
||||||
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/tools/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -202,52 +205,51 @@ node {
|
|||||||
parallel (
|
parallel (
|
||||||
'onroad tests': {
|
'onroad tests': {
|
||||||
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
|
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
|
||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd openpilot/system/manager && ./build.py"),
|
||||||
step("check dirty", "release/check-dirty.sh"),
|
step("check dirty", "tools/release/check-dirty.sh"),
|
||||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
step("onroad tests", "./openpilot/selfdrive/test/test_onroad.py", [timeout: 60]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'HW + Unit Tests': {
|
'HW + Unit Tests': {
|
||||||
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
|
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd openpilot/system/manager && ./build.py"),
|
||||||
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
step("test power draw", "./openpilot/selfdrive/test/test_power_draw.py"),
|
||||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
step("test encoder", "./openpilot/system/loggerd/tests/test_encoder.py", [diffPaths: ["openpilot/system/loggerd/"]]),
|
||||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
step("test manager", "./openpilot/system/manager/test/test_manager.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'camerad OX03C10': {
|
'camerad OX03C10': {
|
||||||
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd openpilot/system/manager && ./build.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"),
|
step("test pandad", "./openpilot/selfdrive/pandad/tests/test_pandad.py"),
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
|
step("test camerad", "./openpilot/system/camerad/test/test_camerad.py", [timeout: 90]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'camerad OS04C10': {
|
'camerad OS04C10': {
|
||||||
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
|
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd openpilot/system/manager && ./build.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"),
|
step("test pandad", "./openpilot/selfdrive/pandad/tests/test_pandad.py"),
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
|
step("test camerad", "./openpilot/system/camerad/test/test_camerad.py", [timeout: 90]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'sensord': {
|
'sensord': {
|
||||||
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
|
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd openpilot/system/manager && ./build.py"),
|
||||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
step("test sensord", "./openpilot/system/sensord/tests/test_sensord.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'replay': {
|
'replay': {
|
||||||
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
|
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
step("build", "cd openpilot/system/manager && ./build.py", [diffPaths: ["openpilot/selfdrive/modeld/", "tinygrad_repo", "openpilot/selfdrive/test/process_replay/model_replay.py"]]),
|
||||||
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
step("model replay", "openpilot/selfdrive/test/process_replay/model_replay.py", [diffPaths: ["openpilot/selfdrive/modeld/", "tinygrad_repo", "openpilot/selfdrive/test/process_replay/model_replay.py"]]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'tizi': {
|
'tizi': {
|
||||||
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd openpilot/system/manager && ./build.py"),
|
||||||
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
step("test pandad loopback", "./openpilot/selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||||
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
step("test pandad spi", "./openpilot/selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
step("test amp", "./openpilot/common/hardware/comma/tests/test_amplifier.py"),
|
||||||
step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]),
|
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+16
-2
@@ -1,7 +1,21 @@
|
|||||||
Version 0.11.1 (2026-04-22)
|
Version 0.11.2 (2026-08-12)
|
||||||
|
=======================
|
||||||
|
* New driving model
|
||||||
|
* Big model with 880M parameters
|
||||||
|
* Support for big models running on an external GPU
|
||||||
|
* Live stream cameras from comma connect
|
||||||
|
* Generate dashcam clips from comma connect
|
||||||
|
* Remote comma body control from comma connect
|
||||||
|
* New alert sounds
|
||||||
|
* CUPRA Born 2021-2023 support thanks to DaHansi!
|
||||||
|
* Volkswagen ID.4 2021-2025 support thanks to DaHansi!
|
||||||
|
|
||||||
|
Version 0.11.1 (2026-05-18)
|
||||||
========================
|
========================
|
||||||
* New driver monitoring model
|
* New driver monitoring model
|
||||||
* Improved image processing pipeline for driver camera
|
* Improved image processing pipeline for driver camera
|
||||||
|
* Improved thermal policy for comma four
|
||||||
|
* Acura MDX 2022-24 support thanks to mvl-boston!
|
||||||
* Rivian R1S and R1T 2025 support thanks to lukasloetkolben!
|
* Rivian R1S and R1T 2025 support thanks to lukasloetkolben!
|
||||||
|
|
||||||
Version 0.11.0 (2026-03-17)
|
Version 0.11.0 (2026-03-17)
|
||||||
@@ -1098,7 +1112,7 @@ Version 0.2.1 (2016-12-14)
|
|||||||
|
|
||||||
Version 0.2 (2016-12-12)
|
Version 0.2 (2016-12-12)
|
||||||
=========================
|
=========================
|
||||||
* Car/Radar abstraction layers have shipped, see cereal/car.capnp
|
* Car/Radar abstraction layers have shipped, see openpilot/cereal/car.capnp
|
||||||
* controlsd has been refactored
|
* controlsd has been refactored
|
||||||
* Shipped plant model and testing maneuvers
|
* Shipped plant model and testing maneuvers
|
||||||
* visiond exits more gracefully now
|
* visiond exits more gracefully now
|
||||||
|
|||||||
+150
-67
@@ -10,35 +10,74 @@ import numpy as np
|
|||||||
import SCons.Errors
|
import SCons.Errors
|
||||||
from SCons.Defaults import _stripixes
|
from SCons.Defaults import _stripixes
|
||||||
|
|
||||||
|
COMMA_HARDWARE = os.path.isfile('/AGNOS')
|
||||||
|
|
||||||
SCons.Warnings.warningAsException(True)
|
SCons.Warnings.warningAsException(True)
|
||||||
|
|
||||||
Decider('MD5-timestamp')
|
Decider('MD5-timestamp')
|
||||||
|
|
||||||
SetOption('num_jobs', max(1, int(os.cpu_count()/2)))
|
SetOption('num_jobs', max(1, int(os.cpu_count()/(1 if "CI" in os.environ else 2))))
|
||||||
|
|
||||||
AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line')
|
AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line')
|
||||||
AddOption('--verbose', action='store_true', default=False, help='show full build commands')
|
AddOption('--verbose', action='store_true', default=False, help='show full build commands')
|
||||||
|
release = not os.path.exists(File('#.gitattributes').abspath) # file absent on release branch, see release_files.py
|
||||||
AddOption('--minimal',
|
AddOption('--minimal',
|
||||||
action='store_false',
|
action='store_false',
|
||||||
dest='extras',
|
dest='extras',
|
||||||
default=os.path.exists(File('#.gitattributes').abspath), # minimal by default on release branch (where there's no LFS)
|
default=(not COMMA_HARDWARE and not release),
|
||||||
help='the minimum build to run openpilot. no tests, tools, etc.')
|
help='the minimum build to run openpilot. no tests, tools, etc.')
|
||||||
|
|
||||||
|
submodule_python_paths = [
|
||||||
|
Dir("#").abspath,
|
||||||
|
Dir("#msgq_repo").abspath,
|
||||||
|
Dir("#opendbc_repo").abspath,
|
||||||
|
Dir("#rednose_repo").abspath,
|
||||||
|
Dir("#teleoprtc_repo").abspath,
|
||||||
|
Dir("#tinygrad_repo").abspath,
|
||||||
|
]
|
||||||
|
for p in reversed(submodule_python_paths):
|
||||||
|
if p not in sys.path:
|
||||||
|
sys.path.insert(0, p)
|
||||||
|
|
||||||
|
if external_pythonpath := os.environ.get("PYTHONPATH"):
|
||||||
|
submodule_python_paths += [p for p in external_pythonpath.split(os.pathsep) if p and p not in submodule_python_paths]
|
||||||
|
|
||||||
# Detect platform
|
# Detect platform
|
||||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||||
if platform.system() == "Darwin":
|
if platform.system() == "Darwin":
|
||||||
arch = "Darwin"
|
arch = "Darwin"
|
||||||
elif arch == "aarch64" and os.path.isfile('/TICI'):
|
elif arch == "aarch64" and COMMA_HARDWARE:
|
||||||
arch = "larch64"
|
arch = "comma_arm64"
|
||||||
assert arch in [
|
assert arch in [
|
||||||
"larch64", # linux tici arm64
|
"comma_arm64", # linux comma hardware (AGNOS) arm64
|
||||||
"aarch64", # linux pc arm64
|
"aarch64", # linux pc arm64
|
||||||
"x86_64", # linux pc x64
|
"x86_64", # linux pc x64
|
||||||
"Darwin", # macOS arm64 (x86 not supported)
|
"Darwin", # macOS arm64 (x86 not supported)
|
||||||
]
|
]
|
||||||
|
|
||||||
pkg_names = ['bzip2', 'capnproto', 'eigen', 'ffmpeg', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd']
|
pkg_names = ['acados', 'capnproto', 'eigen', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd']
|
||||||
pkgs = [importlib.import_module(name) for name in pkg_names]
|
pkgs = [importlib.import_module(name) for name in pkg_names]
|
||||||
|
acados = pkgs[pkg_names.index('acados')]
|
||||||
|
ffmpeg = pkgs[pkg_names.index('ffmpeg')]
|
||||||
|
# Shared package ships .so/.dylib; older device venvs still have static .a only.
|
||||||
|
# Keep static link deps (x264/z/va/drm) when the installed package is static so
|
||||||
|
# COMMA_HARDWARE CI works without upgrading the device venv yet.
|
||||||
|
# TODO: drop the static fallback once device venvs have comma-deps-ffmpeg>=7.1.0.post94
|
||||||
|
_ffmpeg_lib_names = os.listdir(ffmpeg.LIB_DIR) if os.path.isdir(ffmpeg.LIB_DIR) else []
|
||||||
|
ffmpeg_shared = any(
|
||||||
|
n.startswith('libavcodec.so') or (n.startswith('libavcodec') and n.endswith('.dylib'))
|
||||||
|
for n in _ffmpeg_lib_names
|
||||||
|
)
|
||||||
|
ffmpeg_libs = ['avformat', 'avcodec', 'swresample', 'avutil']
|
||||||
|
if not ffmpeg_shared:
|
||||||
|
ffmpeg_libs += ['x264', 'z']
|
||||||
|
if arch != "Darwin":
|
||||||
|
ffmpeg_libs += ['va', 'va-drm', 'drm']
|
||||||
|
acados_include_dirs = [
|
||||||
|
acados.INCLUDE_DIR,
|
||||||
|
os.path.join(acados.INCLUDE_DIR, "blasfeo", "include"),
|
||||||
|
os.path.join(acados.INCLUDE_DIR, "hpipm", "include"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# ***** enforce a whitelist of system libraries *****
|
# ***** enforce a whitelist of system libraries *****
|
||||||
@@ -82,18 +121,19 @@ def _libflags(target, source, env, for_signature):
|
|||||||
env = Environment(
|
env = Environment(
|
||||||
ENV={
|
ENV={
|
||||||
"PATH": os.environ['PATH'],
|
"PATH": os.environ['PATH'],
|
||||||
"PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath,
|
"PYTHONPATH": os.pathsep.join(submodule_python_paths),
|
||||||
"ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath,
|
"ACADOS_SOURCE_DIR": acados.DIR,
|
||||||
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath,
|
"ACADOS_PYTHON_INTERFACE_PATH": acados.TEMPLATE_DIR,
|
||||||
"TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer"
|
"TERA_PATH": acados.TERA_PATH
|
||||||
},
|
},
|
||||||
CCFLAGS=[
|
CCFLAGS=[
|
||||||
"-g",
|
"-g",
|
||||||
"-fPIC",
|
"-fPIC",
|
||||||
|
"-pipe",
|
||||||
"-O2",
|
"-O2",
|
||||||
"-Wunused",
|
"-Wunused",
|
||||||
"-Werror",
|
"-Werror",
|
||||||
"-Wshadow" if arch in ("Darwin", "larch64") else "-Wshadow=local",
|
"-Wshadow" if arch in ("Darwin", "comma_arm64") else "-Wshadow=local",
|
||||||
"-Wno-unknown-warning-option",
|
"-Wno-unknown-warning-option",
|
||||||
"-Wno-inconsistent-missing-override",
|
"-Wno-inconsistent-missing-override",
|
||||||
"-Wno-c99-designator",
|
"-Wno-c99-designator",
|
||||||
@@ -103,44 +143,46 @@ env = Environment(
|
|||||||
CFLAGS=["-std=gnu11"],
|
CFLAGS=["-std=gnu11"],
|
||||||
CXXFLAGS=["-std=c++1z"],
|
CXXFLAGS=["-std=c++1z"],
|
||||||
CPPPATH=[
|
CPPPATH=[
|
||||||
"#",
|
"#openpilot",
|
||||||
"#msgq",
|
"#msgq_repo", # #include "msgq/..."
|
||||||
"#third_party",
|
"#opendbc_repo", # #include "opendbc/..."
|
||||||
"#third_party/json11",
|
"#rednose_repo", # #include "rednose/..."
|
||||||
"#third_party/linux/include",
|
"#rednose_repo/rednose", # #include "logger/..." (rednose package root)
|
||||||
"#third_party/acados/include",
|
"#openpilot/cereal/gen/cpp",
|
||||||
"#third_party/acados/include/blasfeo/include",
|
acados_include_dirs,
|
||||||
"#third_party/acados/include/hpipm/include",
|
|
||||||
"#third_party/catch2/include",
|
|
||||||
[x.INCLUDE_DIR for x in pkgs],
|
[x.INCLUDE_DIR for x in pkgs],
|
||||||
|
"#",
|
||||||
],
|
],
|
||||||
LIBPATH=[
|
LIBPATH=[
|
||||||
"#common",
|
"#openpilot/common",
|
||||||
"#msgq_repo",
|
"#msgq_repo",
|
||||||
"#third_party",
|
"#openpilot/selfdrive/pandad",
|
||||||
"#selfdrive/pandad",
|
"#rednose_repo/rednose/helpers",
|
||||||
"#rednose/helpers",
|
|
||||||
f"#third_party/acados/{arch}/lib",
|
|
||||||
[x.LIB_DIR for x in pkgs],
|
[x.LIB_DIR for x in pkgs],
|
||||||
],
|
],
|
||||||
RPATH=[],
|
RPATH=[ffmpeg.LIB_DIR] if ffmpeg_shared else [],
|
||||||
CYTHONCFILESUFFIX=".cpp",
|
CYTHONCFILESUFFIX=".cpp",
|
||||||
COMPILATIONDB_USE_ABSPATH=True,
|
COMPILATIONDB_USE_ABSPATH=True,
|
||||||
REDNOSE_ROOT="#",
|
REDNOSE_ROOT="#rednose_repo",
|
||||||
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
||||||
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
toolpath=["#msgq_repo/site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||||
)
|
)
|
||||||
if arch != "larch64":
|
# SCons' Darwin linker tool doesn't define the variables used to expand RPATH.
|
||||||
|
if arch == "Darwin":
|
||||||
|
env["RPATHPREFIX"] = "-Wl,-rpath,"
|
||||||
|
env["RPATHSUFFIX"] = ""
|
||||||
|
env["_RPATH"] = "${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}"
|
||||||
|
if arch != "comma_arm64":
|
||||||
env['_LIBFLAGS'] = _libflags
|
env['_LIBFLAGS'] = _libflags
|
||||||
|
|
||||||
# Arch-specific flags and paths
|
# Arch-specific flags and paths
|
||||||
if arch == "larch64":
|
if arch == "comma_arm64":
|
||||||
env["CC"] = "clang"
|
env["CC"] = "clang"
|
||||||
env["CXX"] = "clang++"
|
env["CXX"] = "clang++"
|
||||||
env.Append(LIBPATH=[
|
env.Append(LIBPATH=[
|
||||||
"/usr/lib/aarch64-linux-gnu",
|
"/usr/lib/aarch64-linux-gnu",
|
||||||
])
|
])
|
||||||
arch_flags = ["-D__TICI__", "-mcpu=cortex-a57", "-DQCOM2"]
|
arch_flags = ["-D__COMMA_HARDWARE__", "-mcpu=cortex-a57"]
|
||||||
env.Append(CCFLAGS=arch_flags)
|
env.Append(CCFLAGS=arch_flags)
|
||||||
env.Append(CXXFLAGS=arch_flags)
|
env.Append(CXXFLAGS=arch_flags)
|
||||||
elif arch == "Darwin":
|
elif arch == "Darwin":
|
||||||
@@ -174,16 +216,6 @@ if not GetOption('verbose'):
|
|||||||
):
|
):
|
||||||
env[f"{action}COMSTR"] = f" [{short}] $TARGET"
|
env[f"{action}COMSTR"] = f" [{short}] $TARGET"
|
||||||
|
|
||||||
# progress output
|
|
||||||
node_interval = 5
|
|
||||||
node_count = 0
|
|
||||||
def progress_function(node):
|
|
||||||
global node_count
|
|
||||||
node_count += node_interval
|
|
||||||
sys.stderr.write("progress: %d\n" % node_count)
|
|
||||||
if os.environ.get('SCONS_PROGRESS'):
|
|
||||||
Progress(progress_function, interval=node_interval)
|
|
||||||
|
|
||||||
# ********** Cython build environment **********
|
# ********** Cython build environment **********
|
||||||
envCython = env.Clone()
|
envCython = env.Clone()
|
||||||
envCython["CPPPATH"] += [sysconfig.get_paths()['include'], np.get_include()]
|
envCython["CPPPATH"] += [sysconfig.get_paths()['include'], np.get_include()]
|
||||||
@@ -199,18 +231,28 @@ else:
|
|||||||
np_version = SCons.Script.Value(np.__version__)
|
np_version = SCons.Script.Value(np.__version__)
|
||||||
Export('envCython', 'np_version')
|
Export('envCython', 'np_version')
|
||||||
|
|
||||||
Export('env', 'arch')
|
Export('env', 'arch', 'acados', 'release', 'ffmpeg_libs')
|
||||||
|
|
||||||
# Setup cache dir
|
# Setup cache dir
|
||||||
default_cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
|
default_cache_dir = '/data/scons_cache' if arch == "comma_arm64" else '/tmp/scons_cache'
|
||||||
cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir)
|
cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir)
|
||||||
|
cache_size_limit = 4e9 if "CI" in os.environ else 2e9
|
||||||
CacheDir(cache_dir)
|
CacheDir(cache_dir)
|
||||||
Clean(["."], cache_dir)
|
Clean(["."], cache_dir)
|
||||||
|
|
||||||
|
def prune_cache_dir(target=None, source=None, env=None):
|
||||||
|
cache_files = sorted((os.path.join(root, f) for root, _, files in os.walk(cache_dir) for f in files), key=os.path.getmtime)
|
||||||
|
cache_size = sum(os.path.getsize(f) for f in cache_files)
|
||||||
|
for f in cache_files:
|
||||||
|
if cache_size < cache_size_limit:
|
||||||
|
break
|
||||||
|
cache_size -= os.path.getsize(f)
|
||||||
|
os.unlink(f)
|
||||||
|
|
||||||
# ********** start building stuff **********
|
# ********** start building stuff **********
|
||||||
|
|
||||||
# Build common module
|
# Build common module
|
||||||
SConscript(['common/SConscript'])
|
SConscript(['openpilot/common/SConscript'])
|
||||||
Import('_common')
|
Import('_common')
|
||||||
common = [_common, 'json11', 'zmq']
|
common = [_common, 'json11', 'zmq']
|
||||||
Export('common')
|
Export('common')
|
||||||
@@ -221,7 +263,7 @@ env_swaglog = env.Clone()
|
|||||||
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
|
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
|
||||||
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
|
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
|
||||||
|
|
||||||
SConscript(['cereal/SConscript'])
|
SConscript(['openpilot/cereal/SConscript'])
|
||||||
|
|
||||||
Import('socketmaster', 'msgq')
|
Import('socketmaster', 'msgq')
|
||||||
messaging = [socketmaster, msgq, 'capnp', 'kj',]
|
messaging = [socketmaster, msgq, 'capnp', 'kj',]
|
||||||
@@ -232,38 +274,79 @@ Export('messaging')
|
|||||||
SConscript(['panda/SConscript'])
|
SConscript(['panda/SConscript'])
|
||||||
|
|
||||||
# Build rednose library
|
# Build rednose library
|
||||||
SConscript(['rednose/SConscript'])
|
SConscript(['rednose_repo/rednose/SConscript'])
|
||||||
|
|
||||||
# Build system services
|
# Build system services
|
||||||
SConscript([
|
SConscript([
|
||||||
'system/loggerd/SConscript',
|
'openpilot/system/loggerd/SConscript',
|
||||||
])
|
])
|
||||||
|
|
||||||
if arch == "larch64":
|
if arch == "comma_arm64":
|
||||||
SConscript(['system/camerad/SConscript'])
|
SConscript(['openpilot/system/camerad/SConscript'])
|
||||||
|
|
||||||
# Build openpilot
|
|
||||||
SConscript(['third_party/SConscript'])
|
|
||||||
|
|
||||||
# Build selfdrive
|
# Build selfdrive
|
||||||
SConscript([
|
SConscript([
|
||||||
'selfdrive/pandad/SConscript',
|
'openpilot/selfdrive/pandad/SConscript',
|
||||||
'selfdrive/controls/lib/lateral_mpc_lib/SConscript',
|
'openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript',
|
||||||
'selfdrive/controls/lib/longitudinal_mpc_lib/SConscript',
|
'openpilot/selfdrive/locationd/SConscript',
|
||||||
'selfdrive/locationd/SConscript',
|
'openpilot/selfdrive/modeld/SConscript',
|
||||||
'selfdrive/modeld/SConscript',
|
'openpilot/selfdrive/ui/SConscript',
|
||||||
'selfdrive/ui/SConscript',
|
|
||||||
])
|
])
|
||||||
|
|
||||||
SConscript(['sunnypilot/SConscript'])
|
SConscript(['openpilot/sunnypilot/SConscript'])
|
||||||
|
|
||||||
# Build tools
|
# Build desktop-only tools
|
||||||
if arch != "larch64":
|
if GetOption('extras') and arch != "comma_arm64":
|
||||||
SConscript([
|
SConscript([
|
||||||
'tools/replay/SConscript',
|
'openpilot/tools/replay/SConscript',
|
||||||
'tools/cabana/SConscript',
|
'openpilot/tools/cabana/SConscript',
|
||||||
'tools/jotpluggler/SConscript',
|
'openpilot/tools/jotpluggler/SConscript',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
env.CompilationDatabase('compile_commands.json')
|
env.CompilationDatabase('compile_commands.json')
|
||||||
|
|
||||||
|
# progress output
|
||||||
|
def count_scons_nodes(nodes):
|
||||||
|
seen = set()
|
||||||
|
stack = list(nodes)
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
node = stack.pop().disambiguate()
|
||||||
|
if node in seen:
|
||||||
|
continue
|
||||||
|
seen.add(node)
|
||||||
|
if hasattr(node, 'has_builder') and node.has_builder():
|
||||||
|
build_product_nodes.add(node)
|
||||||
|
executor = node.get_executor()
|
||||||
|
if executor is not None:
|
||||||
|
stack += executor.get_all_prerequisites() + executor.get_all_children()
|
||||||
|
|
||||||
|
return len(seen)
|
||||||
|
|
||||||
|
progress_interval = 5
|
||||||
|
progress_count = 0
|
||||||
|
build_product_nodes = set()
|
||||||
|
progress_total = max(1, count_scons_nodes(env.arg2nodes(BUILD_TARGETS or [Dir('.')], env.fs.Entry)))
|
||||||
|
|
||||||
|
def progress_function(node):
|
||||||
|
global progress_count
|
||||||
|
if progress_count >= progress_total:
|
||||||
|
return
|
||||||
|
progress_count = min(progress_count + progress_interval, progress_total)
|
||||||
|
progress = round(100. * progress_count / progress_total, 1)
|
||||||
|
sys.stderr.write("\rBuilding: %5.1f%%" % progress if sys.stderr.isatty() else "progress: %.1f\n" % progress)
|
||||||
|
if progress == 100. and sys.stderr.isatty():
|
||||||
|
sys.stderr.write("\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
Progress(progress_function, interval=progress_interval)
|
||||||
|
AddPostAction(BUILD_TARGETS or [Dir('.')], prune_cache_dir)
|
||||||
|
|
||||||
|
def check_build_product_size(target, source, env):
|
||||||
|
limit = 50 * 1024 * 1024 # GitHub max size
|
||||||
|
for t in target:
|
||||||
|
if hasattr(t, 'isfile') and t.isfile() and (size := os.path.getsize(t.abspath)) > limit:
|
||||||
|
raise SCons.Errors.UserError(f"{t} is {size / (1024 * 1024):.1f} MiB, exceeding the {limit / (1024 * 1024):.1f} MiB limit")
|
||||||
|
if not GetOption('extras'):
|
||||||
|
AddPostAction(list(build_product_nodes), Action(check_build_product_size, None))
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
Import('env', 'common', 'msgq')
|
|
||||||
|
|
||||||
cereal_dir = Dir('.')
|
|
||||||
gen_dir = Dir('gen')
|
|
||||||
|
|
||||||
# Build cereal
|
|
||||||
schema_files = ['log.capnp', 'car.capnp', 'deprecated.capnp', 'custom.capnp']
|
|
||||||
env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files],
|
|
||||||
schema_files,
|
|
||||||
f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/")
|
|
||||||
|
|
||||||
cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in schema_files])
|
|
||||||
|
|
||||||
# Build messaging
|
|
||||||
services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET')
|
|
||||||
env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc', 'messaging/bridge_zmq.cc'], LIBS=[msgq, common, 'pthread'])
|
|
||||||
|
|
||||||
socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc'])
|
|
||||||
|
|
||||||
Export('cereal', 'socketmaster')
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import os
|
|
||||||
import capnp
|
|
||||||
from importlib.resources import as_file, files
|
|
||||||
|
|
||||||
capnp.remove_import_hook()
|
|
||||||
|
|
||||||
with as_file(files("cereal")) as fspath:
|
|
||||||
CEREAL_PATH = fspath.as_posix()
|
|
||||||
log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"))
|
|
||||||
car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp"))
|
|
||||||
custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp"))
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../opendbc_repo/opendbc/car/car.capnp
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Schema-level cereal compat check between sunnypilot and upstream openpilot.
|
|
||||||
|
|
||||||
Rules (per struct matched across sides by typeId):
|
|
||||||
R1 shared ordinal must reference the same type.
|
|
||||||
R2 sunnypilot-only ordinal in a union -> FAIL (unknown discriminant upstream).
|
|
||||||
R3 sunnypilot-only ordinal on a regular field -> OK (additive struct evolution).
|
|
||||||
R4 upstream-only ordinal -> OK.
|
|
||||||
R5 sunnypilot-only struct referenced via an upstream-shared field -> FAIL.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
NO_DISCRIMINANT = 0xFFFF
|
|
||||||
|
|
||||||
|
|
||||||
def hex_id(value: int) -> str:
|
|
||||||
return f"0x{value:016x}"
|
|
||||||
|
|
||||||
|
|
||||||
def encode_type(type_node: Any) -> dict:
|
|
||||||
which = type_node.which()
|
|
||||||
if which == "struct":
|
|
||||||
return {"kind": "struct", "typeId": hex_id(type_node.struct.typeId)}
|
|
||||||
if which == "enum":
|
|
||||||
return {"kind": "enum", "typeId": hex_id(type_node.enum.typeId)}
|
|
||||||
if which == "interface":
|
|
||||||
return {"kind": "interface", "typeId": hex_id(type_node.interface.typeId)}
|
|
||||||
if which == "list":
|
|
||||||
return {"kind": "list", "element": encode_type(type_node.list.elementType)}
|
|
||||||
if which == "anyPointer":
|
|
||||||
return {"kind": "anyPointer"}
|
|
||||||
return {"kind": which}
|
|
||||||
|
|
||||||
|
|
||||||
def encode_field(name: str, field: Any) -> dict:
|
|
||||||
proto = field.proto
|
|
||||||
ordinal = proto.ordinal.explicit if proto.ordinal.which() == "explicit" else None
|
|
||||||
discriminant = proto.discriminantValue if proto.discriminantValue != NO_DISCRIMINANT else None
|
|
||||||
|
|
||||||
if proto.which() == "group":
|
|
||||||
type_desc = {"kind": "group", "typeId": hex_id(proto.group.typeId)}
|
|
||||||
else:
|
|
||||||
type_desc = encode_type(proto.slot.type)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"name": name,
|
|
||||||
"ordinal": ordinal,
|
|
||||||
"discriminant": discriminant,
|
|
||||||
"type": type_desc,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def encode_struct(schema: Any) -> dict:
|
|
||||||
node = schema.node
|
|
||||||
return {
|
|
||||||
"typeId": hex_id(node.id),
|
|
||||||
"displayName": node.displayName,
|
|
||||||
"hasUnion": node.struct.discriminantCount > 0,
|
|
||||||
"fields": [encode_field(name, field) for name, field in schema.fields.items()],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _child_struct_schema(field: Any) -> Any:
|
|
||||||
proto = field.proto
|
|
||||||
if proto.which() == "group":
|
|
||||||
return field.schema
|
|
||||||
type_node = proto.slot.type
|
|
||||||
which = type_node.which()
|
|
||||||
if which == "struct":
|
|
||||||
return field.schema
|
|
||||||
if which == "list":
|
|
||||||
container = field.schema
|
|
||||||
element_type = type_node.list.elementType
|
|
||||||
while element_type.which() == "list":
|
|
||||||
container = container.elementType
|
|
||||||
element_type = element_type.list.elementType
|
|
||||||
if element_type.which() == "struct":
|
|
||||||
return container.elementType
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def collect_schema(root: Any) -> dict[str, dict]:
|
|
||||||
structs: dict[str, dict] = {}
|
|
||||||
stack = [root]
|
|
||||||
while stack:
|
|
||||||
schema = stack.pop()
|
|
||||||
type_id = hex_id(schema.node.id)
|
|
||||||
if type_id in structs:
|
|
||||||
continue
|
|
||||||
structs[type_id] = encode_struct(schema)
|
|
||||||
for _name, field in schema.fields.items():
|
|
||||||
try:
|
|
||||||
child = _child_struct_schema(field)
|
|
||||||
except Exception:
|
|
||||||
child = None
|
|
||||||
if child is not None:
|
|
||||||
stack.append(child)
|
|
||||||
return structs
|
|
||||||
|
|
||||||
|
|
||||||
def dump_schema(path: str) -> None:
|
|
||||||
from cereal import log
|
|
||||||
payload = {
|
|
||||||
"root": hex_id(log.Event.schema.node.id),
|
|
||||||
"structs": collect_schema(log.Event.schema),
|
|
||||||
}
|
|
||||||
with open(path, "w", encoding="utf-8") as handle:
|
|
||||||
json.dump(payload, handle, indent=2, sort_keys=True)
|
|
||||||
print(f"wrote schema dump with {len(payload['structs'])} structs to {path}")
|
|
||||||
|
|
||||||
|
|
||||||
def types_equal(a: dict, b: dict) -> bool:
|
|
||||||
if a.get("kind") != b.get("kind"):
|
|
||||||
return False
|
|
||||||
kind = a["kind"]
|
|
||||||
if kind in ("struct", "enum", "interface", "group"):
|
|
||||||
return a.get("typeId") == b.get("typeId")
|
|
||||||
if kind == "list":
|
|
||||||
return types_equal(a["element"], b["element"])
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def type_repr(t: dict) -> str:
|
|
||||||
kind = t.get("kind", "?")
|
|
||||||
if kind in ("struct", "enum", "interface", "group"):
|
|
||||||
return f"{kind}({t.get('typeId')})"
|
|
||||||
if kind == "list":
|
|
||||||
return f"list<{type_repr(t['element'])}>"
|
|
||||||
return kind
|
|
||||||
|
|
||||||
|
|
||||||
def field_is_union_variant(field: dict) -> bool:
|
|
||||||
return field.get("discriminant") is not None
|
|
||||||
|
|
||||||
|
|
||||||
def index_fields_by_ordinal(struct: dict) -> dict[int, dict]:
|
|
||||||
indexed: dict[int, dict] = {}
|
|
||||||
for field in struct["fields"]:
|
|
||||||
ordinal = field.get("ordinal")
|
|
||||||
if ordinal is None:
|
|
||||||
continue
|
|
||||||
indexed[ordinal] = field
|
|
||||||
return indexed
|
|
||||||
|
|
||||||
|
|
||||||
def compare(sunnypilot_dump: dict, upstream_dump: dict) -> list[str]:
|
|
||||||
violations: list[str] = []
|
|
||||||
sunnypilot_structs: dict[str, dict] = sunnypilot_dump["structs"]
|
|
||||||
upstream_structs: dict[str, dict] = upstream_dump["structs"]
|
|
||||||
|
|
||||||
sunnypilot_struct_referenced_from_shared: set[str] = set()
|
|
||||||
|
|
||||||
for type_id, sunnypilot_struct in sunnypilot_structs.items():
|
|
||||||
upstream_struct = upstream_structs.get(type_id)
|
|
||||||
if upstream_struct is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
sunnypilot_fields = index_fields_by_ordinal(sunnypilot_struct)
|
|
||||||
upstream_fields = index_fields_by_ordinal(upstream_struct)
|
|
||||||
display = sunnypilot_struct["displayName"]
|
|
||||||
|
|
||||||
for ordinal, sunnypilot_field in sunnypilot_fields.items():
|
|
||||||
upstream_field = upstream_fields.get(ordinal)
|
|
||||||
if upstream_field is None:
|
|
||||||
if field_is_union_variant(sunnypilot_field):
|
|
||||||
violations.append(
|
|
||||||
f"[R2] {display} @{ordinal} ('{sunnypilot_field['name']}', {type_repr(sunnypilot_field['type'])}): "
|
|
||||||
f"union variant not present upstream. upstream cannot parse this discriminant."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not types_equal(sunnypilot_field["type"], upstream_field["type"]):
|
|
||||||
violations.append(
|
|
||||||
f"[R1] {display} @{ordinal}: type mismatch. "
|
|
||||||
f"sunnypilot='{sunnypilot_field['name']}' {type_repr(sunnypilot_field['type'])} vs "
|
|
||||||
f"upstream='{upstream_field['name']}' {type_repr(upstream_field['type'])}."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
cursor = sunnypilot_field["type"]
|
|
||||||
while cursor.get("kind") == "list":
|
|
||||||
cursor = cursor["element"]
|
|
||||||
if cursor.get("kind") in ("struct", "group", "interface") and cursor.get("typeId"):
|
|
||||||
sunnypilot_struct_referenced_from_shared.add(cursor["typeId"])
|
|
||||||
|
|
||||||
for type_id, sunnypilot_struct in sunnypilot_structs.items():
|
|
||||||
if type_id in upstream_structs:
|
|
||||||
continue
|
|
||||||
if type_id in sunnypilot_struct_referenced_from_shared:
|
|
||||||
violations.append(
|
|
||||||
f"[R5] struct {sunnypilot_struct['displayName']} ({type_id}) exists only on sunnypilot "
|
|
||||||
f"but is referenced from an upstream-shared field. upstream cannot resolve this type."
|
|
||||||
)
|
|
||||||
|
|
||||||
return violations
|
|
||||||
|
|
||||||
|
|
||||||
def load_peer(path: str) -> dict:
|
|
||||||
with open(path, "r", encoding="utf-8") as handle:
|
|
||||||
return json.load(handle)
|
|
||||||
|
|
||||||
|
|
||||||
def run_read(peer_path: str) -> int:
|
|
||||||
from cereal import log
|
|
||||||
peer_dump = load_peer(peer_path)
|
|
||||||
local_dump = {
|
|
||||||
"root": hex_id(log.Event.schema.node.id),
|
|
||||||
"structs": collect_schema(log.Event.schema),
|
|
||||||
}
|
|
||||||
violations = compare(sunnypilot_dump=peer_dump, upstream_dump=local_dump)
|
|
||||||
|
|
||||||
if not violations:
|
|
||||||
print("cereal compat OK: upstream openpilot can parse sunnypilot routes "
|
|
||||||
"(no leaked structs, no ordinal collisions).")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
print(f"cereal compat FAIL: upstream openpilot would misparse sunnypilot routes "
|
|
||||||
f"({len(violations)} violation(s)):")
|
|
||||||
for v in violations:
|
|
||||||
print(f" {v}")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="sunnypilot <-> upstream cereal compatibility validator (schema-level)."
|
|
||||||
)
|
|
||||||
mode = parser.add_mutually_exclusive_group(required=True)
|
|
||||||
mode.add_argument("-g", "--generate", action="store_true", help="dump local schema to JSON")
|
|
||||||
mode.add_argument("-r", "--read", action="store_true", help="load peer JSON and diff against local")
|
|
||||||
parser.add_argument("-f", "--file", default="schema.json", help="JSON file path (default: schema.json)")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
try:
|
|
||||||
if args.generate:
|
|
||||||
dump_schema(args.file)
|
|
||||||
return 0
|
|
||||||
return run_read(args.file)
|
|
||||||
except ImportError as exc:
|
|
||||||
print(f"error: cannot import cereal ({exc}). did scons build cereal?")
|
|
||||||
return 2
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
Import('env', 'envCython')
|
|
||||||
|
|
||||||
common_libs = [
|
|
||||||
'params.cc',
|
|
||||||
'swaglog.cc',
|
|
||||||
'util.cc',
|
|
||||||
'ratekeeper.cc',
|
|
||||||
]
|
|
||||||
|
|
||||||
_common = env.Library('common', common_libs, LIBS="json11")
|
|
||||||
Export('_common')
|
|
||||||
|
|
||||||
if GetOption('extras'):
|
|
||||||
env.Program('tests/test_common',
|
|
||||||
['tests/test_runner.cc', 'tests/test_params.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'],
|
|
||||||
LIBS=[_common, 'json11', 'zmq', 'pthread'])
|
|
||||||
|
|
||||||
# Cython bindings
|
|
||||||
params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11'])
|
|
||||||
|
|
||||||
common_python = [params_python]
|
|
||||||
|
|
||||||
Export('common_python')
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import math
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
CHUNK_SIZE = 45 * 1024 * 1024 # 45MB, under GitHub's 50MB limit
|
|
||||||
|
|
||||||
def get_chunk_name(name, idx, num_chunks):
|
|
||||||
return f"{name}.chunk{idx+1:02d}of{num_chunks:02d}"
|
|
||||||
|
|
||||||
def get_manifest_path(name):
|
|
||||||
return f"{name}.chunkmanifest"
|
|
||||||
|
|
||||||
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)]
|
|
||||||
|
|
||||||
def chunk_file(path, targets):
|
|
||||||
manifest_path, *chunk_paths = targets
|
|
||||||
with open(path, 'rb') as f:
|
|
||||||
data = f.read()
|
|
||||||
actual_num_chunks = max(1, math.ceil(len(data) / CHUNK_SIZE))
|
|
||||||
assert len(chunk_paths) >= actual_num_chunks, f"Allowed {len(chunk_paths)} chunks but needs at least {actual_num_chunks}, for path {path}"
|
|
||||||
for i, chunk_path in enumerate(chunk_paths):
|
|
||||||
with open(chunk_path, 'wb') as f:
|
|
||||||
f.write(data[i * CHUNK_SIZE:(i + 1) * CHUNK_SIZE])
|
|
||||||
Path(manifest_path).write_text(str(len(chunk_paths)))
|
|
||||||
os.remove(path)
|
|
||||||
|
|
||||||
|
|
||||||
def read_file_chunked(path):
|
|
||||||
manifest_path = get_manifest_path(path)
|
|
||||||
if os.path.isfile(manifest_path):
|
|
||||||
num_chunks = int(Path(manifest_path).read_text().strip())
|
|
||||||
return b''.join(Path(get_chunk_name(path, i, num_chunks)).read_bytes() for i in range(num_chunks))
|
|
||||||
if os.path.isfile(path):
|
|
||||||
return Path(path).read_bytes()
|
|
||||||
raise FileNotFoundError(path)
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
from cereal import messaging
|
|
||||||
|
|
||||||
|
|
||||||
def generate_livePose():
|
|
||||||
msg = messaging.new_message('livePose')
|
|
||||||
meas = {'x': 0.0, 'y': 0.0, 'z': 0.0, 'xStd': 0.0, 'yStd': 0.0, 'zStd': 0.0, 'valid': True}
|
|
||||||
msg.livePose.orientationNED = meas
|
|
||||||
msg.livePose.velocityDevice = meas
|
|
||||||
msg.livePose.angularVelocityDevice = meas
|
|
||||||
msg.livePose.accelerationDevice = meas
|
|
||||||
msg.livePose.inputsOK = True
|
|
||||||
msg.livePose.posenetOK = True
|
|
||||||
msg.livePose.sensorsOK = True
|
|
||||||
return msg
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#define DEFAULT_MODEL "POP model (Default)"
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import sys
|
|
||||||
import pytest
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
|
|
||||||
class parameterized:
|
|
||||||
@staticmethod
|
|
||||||
def expand(cases):
|
|
||||||
cases = list(cases)
|
|
||||||
|
|
||||||
if not cases:
|
|
||||||
return lambda func: pytest.mark.skip("no parameterized cases")(func)
|
|
||||||
|
|
||||||
def decorator(func):
|
|
||||||
params = [p for p in inspect.signature(func).parameters if p != 'self']
|
|
||||||
normalized = [c if isinstance(c, tuple) else (c,) for c in cases]
|
|
||||||
# Infer arg count from first case so extra params (e.g. from @given) are left untouched
|
|
||||||
expand_params = params[: len(normalized[0])]
|
|
||||||
if len(expand_params) == 1:
|
|
||||||
return pytest.mark.parametrize(expand_params[0], [c[0] for c in normalized])(func)
|
|
||||||
return pytest.mark.parametrize(', '.join(expand_params), normalized)(func)
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def parameterized_class(attrs, input_list=None):
|
|
||||||
if isinstance(attrs, list) and (not attrs or isinstance(attrs[0], dict)):
|
|
||||||
params_list = attrs
|
|
||||||
else:
|
|
||||||
assert input_list is not None
|
|
||||||
attr_names = (attrs,) if isinstance(attrs, str) else tuple(attrs)
|
|
||||||
params_list = [dict(zip(attr_names, v if isinstance(v, (tuple, list)) else (v,), strict=False)) for v in input_list]
|
|
||||||
|
|
||||||
def decorator(cls):
|
|
||||||
globs = sys._getframe(1).f_globals
|
|
||||||
for i, params in enumerate(params_list):
|
|
||||||
name = f"{cls.__name__}_{i}"
|
|
||||||
new_cls = type(name, (cls,), dict(params))
|
|
||||||
new_cls.__module__ = cls.__module__
|
|
||||||
new_cls.__test__ = True # override inherited False so pytest collects this subclass
|
|
||||||
globs[name] = new_cls
|
|
||||||
# Don't collect the un-parametrised base, but return it so outer decorators
|
|
||||||
# (e.g. @pytest.mark.skip) land on it and propagate to subclasses via MRO.
|
|
||||||
cls.__test__ = False
|
|
||||||
return cls
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
from openpilot.common.params_pyx import Params, ParamKeyFlag, ParamKeyType, UnknownKeyName
|
|
||||||
assert Params
|
|
||||||
assert ParamKeyFlag
|
|
||||||
assert ParamKeyType
|
|
||||||
assert UnknownKeyName
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import sys
|
|
||||||
|
|
||||||
params = Params()
|
|
||||||
key = sys.argv[1]
|
|
||||||
assert params.check_key(key), f"unknown param: {key}"
|
|
||||||
|
|
||||||
if len(sys.argv) == 3:
|
|
||||||
val = sys.argv[2]
|
|
||||||
print(f"SET: {key} = {val}")
|
|
||||||
params.put(key, val)
|
|
||||||
elif len(sys.argv) == 2:
|
|
||||||
print(f"GET: {key} = {params.get(key)}")
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
# distutils: language = c++
|
|
||||||
# cython: language_level = 3
|
|
||||||
import builtins
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
from libcpp cimport bool
|
|
||||||
from libcpp.string cimport string
|
|
||||||
from libcpp.vector cimport vector
|
|
||||||
from libcpp.optional cimport optional
|
|
||||||
|
|
||||||
from openpilot.common.swaglog import cloudlog
|
|
||||||
|
|
||||||
cdef extern from "common/params.h":
|
|
||||||
cpdef enum ParamKeyFlag:
|
|
||||||
PERSISTENT
|
|
||||||
CLEAR_ON_MANAGER_START
|
|
||||||
CLEAR_ON_ONROAD_TRANSITION
|
|
||||||
CLEAR_ON_OFFROAD_TRANSITION
|
|
||||||
DEVELOPMENT_ONLY
|
|
||||||
CLEAR_ON_IGNITION_ON
|
|
||||||
BACKUP
|
|
||||||
ALL
|
|
||||||
|
|
||||||
cpdef enum ParamKeyType:
|
|
||||||
STRING
|
|
||||||
BOOL
|
|
||||||
INT
|
|
||||||
FLOAT
|
|
||||||
TIME
|
|
||||||
JSON
|
|
||||||
BYTES
|
|
||||||
|
|
||||||
cdef cppclass c_Params "Params":
|
|
||||||
c_Params(string) except + nogil
|
|
||||||
string get(string, bool) nogil
|
|
||||||
bool getBool(string, bool) nogil
|
|
||||||
int remove(string) nogil
|
|
||||||
int put(string, string) nogil
|
|
||||||
void putNonBlocking(string, string) nogil
|
|
||||||
void putBoolNonBlocking(string, bool) nogil
|
|
||||||
int putBool(string, bool) nogil
|
|
||||||
bool checkKey(string) nogil
|
|
||||||
ParamKeyType getKeyType(string) nogil
|
|
||||||
optional[string] getKeyDefaultValue(string) nogil
|
|
||||||
string getParamPath(string) nogil
|
|
||||||
void clearAll(ParamKeyFlag)
|
|
||||||
vector[string] allKeys(ParamKeyFlag)
|
|
||||||
|
|
||||||
PYTHON_2_CPP = {
|
|
||||||
(str, STRING): lambda v: v,
|
|
||||||
(builtins.bool, BOOL): lambda v: "1" if v else "0",
|
|
||||||
(int, INT): str,
|
|
||||||
(float, FLOAT): str,
|
|
||||||
(datetime.datetime, TIME): lambda v: v.isoformat(),
|
|
||||||
(dict, JSON): json.dumps,
|
|
||||||
(list, JSON): json.dumps,
|
|
||||||
(bytes, BYTES): lambda v: v,
|
|
||||||
}
|
|
||||||
CPP_2_PYTHON = {
|
|
||||||
STRING: lambda v: v.decode("utf-8"),
|
|
||||||
BOOL: lambda v: v == b"1",
|
|
||||||
INT: int,
|
|
||||||
FLOAT: float,
|
|
||||||
TIME: lambda v: datetime.datetime.fromisoformat(v.decode("utf-8")),
|
|
||||||
JSON: json.loads,
|
|
||||||
BYTES: lambda v: v,
|
|
||||||
}
|
|
||||||
|
|
||||||
def ensure_bytes(v):
|
|
||||||
return v.encode() if isinstance(v, str) else v
|
|
||||||
|
|
||||||
class UnknownKeyName(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
cdef class Params:
|
|
||||||
cdef c_Params* p
|
|
||||||
cdef str d
|
|
||||||
|
|
||||||
def __cinit__(self, d=""):
|
|
||||||
cdef string path = <string>d.encode()
|
|
||||||
with nogil:
|
|
||||||
self.p = new c_Params(path)
|
|
||||||
self.d = d
|
|
||||||
|
|
||||||
def __reduce__(self):
|
|
||||||
return (type(self), (self.d,))
|
|
||||||
|
|
||||||
def __dealloc__(self):
|
|
||||||
del self.p
|
|
||||||
|
|
||||||
def clear_all(self, tx_flag=ParamKeyFlag.ALL):
|
|
||||||
self.p.clearAll(tx_flag)
|
|
||||||
|
|
||||||
def check_key(self, key):
|
|
||||||
key = ensure_bytes(key)
|
|
||||||
if not self.p.checkKey(key):
|
|
||||||
raise UnknownKeyName(key)
|
|
||||||
return key
|
|
||||||
|
|
||||||
def python2cpp(self, proposed_type, expected_type, value, key):
|
|
||||||
cast = PYTHON_2_CPP.get((proposed_type, expected_type))
|
|
||||||
if cast:
|
|
||||||
return cast(value)
|
|
||||||
raise TypeError(f"Type mismatch while writing param {key}: {proposed_type=} {expected_type=} {value=}")
|
|
||||||
|
|
||||||
def _cpp2python(self, t, value, default, key):
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return CPP_2_PYTHON[t](value)
|
|
||||||
except (KeyError, TypeError, ValueError):
|
|
||||||
cloudlog.warning(f"Failed to cast param {key} with {value=} from type {t=}")
|
|
||||||
return self._cpp2python(t, default, None, key)
|
|
||||||
|
|
||||||
def get(self, key, bool block=False, bool return_default=False):
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
cdef ParamKeyType t = self.p.getKeyType(k)
|
|
||||||
cdef optional[string] default = self.p.getKeyDefaultValue(k)
|
|
||||||
cdef string val
|
|
||||||
with nogil:
|
|
||||||
val = self.p.get(k, block)
|
|
||||||
|
|
||||||
default_val = (default.value() if default.has_value() else None) if return_default else None
|
|
||||||
if val == b"":
|
|
||||||
if block:
|
|
||||||
# If we got no value while running in blocked mode
|
|
||||||
# it means we got an interrupt while waiting
|
|
||||||
raise KeyboardInterrupt
|
|
||||||
else:
|
|
||||||
return self._cpp2python(t, default_val, None, key)
|
|
||||||
return self._cpp2python(t, val, default_val, key)
|
|
||||||
|
|
||||||
def get_bool(self, key, bool block=False):
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
cdef bool r
|
|
||||||
with nogil:
|
|
||||||
r = self.p.getBool(k, block)
|
|
||||||
return r
|
|
||||||
|
|
||||||
def _put_cast(self, key, dat):
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
cdef ParamKeyType t = self.p.getKeyType(k)
|
|
||||||
return ensure_bytes(self.python2cpp(type(dat), t, dat, key))
|
|
||||||
|
|
||||||
def put(self, key, dat):
|
|
||||||
"""
|
|
||||||
Warning: This function blocks until the param is written to disk!
|
|
||||||
In very rare cases this can take over a second, and your code will hang.
|
|
||||||
Use the put_nonblocking, put_bool_nonblocking in time sensitive code, but
|
|
||||||
in general try to avoid writing params as much as possible.
|
|
||||||
"""
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
cdef string dat_bytes = self._put_cast(key, dat)
|
|
||||||
with nogil:
|
|
||||||
self.p.put(k, dat_bytes)
|
|
||||||
|
|
||||||
def put_bool(self, key, bool val):
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
with nogil:
|
|
||||||
self.p.putBool(k, val)
|
|
||||||
|
|
||||||
def put_nonblocking(self, key, dat):
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
cdef string dat_bytes = self._put_cast(key, dat)
|
|
||||||
with nogil:
|
|
||||||
self.p.putNonBlocking(k, dat_bytes)
|
|
||||||
|
|
||||||
def put_bool_nonblocking(self, key, bool val):
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
with nogil:
|
|
||||||
self.p.putBoolNonBlocking(k, val)
|
|
||||||
|
|
||||||
def remove(self, key):
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
with nogil:
|
|
||||||
self.p.remove(k)
|
|
||||||
|
|
||||||
def get_param_path(self, key=""):
|
|
||||||
cdef string key_bytes = ensure_bytes(key)
|
|
||||||
return self.p.getParamPath(key_bytes).decode("utf-8")
|
|
||||||
|
|
||||||
def get_type(self, key):
|
|
||||||
return self.p.getKeyType(self.check_key(key))
|
|
||||||
|
|
||||||
def all_keys(self, flag=ParamKeyFlag.ALL):
|
|
||||||
return self.p.allKeys(flag)
|
|
||||||
|
|
||||||
def get_default_value(self, key):
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
cdef ParamKeyType t = self.p.getKeyType(k)
|
|
||||||
cdef optional[string] default = self.p.getKeyDefaultValue(k)
|
|
||||||
return self._cpp2python(t, default.value(), None, key) if default.has_value() else None
|
|
||||||
|
|
||||||
def cpp2python(self, key, value):
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
cdef ParamKeyType t = self.p.getKeyType(k)
|
|
||||||
return self._cpp2python(t, value, None, key)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
test_common
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#include "catch2/catch.hpp"
|
|
||||||
#define private public
|
|
||||||
#include "common/params.h"
|
|
||||||
#include "common/util.h"
|
|
||||||
|
|
||||||
TEST_CASE("params_nonblocking_put") {
|
|
||||||
char tmp_path[] = "/tmp/asyncWriter_XXXXXX";
|
|
||||||
const std::string param_path = mkdtemp(tmp_path);
|
|
||||||
auto param_names = {"CarParams", "IsMetric"};
|
|
||||||
{
|
|
||||||
Params params(param_path);
|
|
||||||
for (const auto &name : param_names) {
|
|
||||||
params.putNonBlocking(name, "1");
|
|
||||||
// param is empty
|
|
||||||
REQUIRE(params.get(name).empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if thread is running
|
|
||||||
REQUIRE(params.future.valid());
|
|
||||||
REQUIRE(params.future.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout);
|
|
||||||
}
|
|
||||||
// check results
|
|
||||||
Params p(param_path);
|
|
||||||
for (const auto &name : param_names) {
|
|
||||||
REQUIRE(p.get(name) == "1");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#define CATCH_CONFIG_MAIN
|
|
||||||
#include "catch2/catch.hpp"
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#include <zmq.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "catch2/catch.hpp"
|
|
||||||
#include "common/swaglog.h"
|
|
||||||
#include "common/util.h"
|
|
||||||
#include "common/version.h"
|
|
||||||
#include "system/hardware/hw.h"
|
|
||||||
#include "third_party/json11/json11.hpp"
|
|
||||||
|
|
||||||
#include "sunnypilot/common/version.h"
|
|
||||||
|
|
||||||
std::string daemon_name = "testy";
|
|
||||||
std::string dongle_id = "test_dongle_id";
|
|
||||||
int LINE_NO = 0;
|
|
||||||
|
|
||||||
void log_thread(int thread_id, int msg_cnt) {
|
|
||||||
for (int i = 0; i < msg_cnt; ++i) {
|
|
||||||
LOGD("%d", thread_id);
|
|
||||||
LINE_NO = __LINE__ - 1;
|
|
||||||
usleep(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void recv_log(int thread_cnt, int thread_msg_cnt) {
|
|
||||||
void *zctx = zmq_ctx_new();
|
|
||||||
void *sock = zmq_socket(zctx, ZMQ_PULL);
|
|
||||||
zmq_bind(sock, Path::swaglog_ipc().c_str());
|
|
||||||
std::vector<int> thread_msgs(thread_cnt);
|
|
||||||
int total_count = 0;
|
|
||||||
|
|
||||||
for (auto start = std::chrono::steady_clock::now(), now = start;
|
|
||||||
now < start + std::chrono::seconds{1} && total_count < (thread_cnt * thread_msg_cnt);
|
|
||||||
now = std::chrono::steady_clock::now()) {
|
|
||||||
char buf[4096] = {};
|
|
||||||
if (zmq_recv(sock, buf, sizeof(buf), ZMQ_DONTWAIT) <= 0) {
|
|
||||||
if (errno == EAGAIN || errno == EINTR || errno == EFSM) continue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
REQUIRE(buf[0] == CLOUDLOG_DEBUG);
|
|
||||||
std::string err;
|
|
||||||
auto msg = json11::Json::parse(buf + 1, err);
|
|
||||||
REQUIRE(!msg.is_null());
|
|
||||||
|
|
||||||
REQUIRE(msg["levelnum"].int_value() == CLOUDLOG_DEBUG);
|
|
||||||
REQUIRE_THAT(msg["filename"].string_value(), Catch::Contains("test_swaglog.cc"));
|
|
||||||
REQUIRE(msg["funcname"].string_value() == "log_thread");
|
|
||||||
REQUIRE(msg["lineno"].int_value() == LINE_NO);
|
|
||||||
|
|
||||||
auto ctx = msg["ctx"];
|
|
||||||
|
|
||||||
REQUIRE(ctx["daemon"].string_value() == daemon_name);
|
|
||||||
REQUIRE(ctx["dongle_id"].string_value() == dongle_id);
|
|
||||||
REQUIRE(ctx["dirty"].bool_value() == true);
|
|
||||||
|
|
||||||
REQUIRE(ctx["version"].string_value() == SUNNYPILOT_VERSION);
|
|
||||||
|
|
||||||
std::string device = Hardware::get_name();
|
|
||||||
REQUIRE(ctx["device"].string_value() == device);
|
|
||||||
|
|
||||||
int thread_id = atoi(msg["msg"].string_value().c_str());
|
|
||||||
REQUIRE((thread_id >= 0 && thread_id < thread_cnt));
|
|
||||||
thread_msgs[thread_id]++;
|
|
||||||
total_count++;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < thread_cnt; ++i) {
|
|
||||||
INFO("thread :" << i);
|
|
||||||
REQUIRE(thread_msgs[i] == thread_msg_cnt);
|
|
||||||
}
|
|
||||||
zmq_close(sock);
|
|
||||||
zmq_ctx_destroy(zctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("swaglog") {
|
|
||||||
setenv("MANAGER_DAEMON", daemon_name.c_str(), 1);
|
|
||||||
setenv("DONGLE_ID", dongle_id.c_str(), 1);
|
|
||||||
setenv("dirty", "1", 1);
|
|
||||||
const int thread_cnt = 5;
|
|
||||||
const int thread_msg_cnt = 100;
|
|
||||||
|
|
||||||
std::vector<std::thread> log_threads;
|
|
||||||
for (int i = 0; i < thread_cnt; ++i) {
|
|
||||||
log_threads.push_back(std::thread(log_thread, i, thread_msg_cnt));
|
|
||||||
}
|
|
||||||
for (auto &t : log_threads) t.join();
|
|
||||||
|
|
||||||
recv_log(thread_cnt, thread_msg_cnt);
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <climits>
|
|
||||||
#include <fstream>
|
|
||||||
#include <random>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "catch2/catch.hpp"
|
|
||||||
#include "common/util.h"
|
|
||||||
|
|
||||||
std::string random_bytes(int size) {
|
|
||||||
std::random_device rd;
|
|
||||||
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char> rbe(rd());
|
|
||||||
std::string bytes(size + 1, '\0');
|
|
||||||
std::generate(bytes.begin(), bytes.end(), std::ref(rbe));
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("util::read_file") {
|
|
||||||
SECTION("read /proc/version") {
|
|
||||||
std::string ret = util::read_file("/proc/version");
|
|
||||||
REQUIRE(ret.find("Linux version") != std::string::npos);
|
|
||||||
}
|
|
||||||
SECTION("read from sysfs") {
|
|
||||||
std::string ret = util::read_file("/sys/power/wakeup_count");
|
|
||||||
REQUIRE(!ret.empty());
|
|
||||||
}
|
|
||||||
SECTION("read file") {
|
|
||||||
char filename[] = "/tmp/test_read_XXXXXX";
|
|
||||||
int fd = mkstemp(filename);
|
|
||||||
|
|
||||||
REQUIRE(util::read_file(filename).empty());
|
|
||||||
|
|
||||||
std::string content = random_bytes(64 * 1024);
|
|
||||||
REQUIRE(write(fd, content.c_str(), content.size()) == (ssize_t)content.size());
|
|
||||||
std::string ret = util::read_file(filename);
|
|
||||||
bool equal = (ret == content);
|
|
||||||
REQUIRE(equal);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
SECTION("read directory") {
|
|
||||||
REQUIRE(util::read_file(".").empty());
|
|
||||||
}
|
|
||||||
SECTION("read non-existent file") {
|
|
||||||
std::string ret = util::read_file("does_not_exist");
|
|
||||||
REQUIRE(ret.empty());
|
|
||||||
}
|
|
||||||
SECTION("read non-permission") {
|
|
||||||
REQUIRE(util::read_file("/proc/kmsg").empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("util::file_exists") {
|
|
||||||
char filename[] = "/tmp/test_file_exists_XXXXXX";
|
|
||||||
int fd = mkstemp(filename);
|
|
||||||
REQUIRE(fd != -1);
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
SECTION("existent file") {
|
|
||||||
REQUIRE(util::file_exists(filename));
|
|
||||||
REQUIRE(util::file_exists("/tmp"));
|
|
||||||
}
|
|
||||||
SECTION("nonexistent file") {
|
|
||||||
std::string fn = filename;
|
|
||||||
REQUIRE(!util::file_exists(fn + "/nonexistent"));
|
|
||||||
}
|
|
||||||
SECTION("file has no access permissions") {
|
|
||||||
std::string fn = "/proc/kmsg";
|
|
||||||
std::ifstream f(fn);
|
|
||||||
REQUIRE(f.good() == false);
|
|
||||||
REQUIRE(util::file_exists(fn));
|
|
||||||
}
|
|
||||||
::remove(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("util::read_files_in_dir") {
|
|
||||||
char tmp_path[] = "/tmp/test_XXXXXX";
|
|
||||||
const std::string test_path = mkdtemp(tmp_path);
|
|
||||||
const std::string files[] = {".test1", "'test2'", "test3"};
|
|
||||||
for (auto fn : files) {
|
|
||||||
std::ofstream{test_path + "/" + fn} << fn;
|
|
||||||
}
|
|
||||||
mkdir((test_path + "/dir").c_str(), 0777);
|
|
||||||
|
|
||||||
std::map<std::string, std::string> result = util::read_files_in_dir(test_path);
|
|
||||||
REQUIRE(result.find("dir") == result.end());
|
|
||||||
REQUIRE(result.size() == std::size(files));
|
|
||||||
for (auto& [k, v] : result) {
|
|
||||||
REQUIRE(k == v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("util::safe_fwrite") {
|
|
||||||
char filename[] = "/tmp/XXXXXX";
|
|
||||||
int fd = mkstemp(filename);
|
|
||||||
close(fd);
|
|
||||||
std::string dat = random_bytes(1024 * 1024);
|
|
||||||
|
|
||||||
FILE *f = util::safe_fopen(filename, "wb");
|
|
||||||
REQUIRE(f != nullptr);
|
|
||||||
size_t size = util::safe_fwrite(dat.data(), 1, dat.size(), f);
|
|
||||||
REQUIRE(size == dat.size());
|
|
||||||
int ret = util::safe_fflush(f);
|
|
||||||
REQUIRE(ret == 0);
|
|
||||||
ret = fclose(f);
|
|
||||||
REQUIRE(ret == 0);
|
|
||||||
bool equal = (dat == util::read_file(filename));
|
|
||||||
REQUIRE(equal);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("util::create_directories") {
|
|
||||||
REQUIRE(system("rm /tmp/test_create_directories -rf") == 0);
|
|
||||||
std::string dir = "/tmp/test_create_directories/a/b/c/d/e/f";
|
|
||||||
|
|
||||||
auto check_dir_permissions = [](const std::string &path, mode_t mode) -> bool {
|
|
||||||
struct stat st = {};
|
|
||||||
return stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode;
|
|
||||||
};
|
|
||||||
|
|
||||||
SECTION("create_directories") {
|
|
||||||
REQUIRE(util::create_directories(dir, 0755));
|
|
||||||
REQUIRE(check_dir_permissions(dir, 0755));
|
|
||||||
}
|
|
||||||
SECTION("dir already exists") {
|
|
||||||
REQUIRE(util::create_directories(dir, 0755));
|
|
||||||
REQUIRE(util::create_directories(dir, 0755));
|
|
||||||
}
|
|
||||||
SECTION("a file exists with the same name") {
|
|
||||||
REQUIRE(util::create_directories(dir, 0755));
|
|
||||||
int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT, 0644);
|
|
||||||
REQUIRE(f != -1);
|
|
||||||
close(f);
|
|
||||||
REQUIRE(util::create_directories(dir + "/file", 0755) == false);
|
|
||||||
REQUIRE(util::create_directories(dir + "/file/1/2/3", 0755) == false);
|
|
||||||
}
|
|
||||||
SECTION("end with slashes") {
|
|
||||||
REQUIRE(util::create_directories(dir + "/", 0755));
|
|
||||||
}
|
|
||||||
SECTION("empty") {
|
|
||||||
REQUIRE(util::create_directories("", 0755) == false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#define COMMA_VERSION "0.11.1"
|
|
||||||
-111
@@ -1,111 +0,0 @@
|
|||||||
import contextlib
|
|
||||||
import gc
|
|
||||||
import os
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from openpilot.common.prefix import OpenpilotPrefix
|
|
||||||
from openpilot.system.manager import manager
|
|
||||||
from openpilot.system.hardware import TICI, HARDWARE
|
|
||||||
|
|
||||||
# TODO: pytest-cpp doesn't support FAIL, and we need to create test translations in sessionstart
|
|
||||||
# pending https://github.com/pytest-dev/pytest-cpp/pull/147
|
|
||||||
collect_ignore = [
|
|
||||||
"selfdrive/test/process_replay/test_processes.py",
|
|
||||||
"selfdrive/test/process_replay/test_regen.py",
|
|
||||||
]
|
|
||||||
collect_ignore_glob = [
|
|
||||||
"selfdrive/debug/*.py",
|
|
||||||
"selfdrive/modeld/*.py",
|
|
||||||
"sunnypilot/modeld*/*.py",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_sessionstart(session):
|
|
||||||
# TODO: fix tests and enable test order randomization
|
|
||||||
if session.config.pluginmanager.hasplugin('randomly'):
|
|
||||||
session.config.option.randomly_reorganize = False
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, trylast=True)
|
|
||||||
def pytest_runtest_call(item):
|
|
||||||
# ensure we run as a hook after capturemanager's
|
|
||||||
if item.get_closest_marker("nocapture") is not None:
|
|
||||||
capmanager = item.config.pluginmanager.getplugin("capturemanager")
|
|
||||||
with capmanager.global_and_fixture_disabled():
|
|
||||||
yield
|
|
||||||
else:
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def clean_env():
|
|
||||||
starting_env = dict(os.environ)
|
|
||||||
yield
|
|
||||||
os.environ.clear()
|
|
||||||
os.environ.update(starting_env)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function", autouse=True)
|
|
||||||
def openpilot_function_fixture(request):
|
|
||||||
with clean_env():
|
|
||||||
# setup a clean environment for each test
|
|
||||||
with OpenpilotPrefix(shared_download_cache=request.node.get_closest_marker("shared_download_cache") is not None) as prefix:
|
|
||||||
prefix = os.environ["OPENPILOT_PREFIX"]
|
|
||||||
|
|
||||||
yield
|
|
||||||
|
|
||||||
# ensure the test doesn't change the prefix
|
|
||||||
assert "OPENPILOT_PREFIX" in os.environ and prefix == os.environ["OPENPILOT_PREFIX"]
|
|
||||||
|
|
||||||
# cleanup any started processes
|
|
||||||
manager.manager_cleanup()
|
|
||||||
|
|
||||||
# some processes disable gc for performance, re-enable here
|
|
||||||
if not gc.isenabled():
|
|
||||||
gc.enable()
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
# If you use setUpClass, the environment variables won't be cleared properly,
|
|
||||||
# so we need to hook both the function and class pytest fixtures
|
|
||||||
@pytest.fixture(scope="class", autouse=True)
|
|
||||||
def openpilot_class_fixture():
|
|
||||||
with clean_env():
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
|
||||||
def tici_setup_fixture(request, openpilot_function_fixture):
|
|
||||||
"""Ensure a consistent state for tests on-device. Needs the openpilot function fixture to run first."""
|
|
||||||
if 'skip_tici_setup' in request.keywords:
|
|
||||||
return
|
|
||||||
HARDWARE.initialize_hardware()
|
|
||||||
HARDWARE.set_power_save(False)
|
|
||||||
os.system("pkill -9 -f athena")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(tryfirst=True)
|
|
||||||
def pytest_collection_modifyitems(config, items):
|
|
||||||
skipper = pytest.mark.skip(reason="Skipping tici test on PC")
|
|
||||||
for item in items:
|
|
||||||
if "tici" in item.keywords:
|
|
||||||
if not TICI:
|
|
||||||
item.add_marker(skipper)
|
|
||||||
else:
|
|
||||||
item.fixturenames.append('tici_setup_fixture')
|
|
||||||
|
|
||||||
if "xdist_group_class_property" in item.keywords:
|
|
||||||
class_property_name = item.get_closest_marker('xdist_group_class_property').args[0]
|
|
||||||
class_property_value = getattr(item.cls, class_property_name)
|
|
||||||
item.add_marker(pytest.mark.xdist_group(class_property_value))
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(trylast=True)
|
|
||||||
def pytest_configure(config):
|
|
||||||
config_line = "xdist_group_class_property: group tests by a property of the class that contains them"
|
|
||||||
config.addinivalue_line("markers", config_line)
|
|
||||||
|
|
||||||
config_line = "nocapture: don't capture test output"
|
|
||||||
config.addinivalue_line("markers", config_line)
|
|
||||||
|
|
||||||
config_line = "shared_download_cache: share download cache between tests"
|
|
||||||
config.addinivalue_line("markers", config_line)
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# AI policy
|
||||||
|
|
||||||
|
## Why this exists
|
||||||
|
|
||||||
|
We use AI tools ourselves, so this isn't an anti-AI stance. The problem is people submitting code, issues, or comments they don't actually understand. AI makes that very easy to do, and it creates real work for reviewers who have to figure out what you meant when you can't explain it yourself.
|
||||||
|
|
||||||
|
If you're not going to put effort into understanding and verifying your submission, we're not going to put effort into reviewing it.
|
||||||
|
|
||||||
|
## The rule
|
||||||
|
|
||||||
|
You are responsible for everything you submit: code, PR descriptions, issues, bug reports, comments.
|
||||||
|
|
||||||
|
1. Understand what you submit. If a reviewer asks why you did something, you answer from your own understanding, not by re-prompting. If you can't do that, don't submit it.
|
||||||
|
|
||||||
|
2. Test your change. AI gets things wrong all the time. Run it, break it, confirm it actually works.
|
||||||
|
|
||||||
|
3. Driving fixes need real evidence. Attach a dongle ID, upload logs, and include segments that show the fix working. A route hash by itself proves nothing.
|
||||||
|
|
||||||
|
4. No AI-generated media (images, diagrams, videos) in issues or PRs.
|
||||||
|
|
||||||
|
## Disclosure
|
||||||
|
|
||||||
|
If AI tools helped you write something, say so. Add an `Assisted-by:` line in your commit message:
|
||||||
|
|
||||||
|
```
|
||||||
|
Assisted-by: GitHub Copilot
|
||||||
|
Assisted-by: Claude
|
||||||
|
```
|
||||||
|
|
||||||
|
Disclosing won't count against your PR. It helps reviewers know where to look. Hiding it and getting caught will.
|
||||||
|
|
||||||
|
## How we review
|
||||||
|
|
||||||
|
Reviewers are looking at whether you understand your own change. Can you explain it? Can you respond to feedback without re-prompting? Does your PR description say why you made the change, not just list what changed?
|
||||||
|
|
||||||
|
Good code from someone who used AI and understands what they wrote is fine. How you got there doesn't matter as long as you can stand behind it.
|
||||||
|
|
||||||
|
## What happens
|
||||||
|
|
||||||
|
Submissions that don't meet this bar get closed. If it keeps happening, you get blocked.
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
Maintainers use AI at their discretion. They've earned that through sustained contribution and they know the codebase.
|
||||||
+129
-123
@@ -1,27 +1,27 @@
|
|||||||
<!--- AUTOGENERATED FROM selfdrive/car/CARS_template.md, DO NOT EDIT. --->
|
<!--- AUTOGENERATED FROM openpilot/selfdrive/car/CARS_template.md, DO NOT EDIT. --->
|
||||||
|
|
||||||
# Supported Cars
|
# Supported Cars
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
# 340 Supported Cars
|
# 345 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|
|
|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|
|
||||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Acura ILX 2016-18">Buy Here</a></sub></details>|||
|
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Acura ILX 2016-18">Buy Here</a></sub></details>|||
|
||||||
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Acura ILX 2019">Buy Here</a></sub></details>|||
|
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Acura ILX 2019">Buy Here</a></sub></details>|||
|
||||||
|Acura|MDX 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Acura MDX 2022-24">Buy Here</a></sub></details>|||
|
|Acura|MDX 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Acura MDX 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Acura|MDX 2025-26|All except Type S|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Acura MDX 2025-26">Buy Here</a></sub></details>|||
|
|Acura|MDX 2025-26|All except Type S|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Acura MDX 2025-26">Buy Here</a></sub></details>|||
|
||||||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|
||||||
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|
|Acura|RDX 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|
||||||
|Acura|TLX 2021-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Acura TLX 2021-22">Buy Here</a></sub></details>|||
|
|Acura|TLX 2021-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Acura TLX 2021-22">Buy Here</a></sub></details>|||
|
||||||
|Acura|TLX 2025|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Acura TLX 2025">Buy Here</a></sub></details>|||
|
|Acura|TLX 2025|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Acura TLX 2025">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>11</sup>](#footnotes)|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi A3 2014-19">Buy Here</a></sub></details>|||
|
|Audi[<sup>12</sup>](#footnotes)|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi A3 2014-19">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>11</sup>](#footnotes)|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>|||
|
|Audi[<sup>12</sup>](#footnotes)|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>11</sup>](#footnotes)|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi Q2 2018">Buy Here</a></sub></details>|||
|
|Audi[<sup>12</sup>](#footnotes)|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi Q2 2018">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>11</sup>](#footnotes)|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi Q3 2019-24">Buy Here</a></sub></details>|||
|
|Audi[<sup>12</sup>](#footnotes)|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi Q3 2019-24">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>11</sup>](#footnotes)|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi RS3 2018">Buy Here</a></sub></details>|||
|
|Audi[<sup>12</sup>](#footnotes)|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi RS3 2018">Buy Here</a></sub></details>|||
|
||||||
|Audi[<sup>11</sup>](#footnotes)|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi S3 2015-17">Buy Here</a></sub></details>|||
|
|Audi[<sup>12</sup>](#footnotes)|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Audi S3 2015-17">Buy Here</a></sub></details>|||
|
||||||
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EUV 2022-23">Buy Here</a></sub></details>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EUV 2022-23">Buy Here</a></sub></details>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV 2022-23">Buy Here</a></sub></details>|||
|
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Equinox 2019-22">Buy Here</a></sub></details>|||
|
|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Equinox 2019-22">Buy Here</a></sub></details>|||
|
||||||
@@ -33,7 +33,8 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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=Chrysler Pacifica Hybrid 2017-18">Buy Here</a></sub></details>|||
|
|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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=Chrysler Pacifica Hybrid 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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=Chrysler Pacifica Hybrid 2019-25">Buy Here</a></sub></details>|||
|
|Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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=Chrysler Pacifica Hybrid 2019-25">Buy Here</a></sub></details>|||
|
||||||
|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None|<a href="https://youtu.be/VT-i3yRsX2s?t=2736" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None|<a href="https://youtu.be/VT-i3yRsX2s?t=2736" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|CUPRA[<sup>11</sup>](#footnotes)|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=CUPRA Ateca 2018-23">Buy Here</a></sub></details>|||
|
|CUPRA[<sup>12</sup>](#footnotes)|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=CUPRA Ateca 2018-23">Buy Here</a></sub></details>|||
|
||||||
|
|CUPRA[<sup>12</sup>](#footnotes)|Born 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot[<sup>16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=CUPRA Born 2021-23">Buy Here</a></sub></details>|||
|
||||||
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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=Dodge Durango 2020-21">Buy Here</a></sub></details>|||
|
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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=Dodge Durango 2020-21">Buy Here</a></sub></details>|||
|
||||||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Bronco Sport 2021-24">Buy Here</a></sub></details>|||
|
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Bronco Sport 2021-24">Buy Here</a></sub></details>|||
|
||||||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape 2020-22">Buy Here</a></sub></details>|||
|
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape 2020-22">Buy Here</a></sub></details>|||
|
||||||
@@ -47,8 +48,8 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Explorer Hybrid 2020-24">Buy Here</a></sub></details>|||
|
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Explorer Hybrid 2020-24">Buy Here</a></sub></details>|||
|
||||||
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Ford F-150 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Ford F-150 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
||||||
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Ford F-150 Hybrid 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Ford F-150 Hybrid 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
||||||
|Ford|Focus 2018[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus 2018">Buy Here</a></sub></details>|||
|
|Ford|Focus 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus 2018-22">Buy Here</a></sub></details>|||
|
||||||
|Ford|Focus Hybrid 2018[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus Hybrid 2018">Buy Here</a></sub></details>|||
|
|Ford|Focus Hybrid 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus Hybrid 2018-22">Buy Here</a></sub></details>|||
|
||||||
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga 2020-23">Buy Here</a></sub></details>|||
|
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga Hybrid 2020-23">Buy Here</a></sub></details>|||
|
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga Hybrid 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Ford|Kuga Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Ford Kuga Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Ford|Kuga Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Ford Kuga Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
||||||
@@ -75,35 +76,35 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Genesis|GV70 Electrified (with HDA II) 2023-24|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q 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=Genesis GV70 Electrified (with HDA II) 2023-24">Buy Here</a></sub></details>|||
|
|Genesis|GV70 Electrified (with HDA II) 2023-24|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q 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=Genesis GV70 Electrified (with HDA II) 2023-24">Buy Here</a></sub></details>|||
|
||||||
|Genesis|GV80 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M 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=Genesis GV80 2023">Buy Here</a></sub></details>|||
|
|Genesis|GV80 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M 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=Genesis GV80 2023">Buy Here</a></sub></details>|||
|
||||||
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=GMC Sierra 1500 2020-21">Buy Here</a></sub></details>|<a href="https://youtu.be/5HbNoBLzRwE" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=GMC Sierra 1500 2020-21">Buy Here</a></sub></details>|<a href="https://youtu.be/5HbNoBLzRwE" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Honda|Accord 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Honda|Accord 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Honda|Accord 2023-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|
|Honda|Accord 2023-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|
||||||
|Honda|Accord Hybrid 2023-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|
|Honda|Accord Hybrid 2023-26|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Accord Hybrid 2023-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|
|Honda|City (Brazil only) 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda City (Brazil only) 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Honda|Civic 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Honda|Civic 2022-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic 2022-26">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hatchback Hybrid 2025-26">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hatchback Hybrid 2025-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hybrid 2025-26">Buy Here</a></sub></details>|||
|
|Honda|Civic Hybrid 2025-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hybrid 2025-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|15 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|15 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V 2023-26|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda CR-V 2023-26">Buy Here</a></sub></details>|||
|
|Honda|CR-V 2023-26|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda CR-V 2023-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V Hybrid 2023-26|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda CR-V Hybrid 2023-26">Buy Here</a></sub></details>|||
|
|Honda|CR-V Hybrid 2023-26|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda CR-V Hybrid 2023-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|e 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda e 2020">Buy Here</a></sub></details>|||
|
|Honda|e 2020|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda e 2020">Buy Here</a></sub></details>|||
|
||||||
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|
||||||
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Freed 2020">Buy Here</a></sub></details>|||
|
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Freed 2020">Buy Here</a></sub></details>|||
|
||||||
|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda HR-V 2019-22">Buy Here</a></sub></details>|||
|
|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda HR-V 2019-22">Buy Here</a></sub></details>|||
|
||||||
|Honda|HR-V 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|
|Honda|HR-V 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|Insight 2019-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|
|Honda|Insight 2019-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|
||||||
|Honda|Inspire 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Inspire 2018">Buy Here</a></sub></details>|||
|
|Honda|Inspire 2018|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Inspire 2018">Buy Here</a></sub></details>|||
|
||||||
|Honda|N-Box 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|11 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda N-Box 2018">Buy Here</a></sub></details>|||
|
|Honda|N-Box 2018|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|11 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda N-Box 2018">Buy Here</a></sub></details>|||
|
||||||
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|
||||||
|Honda|Odyssey 2021-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Odyssey 2021-26">Buy Here</a></sub></details>|||
|
|Honda|Odyssey 2021-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|43 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A 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=Honda Odyssey 2021-26">Buy Here</a></sub></details>|||
|
||||||
|Honda|Odyssey (Singapore) 2021|Honda Sensing|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Odyssey (Singapore) 2021">Buy Here</a></sub></details>|||
|
|Honda|Odyssey (Singapore) 2021|Honda Sensing|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Odyssey (Singapore) 2021">Buy Here</a></sub></details>|||
|
||||||
|Honda|Odyssey (Taiwan) 2018-19|Honda Sensing|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Odyssey (Taiwan) 2018-19">Buy Here</a></sub></details>|||
|
|Honda|Odyssey (Taiwan) 2018-19|Honda Sensing|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Odyssey (Taiwan) 2018-19">Buy Here</a></sub></details>|||
|
||||||
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|
||||||
@@ -126,6 +127,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Hyundai|Ioniq 5 (with HDA II) 2022-24|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q 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 Ioniq 5 (with HDA II) 2022-24">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq 5 (with HDA II) 2022-24|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q 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 Ioniq 5 (with HDA II) 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq 5 (without HDA II) 2022-24|Highway Driving Assist|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 Ioniq 5 (without HDA II) 2022-24">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq 5 (without HDA II) 2022-24|Highway Driving Assist|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 Ioniq 5 (without HDA II) 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq 6 (with HDA II) 2023-24|Highway Driving Assist II|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P 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 Ioniq 6 (with HDA II) 2023-24">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq 6 (with HDA II) 2023-24|Highway Driving Assist II|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P 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 Ioniq 6 (with HDA II) 2023-24">Buy Here</a></sub></details>|||
|
||||||
|
|Hyundai|Ioniq 6 (without HDA II) 2023-24|Highway Driving Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L 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 Ioniq 6 (without HDA II) 2023-24">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 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 Ioniq Electric 2019">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 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 Ioniq Electric 2019">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq Electric 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H 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 Ioniq Electric 2020">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq Electric 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H 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 Ioniq Electric 2020">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 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 Ioniq Hybrid 2017-19">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 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 Ioniq Hybrid 2017-19">Buy Here</a></sub></details>|||
|
||||||
@@ -157,7 +159,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N 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 Tucson Plug-in Hybrid 2024">Buy Here</a></sub></details>|||
|
|Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N 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 Tucson Plug-in Hybrid 2024">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E 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 Veloster 2019-20">Buy Here</a></sub></details>|||
|
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E 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 Veloster 2019-20">Buy Here</a></sub></details>|||
|
||||||
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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=Jeep Grand Cherokee 2016-18">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=eLR9o2JkuRk" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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=Jeep Grand Cherokee 2016-18">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=eLR9o2JkuRk" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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=Jeep Grand Cherokee 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=jBe4lWnRSu4" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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=Jeep Grand Cherokee 2019-21">Buy Here</a></sub></details>|||
|
||||||
|Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A 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=Kia Carnival 2022-24">Buy Here</a></sub></details>|||
|
|Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A 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=Kia Carnival 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 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=Kia Carnival (China only) 2023">Buy Here</a></sub></details>|||
|
|Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 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=Kia Carnival (China only) 2023">Buy Here</a></sub></details>|||
|
||||||
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E 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=Kia Ceed 2019-21">Buy Here</a></sub></details>|||
|
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E 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=Kia Ceed 2019-21">Buy Here</a></sub></details>|||
|
||||||
@@ -186,7 +188,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F 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=Kia Niro Plug-in Hybrid 2022">Buy Here</a></sub></details>|||
|
|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F 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=Kia Niro Plug-in Hybrid 2022">Buy Here</a></sub></details>|||
|
||||||
|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 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=Kia Optima 2017">Buy Here</a></sub></details>|||
|
|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 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=Kia Optima 2017">Buy Here</a></sub></details>|||
|
||||||
|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 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=Kia Optima 2019-20">Buy Here</a></sub></details>|||
|
|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 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=Kia Optima 2019-20">Buy Here</a></sub></details>|||
|
||||||
|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H 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=Kia Optima Hybrid 2019">Buy Here</a></sub></details>|||
|
|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H 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=Kia Optima Hybrid 2019">Buy Here</a></sub></details>|||
|
||||||
|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A 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=Kia Seltos 2021">Buy Here</a></sub></details>|||
|
|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A 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=Kia Seltos 2021">Buy Here</a></sub></details>|||
|
||||||
|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E 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=Kia Sorento 2018">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E 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=Kia Sorento 2018">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E 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=Kia Sorento 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E 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=Kia Sorento 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
@@ -223,50 +225,51 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Lexus UX Hybrid 2019-24">Buy Here</a></sub></details>|||
|
|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Lexus UX Hybrid 2019-24">Buy Here</a></sub></details>|||
|
||||||
|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator 2020-24">Buy Here</a></sub></details>|||
|
|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator 2020-24">Buy Here</a></sub></details>|||
|
||||||
|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator Plug-in Hybrid 2020-24">Buy Here</a></sub></details>|||
|
|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator Plug-in Hybrid 2020-24">Buy Here</a></sub></details>|||
|
||||||
|MAN[<sup>11</sup>](#footnotes)|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=MAN eTGE 2020-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|MAN[<sup>12</sup>](#footnotes)|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=MAN eTGE 2020-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|MAN[<sup>11</sup>](#footnotes)|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=MAN TGE 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|MAN[<sup>12</sup>](#footnotes)|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=MAN TGE 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda 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=Mazda CX-5 2022-25">Buy Here</a></sub></details>|||
|
|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda 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=Mazda CX-5 2022-25">Buy Here</a></sub></details>|||
|
||||||
|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda 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=Mazda CX-9 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/dA3duO4a0O4" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda 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=Mazda CX-9 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/dA3duO4a0O4" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Nissan[<sup>5</sup>](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Nissan Altima 2019-20, 2024">Buy Here</a></sub></details>|||
|
|Nissan[<sup>6</sup>](#footnotes)|Altima 2019-24|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Nissan Altima 2019-24">Buy Here</a></sub></details>|||
|
||||||
|Nissan[<sup>5</sup>](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Nissan Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Nissan[<sup>6</sup>](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Nissan Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Nissan[<sup>5</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|
|Nissan[<sup>6</sup>](#footnotes)|Leaf IC 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Nissan Leaf IC 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Nissan[<sup>5</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|
|Nissan[<sup>6</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|
||||||
|
|Nissan[<sup>6</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<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=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|
||||||
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram 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=Ram 1500 2019-24">Buy Here</a></sub></details>|||
|
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram 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=Ram 1500 2019-24">Buy Here</a></sub></details>|||
|
||||||
|Ram|2500 2020-24|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram 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=Ram 2500 2020-24">Buy Here</a></sub></details>|||
|
|Ram|2500 2020-24|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram 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=Ram 2500 2020-24">Buy Here</a></sub></details>|||
|
||||||
|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram 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=Ram 3500 2019-22">Buy Here</a></sub></details>|||
|
|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Ram 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=Ram 3500 2019-22">Buy Here</a></sub></details>|||
|
||||||
|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<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=Rivian R1S 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<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=Rivian R1S 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/dflSSGQwYNc" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
||||||
|Rivian|R1S 2025|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<br>- 1 comma four<br>- 1 comma power v3<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=Rivian R1S 2025">Buy Here</a></sub></details>|||
|
|Rivian|R1S 2025|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<br>- 1 comma four<br>- 1 comma power v3<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=Rivian R1S 2025">Buy Here</a></sub></details>|||
|
||||||
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<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=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<br>- 1 comma four<br>- 1 comma power v3<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=Rivian R1T 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/dflSSGQwYNc" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>|
|
||||||
|Rivian|R1T 2025|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<br>- 1 comma four<br>- 1 comma power v3<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=Rivian R1T 2025">Buy Here</a></sub></details>|||
|
|Rivian|R1T 2025|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<br>- 1 comma four<br>- 1 comma power v3<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=Rivian R1T 2025">Buy Here</a></sub></details>|||
|
||||||
|SEAT[<sup>11</sup>](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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[<sup>12</sup>](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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[<sup>11</sup>](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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>|||
|
|SEAT[<sup>12</sup>](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 2019-21|All[<sup>7</sup>](#footnotes)|Stock|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|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" /></a>||
|
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|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" /></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 2020-23|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|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|Forester 2017-18|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|Stock|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 2017-18">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 2017-18|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|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 2017-18">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 2019-21|All[<sup>7</sup>](#footnotes)|Stock|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|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 2017-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|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|Impreza 2020-22|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|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 2015-18|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|Stock|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 Legacy 2015-18">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 2015-18|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|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 Legacy 2015-18">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|Legacy 2020-22|All[<sup>7</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 2015-17|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|Stock|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 Outback 2015-17">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 2015-17|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|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 Outback 2015-17">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 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|Stock|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 Outback 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>|||
|
|Subaru|Outback 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|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 Outback 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>|||
|
||||||
|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 2020-22|All[<sup>7</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|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" /></a>||
|
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|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" /></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>|||
|
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|Stock|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>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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>16</sup>](#footnotes)|||
|
|Škoda|Fabia 2022-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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>17</sup>](#footnotes)|||
|
||||||
|Škoda|Kamiq 2021-23[<sup>12,14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Kamiq 2021-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
|Škoda|Kamiq 2021-23[<sup>13,15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Kamiq 2021-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
||||||
|Škoda[<sup>11</sup>](#footnotes)|Karoq 2019-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Karoq 2019-23">Buy Here</a></sub></details>|||
|
|Škoda[<sup>12</sup>](#footnotes)|Karoq 2019-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Karoq 2019-23">Buy Here</a></sub></details>|||
|
||||||
|Škoda[<sup>11</sup>](#footnotes)|Kodiaq 2017-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Kodiaq 2017-23">Buy Here</a></sub></details>|||
|
|Škoda[<sup>12</sup>](#footnotes)|Kodiaq 2017-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Kodiaq 2017-23">Buy Here</a></sub></details>|||
|
||||||
|Škoda[<sup>11</sup>](#footnotes)|Octavia 2015-19[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Octavia 2015-19">Buy Here</a></sub></details>|||
|
|Škoda[<sup>12</sup>](#footnotes)|Octavia 2015-19[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Octavia 2015-19">Buy Here</a></sub></details>|||
|
||||||
|Škoda[<sup>11</sup>](#footnotes)|Octavia RS 2016[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Octavia RS 2016">Buy Here</a></sub></details>|||
|
|Škoda[<sup>12</sup>](#footnotes)|Octavia RS 2016[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Octavia RS 2016">Buy Here</a></sub></details>|||
|
||||||
|Škoda[<sup>11</sup>](#footnotes)|Octavia Scout 2017-19[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Octavia Scout 2017-19">Buy Here</a></sub></details>|||
|
|Škoda[<sup>12</sup>](#footnotes)|Octavia Scout 2017-19[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Octavia Scout 2017-19">Buy Here</a></sub></details>|||
|
||||||
|Škoda|Scala 2020-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Scala 2020-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
|Škoda|Scala 2020-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Scala 2020-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
||||||
|Škoda[<sup>11</sup>](#footnotes)|Superb 2015-22[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Superb 2015-22">Buy Here</a></sub></details>|||
|
|Škoda[<sup>12</sup>](#footnotes)|Superb 2015-22[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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 Superb 2015-22">Buy Here</a></sub></details>|||
|
||||||
|Tesla[<sup>9</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A 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=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|
|Tesla[<sup>10</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A 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=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|
||||||
|Tesla[<sup>9</sup>](#footnotes)|Model 3 (with HW4) 2024-25[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B 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=Tesla Model 3 (with HW4) 2024-25">Buy Here</a></sub></details>|||
|
|Tesla[<sup>10</sup>](#footnotes)|Model 3 (with HW4) 2024-25[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B 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=Tesla Model 3 (with HW4) 2024-25">Buy Here</a></sub></details>|||
|
||||||
|Tesla[<sup>9</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A 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=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A 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=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|
||||||
|Tesla[<sup>9</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B 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=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>|||
|
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B 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=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Alphard 2019-20">Buy Here</a></sub></details>|||
|
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Alphard 2019-20">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Alphard Hybrid 2021">Buy Here</a></sub></details>|||
|
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Alphard Hybrid 2021">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Avalon 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Avalon 2016">Buy Here</a></sub></details>|||
|
|Toyota|Avalon 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Avalon 2016">Buy Here</a></sub></details>|||
|
||||||
@@ -279,8 +282,8 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Toyota|C-HR 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota C-HR 2021">Buy Here</a></sub></details>|||
|
|Toyota|C-HR 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota C-HR 2021">Buy Here</a></sub></details>|||
|
||||||
|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota C-HR Hybrid 2017-20">Buy Here</a></sub></details>|||
|
|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota C-HR Hybrid 2017-20">Buy Here</a></sub></details>|||
|
||||||
|Toyota|C-HR Hybrid 2021-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota C-HR Hybrid 2021-22">Buy Here</a></sub></details>|||
|
|Toyota|C-HR Hybrid 2021-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota C-HR Hybrid 2021-22">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Camry 2018-20|All|Stock|0 mph[<sup>10</sup>](#footnotes)|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Camry 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=fkcjviZY9CM" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Camry 2018-20|All|Stock|0 mph[<sup>11</sup>](#footnotes)|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Camry 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=fkcjviZY9CM" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Toyota|Camry 2021-24|All|openpilot|0 mph[<sup>10</sup>](#footnotes)|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Camry 2021-24">Buy Here</a></sub></details>|||
|
|Toyota|Camry 2021-24|All|openpilot|0 mph[<sup>11</sup>](#footnotes)|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Camry 2021-24">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Camry Hybrid 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Q2DYY0AWKgk" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Camry Hybrid 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Q2DYY0AWKgk" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Camry Hybrid 2021-24">Buy Here</a></sub></details>|||
|
|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Camry Hybrid 2021-24">Buy Here</a></sub></details>|||
|
||||||
|Toyota|Corolla 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Corolla 2017-19">Buy Here</a></sub></details>|||
|
|Toyota|Corolla 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Corolla 2017-19">Buy Here</a></sub></details>|||
|
||||||
@@ -312,60 +315,63 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Toyota|Sienna 2018-20|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Toyota|Sienna 2018-20|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota 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=Toyota Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Arteon Shooting Brake 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Arteon Shooting Brake 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Atlas 2018-23">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Atlas 2018-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Atlas Cross Sport 2020-22">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Atlas Cross Sport 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen California 2021-23">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen California 2021-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Caravelle 2020">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Caravelle 2020">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen CC 2018-22">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen[<sup>12</sup>](#footnotes)|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen CC 2018-22">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Crafter 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Crafter 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen e-Crafter 2018-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen[<sup>12</sup>](#footnotes)|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen e-Crafter 2018-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen e-Golf 2014-20">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen e-Golf 2014-20">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf 2015-20">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf 2015-20">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf Alltrack 2015-19">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf Alltrack 2015-19">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf GTD 2015-20">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf GTD 2015-20">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf GTE 2015-20">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf GTE 2015-20">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf GTI 2015-21">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf GTI 2015-21">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf R 2015-19">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf R 2015-19">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf SportsVan 2015-20">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Golf SportsVan 2015-20">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Jetta 2019-23">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|ID.4 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot[<sup>16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen ID.4 2021-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Jetta GLI 2021-23">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|ID.4 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot[<sup>16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen ID.4 2024-25">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen|Passat 2015-22[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Passat 2015-22">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Jetta 2019-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Passat Alltrack 2015-22">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Jetta GLI 2021-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Passat GTE 2015-22">Buy Here</a></sub></details>|||
|
|Volkswagen|Passat 2015-22[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Passat 2015-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Polo 2018-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Passat Alltrack 2015-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Polo GTI 2018-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Passat GTE 2015-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen T-Cross 2021">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|
|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Polo 2018-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen T-Roc 2018-23">Buy Here</a></sub></details>|||
|
|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Polo GTI 2018-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Taos 2022-24">Buy Here</a></sub></details>|||
|
|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen T-Cross 2021">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Teramont 2018-22">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen T-Roc 2018-23">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Teramont Cross Sport 2021-22">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Taos 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Teramont X 2021-22">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Teramont 2018-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Tiguan 2018-24">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Teramont Cross Sport 2021-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Tiguan eHybrid 2021-23">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Teramont X 2021-22">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen[<sup>11</sup>](#footnotes)|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Touran 2016-23">Buy Here</a></sub></details>|||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Tiguan 2018-24">Buy Here</a></sub></details>|||
|
||||||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Tiguan eHybrid 2021-23">Buy Here</a></sub></details>|||
|
||||||
|
|Volkswagen[<sup>12</sup>](#footnotes)|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<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=Volkswagen Touran 2016-23">Buy Here</a></sub></details>|||
|
||||||
|
|
||||||
### Footnotes
|
### Footnotes
|
||||||
<sup>1</sup>openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `nightly-dev`. <br />
|
<sup>1</sup>openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `nightly-dev`. <br />
|
||||||
<sup>2</sup>Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia. <br />
|
<sup>2</sup>Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia. <br />
|
||||||
<sup>3</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/gm" target="_blank">GM</a>. <br />
|
<sup>3</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/gm" target="_blank">GM</a>. <br />
|
||||||
<sup>4</sup>2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph. <br />
|
<sup>4</sup>2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph. <br />
|
||||||
<sup>5</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/nissan" target="_blank">Nissan</a>. <br />
|
<sup>5</sup>Enabling longitudinal control (alpha) will disable all CMBS functionality, including AEB and FCW. <br />
|
||||||
<sup>6</sup>In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance. <br />
|
<sup>6</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/nissan" target="_blank">Nissan</a>. <br />
|
||||||
<sup>7</sup>Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB. <br />
|
<sup>7</sup>In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance. <br />
|
||||||
<sup>8</sup>Some 2023 model years have HW4. To check which hardware type your vehicle has, look for <b>Autopilot computer</b> under <b>Software -> Additional Vehicle Information</b> on your vehicle's touchscreen. See <a href="https://www.notateslaapp.com/news/2173/how-to-check-if-your-tesla-has-hardware-4-ai4-or-hardware-3">this page</a> for more information. <br />
|
<sup>8</sup>Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB. <br />
|
||||||
<sup>9</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/tesla" target="_blank">Tesla</a>. <br />
|
<sup>9</sup>Some 2023 model years have HW4. To check which hardware type your vehicle has, look for <b>Autopilot computer</b> under <b>Software -> Additional Vehicle Information</b> on your vehicle's touchscreen. See <a href="https://www.notateslaapp.com/news/2173/how-to-check-if-your-tesla-has-hardware-4-ai4-or-hardware-3">this page</a> for more information. <br />
|
||||||
<sup>10</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
|
<sup>10</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/tesla" target="_blank">Tesla</a>. <br />
|
||||||
<sup>11</sup>The J533 harness plugs in at the CAN gateway under the dashboard, just above the steering column. More information can be found at <a href="https://docs.howtocomma.com/docs/j533-harness-install" target="_blank">this guide</a>. <br />
|
<sup>11</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
|
||||||
<sup>12</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
|
<sup>12</sup>The J533 harness plugs in at the CAN gateway under the dashboard, just above the steering column. More information can be found at <a href="https://docs.howtocomma.com/docs/j533-harness-install" target="_blank">this guide</a>. <br />
|
||||||
<sup>13</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br />
|
<sup>13</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
|
||||||
<sup>14</sup>Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality. <br />
|
<sup>14</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br />
|
||||||
<sup>15</sup>Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC. <br />
|
<sup>15</sup>Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality. <br />
|
||||||
<sup>16</sup>Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store. <br />
|
<sup>16</sup>Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC. <br />
|
||||||
|
<sup>17</sup>Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store. <br />
|
||||||
|
|
||||||
## Community Maintained Cars
|
## Community Maintained Cars
|
||||||
Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).
|
Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
> sunnypilot follows [commaai/openpilot](https://github.com/commaai/openpilot)'s contributing guidelines. The following applies to all contributions here.
|
||||||
|
|
||||||
# How to contribute
|
# How to contribute
|
||||||
|
|
||||||
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/).
|
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/).
|
||||||
@@ -35,6 +37,7 @@ All of these are examples of good PRs:
|
|||||||
* **UI design**: we do not have a good review process for this yet
|
* **UI design**: we do not have a good review process for this yet
|
||||||
* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't.
|
* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't.
|
||||||
* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged.
|
* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged.
|
||||||
|
* **AI-generated contributions**: see our [AI policy](AI_POLICY.md)
|
||||||
|
|
||||||
### First contribution
|
### First contribution
|
||||||
|
|
||||||
@@ -58,7 +61,7 @@ A good pull request has all of the following:
|
|||||||
|
|
||||||
* Report bugs in GitHub issues.
|
* Report bugs in GitHub issues.
|
||||||
* Report driving issues in the `#driving-feedback` Discord channel.
|
* Report driving issues in the `#driving-feedback` Discord channel.
|
||||||
* Consider opting into driver camera uploads to improve the driver monitoring model.
|
* Consider opting into cabin camera uploads to improve the driver monitoring model.
|
||||||
* Connect your device to Wi-Fi regularly, so that we can pull data for training better driving models.
|
* Connect your device to Wi-Fi regularly, so that we can pull data for training better driving models.
|
||||||
* Run the `nightly` branch and report issues. This branch is like `master` but it's built just like a release.
|
* Run the `nightly` branch and report issues. This branch is like `master` but it's built just like a release.
|
||||||
* Annotate images in the [comma10k dataset](https://github.com/commaai/comma10k).
|
* Annotate images in the [comma10k dataset](https://github.com/commaai/comma10k).
|
||||||
@@ -68,6 +71,6 @@ A good pull request has all of the following:
|
|||||||
### A guide for forks
|
### A guide for forks
|
||||||
|
|
||||||
In order for your fork's data to be eligible for the training set:
|
In order for your fork's data to be eligible for the training set:
|
||||||
* **Your cereal messaging structs must be [compatible](../cereal#custom-forks)**
|
* **Your cereal messaging structs must be [compatible](../openpilot/cereal#custom-forks)**
|
||||||
* **The definitions of all the stock messaging structs must not change**: Do not change how any of the fields are set, including everything from `selfdriveState.enabled` to `carState.steeringAngleDeg`. Instead, create your own structs and set them however you'd like.
|
* **The definitions of all the stock messaging structs must not change**: Do not change how any of the fields are set, including everything from `selfdriveState.enabled` to `carState.steeringAngleDeg`. Instead, create your own structs and set them however you'd like.
|
||||||
* **Do not include cars that are not supported in upstream platforms**: Instead, create new opendbc platforms for cars that you'd like to support outside of upstream, even if it's just a trim-level difference.
|
* **Do not include cars that are not supported in upstream platforms**: Instead, create new opendbc platforms for cars that you'd like to support outside of upstream, even if it's just a trim-level difference.
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# Docs development
|
|
||||||
|
|
||||||
The `docs/` tree is the source for [docs.comma.ai](https://docs.comma.ai).
|
|
||||||
The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml).
|
|
||||||
|
|
||||||
Those commands must be run in the root directory of openpilot, **not /docs**
|
|
||||||
|
|
||||||
**1. Install the docs dependencies**
|
|
||||||
``` bash
|
|
||||||
uv pip install .[docs]
|
|
||||||
```
|
|
||||||
|
|
||||||
**2. Build the new site**
|
|
||||||
``` bash
|
|
||||||
docs build
|
|
||||||
```
|
|
||||||
|
|
||||||
**3. Run the new site locally**
|
|
||||||
``` bash
|
|
||||||
docs serve
|
|
||||||
```
|
|
||||||
|
|
||||||
References:
|
|
||||||
* https://zensical.org/docs/
|
|
||||||
+2
-2
@@ -52,7 +52,7 @@ Many factors can impact the performance of openpilot DM, causing it to be unable
|
|||||||
|
|
||||||
* Low light conditions, such as driving at night or in dark tunnels.
|
* Low light conditions, such as driving at night or in dark tunnels.
|
||||||
* Bright light (due to oncoming headlights, direct sunlight, etc.).
|
* Bright light (due to oncoming headlights, direct sunlight, etc.).
|
||||||
* The driver's face is partially or completely outside field of view of the driver facing camera.
|
* The driver's face is partially or completely outside field of view of the cabin camera.
|
||||||
* The driver facing camera is obstructed, covered, or damaged.
|
* The cabin camera is obstructed, covered, or damaged.
|
||||||
|
|
||||||
The list above does not represent an exhaustive list of situations that may interfere with proper operation of openpilot components. A driver should not rely on openpilot DM to assess their level of attention.
|
The list above does not represent an exhaustive list of situations that may interfere with proper operation of openpilot components. A driver should not rely on openpilot DM to assess their level of attention.
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Docs development
|
||||||
|
|
||||||
|
The `docs/` tree is the source for [docs.comma.ai](https://docs.comma.ai).
|
||||||
|
The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml).
|
||||||
|
|
||||||
|
**1. Build the site**
|
||||||
|
``` bash
|
||||||
|
python docs/serve.py --build
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Run the site locally** (rebuilds on change)
|
||||||
|
``` bash
|
||||||
|
python docs/serve.py
|
||||||
|
```
|
||||||
+4
-4
@@ -4,8 +4,8 @@ openpilot is an Adaptive Cruise Control (ACC) and Automated Lane Centering (ALC)
|
|||||||
Like other ACC and ALC systems, openpilot is a failsafe passive system and it requires the
|
Like other ACC and ALC systems, openpilot is a failsafe passive system and it requires the
|
||||||
driver to be alert and to pay attention at all times.
|
driver to be alert and to pay attention at all times.
|
||||||
|
|
||||||
In order to enforce driver alertness, openpilot includes a driver monitoring feature
|
To assist the driver in maintaining alertness, openpilot includes a driver monitoring feature
|
||||||
that alerts the driver when distracted.
|
that alerts when it detects driver distraction.
|
||||||
|
|
||||||
However, even with an attentive driver, we must make further efforts for the system to be
|
However, even with an attentive driver, we must make further efforts for the system to be
|
||||||
safe. We repeat, **driver alertness is necessary, but not sufficient, for openpilot to be
|
safe. We repeat, **driver alertness is necessary, but not sufficient, for openpilot to be
|
||||||
@@ -35,8 +35,8 @@ For additional safety implementation details, refer to [panda safety model](http
|
|||||||
|
|
||||||
### Forks of openpilot
|
### Forks of openpilot
|
||||||
|
|
||||||
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/selfdrive/monitoring)
|
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/openpilot/selfdrive/monitoring)
|
||||||
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/selfdrive/selfdrived/helpers.py)
|
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/openpilot/selfdrive/selfdrived/helpers.py)
|
||||||
* If your fork modifies any of the code in `opendbc/safety/`:
|
* If your fork modifies any of the code in `opendbc/safety/`:
|
||||||
* your fork cannot use the openpilot trademark
|
* your fork cannot use the openpilot trademark
|
||||||
* your fork must preserve the full [safety test suite](https://github.com/commaai/opendbc/tree/master/opendbc/safety/tests) and all tests must pass, including any new coverage required by the fork's changes
|
* your fork must preserve the full [safety test suite](https://github.com/commaai/opendbc/tree/master/opendbc/safety/tests) and all tests must pass, including any new coverage required by the fork's changes
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
../../selfdrive/assets/icons_mici/settings/comma_icon.png
|
../../openpilot/selfdrive/assets/icons_mici/settings/comma_icon.png
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9e019ed5af500e8820d05934d47e0380728e1b30e01a179f786dc4edb1eccbd7
|
||||||
|
size 349
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# openpilot glossary
|
|
||||||
|
|
||||||
{{GLOSSARY_DEFINITIONS}}
|
|
||||||
@@ -2,25 +2,25 @@
|
|||||||
|
|
||||||
openpilot records routes in one minute chunks called segments. A route starts on the rising edge of ignition and ends on the falling edge.
|
openpilot records routes in one minute chunks called segments. A route starts on the rising edge of ignition and ends on the falling edge.
|
||||||
|
|
||||||
Check out our [Python library](https://github.com/commaai/openpilot/blob/master/tools/lib/logreader.py) for reading openpilot logs. Also checkout our [tools](https://github.com/commaai/openpilot/tree/master/tools) to replay and view your data. These are the same tools we use to debug and develop openpilot.
|
Check out our [Python library](https://github.com/commaai/openpilot/blob/master/openpilot/tools/lib/logreader.py) for reading openpilot logs. Also checkout our [tools](https://github.com/commaai/openpilot/tree/master/openpilot/tools) to replay and view your data. These are the same tools we use to debug and develop openpilot.
|
||||||
|
|
||||||
For each segment, openpilot records the following log types:
|
For each segment, openpilot records the following log types:
|
||||||
|
|
||||||
## rlog.zst
|
## rlog.zst
|
||||||
|
|
||||||
rlogs contain all the messages passed amongst openpilot's processes. See [cereal/services.py](https://github.com/commaai/openpilot/blob/master/cereal/services.py) for a list of all the logged services. They're a zstd archive of the serialized [Cap’n Proto](https://capnproto.org/) messages.
|
rlogs contain all the messages passed amongst openpilot's processes. See [openpilot/cereal/services.py](https://github.com/commaai/openpilot/blob/master/openpilot/cereal/services.py) for a list of all the logged services. They're a zstd archive of the serialized [Cap’n Proto](https://capnproto.org/) messages.
|
||||||
|
|
||||||
## {f,e,d}camera.hevc
|
## camera video files
|
||||||
|
|
||||||
Each camera stream is H.265 encoded and written to its respective file.
|
Each camera stream is H.265 encoded and written to its respective file.
|
||||||
|
|
||||||
* `fcamera.hevc` is the road camera
|
* `fcamera.hevc` is the narrow road camera (the main forward camera)
|
||||||
* `ecamera.hevc` is the wide road camera
|
* `ecamera.hevc` is the wide road camera
|
||||||
* `dcamera.hevc` is the driver camera
|
* `dcamera.hevc` is the cabin camera
|
||||||
|
|
||||||
## qlog.zst & qcamera.ts
|
## qlog.zst & qcamera.ts
|
||||||
|
|
||||||
qlogs are a decimated subset of the rlogs. Check out [cereal/services.py](https://github.com/commaai/cereal/blob/master/services.py) for the decimation.
|
qlogs are a decimated subset of the rlogs. Check out [openpilot/cereal/services.py](https://github.com/commaai/openpilot/blob/master/openpilot/cereal/services.py) for the decimation.
|
||||||
|
|
||||||
qcameras are H.264 encoded, lower res versions of the fcamera.hevc. The video shown in [comma connect](https://connect.comma.ai/) is from the qcameras.
|
qcameras are H.264 encoded, lower res versions of the fcamera.hevc. The video shown in [comma connect](https://connect.comma.ai/) is from the qcameras.
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ In general, driver monitoring feedback is very actionable, and we can fix your c
|
|||||||
To post your feedback:
|
To post your feedback:
|
||||||
|
|
||||||
1. Join the [community Discord](https://discord.comma.ai).
|
1. Join the [community Discord](https://discord.comma.ai).
|
||||||
2. If driver camera recording is toggled off, temporarily enable driver camera recording in the settings until you reproduce the issue.
|
2. If cabin camera recording is toggled off, temporarily enable cabin camera recording in the settings until you reproduce the issue.
|
||||||
3. Using comma connect, identify the relevant segment and upload the segment's logs and driver camera.
|
3. Using comma connect, identify the relevant segment and upload the segment's logs and cabin camera.
|
||||||
4. Post the segment in the `#openpilot-experience` channel on Discord with a good description.
|
4. Post the segment in the `#openpilot-experience` channel on Discord with a good description.
|
||||||
|
|
||||||
Before posting feedback, please ensure:
|
Before posting feedback, please ensure:
|
||||||
|
|
||||||
- **openpilot is up to date** you should be on the latest openpilot release or nightly
|
- **openpilot is up to date** you should be on the latest openpilot release or nightly
|
||||||
- **the driver camera has a clear view of the driver** ensure nothing blocks view of the driver (e.g. a cable), the lens is clean, etc.
|
- **the cabin camera has a clear view of the driver** ensure nothing blocks view of the driver (e.g. a cable), the lens is clean, etc.
|
||||||
- **your device is mounted properly** your device must be mounted horizontally center and relatively high on the windshield
|
- **your device is mounted properly** your device must be mounted horizontally center and relatively high on the windshield
|
||||||
|
|
||||||
## Other bugs
|
## Other bugs
|
||||||
|
|||||||
@@ -1,216 +0,0 @@
|
|||||||
import posixpath
|
|
||||||
import re
|
|
||||||
import tomllib
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from markdown.extensions import Extension
|
|
||||||
from markdown.preprocessors import Preprocessor
|
|
||||||
from markdown.treeprocessors import Treeprocessor
|
|
||||||
|
|
||||||
from zensical.extensions.links import LinksProcessor
|
|
||||||
|
|
||||||
GlossaryTerm = tuple[str, re.Pattern[str], str]
|
|
||||||
|
|
||||||
GLOSSARY_FILE = Path(__file__).with_name("glossary.toml")
|
|
||||||
GLOSSARY_PAGE = "concepts/glossary.md"
|
|
||||||
GLOSSARY_PLACEHOLDER = "{{GLOSSARY_DEFINITIONS}}"
|
|
||||||
|
|
||||||
SKIP_TAGS = {
|
|
||||||
"a",
|
|
||||||
"code",
|
|
||||||
"h1",
|
|
||||||
"h2",
|
|
||||||
"h3",
|
|
||||||
"h4",
|
|
||||||
"h5",
|
|
||||||
"h6",
|
|
||||||
"kbd",
|
|
||||||
"pre",
|
|
||||||
"script",
|
|
||||||
"style",
|
|
||||||
}
|
|
||||||
|
|
||||||
def clean_tooltip(description: str) -> str:
|
|
||||||
text = re.sub(r"\[([^\]]+)]\([^)]+\)", r"\1", description)
|
|
||||||
text = re.sub(r"`([^`]+)`", r"\1", text)
|
|
||||||
text = re.sub(r"[*_~]", "", text)
|
|
||||||
return re.sub(r"\s+", " ", text).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def load_glossary() -> tuple[list[GlossaryTerm], str]:
|
|
||||||
with GLOSSARY_FILE.open("rb") as f:
|
|
||||||
glossary_data = tomllib.load(f).get("glossary", {})
|
|
||||||
|
|
||||||
glossary: list[GlossaryTerm] = []
|
|
||||||
rendered = []
|
|
||||||
for key, value in glossary_data.items():
|
|
||||||
label = str(key).strip().replace("_", " ")
|
|
||||||
description = str(value).strip()
|
|
||||||
if not description:
|
|
||||||
continue
|
|
||||||
|
|
||||||
slug = label.replace(" ", "-").replace("_", "-").lower()
|
|
||||||
glossary.append((slug, re.compile(rf"(?<!\w){re.escape(label)}(?!\w)", re.IGNORECASE), clean_tooltip(description)))
|
|
||||||
rendered.append(f'* <span id="{slug}"></span>**{label}**: {description}')
|
|
||||||
|
|
||||||
return glossary, "\n".join(rendered)
|
|
||||||
|
|
||||||
|
|
||||||
class GlossaryPreprocessor(Preprocessor):
|
|
||||||
def __init__(self, md, glossary: str):
|
|
||||||
super().__init__(md)
|
|
||||||
self.glossary = glossary
|
|
||||||
|
|
||||||
def run(self, lines: list[str]) -> list[str]:
|
|
||||||
markdown = "\n".join(lines)
|
|
||||||
if GLOSSARY_PLACEHOLDER not in markdown:
|
|
||||||
return lines
|
|
||||||
return markdown.replace(GLOSSARY_PLACEHOLDER, self.glossary).splitlines()
|
|
||||||
|
|
||||||
|
|
||||||
class GlossaryTreeprocessor(Treeprocessor):
|
|
||||||
def __init__(self, md, glossary: list[GlossaryTerm]):
|
|
||||||
super().__init__(md)
|
|
||||||
self.glossary = glossary
|
|
||||||
self.seen: set[str] = set()
|
|
||||||
|
|
||||||
def run(self, root: ET.Element) -> None:
|
|
||||||
at = self.md.treeprocessors.get_index_for_name("zrelpath")
|
|
||||||
processor = self.md.treeprocessors[at]
|
|
||||||
if not isinstance(processor, LinksProcessor):
|
|
||||||
raise TypeError("Links processor not registered")
|
|
||||||
if processor.path == GLOSSARY_PAGE:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.seen.clear()
|
|
||||||
glossary_href = f"{posixpath.relpath(GLOSSARY_PAGE, posixpath.dirname(processor.path) or '.')}#"
|
|
||||||
self._walk(root, glossary_href)
|
|
||||||
|
|
||||||
def _walk(self, element: ET.Element, glossary_href: str) -> None:
|
|
||||||
if element.tag in SKIP_TAGS or element.attrib.get("data-glossary-skip") is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._replace(element, glossary_href)
|
|
||||||
|
|
||||||
idx = 0
|
|
||||||
while idx < len(element):
|
|
||||||
child = element[idx]
|
|
||||||
self._walk(child, glossary_href)
|
|
||||||
idx = self._replace(element, glossary_href, idx) + 1
|
|
||||||
|
|
||||||
def _replace(self, parent: ET.Element, glossary_href: str, index: int | None = None) -> int:
|
|
||||||
child = None if index is None else parent[index]
|
|
||||||
text = parent.text if child is None else child.tail
|
|
||||||
pieces = self._pieces(text or "", glossary_href)
|
|
||||||
if not pieces:
|
|
||||||
return -1 if index is None else index
|
|
||||||
|
|
||||||
if child is None:
|
|
||||||
parent.text = pieces[0] if isinstance(pieces[0], str) else ""
|
|
||||||
# Insert replacements for parent.text before the first existing child.
|
|
||||||
insert_at = -1
|
|
||||||
else:
|
|
||||||
assert index is not None
|
|
||||||
child.tail = pieces[0] if isinstance(pieces[0], str) else ""
|
|
||||||
insert_at = index
|
|
||||||
|
|
||||||
start = 1 if isinstance(pieces[0], str) else 0
|
|
||||||
previous = child
|
|
||||||
|
|
||||||
for piece in pieces[start:]:
|
|
||||||
if isinstance(piece, str):
|
|
||||||
previous.tail = (previous.tail or "") + piece
|
|
||||||
continue
|
|
||||||
|
|
||||||
insert_at += 1
|
|
||||||
parent.insert(insert_at, piece)
|
|
||||||
previous = piece
|
|
||||||
|
|
||||||
return insert_at
|
|
||||||
|
|
||||||
def _pieces(self, text: str, glossary_href: str) -> list[str | ET.Element]:
|
|
||||||
if not text.strip():
|
|
||||||
return []
|
|
||||||
|
|
||||||
pieces: list[str | ET.Element] = []
|
|
||||||
cursor = 0
|
|
||||||
|
|
||||||
while True:
|
|
||||||
best = None
|
|
||||||
for slug, pattern, tooltip in self.glossary:
|
|
||||||
if slug in self.seen:
|
|
||||||
continue
|
|
||||||
|
|
||||||
found = pattern.search(text, cursor)
|
|
||||||
if found is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
candidate = (slug, tooltip, found.start(), found.end())
|
|
||||||
if best is None:
|
|
||||||
best = candidate
|
|
||||||
continue
|
|
||||||
|
|
||||||
_, _, best_start, best_end = best
|
|
||||||
_, _, current_start, current_end = candidate
|
|
||||||
if current_start < best_start:
|
|
||||||
best = candidate
|
|
||||||
continue
|
|
||||||
|
|
||||||
if current_start == best_start and current_end - current_start > best_end - best_start:
|
|
||||||
best = candidate
|
|
||||||
|
|
||||||
if best is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
slug, tooltip, start, end = best
|
|
||||||
if start > cursor:
|
|
||||||
pieces.append(text[cursor:start])
|
|
||||||
|
|
||||||
link = ET.Element(
|
|
||||||
"a",
|
|
||||||
{
|
|
||||||
"class": "glossary-term",
|
|
||||||
"data-glossary-term": "",
|
|
||||||
"href": f"{glossary_href}{slug}",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
ET.SubElement(link, "span", {"class": "glossary-term__label"}).text = text[start:end]
|
|
||||||
ET.SubElement(
|
|
||||||
link,
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
"class": "glossary-term__tooltip",
|
|
||||||
"data-search-exclude": "",
|
|
||||||
},
|
|
||||||
).text = tooltip
|
|
||||||
pieces.append(link)
|
|
||||||
self.seen.add(slug)
|
|
||||||
cursor = end
|
|
||||||
|
|
||||||
if not pieces:
|
|
||||||
return []
|
|
||||||
if cursor < len(text):
|
|
||||||
pieces.append(text[cursor:])
|
|
||||||
return pieces
|
|
||||||
|
|
||||||
|
|
||||||
class GlossaryExtension(Extension):
|
|
||||||
def extendMarkdown(self, md) -> None:
|
|
||||||
md.registerExtension(self)
|
|
||||||
glossary, rendered = load_glossary()
|
|
||||||
|
|
||||||
md.preprocessors.register(
|
|
||||||
GlossaryPreprocessor(md, rendered),
|
|
||||||
"docs-ext-glossary-preprocessor",
|
|
||||||
27,
|
|
||||||
)
|
|
||||||
md.treeprocessors.register(
|
|
||||||
GlossaryTreeprocessor(md, glossary),
|
|
||||||
"docs-ext-glossary-treeprocessor",
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def makeExtension(**kwargs) -> GlossaryExtension:
|
|
||||||
return GlossaryExtension(**kwargs)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
[glossary]
|
|
||||||
onroad = "openpilot's system state while ignition is on."
|
|
||||||
offroad = "openpilot's system state while ignition is off."
|
|
||||||
route = "A route is a recording of an onroad session."
|
|
||||||
segment = "Routes are split into one minute chunks called segments."
|
|
||||||
"comma connect" = "The web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai)."
|
|
||||||
panda = "The secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda)."
|
|
||||||
"comma four" = "The latest hardware by comma.ai for running openpilot. More info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four)."
|
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# Offline Ford selected-action candidate
|
||||||
|
|
||||||
|
This document and `ford_model_action_validation.json` record the offline
|
||||||
|
stage committed as `7ca3c6e3b`. The candidate is now available behind a
|
||||||
|
separate default-off Sunnylink toggle; see
|
||||||
|
[drive-test setup and validation](ford_model_action_drive_test.md).
|
||||||
|
The counts, source hashes and selector status below describe that earlier
|
||||||
|
stage, not the subsequent wiring change.
|
||||||
|
|
||||||
|
The decision is `C0 = current model y(7 m)`,
|
||||||
|
`C1 = max(7 m, speed × 1 s) × selected upstream-limited desiredCurvature`,
|
||||||
|
with C2=C3=0. The 7 m station and one-second scale are engineering choices,
|
||||||
|
not identified PSCM gains. `calibration_approved=false`.
|
||||||
|
|
||||||
|
`openpilot/selfdrive/controls/lib/ford_model_action.py` contains the core
|
||||||
|
and a separate adapter compatible with the existing controlsd call.
|
||||||
|
At that stage, the production selector, v8 implementation, settings, opendbc
|
||||||
|
submodule and Panda safety remained unchanged. Tests injected the adapter
|
||||||
|
offline; there was no production setting. No hardware or CAN transmission
|
||||||
|
occurs in the lab tools.
|
||||||
|
|
||||||
|
## Construction and integration
|
||||||
|
|
||||||
|
Only the unquantized C0 and C1 slew positions persist in the core.
|
||||||
|
Each field is clipped independently (±5.11 m / ±0.5 rad), slewed independently
|
||||||
|
(4 m/s / 0.5 rad/s), then packed using the existing Float32/sign-negation
|
||||||
|
rounding contract (0.01 m / 0.0005 rad). Heading overflow is not transferred
|
||||||
|
to C0. No yaw integral, blend, additional curvature contribution, reference
|
||||||
|
filter, turn modes, or 10 m C1 cap is introduced.
|
||||||
|
|
||||||
|
The selected standalone implementation from worktree 3548 is the provenance
|
||||||
|
for this law. Its two-state packer has been moved into the library core so
|
||||||
|
the controller does not depend on experimental lab code. Invalid numeric
|
||||||
|
types, overflowing arc geometry and malformed paths reset the core instead
|
||||||
|
of throwing or retaining a command.
|
||||||
|
|
||||||
|
Arc stations use cumulative model x/y distance, not forward x. As in the
|
||||||
|
reviewed standalone core, a path ending before 7 m holds its available
|
||||||
|
endpoint instead of extrapolating. This matters: route95 contains 44 active
|
||||||
|
cycles with 5.45–6.94 m of path at 2.78–3.46 m/s. A tested strict 7 m
|
||||||
|
coverage gate would have introduced disengagements and was removed. There
|
||||||
|
is no speed-dependent C0 horizon beyond this existing endpoint behavior.
|
||||||
|
|
||||||
|
The adapter retains the existing input age allowance (−5 to +150 ms),
|
||||||
|
speed domain (0.3–55 m/s), yaw sanity bound (±3 rad/s), selected curvature
|
||||||
|
sanity bound (±1/m), and control interval (2–100 ms). It rejects backward
|
||||||
|
model/measurement timestamps and invalid services. Repeated timestamps may
|
||||||
|
continue slew, but geometry is validated again on each tick. Disengagement,
|
||||||
|
invalid inputs and timing faults clear all command and adapter timing state.
|
||||||
|
The first valid tick after reset uses 10 ms, as v8 does.
|
||||||
|
|
||||||
|
controlsd still owns reference selection, upstream curvature limiting,
|
||||||
|
service health and engagement. Tests execute its actual source-selection
|
||||||
|
and limiter code, its Ford call, Float32 publication in ControlsExt, conversion
|
||||||
|
to CarControlSP, and the pinned Ford CarController's in-memory CAN builder.
|
||||||
|
Both model-action and maneuver-planner selection are covered, including
|
||||||
|
disabling latActive after invalid output. Only the test chooses the adapter.
|
||||||
|
|
||||||
|
Yaw is not an input to the control law. The adapter checks it solely for the
|
||||||
|
inherited invalid-input policy. Driver override and optional PSCM status
|
||||||
|
do not modify the candidate base; existing engagement and downstream driver
|
||||||
|
arbitration remain responsible for authorization, as with v8's base request.
|
||||||
|
|
||||||
|
## Offline evidence
|
||||||
|
|
||||||
|
The checked-in `ford_model_action_validation.json` records the completed
|
||||||
|
checks and source hashes. Full arrays and detailed reports are generated
|
||||||
|
locally under `.cache/ford_model_action/`; original route files are read-only.
|
||||||
|
|
||||||
|
Completed validation: **264 Ford tests and 150 subtests pass**, including
|
||||||
|
120 new core/adapter/replay-validator cases. The candidate module has 100%
|
||||||
|
statement and branch coverage (78 statements, 24 branches). Ruff and Ty pass.
|
||||||
|
The 200,000-cycle numerical stress test also checks 200,000 mirrored core
|
||||||
|
updates and 18,138 field-boundary cases. Across route and stress runs,
|
||||||
|
485,238 Float32/CAN round trips pass. Eight deliberately injected faults
|
||||||
|
(heading gain/cap, erased C0, wrong C0 slew, retained invalid state, stale
|
||||||
|
model acceptance, model clock rollback and reversed C0 sign) are all caught
|
||||||
|
by the tests. Mutation runs replace code only inside isolated Python
|
||||||
|
processes; production source files are never modified by those probes.
|
||||||
|
|
||||||
|
Independent Standards and Spec reviews reported zero findings. The full
|
||||||
|
suite's Params setting test uses an existing local native library from
|
||||||
|
worktree 3548 after checking relevant source files are byte-identical;
|
||||||
|
its hash and provenance are in the manifest. That library is an ignored
|
||||||
|
test dependency, not part of this change. This is the full relevant Ford
|
||||||
|
suite, not the hardware-dependent test suite for every openpilot subsystem.
|
||||||
|
|
||||||
|
The replay has two separate passes:
|
||||||
|
|
||||||
|
* Core compatibility uses the archived eligibility mask and requires exact
|
||||||
|
equality with the independently implemented `action_heading` commands.
|
||||||
|
* Adapter reconstruction derives eligibility from recorded service streams
|
||||||
|
independently of the archived output mask. It retains original timestamps,
|
||||||
|
gaps and consumed model frames. Controls publication time proxies the
|
||||||
|
unlogged computation clock, and complete SubMaster health is unavailable.
|
||||||
|
|
||||||
|
All 54,738 route95 and 78,812 route90 core cycles match exactly, including
|
||||||
|
37,614 and 73,055 active cycles. The adapter preserves those active counts.
|
||||||
|
Its 59 / 19 changed commands arise solely from the fresh 10 ms engagement
|
||||||
|
tick instead of the archived harness's preceding publication interval;
|
||||||
|
the replay checks that attribution on every cycle. Maximum differences are
|
||||||
|
0.01 m / 0.001 rad (95) and 0.02 m / 0.002 rad (90).
|
||||||
|
|
||||||
|
Every core and adapter replay output is round-tripped through Float32 and
|
||||||
|
the real CAN packer/parser, including zero C2/C3, signs, mode and counter.
|
||||||
|
Continuous field slew and quantization allowance are checked separately
|
||||||
|
from immediate invalid-command resets. The original driver-clean cohorts,
|
||||||
|
speed strata and command RMS are reproduced without redoing the encoder search.
|
||||||
|
|
||||||
|
The numerical stress harness uses analytic rotated paths, scalar slew
|
||||||
|
arithmetic, mirrored requests, irregular intervals and invalid-input resets.
|
||||||
|
It also sweeps every representable host field value and the Float32 values
|
||||||
|
immediately below, at and above every half-quantum boundary. Direct CAN
|
||||||
|
packing of the continuous state must agree with the host's quantized output.
|
||||||
|
The unit tests cover releases, reversals, clipping, service freshness,
|
||||||
|
clock resets, malformed inputs, endpoint fallback and actual integration.
|
||||||
|
|
||||||
|
## Limits of the result
|
||||||
|
|
||||||
|
On turns at ≥15 m/s, candidate C0 RMS is 79%/81% below v8 on routes95/90,
|
||||||
|
while C1 is 33%/41% higher. Those are command changes, not evidence of
|
||||||
|
equivalent steering authority. The PSCM's independent C0/C1 response remains
|
||||||
|
unknown. Replay cannot establish physical model following, strong turns,
|
||||||
|
centering, overshoot, oscillation or closed-loop stability.
|
||||||
|
|
||||||
|
The release probe is intentionally explicit: a model bend can increase
|
||||||
|
while selected curvature decreases. At 20 m/s, one synthetic probe changes
|
||||||
|
C0/C1 from 0.24 m / 0.10 rad to 0.49 m / 0.08 rad. Zero selected curvature
|
||||||
|
sets the C1 target to zero but does not erase a nonzero current model C0.
|
||||||
|
Removing a yaw-integral tail does not prove that physical overshoot is solved.
|
||||||
|
No additional release policy or unsupported plant model is added to hide
|
||||||
|
that uncertainty.
|
||||||
|
|
||||||
|
## Reproduce
|
||||||
|
|
||||||
|
From this worktree, use the logged construction dependency explicitly:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export PYTHONDONTWRITEBYTECODE=1
|
||||||
|
export PYTHONPATH=.:/Users/ibpersonal/.codex/worktrees/b926/sunnypilot/opendbc_repo
|
||||||
|
PY=/Users/ibpersonal/dev/sunnypilot/.venv/bin/python
|
||||||
|
EVIDENCE=/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904
|
||||||
|
$PY -m pytest -q -p no:cacheprovider openpilot/selfdrive/controls/tests/test_ford_*.py tools/ford_pscm_lab openpilot/selfdrive/car/tests/test_ford_pscm_status.py
|
||||||
|
$PY -m tools.ford_pscm_lab.model_action_replay "$EVIDENCE/route95" --output .cache/ford_model_action/route95
|
||||||
|
$PY -m tools.ford_pscm_lab.model_action_replay "$EVIDENCE/route90" --output .cache/ford_model_action/route90
|
||||||
|
$PY -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260907 --output .cache/ford_model_action/stress.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The route replay refuses an opendbc revision other than
|
||||||
|
`72a775d35e54c21ff5c5798acef22016eedcc0a7`. Stress defaults to this pin and
|
||||||
|
also accepts an explicitly required commit with `--opendbc-revision` for
|
||||||
|
deployment checks. A mismatch still fails. This historical pin reproduces
|
||||||
|
logged construction; it does not change the merge's submodule pointer.
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Experimental Ford offset damping, v2
|
||||||
|
|
||||||
|
This document and its validation counts describe the archived v2 source. The
|
||||||
|
[current v4 experiment](ford_model_action_full_prediction.md) uses full path prediction.
|
||||||
|
|
||||||
|
Segment 10 of the supplied route9b recording shows measured turning persisting
|
||||||
|
as requested right curvature falls. At about 643.0 s, before strong driver
|
||||||
|
intervention, device-gyro curvature is approximately 0.01786/m against a
|
||||||
|
0.01172/m request. Around 643.9 s, heading demand has reversed slightly but
|
||||||
|
C0 still requests approximately +0.12 m into the turn. Strong column input
|
||||||
|
starts around 643.852 s; later motion cannot establish autonomous recovery.
|
||||||
|
Earlier light driver input also exists.
|
||||||
|
|
||||||
|
The outgoing CAN commands match preceding publications. All 70,937 decoded
|
||||||
|
frames have zero C2/C3 and valid checksums. Focused exit diagnostics have fresh
|
||||||
|
model/carState inputs and targets within normal quantization of the outputs.
|
||||||
|
This supports trying less residual C0; it does not identify PSCM dynamics or
|
||||||
|
prove C0 alone caused the physical oversteer.
|
||||||
|
|
||||||
|
## Change
|
||||||
|
|
||||||
|
C0 starts from the clipped current model offset at 7 m. When C0 and measured
|
||||||
|
host yaw point in the same direction, compute:
|
||||||
|
|
||||||
|
```
|
||||||
|
requested_yaw = max(0, sign(C0) * speed * desiredCurvature)
|
||||||
|
excess = max(0, sign(C0) * yaw - requested_yaw - 0.02 rad/s)
|
||||||
|
reduction = 7 m * 0.2 s * excess
|
||||||
|
target = sign(C0) * max(0, abs(C0) - reduction)
|
||||||
|
```
|
||||||
|
|
||||||
|
Opposing centering demand is unchanged. The correction cannot increase the
|
||||||
|
target's magnitude or reverse its sign. Opposite-direction planned curvature
|
||||||
|
cannot amplify a small yaw bias into a correction. Existing 4 m/s C0 slew still
|
||||||
|
applies; this target bound is not a claim that every stateful output is smaller than a
|
||||||
|
separate v1 controller after arbitrary direction reversals. C1 construction,
|
||||||
|
clipping and slew are unchanged; C2=C3=0. Only C0/C1 slew states persist.
|
||||||
|
There is no integral, model-history filter, turn state machine or fitted plant.
|
||||||
|
|
||||||
|
Host yaw is `-carState.yawRate`, as in the existing Ford call path. The
|
||||||
|
0.02 rad/s deadband exceeds the approximately 0.008 rad/s offset measured
|
||||||
|
against the device gyro on quiet straights. The 0.2 s scale is an initial
|
||||||
|
engineering choice, not an identified delay or gain. Both remain physically
|
||||||
|
unvalidated. Large biased or noisy yaw within the existing sanity gate can
|
||||||
|
still attenuate useful centering; fixed-input replay cannot establish stability.
|
||||||
|
|
||||||
|
## Offline evidence
|
||||||
|
|
||||||
|
The complete 12-rlog route is replayed at original controls publication times,
|
||||||
|
with exact consumed model geometry, causal carState, and carControl matched
|
||||||
|
within 5 ms. These times proxy computation; full SubMaster health is unavailable.
|
||||||
|
V1 reconstruction is within one field quantum of all 64,701 paired active
|
||||||
|
publications. V2 has identical eligibility and exactly identical C1.
|
||||||
|
|
||||||
|
On 381.78 seconds of driver-clean low requests above 8 m/s, only 3 of 37,937 cycles
|
||||||
|
change C0, each by one 0.01 m quantum. Across the 642.7–643.852 s exit window,
|
||||||
|
C0 changes on all 115 cycles, averaging 0.080 m reduction. Entry/peak C0
|
||||||
|
maximum stays 2.80 m; some entry-window samples decrease by up to 0.05 m.
|
||||||
|
These are command comparisons on recorded inputs, not predicted tracking.
|
||||||
|
Low request means requested lateral acceleration below 0.15 m/s²; it is a
|
||||||
|
proxy for straight driving and does not establish a physically straight path.
|
||||||
|
|
||||||
|
The original routes90/95 also run through v2. Their zero-yaw baseline pass
|
||||||
|
checks archived v1 compatibility; the measured-yaw adapter pass checks current
|
||||||
|
construction, eligibility, field limits and packing. It is not an exact match
|
||||||
|
to v1 or v8. Randomized testing checks the damping against an independent
|
||||||
|
piecewise oracle, mirror symmetry, resets and slew, with real Float32/CAN
|
||||||
|
round trips. See `ford_model_action_damping_validation.json` for counts and hashes.
|
||||||
|
|
||||||
|
Final validation passes 325 tests and 26 subtests, with 100% controller
|
||||||
|
statement/branch coverage, 204,946 original route cycles and 628,030 CAN round
|
||||||
|
trips. The module is 166 total lines, including 107 code lines excluding
|
||||||
|
comments, blanks and docstrings. Standards and Spec reviews have no remaining findings.
|
||||||
|
|
||||||
|
## Reproduce
|
||||||
|
|
||||||
|
Use the dependency setup and suite command in the [drive-test guide](ford_model_action_drive_test.md).
|
||||||
|
The new route replay requires the deployment opendbc pin recorded there:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python -m tools.ford_pscm_lab.damping_replay /path/to/complete/rlogs --baseline v1 --candidate v2 --window segment10_entry_peak 637 640 --window segment10_exit_before_strong_input 642.7 643.852 --output /path/to/separate/results
|
||||||
|
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260907 --opendbc-revision c21a9013700734dd20b09e05aa68329ad8cc20f9 --output /path/to/stress.json
|
||||||
|
```
|
||||||
|
|
||||||
|
At the v2 revision, the same default-off Sunnylink toggle selects v2; no additional setting is
|
||||||
|
introduced. Updating an installation with the toggle already enabled selects
|
||||||
|
v2 at the next controlsd startup. `calibration_approved=false` remains explicit.
|
||||||
|
No physical fix, hardware build or device boot is established by these checks.
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
{
|
||||||
|
"date": "2026-09-07",
|
||||||
|
"baseline_commit": "5fc16abc7662020706e29f57d31a6d5e2bc1293a",
|
||||||
|
"deployment_target": {
|
||||||
|
"repository": "sunnypilot/sunnypilot",
|
||||||
|
"branch": "hiimisaac-dev"
|
||||||
|
},
|
||||||
|
"hypothesis": "model-action-c0-c1-yaw-damping-v2",
|
||||||
|
"scope": "Experimental bounded offset damping; fixed-input offline evidence only.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"hardware_build_and_device_boot": "not performed",
|
||||||
|
"controller_size": {
|
||||||
|
"total_lines": 166,
|
||||||
|
"code_lines_excluding_blanks_comments_docstrings": 107,
|
||||||
|
"core_persistent_values": 2,
|
||||||
|
"adapter_timestamps": 3
|
||||||
|
},
|
||||||
|
"checks": {
|
||||||
|
"combined_ford_params_sunnylink_suite": "325 passed, 26 subtests passed; no skips",
|
||||||
|
"suite_log_sha256": "69cb8ab40e93e00a9ff7b6ea1933e4c3554336746860efb53dad3b29f94fc9d3",
|
||||||
|
"coverage": {
|
||||||
|
"statements": 98,
|
||||||
|
"branches": 28,
|
||||||
|
"percent": 100.0
|
||||||
|
},
|
||||||
|
"ruff": "pass",
|
||||||
|
"ty_controller_and_lab": "pass",
|
||||||
|
"settings_compiler_check": "pass",
|
||||||
|
"standards_review_remaining_findings": 0,
|
||||||
|
"spec_review_remaining_findings": 0,
|
||||||
|
"review_resolutions": [
|
||||||
|
"Prevent opposite-direction planned curvature amplifying small yaw bias; eight new cases failed before the fix and passed after it.",
|
||||||
|
"Relabel requested-acceleration cohort as low request; it does not establish physically straight driving."
|
||||||
|
],
|
||||||
|
"mutation_probe": "Disabling damping fails all four mirrored recorded-exit cases."
|
||||||
|
},
|
||||||
|
"stress": {
|
||||||
|
"random_cycles": 200000,
|
||||||
|
"mirrored_core_updates": 200000,
|
||||||
|
"field_boundary_cases": 18138,
|
||||||
|
"float32_can_round_trips": 218138,
|
||||||
|
"bounded_excess_yaw_damping_checked": true,
|
||||||
|
"opendbc_import_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9"
|
||||||
|
},
|
||||||
|
"routes": {
|
||||||
|
"route90": {
|
||||||
|
"cycles": 78812,
|
||||||
|
"core_exact_archived_match": true,
|
||||||
|
"adapter_active_cycles": 73055,
|
||||||
|
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||||
|
"adapter_validity_differs_from_archive_cycles": 0,
|
||||||
|
"adapter_command_differs_from_archive_cycles": 2280,
|
||||||
|
"adapter_max_absolute_command_difference_c0_c1": [
|
||||||
|
0.1900000000000004,
|
||||||
|
0.0020000000000000018
|
||||||
|
],
|
||||||
|
"float32_can_round_trips": 157624,
|
||||||
|
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7"
|
||||||
|
},
|
||||||
|
"route95": {
|
||||||
|
"cycles": 54738,
|
||||||
|
"core_exact_archived_match": true,
|
||||||
|
"adapter_active_cycles": 37614,
|
||||||
|
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||||
|
"adapter_validity_differs_from_archive_cycles": 0,
|
||||||
|
"adapter_command_differs_from_archive_cycles": 2051,
|
||||||
|
"adapter_max_absolute_command_difference_c0_c1": [
|
||||||
|
0.22999999999999998,
|
||||||
|
0.0010000000000000009
|
||||||
|
],
|
||||||
|
"float32_can_round_trips": 109476,
|
||||||
|
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7"
|
||||||
|
},
|
||||||
|
"route9b": {
|
||||||
|
"cycles": 71396,
|
||||||
|
"eligible_cycles": 64701,
|
||||||
|
"same_validity": true,
|
||||||
|
"c1_exactly_unchanged": true,
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 142792,
|
||||||
|
"v1_reconstruction_vs_recorded": {
|
||||||
|
"paired_cycles": 64701,
|
||||||
|
"within_one_quantum_cycles": 64701,
|
||||||
|
"maximum_absolute_error_c0_c1": [
|
||||||
|
0.010000114440917862,
|
||||||
|
0.0005000143051147043
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timing": "Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.",
|
||||||
|
"baseline": "Current adapter with zero yaw retains v1 targets and actual-yaw sanity gate.",
|
||||||
|
"opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||||
|
"cohorts": {
|
||||||
|
"driver_clean_low_request_above_8mps": {
|
||||||
|
"cycles": 37937,
|
||||||
|
"seconds": 381.77672216900055,
|
||||||
|
"changed_c0_cycles": 3,
|
||||||
|
"mean_absolute_c0_change_m": 8.419774473786704e-07,
|
||||||
|
"max_absolute_c0_change_m": 0.009999999999999787,
|
||||||
|
"v1_peak_absolute_c0_m": 0.08999999999999986,
|
||||||
|
"v2_peak_absolute_c0_m": 0.08999999999999986
|
||||||
|
},
|
||||||
|
"segment10_entry_peak": {
|
||||||
|
"cycles": 298,
|
||||||
|
"seconds": 2.9932484459999387,
|
||||||
|
"changed_c0_cycles": 100,
|
||||||
|
"mean_absolute_c0_change_m": 0.007564164795694466,
|
||||||
|
"max_absolute_c0_change_m": 0.04999999999999982,
|
||||||
|
"v1_peak_absolute_c0_m": 2.8000000000000003,
|
||||||
|
"v2_peak_absolute_c0_m": 2.8000000000000003
|
||||||
|
},
|
||||||
|
"segment10_exit_before_strong_input": {
|
||||||
|
"cycles": 115,
|
||||||
|
"seconds": 1.156770048999988,
|
||||||
|
"changed_c0_cycles": 115,
|
||||||
|
"mean_absolute_c0_change_m": 0.0799313220721162,
|
||||||
|
"max_absolute_c0_change_m": 0.1200000000000001,
|
||||||
|
"v1_peak_absolute_c0_m": 0.75,
|
||||||
|
"v2_peak_absolute_c0_m": 0.7000000000000002
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exit_c0_strictly_lower_on_all_115_cycles": true,
|
||||||
|
"rlog_sha256_by_segment": {
|
||||||
|
"0": "22746f7119109b73ed7f2c26ce8c99f87136e9124fb7fc14c9554409a28a7c3f",
|
||||||
|
"1": "4c2e1d7083c31a2b37d0f8dd3be4d330898511b7e02c26f7d40ca9bc2779397d",
|
||||||
|
"2": "62f3e049e220cd3681fadf386f2969537bd571998ae2f6ba2d08479428b5a28f",
|
||||||
|
"3": "83bf0131b2d36b2ba7e5ba050bbc13c0a3350feb5c9b89dc9c87d3a37abebfb3",
|
||||||
|
"4": "430985a80dd6e10f7abeb89457a17022e6bb6978617f415c905f584b1647603e",
|
||||||
|
"5": "8c0c5ae6323ec33b3e14f84ca834f70cb56f6b29f471a350f1e3efc06b6ba553",
|
||||||
|
"6": "db53dfa8156b9d66792c3eff0b2ce5d31b71ad41cc580dec85f528845593c184",
|
||||||
|
"7": "91b0b3be10cb7d7d7f7dd2024d8f9ee99d1e9fd2204203a3a9a2f2f1c6e3fa03",
|
||||||
|
"8": "687dbbfc49837efbfe8fa6bc091e40f7fad2908832234d7884f4616d1bc9ccff",
|
||||||
|
"9": "a88ec4d25b04cdbf5844686fc77f6b28dca920c9b164e37ebf69844a3ae398fc",
|
||||||
|
"10": "fe6b29580a6c94e1c236d13e18db4cd9f31cc1b25d52e1e6e19a5021125c9932",
|
||||||
|
"11": "150d31b1944d7a1b8c562f3aee20b66cefa6c4e8d02660ec889907d835142f45"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"total_original_route_cycles": 204946,
|
||||||
|
"total_float32_can_round_trips": 628030,
|
||||||
|
"source_sha256": {
|
||||||
|
"docs/ford_model_action_damping.md": "1aca8ca8e78d953beeda5b0c9803161a1d7c58556b1966040c71f809c3960bf8",
|
||||||
|
"docs/ford_model_action_drive_test.md": "3ce8bf6cb511a4461fa7abf194f47020af7ef5a6c90fcae7ed09571d1f750846",
|
||||||
|
"openpilot/selfdrive/controls/lib/ford_model_action.py": "59d66297a017557f3d4f28b115be3f6220b800566c11935e2284b3814783fb7e",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_controlsd_logging.py": "318742bd707ae526d0f5181bf7f081660c2c55de4bdf28518ccdf50d60e88080",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "de6f8524347f7c4a339941bc8565ccaa131cb93aa0418c75006ce08ab7edeb99",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_damping.py": "76466997ab4fef435f44339a6cb2d06303d5f0ab8717d487f27655297bd429d1",
|
||||||
|
"openpilot/sunnypilot/sunnylink/settings_ui.json": "dbb78c98f57eef532f0dff0cb0b38396442882876e6d115f0d3c36159f64baf5",
|
||||||
|
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "b92b55e23467e74227988fb39fff13a4ebba3c960fb90e34a6b341411699fc95",
|
||||||
|
"tools/ford_pscm_lab/damping_replay.py": "2f52fef12116ce87c0a1f465cf13d6af5e9fd76ce05d4318d72e34b70bc6a11f",
|
||||||
|
"tools/ford_pscm_lab/model_action_replay.py": "05a658dfcaf81693bf0d92184c0edff0172802f351a61a9e866a7967e74ae46d",
|
||||||
|
"tools/ford_pscm_lab/stress_model_action.py": "3e308733f4af0101ad0c414fbd724f6a99f56269ed8e791f0e15526d8bfd8f17"
|
||||||
|
},
|
||||||
|
"artifact_sha256": {
|
||||||
|
"route9b/report.json": "1b9317850c1724f433269c6a58349d0a0ee4eb6c9a03d6ec5858fa17713796c5",
|
||||||
|
"route9b/commands.npz": "d9f553a5384c84416a27c52b5dda0e751dda25edc5d6620fc311977f34a7a946",
|
||||||
|
"route90/report.json": "e37f71dc032e375b1c9b0beb4d6e0c915257bf72e59b0785ac0ca49c2472d2c2",
|
||||||
|
"route95/report.json": "d42d5a080fef8a1f0b3c7ae2cabcad20c88ce01d91be07ab33770ff5948c06b6",
|
||||||
|
"stress.json": "06e69a23340e3f5ed174e8e0b2e5791b320686dce7df963233d50a9982dca17b",
|
||||||
|
"coverage.json": "8f7915b9bd884abedfdbc2e0c18ef4474737e27225a714414384242535cc396f",
|
||||||
|
"mutation.txt": "67a76549fd7bb71e7092a155d4d0c3459be7b04dc2ca36eef9b54fbb574ad83d",
|
||||||
|
"bias_regression_red.txt": "8903c5a967e8c376050db85f7cf5f73abf71ee972f6025b25eb874479f94c62c",
|
||||||
|
"segment10_damping.png": "7d60cf9aabdcca9000fcf49bd14bd6130418aa6ab1b57af2678b171498ee9505"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Ford selected-action drive-test branch
|
||||||
|
|
||||||
|
The candidate is selectable on the **Ford CAN FD F-150 Lightning** behind
|
||||||
|
its own persistent, default-off Sunnylink toggle. Version 5 retains full geometric
|
||||||
|
path prediction and [removes yaw damping](ford_model_action_no_yaw_damping.md).
|
||||||
|
Measured yaw is used only for input-health checks and diagnostics. Input gates are unchanged.
|
||||||
|
`calibration_approved=false`: offline checks do not establish physical tracking,
|
||||||
|
turn-exit behavior or closed-loop stability.
|
||||||
|
|
||||||
|
## Select and restore
|
||||||
|
|
||||||
|
1. Install branch `hiimisaac-dev` from
|
||||||
|
`sunnypilot/sunnypilot` on the device using your normal branch-switch process.
|
||||||
|
Allow its build to finish before changing the setting.
|
||||||
|
2. While offroad, open Sunnylink device settings → Vehicle → Ford and enable
|
||||||
|
**Selected-Action Path Tracking (Experimental)** (`FordModelActionController`).
|
||||||
|
3. Complete a real offroad-to-onroad cycle. Selection occurs when `controlsd`
|
||||||
|
starts; changing a stored toggle or disengaging alone cannot swap an active
|
||||||
|
controller. Initial physical evaluation remains controlled testing.
|
||||||
|
|
||||||
|
The startup log event `Ford path controller selected` should report
|
||||||
|
`FordModelActionController`. Periodic `Ford C2-free path tracking` events
|
||||||
|
identify `hypothesis=model-action-c0-c1-prediction-v5` and report host yaw and the command tuple.
|
||||||
|
|
||||||
|
Turning the new toggle off and completing another offroad-to-onroad cycle
|
||||||
|
restores **PSCM Coefficient Observer** if selected, otherwise the original
|
||||||
|
Ford path controller. The stored observer selection is preserved. The candidate
|
||||||
|
takes priority on the supported vehicle, independently of EPS firmware query
|
||||||
|
results. Other vehicles retain their existing selection.
|
||||||
|
|
||||||
|
The v8 implementation, its Sunnylink toggle and its dedicated tests are removed.
|
||||||
|
A leftover `FordVirtualAngleController=1` file cannot enable the new controller.
|
||||||
|
The shared Float32/CAN rounding helper now lives in `ford_model_action.py`;
|
||||||
|
unused v8 PSCM-feedback plumbing is removed. Historical v8 route evidence remains
|
||||||
|
in Git history and the archived validation documents.
|
||||||
|
|
||||||
|
## Wiring and validation
|
||||||
|
|
||||||
|
`Controls.__init__` selects the candidate once at startup. It shares the
|
||||||
|
existing Ford call path, selected upstream-limited curvature, service gates,
|
||||||
|
invalid-output disengagement, Float32 publication and downstream CAN builder.
|
||||||
|
C2 and C3 stay zero. No opendbc pointer or Panda safety change is included.
|
||||||
|
|
||||||
|
Sunnylink publishes the toggle through its generated settings schema and
|
||||||
|
writes the registered Boolean through the existing parameter endpoint. The
|
||||||
|
offroad UI rule and `needs_onroad_cycle` metadata describe when it can be
|
||||||
|
changed and when it takes effect. An onroad backend write changes storage
|
||||||
|
only; the controller continues using its startup selection.
|
||||||
|
|
||||||
|
Native validation also exposed a pre-existing `params_keys_by_flag` bug:
|
||||||
|
every returned buffer referenced the same reusable string. Sunnylink backup
|
||||||
|
key enumeration could therefore return corrupted names. The bridge now
|
||||||
|
returns separate strings owned by the parameter handle. Regression tests
|
||||||
|
check distinct registered keys across flags, and toggle tests check its
|
||||||
|
persistence and backup registration using the rebuilt native library.
|
||||||
|
|
||||||
|
The current validation record is `ford_model_action_no_yaw_damping_validation.json`;
|
||||||
|
the [v5 notes](ford_model_action_no_yaw_damping.md) explain damping removal and its
|
||||||
|
scope. `ford_model_action_full_prediction_validation.json` archives v4 checks;
|
||||||
|
the [full-prediction notes](ford_model_action_full_prediction.md) explain cap removal
|
||||||
|
and remaining physical uncertainty. `ford_model_action_prediction_validation.json`
|
||||||
|
archives the capped v3 evaluation. `ford_model_action_damping_validation.json`
|
||||||
|
archives the preceding v2 checks at their recorded source hashes.
|
||||||
|
`ford_model_action_drive_test_validation.json` archives v1 wiring validation
|
||||||
|
at the recorded source hashes, including 284 tests and 26 subtests. Its counts
|
||||||
|
and 145-line controller size describe v1. The 469-line v8 module remains removed.
|
||||||
|
|
||||||
|
The original 133,550-cycle route reconstruction, 485,238 packing round trips
|
||||||
|
and mutation probes remain recorded separately in
|
||||||
|
`ford_model_action_validation.json` at the offline-stage source hashes.
|
||||||
|
|
||||||
|
## Reproduce deployment checks
|
||||||
|
|
||||||
|
Initialize the branch's exact opendbc submodule (`c21a9013700734dd20b09e05aa68329ad8cc20f9`)
|
||||||
|
and build the native Params library from this branch before testing.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export PYTHONDONTWRITEBYTECODE=1
|
||||||
|
export PYTHONPATH=.:opendbc_repo
|
||||||
|
python -m pytest -q -p no:cacheprovider openpilot/selfdrive/controls/tests/test_ford_*.py tools/ford_pscm_lab openpilot/selfdrive/car/tests/test_ford_pscm_status.py openpilot/sunnypilot/sunnylink/tests openpilot/common/tests/test_params.py opendbc_repo/opendbc/car/ford/tests/test_ford.py
|
||||||
|
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260907 --opendbc-revision c21a9013700734dd20b09e05aa68329ad8cc20f9 --output .cache/ford_model_action_drive_test/stress.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The full hardware build and device boot are not performed by these offline
|
||||||
|
tests. Installing the branch and enabling the toggle are separate actions;
|
||||||
|
pushing the branch does not change a device's selected software or settings.
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
{
|
||||||
|
"date": "2026-09-07",
|
||||||
|
"baseline_commit": "7ca3c6e3b3e659c6f446039501c5826bbd14092e",
|
||||||
|
"branch": "codex/ford-model-action-drive-test",
|
||||||
|
"scope": "Default-off Sunnylink selection and v8 retirement; offline validation only. No device installation or physical performance validation.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"production_selector_changed": true,
|
||||||
|
"toggle": "FordModelActionController",
|
||||||
|
"default_enabled": false,
|
||||||
|
"v8_removed": true,
|
||||||
|
"panda_safety_changed": false,
|
||||||
|
"opendbc_submodule_changed": false,
|
||||||
|
"deployment_opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||||
|
"controller_size": {
|
||||||
|
"total_lines": 145,
|
||||||
|
"code_lines_excluding_blanks_comments_docstrings": 95,
|
||||||
|
"core_persistent_values": 2,
|
||||||
|
"adapter_timestamps": 3,
|
||||||
|
"removed_v8_module_lines": 469
|
||||||
|
},
|
||||||
|
"tests": {
|
||||||
|
"combined_ford_params_sunnylink_suite": "284 passed, 26 subtests passed in 2.63s",
|
||||||
|
"suite_log_sha256": "2e223a507f0630481cf6f83b9f8893d226f3f4273a79a09fc35905aa875b1d2c",
|
||||||
|
"coverage": {
|
||||||
|
"covered_lines": 87,
|
||||||
|
"num_statements": 87,
|
||||||
|
"percent_covered": 100.0,
|
||||||
|
"percent_covered_display": "100",
|
||||||
|
"missing_lines": 0,
|
||||||
|
"excluded_lines": 0,
|
||||||
|
"percent_statements_covered": 100.0,
|
||||||
|
"percent_statements_covered_display": "100",
|
||||||
|
"num_branches": 26,
|
||||||
|
"num_partial_branches": 0,
|
||||||
|
"covered_branches": 26,
|
||||||
|
"missing_branches": 0,
|
||||||
|
"percent_branches_covered": 100.0,
|
||||||
|
"percent_branches_covered_display": "100"
|
||||||
|
},
|
||||||
|
"ruff": "pass",
|
||||||
|
"ty_controller_and_lab": "pass",
|
||||||
|
"settings_compiler_check": "pass",
|
||||||
|
"standards_review_remaining_findings": 0,
|
||||||
|
"spec_review_remaining_findings": 0,
|
||||||
|
"resolved_review_finding": "Updated YAML authoring source and regenerated settings JSON before final compiler/schema suite."
|
||||||
|
},
|
||||||
|
"routes": {
|
||||||
|
"route95": {
|
||||||
|
"cycles": 54738,
|
||||||
|
"core_active_cycles": 37614,
|
||||||
|
"core_exact_archived_match": true,
|
||||||
|
"cohorts_reproduced": true,
|
||||||
|
"adapter_active_cycles": 37614,
|
||||||
|
"adapter_exact_match_with_fresh_engagement_dt": true,
|
||||||
|
"adapter_validity_differs_from_archive_cycles": 0,
|
||||||
|
"adapter_command_differs_from_archive_cycles": 59,
|
||||||
|
"adapter_max_absolute_command_difference_c0_c1": [
|
||||||
|
0.010000000000000675,
|
||||||
|
0.0010000000000000009
|
||||||
|
],
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 109476,
|
||||||
|
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||||
|
"report_sha256": "72fab710dc81c8c7d9b75d371b97fec32402d3b01fa05517dfa814d8daff3134"
|
||||||
|
},
|
||||||
|
"route90": {
|
||||||
|
"cycles": 78812,
|
||||||
|
"core_active_cycles": 73055,
|
||||||
|
"core_exact_archived_match": true,
|
||||||
|
"cohorts_reproduced": true,
|
||||||
|
"adapter_active_cycles": 73055,
|
||||||
|
"adapter_exact_match_with_fresh_engagement_dt": true,
|
||||||
|
"adapter_validity_differs_from_archive_cycles": 0,
|
||||||
|
"adapter_command_differs_from_archive_cycles": 19,
|
||||||
|
"adapter_max_absolute_command_difference_c0_c1": [
|
||||||
|
0.020000000000000462,
|
||||||
|
0.0020000000000000018
|
||||||
|
],
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 157624,
|
||||||
|
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||||
|
"report_sha256": "cc2224bae597a341697a7681560a077cb209d77e4d060c0850997691c57d32fb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stress": {
|
||||||
|
"seed": 20260907,
|
||||||
|
"random_cycles": 200000,
|
||||||
|
"mirrored_core_updates": 200000,
|
||||||
|
"invalid_or_inactive_resets": 3537,
|
||||||
|
"field_boundary_cases": 18138,
|
||||||
|
"float32_can_round_trips": 218138,
|
||||||
|
"analytic_targets_scalar_slew_and_mirror_checks_pass": true,
|
||||||
|
"direct_raw_float32_packing_matches_host_output": true,
|
||||||
|
"max_continuous_step_c0_c1": [
|
||||||
|
0.40000000000000147,
|
||||||
|
0.05000000000000002
|
||||||
|
],
|
||||||
|
"calibration_approved": false,
|
||||||
|
"scope": "Numerical construction only; no PSCM response or closed-loop performance claims.",
|
||||||
|
"opendbc_import_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9"
|
||||||
|
},
|
||||||
|
"total_float32_can_round_trips": 485238,
|
||||||
|
"native_params": {
|
||||||
|
"source": "Rebuilt locally from this branch with clang++ and generated Capnp headers; ignored test dependency, not committed binary.",
|
||||||
|
"library_sha256": "270bf43241cf7c02cc432cf78ec9411a62d7653ca445695efe785ae82241aa09",
|
||||||
|
"sources_sha256": {
|
||||||
|
"openpilot/common/params_c.cc": "57e3bcc7eba939bc91aadafb4ed1248b8123a8fe5c48fd8530298d966ea4db63",
|
||||||
|
"openpilot/common/params.cc": "a5adacb1d47cb3bf6e0d87d44ce158b41982d7eaf2e8114e32c48d3a6631304c",
|
||||||
|
"openpilot/common/params.h": "ed03d137e126ecd6f1608016020af18c0339fb987e27d0a2aa6830bba396970c",
|
||||||
|
"openpilot/common/params_keys.h": "39d36465f66405843b926ba18473fb6aee81c0f1c7bea87246aa08ffe3f67c58",
|
||||||
|
"openpilot/common/util.cc": "4479ecf72465e8f453d8af78447f7715f02d9397c58a49048f2bbc87a96d6b8a",
|
||||||
|
"openpilot/common/swaglog.cc": "9c2f88a2f1c3c4253b73defb264cc367a13ade23e02928e1d469b5c5833df176"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test_dependency_notes": {
|
||||||
|
"python": "/Users/ibpersonal/dev/sunnypilot/.venv/bin/python",
|
||||||
|
"pyyaml": "6.0.3 from local uv cache",
|
||||||
|
"jsonschema": "Local cached package appended after venv to run schema validator without skips",
|
||||||
|
"hardware_build_and_device_boot": "not performed"
|
||||||
|
},
|
||||||
|
"source_sha256": {
|
||||||
|
"docs/ford_model_action_candidate.md": "c968132348d20891a9396f6e69db1315d570505796e19e09ffbb2da748e6e687",
|
||||||
|
"docs/ford_virtual_angle_experiment.md": "da6322f3c3d2d81463e44c50cc6cad1a962f97008ff9425c314da333ebe47a87",
|
||||||
|
"openpilot/common/params_c.cc": "57e3bcc7eba939bc91aadafb4ed1248b8123a8fe5c48fd8530298d966ea4db63",
|
||||||
|
"openpilot/common/params_keys.h": "39d36465f66405843b926ba18473fb6aee81c0f1c7bea87246aa08ffe3f67c58",
|
||||||
|
"openpilot/common/tests/test_params.py": "557a1f616af5fd9f5e623fbee0fd6f44cb059a30c290c68ce2b57e9bcceed081",
|
||||||
|
"openpilot/selfdrive/controls/controlsd.py": "102b383e5beff43b8dd7c219178bef62e8a4b54443ebe682694606862bcd4e7f",
|
||||||
|
"openpilot/selfdrive/controls/lib/ford_model_action.py": "8f3bc5d68e0051776f614a2ccffae84a88f7898dc95bdc12c23dcfe10dfe676a",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_controlsd_logging.py": "1c9448d88d8021e5d34a5dccd14a17c6c1bc64b5531342bfc6d15574d9d3e710",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "2c5b14f814e84e59d749f61a43ef1dcfe06253f6e185443fa123c37525ac8466",
|
||||||
|
"openpilot/sunnypilot/sunnylink/settings_ui.json": "9974df3ac4cc58ae78d47848cd18ef4aca1bcbb00edb257f28b4220d92890528",
|
||||||
|
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "7a3fa18e562d5a03a5b85c72ee3f3ebeb836c497285dcd9aaad24f8fb4dd6942",
|
||||||
|
"openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py": "3db566612381fd87d3655a3ccff470be7da998c4ea7f6365f53e58cdb9c0ffb7",
|
||||||
|
"tools/ford_pscm_lab/model_action_replay.py": "827a6dc488d554bdf6e87438c6a2a985b3195bf6d01ab09002bfd6049d22a868",
|
||||||
|
"tools/ford_pscm_lab/stress_model_action.py": "2d5c72cc4b8ae214f2f5a19a050fe138f5c2ab0294d92d24e2481af5f8185613",
|
||||||
|
"tools/ford_pscm_lab/test_model_action_replay.py": "ebf6bcd9260745100311521f8e11e85b7aebdd5561ab0876bfc2e802429d6896",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_selection.py": "f826c6328f0abac2a61f1a0a6f8d119fdbab858e363cb466d84ba9a7783059cd",
|
||||||
|
"docs/ford_model_action_drive_test.md": "d825b177cd099efd797fe89b7041695d6d41e4b9e8bba6bcaeb32aece164262b"
|
||||||
|
},
|
||||||
|
"deployment_target": {
|
||||||
|
"repository": "sunnypilot/sunnypilot",
|
||||||
|
"branch": "hiimisaac-dev",
|
||||||
|
"validated_code_commit": "ea1ed70c718d32539ef6b9a89b89c0e297c92e06"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Experimental Ford full path prediction, v4
|
||||||
|
|
||||||
|
This document archives v4. The [current v5 controller](ford_model_action_no_yaw_damping.md)
|
||||||
|
retains this prediction and removes yaw damping.
|
||||||
|
|
||||||
|
V4 removes the extra 15 cm / 25% limit on the geometric prediction introduced
|
||||||
|
in [v3](ford_model_action_prediction.md). Those numbers were hand-chosen tuning
|
||||||
|
bounds, not identified Ford response limits. The current user request is to
|
||||||
|
remove that restriction; the existing default-off Sunnylink toggle remains.
|
||||||
|
|
||||||
|
The controller now uses the full predicted offset from the same model path,
|
||||||
|
assuming 150 ms of motion along the selected, upstream-limited curvature. The
|
||||||
|
150 ms horizon remains an engineering assumption. Available model geometry
|
||||||
|
still limits the prediction distance, with endpoint hold for short paths and
|
||||||
|
fallback to the valid base offset if prediction arithmetic is nonfinite.
|
||||||
|
|
||||||
|
The existing total command limits (C0 ±5.11 m, C1 ±0.5 rad), independent slew
|
||||||
|
rates (4 m/s, 0.5 rad/s), quantization, yaw damping, input/service gates and
|
||||||
|
zero C2/C3 remain unchanged. Only two control states persist. No integrator,
|
||||||
|
model history, extra toggle or PSCM feedback loop is added.
|
||||||
|
|
||||||
|
Removing the adjustment cap also permits the predicted C0 to oppose the
|
||||||
|
original offset or become nonzero from a zero original offset. For example,
|
||||||
|
a straight path with a nonzero selected turn request can have an opposing
|
||||||
|
future-frame offset. Tests cover that behavior, mirrored turn releases,
|
||||||
|
return to zero, and unchanged slew; sign preservation of the original C0 is
|
||||||
|
no longer claimed. The yaw damper still cannot reverse its input target.
|
||||||
|
|
||||||
|
## Evidence and interpretation
|
||||||
|
|
||||||
|
The PSCM reports a generic `LimitReached` state. It does not tell us whether
|
||||||
|
an incoming target is geometrically correct or well timed. Its internal
|
||||||
|
limits cannot establish the tracking performance of this predictor. Removal
|
||||||
|
is an experiment supported by command comparisons, not by an assumption that
|
||||||
|
the PSCM will correct an excessive or mistimed request.
|
||||||
|
|
||||||
|
Compared with capped v3 on identical recorded inputs:
|
||||||
|
|
||||||
|
| Interval | Effect of removing the extra cap |
|
||||||
|
| --- | --- |
|
||||||
|
| Latest tight-left entry, 173–175.4 s | Mean C0 magnitude +0.032 m, maximum change 0.07 m |
|
||||||
|
| Earlier right entry, 637–640 s | Mean magnitude +0.028 m, maximum change 0.10 m |
|
||||||
|
| Earlier right exit, 642.7–643.852 s | All 115 commands unchanged |
|
||||||
|
| Driver-clean low requests above 8 m/s, routes9b/9e | Mean absolute change 0.0018 / 0.0014 m; maximum 0.02 m |
|
||||||
|
| All eligible samples on either recent route | Maximum absolute command change 0.13 m |
|
||||||
|
|
||||||
|
C1 and command eligibility are exactly identical to v3 on both recent routes.
|
||||||
|
Some command signs change near zero: this is an intended consequence of using
|
||||||
|
the full transform, not proof those corrections improve driving. Entry windows
|
||||||
|
include driver input, reported in the validation record. The magnitude changes
|
||||||
|
above describe controller C0, not measured lateral vehicle displacement.
|
||||||
|
|
||||||
|
The earlier v1 archive is also reproduced exactly on routes90/95, separately
|
||||||
|
from the current controller pass. All replay uses original timestamps;
|
||||||
|
publication times proxy computation, exact consumed model frames and causal
|
||||||
|
carState are retained, and complete SubMaster health is unavailable. The
|
||||||
|
recorded model and vehicle motion remain fixed. There is no measured physical
|
||||||
|
improvement, stability result or new desired-versus-actual steering trajectory.
|
||||||
|
|
||||||
|
Final validation passes 374 tests and 26 subtests with 100% controller statement
|
||||||
|
and branch coverage, 299,604 original route cycles and 817,346 Float32/CAN
|
||||||
|
round trips. The controller is 190 total lines / 122 code lines excluding
|
||||||
|
blanks, comments and docstrings; two control states persist.
|
||||||
|
|
||||||
|
See `ford_model_action_full_prediction_validation.json` for final test counts,
|
||||||
|
coverage, dependency pins, source hashes and route/packing results. The initial
|
||||||
|
uncapped variant was evaluated in a separate lab file before editing production;
|
||||||
|
final route checks execute the production v4 source.
|
||||||
|
|
||||||
|
## Reproduce and select
|
||||||
|
|
||||||
|
Use the dependencies and combined suite command in the
|
||||||
|
[drive-test guide](ford_model_action_drive_test.md). For the recent routes:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python -m tools.ford_pscm_lab.damping_replay /path/to/route9e/rlogs --baseline v3 --candidate current --window left_entry 173 175.4 --window left_peak 175.4 178.3 --window left_exit 178.3 180.5 --window reversal 728 734 --output /path/to/separate/route9e-results
|
||||||
|
python -m tools.ford_pscm_lab.damping_replay /path/to/route9b/rlogs --baseline v3 --candidate current --window right_entry 637 640 --window right_exit 642.7 643.852 --output /path/to/separate/route9b-results
|
||||||
|
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260907 --opendbc-revision c21a9013700734dd20b09e05aa68329ad8cc20f9 --output /path/to/stress.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The same **Selected-Action Path Tracking (Experimental)** toggle selects v4
|
||||||
|
on the CAN FD F-150 Lightning. An installation with the toggle already enabled
|
||||||
|
selects v4 after updating and restarting controlsd. Diagnostics identify
|
||||||
|
`model-action-c0-c1-prediction-v4`; `calibration_approved=false` remains explicit.
|
||||||
|
Deployment branch: `sunnypilot/sunnypilot`, `hiimisaac-dev`. This work does not
|
||||||
|
install software on the device or change its settings.
|
||||||
@@ -0,0 +1,799 @@
|
|||||||
|
{
|
||||||
|
"date": "2026-09-07",
|
||||||
|
"baseline_commit": "01f8d51c82b3e863f1012d383b5994813ef01b81",
|
||||||
|
"hypothesis": "model-action-c0-c1-prediction-v4",
|
||||||
|
"scope": "Removal of only the extra prediction adjustment cap; no physical tracking or stability claim.",
|
||||||
|
"deployment_target": {
|
||||||
|
"repository": "sunnypilot/sunnypilot",
|
||||||
|
"branch": "hiimisaac-dev"
|
||||||
|
},
|
||||||
|
"calibration_approved": false,
|
||||||
|
"hardware_build_and_device_boot": "not performed",
|
||||||
|
"controller_size": {
|
||||||
|
"total_lines": 190,
|
||||||
|
"code_lines_excluding_blanks_comments_docstrings": 122,
|
||||||
|
"core_persistent_values": 2,
|
||||||
|
"adapter_timestamps": 3
|
||||||
|
},
|
||||||
|
"checks": {
|
||||||
|
"combined_ford_params_sunnylink_suite": "374 passed, 26 subtests passed; no skips",
|
||||||
|
"coverage": {
|
||||||
|
"covered_lines": 113,
|
||||||
|
"num_statements": 113,
|
||||||
|
"percent_covered": 100.0,
|
||||||
|
"percent_covered_display": "100",
|
||||||
|
"missing_lines": 0,
|
||||||
|
"excluded_lines": 0,
|
||||||
|
"percent_statements_covered": 100.0,
|
||||||
|
"percent_statements_covered_display": "100",
|
||||||
|
"num_branches": 32,
|
||||||
|
"num_partial_branches": 0,
|
||||||
|
"covered_branches": 32,
|
||||||
|
"missing_branches": 0,
|
||||||
|
"percent_branches_covered": 100.0,
|
||||||
|
"percent_branches_covered_display": "100"
|
||||||
|
},
|
||||||
|
"ruff": "pass",
|
||||||
|
"ty_controller_and_lab": "pass",
|
||||||
|
"settings_compiler_check": "pass",
|
||||||
|
"cap_removal_red_probe": "11 tests fail with the v3 cap present; all 40 prediction tests pass after removal.",
|
||||||
|
"standards_review_remaining_findings": 0,
|
||||||
|
"spec_review_remaining_findings": 0,
|
||||||
|
"independent_review_verification": "Each reviewer passed 172 focused tests and verified source/artifact hashes and route/packing totals."
|
||||||
|
},
|
||||||
|
"stress": {
|
||||||
|
"seed": 20260907,
|
||||||
|
"random_cycles": 200000,
|
||||||
|
"mirrored_core_updates": 200000,
|
||||||
|
"invalid_or_inactive_resets": 3537,
|
||||||
|
"field_boundary_cases": 18138,
|
||||||
|
"float32_can_round_trips": 218138,
|
||||||
|
"analytic_targets_scalar_slew_and_mirror_checks_pass": true,
|
||||||
|
"bounded_excess_yaw_damping_checked": true,
|
||||||
|
"full_geometric_prediction_checked": true,
|
||||||
|
"direct_raw_float32_packing_matches_host_output": true,
|
||||||
|
"max_continuous_step_c0_c1": [
|
||||||
|
0.400000000000329,
|
||||||
|
0.05000000000000002
|
||||||
|
],
|
||||||
|
"calibration_approved": false,
|
||||||
|
"scope": "Numerical construction only; no PSCM response or closed-loop performance claims.",
|
||||||
|
"opendbc_import_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||||
|
"source_sha256": {
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/stress_model_action.py": "66adef6cba120a1a3d8e9730ea987ef49f8fe283023362dbb99608dee0e3229f",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "7cddac7ce9e88cc32bc7afbec7f9df79b66bb4dfa2fa5f9b36654891fae23a19"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routes": {
|
||||||
|
"route90": {
|
||||||
|
"scope": "Command construction and adapter reconstruction only; no counterfactual closed-loop score.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"executes_live_selector": false,
|
||||||
|
"cycles": 78812,
|
||||||
|
"core_active_cycles": 73055,
|
||||||
|
"core_exact_archived_match": true,
|
||||||
|
"cohorts_reproduced": true,
|
||||||
|
"adapter_active_cycles": 73055,
|
||||||
|
"adapter_status_counts": {
|
||||||
|
"inactive": 5757,
|
||||||
|
"active": 73055
|
||||||
|
},
|
||||||
|
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||||
|
"core_active_path_shorter_than_7m_cycles": 0,
|
||||||
|
"adapter_validity_differs_from_archive_cycles": 0,
|
||||||
|
"adapter_command_differs_from_archive_cycles": 42170,
|
||||||
|
"adapter_max_absolute_command_difference_c0_c1": [
|
||||||
|
0.33999999999999986,
|
||||||
|
0.0020000000000000018
|
||||||
|
],
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 157624,
|
||||||
|
"timing": "Original controls publication timestamps proxy computation time; repeated frames and gaps retained. No identified delay.",
|
||||||
|
"eligibility": "Adapter checks recorded services independently; full SubMaster health is unavailable. Core uses archived validity.",
|
||||||
|
"reference": "Recorded controlsState.desiredCurvature, already selected/limited. These two routes have no maneuver publications.",
|
||||||
|
"host_yaw": "Extract cs.yaw equals -carState.yawRate; current adapter uses it for bounded damping.",
|
||||||
|
"archived_core_revision": "5fc16abc7662020706e29f57d31a6d5e2bc1293a",
|
||||||
|
"archived_core_source_sha256": "8f3bc5d68e0051776f614a2ccffae84a88f7898dc95bdc12c23dcfe10dfe676a",
|
||||||
|
"cohorts": {
|
||||||
|
"small_request": {
|
||||||
|
"seconds": 475.24161910000026,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.06125184365338734,
|
||||||
|
0.0057692154738982855
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.057970374224682646,
|
||||||
|
0.007060795713496332
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 475.24161910000026,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.0666350702799088,
|
||||||
|
0.0057692154738982855
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn": {
|
||||||
|
"seconds": 47.85871389900012,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.18725251016049418,
|
||||||
|
0.046449510236699354
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.4773534845264775,
|
||||||
|
0.04315935101392594
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 47.85871389900012,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.21255148222786066,
|
||||||
|
0.046449510236699354
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_2_8": {
|
||||||
|
"seconds": 38.696717235999785,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.209264275431127,
|
||||||
|
0.014019080484620104
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.1593558816527209,
|
||||||
|
0.01823849401244768
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 38.696717235999785,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.22422047076469706,
|
||||||
|
0.014019080484620104
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_2_8": {
|
||||||
|
"seconds": 0.30159887100000304,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
2.090789090165622,
|
||||||
|
0.21878942570759013
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
2.155728831937131,
|
||||||
|
0.38375302254248855
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 0.30159887100000304,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
2.2977717861671105,
|
||||||
|
0.21878942570759013
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_8_15": {
|
||||||
|
"seconds": 79.2599167590001,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.02250905681378238,
|
||||||
|
0.005843904113223639
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.03477379324171293,
|
||||||
|
0.006545440153133947
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 79.2599167590001,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.027461818062006878,
|
||||||
|
0.005843904113223639
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_8_15": {
|
||||||
|
"seconds": 0.022264622000001566,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.20999999999999996,
|
||||||
|
0.04349999999999998
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.25,
|
||||||
|
0.04050000011920929
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 0.022264622000001566,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.20999999999999996,
|
||||||
|
0.04349999999999998
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_15_55": {
|
||||||
|
"seconds": 357.2849851050004,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.011622097035118838,
|
||||||
|
0.003925571829874466
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.03809717934501699,
|
||||||
|
0.0045587590980714285
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 357.2849851050004,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.017138026451004092,
|
||||||
|
0.003925571829874466
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_15_55": {
|
||||||
|
"seconds": 47.53485040600012,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.08686835454709245,
|
||||||
|
0.043216347944464766
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.4471065308273131,
|
||||||
|
0.030663943784303503
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 47.53485040600012,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.10939067791228956,
|
||||||
|
0.043216347944464766
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pose_quiet": {
|
||||||
|
"seconds": 427.99642020700026,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.01261133542366393,
|
||||||
|
0.00422389167351435
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.03724956081568198,
|
||||||
|
0.0046880581807907966
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 427.99642020700026,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.017590935519277658,
|
||||||
|
0.00422389167351435
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workspace_head": "01f8d51c82b3e863f1012d383b5994813ef01b81",
|
||||||
|
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||||
|
"opendbc_import_path": "/Users/ibpersonal/.codex/worktrees/b926/sunnypilot/opendbc_repo",
|
||||||
|
"source_sha256": {
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "7cddac7ce9e88cc32bc7afbec7f9df79b66bb4dfa2fa5f9b36654891fae23a19",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_path.py": "383538fc7cdae3bc28dffb71fe12ac5f3f9866ffbe6adfb7457f3593e9fc903a",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/route.npz": "51e8c26eedde253e171af47d704c1967ba45ae6825d883393bec1fb9e00251c1",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/metadata.json": "742afec55ed629155b22d387f376f878f7b2765221fe94f515a829052bdd916f",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/encoder_comparison.npz": "7a625d3ed5cbd8013d1028aa3bc421740551dcae5c3d60981208bd047af9794c",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/encoder_comparison.json": "64e1cc4be84394ac7ec408d383b99b48ada9b3fd129cdef2fd846e5fac620d66",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/pose_candidate/pose_replay.npz": "4457ccc0868354749da5b72c1dea0faf750f783dfdc87038101288fdcba1e707"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"route95": {
|
||||||
|
"scope": "Command construction and adapter reconstruction only; no counterfactual closed-loop score.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"executes_live_selector": false,
|
||||||
|
"cycles": 54738,
|
||||||
|
"core_active_cycles": 37614,
|
||||||
|
"core_exact_archived_match": true,
|
||||||
|
"cohorts_reproduced": true,
|
||||||
|
"adapter_active_cycles": 37614,
|
||||||
|
"adapter_status_counts": {
|
||||||
|
"inactive": 17124,
|
||||||
|
"active": 37614
|
||||||
|
},
|
||||||
|
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||||
|
"core_active_path_shorter_than_7m_cycles": 44,
|
||||||
|
"adapter_validity_differs_from_archive_cycles": 0,
|
||||||
|
"adapter_command_differs_from_archive_cycles": 25912,
|
||||||
|
"adapter_max_absolute_command_difference_c0_c1": [
|
||||||
|
0.2999999999999998,
|
||||||
|
0.0010000000000000009
|
||||||
|
],
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 109476,
|
||||||
|
"timing": "Original controls publication timestamps proxy computation time; repeated frames and gaps retained. No identified delay.",
|
||||||
|
"eligibility": "Adapter checks recorded services independently; full SubMaster health is unavailable. Core uses archived validity.",
|
||||||
|
"reference": "Recorded controlsState.desiredCurvature, already selected/limited. These two routes have no maneuver publications.",
|
||||||
|
"host_yaw": "Extract cs.yaw equals -carState.yawRate; current adapter uses it for bounded damping.",
|
||||||
|
"archived_core_revision": "5fc16abc7662020706e29f57d31a6d5e2bc1293a",
|
||||||
|
"archived_core_source_sha256": "8f3bc5d68e0051776f614a2ccffae84a88f7898dc95bdc12c23dcfe10dfe676a",
|
||||||
|
"cohorts": {
|
||||||
|
"small_request": {
|
||||||
|
"seconds": 160.93427628100014,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.027283284164810895,
|
||||||
|
0.006054573235641941
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.04189890174428264,
|
||||||
|
0.0075342351227490395
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 160.93427628100014,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.03232621215993653,
|
||||||
|
0.006054573235641941
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn": {
|
||||||
|
"seconds": 58.39837079799993,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.5425346915921071,
|
||||||
|
0.09102985199238507
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.63700479987207,
|
||||||
|
0.09691236120507696
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 58.39837079799993,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.5674989495695087,
|
||||||
|
0.09102985199238507
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_2_8": {
|
||||||
|
"seconds": 8.083969085000064,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.09941192804175551,
|
||||||
|
0.016572084086884566
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.07597383189748476,
|
||||||
|
0.016403469943482765
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 8.083969085000064,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.11170387210996009,
|
||||||
|
0.016572084086884566
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_2_8": {
|
||||||
|
"seconds": 2.9056519089999995,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
2.286598281178436,
|
||||||
|
0.3420570411209625
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
2.4371443775668484,
|
||||||
|
0.37360610108051345
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 2.9056519089999995,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
2.3669481787447944,
|
||||||
|
0.3420570411209625
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_8_15": {
|
||||||
|
"seconds": 32.021085868,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.028387860150467858,
|
||||||
|
0.0067602044219821
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.036941169841952046,
|
||||||
|
0.007349671742584858
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 32.021085868,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.033317964465740625,
|
||||||
|
0.0067602044219821
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_8_15": {
|
||||||
|
"seconds": 22.160645306000006,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.28668316323581683,
|
||||||
|
0.0671915746138579
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.3391022553367454,
|
||||||
|
0.07297678510653233
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 22.160645306000006,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.32040564199313326,
|
||||||
|
0.0671915746138579
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_15_55": {
|
||||||
|
"seconds": 120.82922132800007,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.010802222799973712,
|
||||||
|
0.004282512931613862
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.03987958989535526,
|
||||||
|
0.0065793876512313535
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 120.82922132800007,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.016212091011218406,
|
||||||
|
0.004282512931613862
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_15_55": {
|
||||||
|
"seconds": 33.332073582999925,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.07257996778378971,
|
||||||
|
0.03628926246224237
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.3416081588451539,
|
||||||
|
0.027322786376822172
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 33.332073582999925,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.08724830487921002,
|
||||||
|
0.03628926246224237
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pose_quiet": {
|
||||||
|
"seconds": 145.24136911700015,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.012566204405760934,
|
||||||
|
0.004614194153565963
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.03855743841849719,
|
||||||
|
0.006621274926345545
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 145.24136911700015,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.01710948709543248,
|
||||||
|
0.004614194153565963
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workspace_head": "01f8d51c82b3e863f1012d383b5994813ef01b81",
|
||||||
|
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||||
|
"opendbc_import_path": "/Users/ibpersonal/.codex/worktrees/b926/sunnypilot/opendbc_repo",
|
||||||
|
"source_sha256": {
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "7cddac7ce9e88cc32bc7afbec7f9df79b66bb4dfa2fa5f9b36654891fae23a19",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_path.py": "383538fc7cdae3bc28dffb71fe12ac5f3f9866ffbe6adfb7457f3593e9fc903a",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/route.npz": "6e5438867ea618e53ca395b60ff4b9b146256f1bf6f07dc8e92d663286074154",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/metadata.json": "f7c95a102e0e084bbcc9938399219080c1d3b26274b4ea3c3a9514ff7dc8ea8d",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/encoder_comparison.npz": "23bd05c4b6400299844acaba1d97051c96c23c682bf17b46e89e7f2cca5fce38",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/encoder_comparison.json": "1ab24a796d14817b8672ea6dfd39711d65cb06744eaaef7dcc02a840c392c4d3",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/pose_candidate/pose_replay.npz": "9cfbb3c6f0b1fdb7e3d38e6b64b8c94cffbcc9fabe41f6344d84a9b657b6af9b"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"route9b": {
|
||||||
|
"scope": "Fixed-input command replay only; no physical improvement or stability claim.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"cycles": 82386,
|
||||||
|
"eligible_cycles": 70703,
|
||||||
|
"status_counts": {
|
||||||
|
"inactive": 11683,
|
||||||
|
"active": 70703
|
||||||
|
},
|
||||||
|
"c1_exactly_unchanged": true,
|
||||||
|
"same_validity": true,
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 164772,
|
||||||
|
"cohorts": {
|
||||||
|
"eligible": {
|
||||||
|
"cycles": 70703,
|
||||||
|
"seconds": 711.571703289,
|
||||||
|
"changed_c0_cycles": 10475,
|
||||||
|
"mean_absolute_c0_change_m": 0.00219777369954635,
|
||||||
|
"max_absolute_c0_change_m": 0.13000000000000078,
|
||||||
|
"increased_absolute_c0_cycles": 9822,
|
||||||
|
"decreased_absolute_c0_cycles": 633,
|
||||||
|
"driver_input_percent": 3.0297623863274845,
|
||||||
|
"baseline_peak_absolute_c0_m": 3.96,
|
||||||
|
"candidate_peak_absolute_c0_m": 3.99
|
||||||
|
},
|
||||||
|
"driver_clean": {
|
||||||
|
"cycles": 63267,
|
||||||
|
"seconds": 636.7469998759999,
|
||||||
|
"changed_c0_cycles": 8901,
|
||||||
|
"mean_absolute_c0_change_m": 0.0014713899370902674,
|
||||||
|
"max_absolute_c0_change_m": 0.05999999999999961,
|
||||||
|
"increased_absolute_c0_cycles": 8314,
|
||||||
|
"decreased_absolute_c0_cycles": 568,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.24,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.24
|
||||||
|
},
|
||||||
|
"driver_clean_low_request_above_8mps": {
|
||||||
|
"cycles": 40656,
|
||||||
|
"seconds": 409.13067679800054,
|
||||||
|
"changed_c0_cycles": 7132,
|
||||||
|
"mean_absolute_c0_change_m": 0.0017587822497730527,
|
||||||
|
"max_absolute_c0_change_m": 0.020000000000000462,
|
||||||
|
"increased_absolute_c0_cycles": 6888,
|
||||||
|
"decreased_absolute_c0_cycles": 244,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.11000000000000032,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.11000000000000032
|
||||||
|
},
|
||||||
|
"turn": {
|
||||||
|
"cycles": 5459,
|
||||||
|
"seconds": 54.939139939000256,
|
||||||
|
"changed_c0_cycles": 119,
|
||||||
|
"mean_absolute_c0_change_m": 0.00021719119817404867,
|
||||||
|
"max_absolute_c0_change_m": 0.010000000000000675,
|
||||||
|
"increased_absolute_c0_cycles": 119,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.24,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.24
|
||||||
|
},
|
||||||
|
"right_entry": {
|
||||||
|
"cycles": 298,
|
||||||
|
"seconds": 2.9932484459999387,
|
||||||
|
"changed_c0_cycles": 216,
|
||||||
|
"mean_absolute_c0_change_m": 0.028050765542768245,
|
||||||
|
"max_absolute_c0_change_m": 0.10000000000000009,
|
||||||
|
"increased_absolute_c0_cycles": 216,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 20.395198628296086,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.95,
|
||||||
|
"candidate_peak_absolute_c0_m": 3.0300000000000002
|
||||||
|
},
|
||||||
|
"right_exit": {
|
||||||
|
"cycles": 115,
|
||||||
|
"seconds": 1.156770048999988,
|
||||||
|
"changed_c0_cycles": 0,
|
||||||
|
"mean_absolute_c0_change_m": 0.0,
|
||||||
|
"max_absolute_c0_change_m": 0.0,
|
||||||
|
"increased_absolute_c0_cycles": 0,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.7199999999999998,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.7199999999999998
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"baseline_commands_vs_recorded_publications": {
|
||||||
|
"paired_cycles": 70703,
|
||||||
|
"within_one_quantum_cycles": 65890,
|
||||||
|
"maximum_absolute_error_c0_c1": [
|
||||||
|
0.25000004768371564,
|
||||||
|
0.0005000143051147043
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timing": "Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.",
|
||||||
|
"baseline_version": "v3",
|
||||||
|
"baseline_revision": "01f8d51c82b3e863f1012d383b5994813ef01b81",
|
||||||
|
"candidate_version": "current",
|
||||||
|
"candidate_revision": "working_tree",
|
||||||
|
"focus_windows": [
|
||||||
|
[
|
||||||
|
"right_entry",
|
||||||
|
"637",
|
||||||
|
"640"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"right_exit",
|
||||||
|
"642.7",
|
||||||
|
"643.852"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"baseline_source_sha256": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||||
|
"candidate_source_sha256": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||||
|
"source_rlog_sha256": {
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--0--rlog.zst": "22746f7119109b73ed7f2c26ce8c99f87136e9124fb7fc14c9554409a28a7c3f",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--1--rlog.zst": "4c2e1d7083c31a2b37d0f8dd3be4d330898511b7e02c26f7d40ca9bc2779397d",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--2--rlog.zst": "62f3e049e220cd3681fadf386f2969537bd571998ae2f6ba2d08479428b5a28f",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--3--rlog.zst": "83bf0131b2d36b2ba7e5ba050bbc13c0a3350feb5c9b89dc9c87d3a37abebfb3",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--4--rlog.zst": "430985a80dd6e10f7abeb89457a17022e6bb6978617f415c905f584b1647603e",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--5--rlog.zst": "8c0c5ae6323ec33b3e14f84ca834f70cb56f6b29f471a350f1e3efc06b6ba553",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--6--rlog.zst": "db53dfa8156b9d66792c3eff0b2ce5d31b71ad41cc580dec85f528845593c184",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--7--rlog.zst": "91b0b3be10cb7d7d7f7dd2024d8f9ee99d1e9fd2204203a3a9a2f2f1c6e3fa03",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--8--rlog.zst": "687dbbfc49837efbfe8fa6bc091e40f7fad2908832234d7884f4616d1bc9ccff",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--9--rlog.zst": "a88ec4d25b04cdbf5844686fc77f6b28dca920c9b164e37ebf69844a3ae398fc",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--10--rlog.zst": "fe6b29580a6c94e1c236d13e18db4cd9f31cc1b25d52e1e6e19a5021125c9932",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--11--rlog.zst": "150d31b1944d7a1b8c562f3aee20b66cefa6c4e8d02660ec889907d835142f45",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--12--rlog.zst": "e105e5f703a70f36f1989c66fb46c35a65ff60a265b4332e10fa2e6875a2ced4",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--13--rlog.zst": "69304cf0c81401374f047c5022ca47d257ff9e59e30ec171325a2ee1e7ed13d4"
|
||||||
|
},
|
||||||
|
"opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||||
|
"source_sha256": {
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/damping_replay.py": "8c4f1f987b65e2be146c473a6e529168f08b1f7ce3c9514e22a9874450fa430e",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"route9e": {
|
||||||
|
"scope": "Fixed-input command replay only; no physical improvement or stability claim.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"cycles": 83668,
|
||||||
|
"eligible_cycles": 74669,
|
||||||
|
"status_counts": {
|
||||||
|
"inactive": 8999,
|
||||||
|
"active": 74669
|
||||||
|
},
|
||||||
|
"c1_exactly_unchanged": true,
|
||||||
|
"same_validity": true,
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 167336,
|
||||||
|
"cohorts": {
|
||||||
|
"eligible": {
|
||||||
|
"cycles": 74669,
|
||||||
|
"seconds": 751.5479497329998,
|
||||||
|
"changed_c0_cycles": 8108,
|
||||||
|
"mean_absolute_c0_change_m": 0.00147612892002983,
|
||||||
|
"max_absolute_c0_change_m": 0.13000000000000034,
|
||||||
|
"increased_absolute_c0_cycles": 7665,
|
||||||
|
"decreased_absolute_c0_cycles": 423,
|
||||||
|
"driver_input_percent": 2.7324748055386094,
|
||||||
|
"baseline_peak_absolute_c0_m": 4.38,
|
||||||
|
"candidate_peak_absolute_c0_m": 4.38
|
||||||
|
},
|
||||||
|
"driver_clean": {
|
||||||
|
"cycles": 66321,
|
||||||
|
"seconds": 667.5291971840106,
|
||||||
|
"changed_c0_cycles": 7257,
|
||||||
|
"mean_absolute_c0_change_m": 0.0012096637080993352,
|
||||||
|
"max_absolute_c0_change_m": 0.08000000000000007,
|
||||||
|
"increased_absolute_c0_cycles": 6883,
|
||||||
|
"decreased_absolute_c0_cycles": 374,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.4699999999999998,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.4699999999999998
|
||||||
|
},
|
||||||
|
"driver_clean_low_request_above_8mps": {
|
||||||
|
"cycles": 39635,
|
||||||
|
"seconds": 398.9613925449994,
|
||||||
|
"changed_c0_cycles": 5427,
|
||||||
|
"mean_absolute_c0_change_m": 0.0013748276784907216,
|
||||||
|
"max_absolute_c0_change_m": 0.020000000000000462,
|
||||||
|
"increased_absolute_c0_cycles": 5341,
|
||||||
|
"decreased_absolute_c0_cycles": 86,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.07000000000000028,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.07000000000000028
|
||||||
|
},
|
||||||
|
"turn": {
|
||||||
|
"cycles": 6219,
|
||||||
|
"seconds": 62.59491144700405,
|
||||||
|
"changed_c0_cycles": 244,
|
||||||
|
"mean_absolute_c0_change_m": 0.000391833417652352,
|
||||||
|
"max_absolute_c0_change_m": 0.010000000000000675,
|
||||||
|
"increased_absolute_c0_cycles": 244,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.4699999999999998,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.4699999999999998
|
||||||
|
},
|
||||||
|
"left_entry": {
|
||||||
|
"cycles": 238,
|
||||||
|
"seconds": 2.4048367620016506,
|
||||||
|
"changed_c0_cycles": 204,
|
||||||
|
"mean_absolute_c0_change_m": 0.03167584138917884,
|
||||||
|
"max_absolute_c0_change_m": 0.07000000000000028,
|
||||||
|
"increased_absolute_c0_cycles": 204,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 2.403816920672437,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.6100000000000003,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.6399999999999997
|
||||||
|
},
|
||||||
|
"left_peak": {
|
||||||
|
"cycles": 288,
|
||||||
|
"seconds": 2.891819394000777,
|
||||||
|
"changed_c0_cycles": 6,
|
||||||
|
"mean_absolute_c0_change_m": 0.00022373297976799967,
|
||||||
|
"max_absolute_c0_change_m": 0.019999999999999574,
|
||||||
|
"increased_absolute_c0_cycles": 6,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 6.41034524444729,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.9400000000000004,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.95
|
||||||
|
},
|
||||||
|
"left_exit": {
|
||||||
|
"cycles": 219,
|
||||||
|
"seconds": 2.203320298998733,
|
||||||
|
"changed_c0_cycles": 11,
|
||||||
|
"mean_absolute_c0_change_m": 0.0008062973780191794,
|
||||||
|
"max_absolute_c0_change_m": 0.019999999999999574,
|
||||||
|
"increased_absolute_c0_cycles": 11,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 9.936619705158035,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.51,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.51
|
||||||
|
},
|
||||||
|
"reversal": {
|
||||||
|
"cycles": 554,
|
||||||
|
"seconds": 5.570295188001182,
|
||||||
|
"changed_c0_cycles": 54,
|
||||||
|
"mean_absolute_c0_change_m": 0.002577813237060219,
|
||||||
|
"max_absolute_c0_change_m": 0.04999999999999982,
|
||||||
|
"increased_absolute_c0_cycles": 29,
|
||||||
|
"decreased_absolute_c0_cycles": 25,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.21999999999999975,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.21999999999999975
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"baseline_commands_vs_recorded_publications": {
|
||||||
|
"paired_cycles": 74669,
|
||||||
|
"within_one_quantum_cycles": 69690,
|
||||||
|
"maximum_absolute_error_c0_c1": [
|
||||||
|
0.369999988079071,
|
||||||
|
0.0010000016689301061
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timing": "Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.",
|
||||||
|
"baseline_version": "v3",
|
||||||
|
"baseline_revision": "01f8d51c82b3e863f1012d383b5994813ef01b81",
|
||||||
|
"candidate_version": "current",
|
||||||
|
"candidate_revision": "working_tree",
|
||||||
|
"focus_windows": [
|
||||||
|
[
|
||||||
|
"left_entry",
|
||||||
|
"173",
|
||||||
|
"175.4"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"left_peak",
|
||||||
|
"175.4",
|
||||||
|
"178.3"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"left_exit",
|
||||||
|
"178.3",
|
||||||
|
"180.5"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"reversal",
|
||||||
|
"728",
|
||||||
|
"734"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"baseline_source_sha256": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||||
|
"candidate_source_sha256": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||||
|
"source_rlog_sha256": {
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--0--rlog.zst": "de63532ae6aedf5dc7fd3ac8e47a2d96a8f065614f4aca79ae2f120ea00390c7",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--1--rlog.zst": "67b58197158b3e8f0581643f6657d2a85c47b0bb75dd3e00306d9ffae790008b",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--2--rlog.zst": "a4840f338f51b5f1864c79ce3a4f2b11dc13d58d52b8459961c9da57237f2cc2",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--3--rlog.zst": "ff3ea8d948006ab19c4dbfeeff59a509de3f193da47b91c88faa27ecd0b3f1fc",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--4--rlog.zst": "5ff1996c2336299128a11e657c32bc21921716b7e3bd401de69a2d49484aa223",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--5--rlog.zst": "238763457e89896933afaf9a5df325469ee8c02ddc53550df252531bc94fc540",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--6--rlog.zst": "2ee86de80cfd762be10cd2dfb2895ddbee6b813706e9c7261460203e09b9bc4d",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--7--rlog.zst": "017801f080861c63d799f87aebe30ab57cdf82f078ac882e6187d3870c403538",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--8--rlog.zst": "3729016bd1f00bb1077613b63fe25b21ba7112822b994e0e0aa2b4cd93bdb940",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--9--rlog.zst": "c40b3c1f6eb9252b85f176fee32cae16c23eaad3db830f6bbf37a730034a5ccf",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--10--rlog.zst": "e0ce8f231798073e5bbc34551fbd9493f169a06a068adc1957ef8cd914b71fbb",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--11--rlog.zst": "90863353f7c862a05618b4d0761dda3fcedd3cf6b4234c21c6dcc2eee3ec20b2",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--12--rlog.zst": "e064f8abde9615b2daf00f469ef36b438cd4b3a1637069467c84de91bee3bdf1",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--13--rlog.zst": "790fc438ce2675e0690fdce3b07bf54fdf6d06b17bbe7f5ff447adfa71189754",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--14--rlog.zst": "b98f5a5be660013b1fe11250fdc24ee4fe1d9dd1301b787ddfdff1f2bcd3e27f"
|
||||||
|
},
|
||||||
|
"opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||||
|
"source_sha256": {
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/damping_replay.py": "8c4f1f987b65e2be146c473a6e529168f08b1f7ce3c9514e22a9874450fa430e",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ablation_comparison": {
|
||||||
|
"route9b": {
|
||||||
|
"lab_variant_commands_exactly_match_production": true,
|
||||||
|
"opposed_nonzero_c0_commands": 24,
|
||||||
|
"max_absolute_difference_on_opposed_commands_m": 0.05999999999999961
|
||||||
|
},
|
||||||
|
"route9e": {
|
||||||
|
"lab_variant_commands_exactly_match_production": true,
|
||||||
|
"opposed_nonzero_c0_commands": 30,
|
||||||
|
"max_absolute_difference_on_opposed_commands_m": 0.02999999999999936
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"total_original_route_cycles": 299604,
|
||||||
|
"total_float32_can_round_trips": 817346,
|
||||||
|
"source_sha256": {
|
||||||
|
"openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action.py": "30cfffb86fcd3830320e9b2ec1f65cdfd219684a55d0bc0bb13f77966cc18b53",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "8ab5dd52958f13a2426e07f8713b7d8d56c52b8a05d173ffba87ffccc0087796",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_damping.py": "a7062d2c3c46b3abcd4c3facd97970644ce609fb55285daa0359681950948250",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_prediction.py": "eb50e1d6acdb5e7332fadc3dcfcfa1b3809545e607c47c54b0ba3de80b2df208",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_selection.py": "f826c6328f0abac2a61f1a0a6f8d119fdbab858e363cb466d84ba9a7783059cd",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_controlsd_logging.py": "175a1969ac83b14b88798bbde37371f740cd05cf3f8f6e5d21bbf9afe857bb57",
|
||||||
|
"tools/ford_pscm_lab/__init__.py": "db4b8b7d2e317ed34ca0ec220bf9d53e7b80a23e766f4e1cb2c8224dada45f2e",
|
||||||
|
"tools/ford_pscm_lab/damping_replay.py": "8c4f1f987b65e2be146c473a6e529168f08b1f7ce3c9514e22a9874450fa430e",
|
||||||
|
"tools/ford_pscm_lab/model_action_replay.py": "7cddac7ce9e88cc32bc7afbec7f9df79b66bb4dfa2fa5f9b36654891fae23a19",
|
||||||
|
"tools/ford_pscm_lab/stress_model_action.py": "66adef6cba120a1a3d8e9730ea987ef49f8fe283023362dbb99608dee0e3229f",
|
||||||
|
"tools/ford_pscm_lab/test_model_action_replay.py": "1f7d90c06ec09b405571565632653dc7d984b106b955877916d4a0836c11e3b5",
|
||||||
|
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "41ac31726b941e470f5be34e2523dc1fc2b25fb6a12705ab267a57ed4f3c27c8",
|
||||||
|
"openpilot/sunnypilot/sunnylink/settings_ui.json": "c44c75e02042a0485b5bd7896e0b794761d768bbe513b6dc9c2c986ddd22b44c"
|
||||||
|
},
|
||||||
|
"artifact_sha256": {
|
||||||
|
".cache/ford_prediction_uncapped/uncapped_red.txt": "09160dbc36320be886561cfdbdd21eef0c8bc420a11bae9bb1a8dd43f7b93a3c",
|
||||||
|
".cache/ford_prediction_uncapped/suite_run.txt": "99b3bd2af256c9a4a083cb015a51a458a26ac02a8cad73c28c47470f9a5eb910",
|
||||||
|
".cache/ford_prediction_uncapped/coverage.json": "50a282c90fa86147ed6925fdf37918471f8e5204a4c1e1ef9adb39335e220f42",
|
||||||
|
".cache/ford_prediction_uncapped/stress.json": "e4226f403a3a914b9f3a7868b14a2b763215ec1f77e7d45ac06604859acc9fb6",
|
||||||
|
".cache/ford_prediction_uncapped/route90/report.json": "c5cee3c56f9f7f292c8069be22dd1ffb1649055fccaf829b28d96bdec6558541",
|
||||||
|
".cache/ford_prediction_uncapped/route95/report.json": "6fcc8a3bd7abb5b513888cb64426670e7e05f8b2100b1876be196a800cec2c90",
|
||||||
|
".cache/ford_prediction_uncapped/final9b/report.json": "79faca3ff89e2015dad4823d4d988ea26ead55c7e51e4a594258102d32701b07",
|
||||||
|
".cache/ford_prediction_uncapped/final9e/report.json": "9b003ce97225e65e03e351c5e133f3fed08dc79e026f73308098d51a98dd67ae",
|
||||||
|
".cache/ford_prediction_uncapped/route90/commands.npz": "8787aabbe7007bcd7c68976c272085761aec6a4f360fa6cc8c8553bd1421c92b",
|
||||||
|
".cache/ford_prediction_uncapped/route95/commands.npz": "349b716bd910cd030716b9ef519ee24f620fae5280776ffa871bc43318939400",
|
||||||
|
".cache/ford_prediction_uncapped/final9b/commands.npz": "ff4a14fc74b144800f459c250be961ebb05a46a20e6ad1fc0394a659c9854394",
|
||||||
|
".cache/ford_prediction_uncapped/final9e/commands.npz": "59f6046acb10b00ef00e7b9feb5b8547139a67a2f8767d7cbfe3603a59e6c77d"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# Experimental Ford selected-action controller, v5
|
||||||
|
|
||||||
|
V5 removes the excess-yaw C0 attenuation at the user's request. The damping
|
||||||
|
function, 0.02 rad/s deadband and 0.2 s reduction scale are deleted. Valid measured
|
||||||
|
yaw no longer changes either command target. Existing yaw input-health checks
|
||||||
|
and logging remain. No replacement gain, increment gate or controller state is added.
|
||||||
|
|
||||||
|
The full 150 ms geometric prediction introduced in v4 remains, along with the
|
||||||
|
7 m offset station and one-second heading scale. This is not a return to v1:
|
||||||
|
v1 did not predict the offset. C0/C1 bounds, slew, quantization, service gates,
|
||||||
|
engagement, downstream driver arbitration and zero C2/C3 are unchanged.
|
||||||
|
|
||||||
|
The latest supplied route a0 ran v2, not v4. Prior same-input comparisons found
|
||||||
|
identical v1/v2 commands during its driver-clean minor-bend warning intervals
|
||||||
|
and the preceding five seconds. That evidence does not identify damping as the
|
||||||
|
cause of those misses. Removing damping can restore C0 demand where the damper
|
||||||
|
was active, including turn exits; it is not evidence of improved tracking or
|
||||||
|
reduced oversteer.
|
||||||
|
|
||||||
|
## Offline validation
|
||||||
|
|
||||||
|
The regression suite checks yaw-independent commands through mirrored turn
|
||||||
|
entry, release and reversal, including valid yaw extremes and small yaw offsets.
|
||||||
|
Six cases fail with v4 damping present and pass after removal. Invalid yaw still
|
||||||
|
resets the controller. Actual controlsd selection, upstream limiting, Float32
|
||||||
|
publication and downstream CAN tests cover both model and maneuver references.
|
||||||
|
|
||||||
|
Full-rlog comparisons run pinned v4 against production v5 on routes9b, 9e and a0.
|
||||||
|
They preserve original clocks, exact consumed model frames and causal carState;
|
||||||
|
publication times proxy computation time, and complete SubMaster health is
|
||||||
|
unavailable. The numerical stress run checks independent geometric targets,
|
||||||
|
scalar slew, mirrored turns and Float32/CAN packing. Recorded vehicle motion
|
||||||
|
stays fixed: none of these checks establishes counterfactual steering response,
|
||||||
|
closed-loop stability or a physical tracking improvement.
|
||||||
|
|
||||||
|
Results and source hashes are recorded in
|
||||||
|
`ford_model_action_no_yaw_damping_validation.json`. Earlier validation documents
|
||||||
|
remain archives of their specified controller versions.
|
||||||
|
|
||||||
|
Validation passes 356 tests and 26 subtests with 100% controller statement and
|
||||||
|
branch coverage, 280,636 recorded route cycles and 779,410 Float32/CAN round trips,
|
||||||
|
including 200,000 random stress cycles. C1 and input eligibility match v4 exactly
|
||||||
|
on all three routes. Commands during all 1,578 driver-clean ordinary-bend warning
|
||||||
|
cycles on route a0 also remain identical to v4. At the earlier right-turn exit,
|
||||||
|
removing damping increases C0 magnitude by a mean 0.079 m, maximum 0.13 m; these
|
||||||
|
are command offsets, not measured vehicle displacement.
|
||||||
|
|
||||||
|
The module is 171 total lines, or 111 code lines excluding blanks, comments and
|
||||||
|
docstrings, with two control states. No hardware build or device boot was performed.
|
||||||
|
|
||||||
|
## Reproduce and select
|
||||||
|
|
||||||
|
Use the dependency setup and combined suite in the
|
||||||
|
[drive-test guide](ford_model_action_drive_test.md). Replay and stress commands:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python -m tools.ford_pscm_lab.damping_replay /path/to/route9b/rlogs --baseline v4 --candidate current --window right_entry 637 640 --window right_exit 642.7 643.852 --output /path/to/separate/route9b-results
|
||||||
|
python -m tools.ford_pscm_lab.damping_replay /path/to/route9e/rlogs --baseline v4 --candidate current --window left_entry 173 175.4 --window left_peak 175.4 178.3 --window left_exit 178.3 180.5 --output /path/to/separate/route9e-results
|
||||||
|
python -m tools.ford_pscm_lab.damping_replay /path/to/routea0/rlogs --baseline v4 --candidate current --window bends_5min 298 338 --window bend_7min 449 458 --window bend_9min 579 588 --output /path/to/separate/routea0-results
|
||||||
|
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260908 --opendbc-revision c21a9013700734dd20b09e05aa68329ad8cc20f9 --output /path/to/stress.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The same default-off **Selected-Action Path Tracking (Experimental)** Sunnylink
|
||||||
|
toggle selects v5 on the CAN FD F-150 Lightning. Deployment remains
|
||||||
|
`sunnypilot/sunnypilot`, branch `hiimisaac-dev`. After updating, restart controlsd
|
||||||
|
through a real offroad-to-onroad cycle. Diagnostics identify
|
||||||
|
`model-action-c0-c1-prediction-v5`; `calibration_approved=false` remains explicit.
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
{
|
||||||
|
"date": "2026-09-08",
|
||||||
|
"baseline_commit": "7e63449749d112f096c56cb848dd289054e5f85b",
|
||||||
|
"hypothesis": "model-action-c0-c1-prediction-v5",
|
||||||
|
"scope": "Remove yaw damping only; retain full path prediction and existing input-health gates. Fixed-input command checks, not physical tracking or stability evidence.",
|
||||||
|
"deployment_target": {
|
||||||
|
"repository": "sunnypilot/sunnypilot",
|
||||||
|
"branch": "hiimisaac-dev"
|
||||||
|
},
|
||||||
|
"calibration_approved": false,
|
||||||
|
"hardware_build_and_device_boot": "not performed",
|
||||||
|
"opendbc_revision": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||||
|
"controller_size": {
|
||||||
|
"total_lines": 171,
|
||||||
|
"code_lines_excluding_blanks_comments_docstrings": 111,
|
||||||
|
"core_persistent_values": 2,
|
||||||
|
"adapter_timestamps": 3
|
||||||
|
},
|
||||||
|
"checks": {
|
||||||
|
"combined_ford_params_sunnylink_suite": "356 passed, 26 subtests passed; no skips",
|
||||||
|
"coverage": {
|
||||||
|
"covered_lines": 102,
|
||||||
|
"num_statements": 102,
|
||||||
|
"percent_covered": 100.0,
|
||||||
|
"percent_covered_display": "100",
|
||||||
|
"missing_lines": 0,
|
||||||
|
"excluded_lines": 0,
|
||||||
|
"percent_statements_covered": 100.0,
|
||||||
|
"percent_statements_covered_display": "100",
|
||||||
|
"num_branches": 30,
|
||||||
|
"num_partial_branches": 0,
|
||||||
|
"covered_branches": 30,
|
||||||
|
"missing_branches": 0,
|
||||||
|
"percent_branches_covered": 100.0,
|
||||||
|
"percent_branches_covered_display": "100"
|
||||||
|
},
|
||||||
|
"ruff": "pass",
|
||||||
|
"ty_controller_and_lab": "pass",
|
||||||
|
"settings_compiler_check": "pass",
|
||||||
|
"removal_regression_probe": "6 cases fail with v4 damping present; all 21 yaw tests pass after removal.",
|
||||||
|
"reviews": {
|
||||||
|
"standards": {
|
||||||
|
"remaining_findings": 0
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"remaining_findings": 0,
|
||||||
|
"independent_focused_tests_passed": 107
|
||||||
|
},
|
||||||
|
"corrected_findings": [
|
||||||
|
"Removed stale damping claim from Sunnylink YAML and regenerated JSON.",
|
||||||
|
"Corrected replay yaw-use metadata to input-health checks and diagnostics."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"total_original_route_cycles": 280636,
|
||||||
|
"total_float32_can_round_trips": 779410,
|
||||||
|
"stress": {
|
||||||
|
"seed": 20260908,
|
||||||
|
"random_cycles": 200000,
|
||||||
|
"mirrored_core_updates": 200000,
|
||||||
|
"invalid_or_inactive_resets": 3537,
|
||||||
|
"field_boundary_cases": 18138,
|
||||||
|
"float32_can_round_trips": 218138,
|
||||||
|
"analytic_targets_scalar_slew_and_mirror_checks_pass": true,
|
||||||
|
"valid_yaw_does_not_affect_targets_checked": true,
|
||||||
|
"full_geometric_prediction_checked": true,
|
||||||
|
"direct_raw_float32_packing_matches_host_output": true,
|
||||||
|
"max_continuous_step_c0_c1": [
|
||||||
|
0.4000000000003041,
|
||||||
|
0.05000000000000002
|
||||||
|
],
|
||||||
|
"calibration_approved": false,
|
||||||
|
"scope": "Numerical construction only; no PSCM response or closed-loop performance claims.",
|
||||||
|
"opendbc_import_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9"
|
||||||
|
},
|
||||||
|
"routes": {
|
||||||
|
"route9b": {
|
||||||
|
"cycles": 82386,
|
||||||
|
"eligible_cycles": 70703,
|
||||||
|
"same_validity": true,
|
||||||
|
"c1_exactly_unchanged": true,
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 164772,
|
||||||
|
"baseline_revision": "7e63449749d112f096c56cb848dd289054e5f85b",
|
||||||
|
"baseline_source_sha256": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||||
|
"candidate_source_sha256": "213a4dfa586092c28d8e6e2c27a7dd3622c5862615db9afd5a4e2d38144b1af8",
|
||||||
|
"source_rlog_sha256": {
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--0--rlog.zst": "22746f7119109b73ed7f2c26ce8c99f87136e9124fb7fc14c9554409a28a7c3f",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--1--rlog.zst": "4c2e1d7083c31a2b37d0f8dd3be4d330898511b7e02c26f7d40ca9bc2779397d",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--2--rlog.zst": "62f3e049e220cd3681fadf386f2969537bd571998ae2f6ba2d08479428b5a28f",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--3--rlog.zst": "83bf0131b2d36b2ba7e5ba050bbc13c0a3350feb5c9b89dc9c87d3a37abebfb3",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--4--rlog.zst": "430985a80dd6e10f7abeb89457a17022e6bb6978617f415c905f584b1647603e",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--5--rlog.zst": "8c0c5ae6323ec33b3e14f84ca834f70cb56f6b29f471a350f1e3efc06b6ba553",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--6--rlog.zst": "db53dfa8156b9d66792c3eff0b2ce5d31b71ad41cc580dec85f528845593c184",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--7--rlog.zst": "91b0b3be10cb7d7d7f7dd2024d8f9ee99d1e9fd2204203a3a9a2f2f1c6e3fa03",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--8--rlog.zst": "687dbbfc49837efbfe8fa6bc091e40f7fad2908832234d7884f4616d1bc9ccff",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--9--rlog.zst": "a88ec4d25b04cdbf5844686fc77f6b28dca920c9b164e37ebf69844a3ae398fc",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--10--rlog.zst": "fe6b29580a6c94e1c236d13e18db4cd9f31cc1b25d52e1e6e19a5021125c9932",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--11--rlog.zst": "150d31b1944d7a1b8c562f3aee20b66cefa6c4e8d02660ec889907d835142f45",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--12--rlog.zst": "e105e5f703a70f36f1989c66fb46c35a65ff60a265b4332e10fa2e6875a2ced4",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--13--rlog.zst": "69304cf0c81401374f047c5022ca47d257ff9e59e30ec171325a2ee1e7ed13d4"
|
||||||
|
},
|
||||||
|
"focus_cohorts": {
|
||||||
|
"right_entry": {
|
||||||
|
"cycles": 298,
|
||||||
|
"seconds": 2.9932484459999387,
|
||||||
|
"changed_c0_cycles": 100,
|
||||||
|
"mean_absolute_c0_change_m": 0.007683282515599457,
|
||||||
|
"max_absolute_c0_change_m": 0.04999999999999982,
|
||||||
|
"increased_absolute_c0_cycles": 100,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 20.395198628296086,
|
||||||
|
"baseline_peak_absolute_c0_m": 3.0300000000000002,
|
||||||
|
"candidate_peak_absolute_c0_m": 3.0300000000000002
|
||||||
|
},
|
||||||
|
"right_exit": {
|
||||||
|
"cycles": 115,
|
||||||
|
"seconds": 1.156770048999988,
|
||||||
|
"changed_c0_cycles": 115,
|
||||||
|
"mean_absolute_c0_change_m": 0.07934493768172877,
|
||||||
|
"max_absolute_c0_change_m": 0.13000000000000078,
|
||||||
|
"increased_absolute_c0_cycles": 115,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.7199999999999998,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.7800000000000002
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"route9e": {
|
||||||
|
"cycles": 83668,
|
||||||
|
"eligible_cycles": 74669,
|
||||||
|
"same_validity": true,
|
||||||
|
"c1_exactly_unchanged": true,
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 167336,
|
||||||
|
"baseline_revision": "7e63449749d112f096c56cb848dd289054e5f85b",
|
||||||
|
"baseline_source_sha256": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||||
|
"candidate_source_sha256": "213a4dfa586092c28d8e6e2c27a7dd3622c5862615db9afd5a4e2d38144b1af8",
|
||||||
|
"source_rlog_sha256": {
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--0--rlog.zst": "de63532ae6aedf5dc7fd3ac8e47a2d96a8f065614f4aca79ae2f120ea00390c7",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--1--rlog.zst": "67b58197158b3e8f0581643f6657d2a85c47b0bb75dd3e00306d9ffae790008b",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--2--rlog.zst": "a4840f338f51b5f1864c79ce3a4f2b11dc13d58d52b8459961c9da57237f2cc2",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--3--rlog.zst": "ff3ea8d948006ab19c4dbfeeff59a509de3f193da47b91c88faa27ecd0b3f1fc",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--4--rlog.zst": "5ff1996c2336299128a11e657c32bc21921716b7e3bd401de69a2d49484aa223",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--5--rlog.zst": "238763457e89896933afaf9a5df325469ee8c02ddc53550df252531bc94fc540",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--6--rlog.zst": "2ee86de80cfd762be10cd2dfb2895ddbee6b813706e9c7261460203e09b9bc4d",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--7--rlog.zst": "017801f080861c63d799f87aebe30ab57cdf82f078ac882e6187d3870c403538",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--8--rlog.zst": "3729016bd1f00bb1077613b63fe25b21ba7112822b994e0e0aa2b4cd93bdb940",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--9--rlog.zst": "c40b3c1f6eb9252b85f176fee32cae16c23eaad3db830f6bbf37a730034a5ccf",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--10--rlog.zst": "e0ce8f231798073e5bbc34551fbd9493f169a06a068adc1957ef8cd914b71fbb",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--11--rlog.zst": "90863353f7c862a05618b4d0761dda3fcedd3cf6b4234c21c6dcc2eee3ec20b2",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--12--rlog.zst": "e064f8abde9615b2daf00f469ef36b438cd4b3a1637069467c84de91bee3bdf1",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--13--rlog.zst": "790fc438ce2675e0690fdce3b07bf54fdf6d06b17bbe7f5ff447adfa71189754",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--14--rlog.zst": "b98f5a5be660013b1fe11250fdc24ee4fe1d9dd1301b787ddfdff1f2bcd3e27f"
|
||||||
|
},
|
||||||
|
"focus_cohorts": {
|
||||||
|
"left_entry": {
|
||||||
|
"cycles": 238,
|
||||||
|
"seconds": 2.4048367620016506,
|
||||||
|
"changed_c0_cycles": 0,
|
||||||
|
"mean_absolute_c0_change_m": 0.0,
|
||||||
|
"max_absolute_c0_change_m": 0.0,
|
||||||
|
"increased_absolute_c0_cycles": 0,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 2.403816920672437,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.6399999999999997,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.6399999999999997
|
||||||
|
},
|
||||||
|
"left_peak": {
|
||||||
|
"cycles": 288,
|
||||||
|
"seconds": 2.891819394000777,
|
||||||
|
"changed_c0_cycles": 0,
|
||||||
|
"mean_absolute_c0_change_m": 0.0,
|
||||||
|
"max_absolute_c0_change_m": 0.0,
|
||||||
|
"increased_absolute_c0_cycles": 0,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 6.41034524444729,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.95,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.95
|
||||||
|
},
|
||||||
|
"left_exit": {
|
||||||
|
"cycles": 219,
|
||||||
|
"seconds": 2.203320298998733,
|
||||||
|
"changed_c0_cycles": 129,
|
||||||
|
"mean_absolute_c0_change_m": 0.037433836245882854,
|
||||||
|
"max_absolute_c0_change_m": 0.11000000000000032,
|
||||||
|
"increased_absolute_c0_cycles": 128,
|
||||||
|
"decreased_absolute_c0_cycles": 1,
|
||||||
|
"driver_input_percent": 9.936619705158035,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.51,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.51
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routea0": {
|
||||||
|
"cycles": 114582,
|
||||||
|
"eligible_cycles": 105382,
|
||||||
|
"same_validity": true,
|
||||||
|
"c1_exactly_unchanged": true,
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 229164,
|
||||||
|
"baseline_revision": "7e63449749d112f096c56cb848dd289054e5f85b",
|
||||||
|
"baseline_source_sha256": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||||
|
"candidate_source_sha256": "213a4dfa586092c28d8e6e2c27a7dd3622c5862615db9afd5a4e2d38144b1af8",
|
||||||
|
"source_rlog_sha256": {
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--0--rlog.zst": "57b8113783f70f9176e1f2703e70185f64df07b3283f739a6fd7b182a2c92417",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--1--rlog.zst": "a821ce7df80c6466110a6f6433b2c9482f5f3a5b4227e8459182c3ee9fb8c7a7",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--2--rlog.zst": "984cca8e311be8a61444ca2bc23bbf3937c6f00264303faefb7efb8f0a3e8aa2",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--3--rlog.zst": "c6ea25b226aa875a15a8f8356522ab3b12501d280f63ebe0438a9d50b2116279",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--4--rlog.zst": "99b129e2965674fb3406855d1b022c0876083984adf2f2ef51c07f1e699974ea",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--5--rlog.zst": "428e2ce9a1ac0ca7135c00221590c286ab32b4ce5cf24d376de1519b39b25fec",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--6--rlog.zst": "efca7fedd0aaed4be4500fd1a0c67f01d8f805cfdd681a0a9c0439061ec2f36f",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--7--rlog.zst": "1da238f57b63efbbf7dfd2b147637c1b846736e198999741817fc0646d4f60b7",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--8--rlog.zst": "b61209325da26751b98da85fe166dab65ab9eaaef129b859a59fe99e4de5ab65",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--9--rlog.zst": "cf555f1c57a9afdbed702b5934892acb5b2174ef48f1fcf494422aba9bd01321",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--10--rlog.zst": "13ff83c236cca64b57c11ffa05cb74511af1258cde3cf0554395922769a39683",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--11--rlog.zst": "848a1d26fd72bbe4e119339bd405aedb0b9d7e6b96329d0b868a27bd01b6b128",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--12--rlog.zst": "9e8bf8597942cd74d8d04c783148b8201f63f56e55b766f7887f27465de276f2",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--13--rlog.zst": "e37575ed1424d574015c20a3066754bcca6fdacbb104db2bedce782428fd48ef",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--14--rlog.zst": "b67c7ed7ddd957bf5dcbcdb0604ddc9c52cd7af9d6aa70c1eae3e7622cee160d",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--15--rlog.zst": "6baaf9fdf64c72a9be70d261580914e2489f363230d95a1291b8479bcc249ba2",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--16--rlog.zst": "f84803fa2a615caf59a6bd4e9b2848059dd9f4a7f0c01ac4ec7e2347e317b5e7",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--17--rlog.zst": "f0f5f36cb155c18f8f9f90f700e1e91e169ef0442fd70670323be4a1c658040e",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--18--rlog.zst": "8674d5822949bafaefc0d0f30b0e91603354b9981de7566419a2e5b780054e64",
|
||||||
|
"84865544361f55cb_000000a0--5e86c30dae--19--rlog.zst": "b5df55701d5bcfbfd750608a1dfbcba9b931a39fba7a82e87bc7915b8ca2333f"
|
||||||
|
},
|
||||||
|
"focus_cohorts": {
|
||||||
|
"bends_5min": {
|
||||||
|
"cycles": 3966,
|
||||||
|
"seconds": 39.99197329400005,
|
||||||
|
"changed_c0_cycles": 8,
|
||||||
|
"mean_absolute_c0_change_m": 1.950586094527418e-05,
|
||||||
|
"max_absolute_c0_change_m": 0.009999999999999787,
|
||||||
|
"increased_absolute_c0_cycles": 8,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 14.825647085260368,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.3099999999999996,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.3099999999999996
|
||||||
|
},
|
||||||
|
"bend_7min": {
|
||||||
|
"cycles": 893,
|
||||||
|
"seconds": 8.995078784999919,
|
||||||
|
"changed_c0_cycles": 0,
|
||||||
|
"mean_absolute_c0_change_m": 0.0,
|
||||||
|
"max_absolute_c0_change_m": 0.0,
|
||||||
|
"increased_absolute_c0_cycles": 0,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.20000000000000018,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.20000000000000018
|
||||||
|
},
|
||||||
|
"bend_9min": {
|
||||||
|
"cycles": 895,
|
||||||
|
"seconds": 8.998970718999999,
|
||||||
|
"changed_c0_cycles": 0,
|
||||||
|
"mean_absolute_c0_change_m": 0.0,
|
||||||
|
"max_absolute_c0_change_m": 0.0,
|
||||||
|
"increased_absolute_c0_cycles": 0,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 0.22147398432951632,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.2400000000000002,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.2400000000000002
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routea0_ordinary_bend_warnings_v4_vs_v5": {
|
||||||
|
"definition": "Driver-clean +/-1s, speed>=8m/s, absolute desired wheel angle 3 to30deg. Proxy for ordinary bends, not map geometry.",
|
||||||
|
"cycles": 1578,
|
||||||
|
"seconds": 15.92979767199978,
|
||||||
|
"changed_c0_cycles": 0,
|
||||||
|
"changed_c1_cycles": 0,
|
||||||
|
"max_c0_change_m": 0.0
|
||||||
|
},
|
||||||
|
"source_sha256": {
|
||||||
|
"openpilot/selfdrive/controls/lib/ford_model_action.py": "213a4dfa586092c28d8e6e2c27a7dd3622c5862615db9afd5a4e2d38144b1af8",
|
||||||
|
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "be610c09e4bbd93e84d1920cfbb3e9609b3fbb7dca8eb229b8e67befb4b2ca84",
|
||||||
|
"openpilot/sunnypilot/sunnylink/settings_ui.json": "4d2dffdfd81190a871bd41e28e5832502aa95d8aacf91daa7e68382085588aca",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action.py": "30cfffb86fcd3830320e9b2ec1f65cdfd219684a55d0bc0bb13f77966cc18b53",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "7f8b13c4d85217cbb6bd32ec193ef18bdb30bdf1f2b59fa7fa9d40988cb20bea",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_prediction.py": "eb50e1d6acdb5e7332fadc3dcfcfa1b3809545e607c47c54b0ba3de80b2df208",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_selection.py": "f826c6328f0abac2a61f1a0a6f8d119fdbab858e363cb466d84ba9a7783059cd",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_yaw.py": "d646f89e2c4d4e112d03e77c5c34b7541b0bdba800419d472a4a69753637c515",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_controlsd_logging.py": "9697db696d5a01838ea5cdcf4f1771d813a647f5038cabe9f0460778a2936929",
|
||||||
|
"tools/ford_pscm_lab/__init__.py": "db4b8b7d2e317ed34ca0ec220bf9d53e7b80a23e766f4e1cb2c8224dada45f2e",
|
||||||
|
"tools/ford_pscm_lab/damping_replay.py": "d21565a29b9c2b4668fafe07f995c15e113888a1f4787f37d72d02d36998f9cf",
|
||||||
|
"tools/ford_pscm_lab/model_action_replay.py": "a90caed1c46c6f964fecb50bd0f531ae355505f1e84be9a1e37243d910b292b4",
|
||||||
|
"tools/ford_pscm_lab/stress_model_action.py": "123c6c0c9f53a5af2ab50bb568e47c0f8960b7d49fad2fb6ea74de5c63e03ad5",
|
||||||
|
"tools/ford_pscm_lab/test_model_action_replay.py": "bf1a1612474cb7308b3640cba535ee534999fdecdf055ae6d7bc83b9a8e66f63"
|
||||||
|
},
|
||||||
|
"artifact_sha256": {
|
||||||
|
".cache/ford_no_yaw_damping/removal_red.txt": "4aa79e5af1066d66db65853faad3923f8645f5dff8383fa3106489a3e53cae18",
|
||||||
|
".cache/ford_no_yaw_damping/suite_run.txt": "434d80389bc7d2859070c9f9df0cac1ac0f854151b5888dcedc2ce38df23d519",
|
||||||
|
".cache/ford_no_yaw_damping/coverage.json": "d99baeb8729a1394b41d9a4739b538372aad27b56d379f4b9cefe682ed99d662",
|
||||||
|
".cache/ford_no_yaw_damping/stress.json": "3e2c49dcc473d90ea03c95dc12905c2216ee573a691891b8a1f56ba82fd78251",
|
||||||
|
".cache/ford_no_yaw_damping/route9b/report.json": "446371173fed1ff632ece4286e53d0d941491be590d467af960d1931431212ca",
|
||||||
|
".cache/ford_no_yaw_damping/route9b/commands.npz": "d543a92991f06b56e20d0ed58daf6e4ef6a0021b79c027570b8d997658336b9d",
|
||||||
|
".cache/ford_no_yaw_damping/route9e/report.json": "fb8ddd8d2c242a14a63993593c96604e7306dcd97219d073bbcfcbeba68af45b",
|
||||||
|
".cache/ford_no_yaw_damping/route9e/commands.npz": "375a94227f3cb1a161a1295496908baa428d5ef0e0b79134a728f876477dbd6b",
|
||||||
|
".cache/ford_no_yaw_damping/routea0/report.json": "7959b77af715016b4da45b9bc8f095872702f662950324f64a0f4d332c814eb0",
|
||||||
|
".cache/ford_no_yaw_damping/routea0/commands.npz": "4f9dcde39ad9e20958ad04e53dbb32830c6a933dc7f743fdffe5a612252cb16b"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
# Experimental Ford path prediction, v3
|
||||||
|
|
||||||
|
This document and its counts describe archived v3. The
|
||||||
|
[current v4 experiment](ford_model_action_full_prediction.md) removes the extra
|
||||||
|
15 cm / 25% prediction adjustment cap.
|
||||||
|
|
||||||
|
The latest driven route9e used v1 (`5fc16abc7`), before the v2 yaw damping.
|
||||||
|
It often follows the requested steering angle closely, but some tight turns
|
||||||
|
fall behind after a reasonable initial turn-in. The requested angle is replanned
|
||||||
|
from the car's changing position; a large late request may partly be a recovery
|
||||||
|
request after arriving wide. It is not proof that the original turn required
|
||||||
|
that much steering. The generic PSCM limit flag does not identify a torque,
|
||||||
|
rate or mechanical limit, and the miss starts before our C1 cap in the clearest
|
||||||
|
left turn.
|
||||||
|
|
||||||
|
Short measured-motion integrations against earlier frozen model paths are
|
||||||
|
consistent with a growing miss, but model uncertainty, reference timing and
|
||||||
|
some driver input prevent a conclusive causal attribution. V3 tests a bounded
|
||||||
|
change to initial path demand. No counterfactual physical tracking score is
|
||||||
|
claimed from replaying fixed logs.
|
||||||
|
|
||||||
|
## Change and bounds
|
||||||
|
|
||||||
|
Start with the current model's lateral offset at 7 m of path arc length, as in
|
||||||
|
v1/v2. Advance the reference pose by speed × 0.15 s along the **selected,
|
||||||
|
upstream-limited curvature**, and read the same model path 7 m beyond that
|
||||||
|
advance, expressed in the predicted ego frame. Bound the change from the
|
||||||
|
original offset to both ±0.15 m and ±25% of its magnitude. Prediction cannot
|
||||||
|
reverse that target or create C0 from a zero offset.
|
||||||
|
|
||||||
|
For advance `d`, selected curvature `k`, rotation `theta = k*d`, and model
|
||||||
|
point `(x, y)` at arc station `7+d`, the predicted lateral coordinate is:
|
||||||
|
|
||||||
|
```
|
||||||
|
y_predicted = cos(theta)*y - sin(theta)*x + (1-cos(theta))/k
|
||||||
|
```
|
||||||
|
|
||||||
|
The code evaluates the last term continuously at zero curvature without
|
||||||
|
cancellation. Available path horizon limits `d`; prediction tapers to zero as
|
||||||
|
the horizon approaches 7 m. Nonfinite prediction falls back to the validated
|
||||||
|
current offset. The existing endpoint hold remains for paths shorter than 7 m.
|
||||||
|
|
||||||
|
A matched constant-radius path retains essentially the same C0, subject to
|
||||||
|
sample interpolation. Developing and flattening bends can move the target
|
||||||
|
earlier. This is a geometric hypothesis assuming motion along selected
|
||||||
|
curvature, not an identified 150 ms actuator delay or a calibrated plant model.
|
||||||
|
Errors in that assumption can increase or reduce useful steering demand.
|
||||||
|
|
||||||
|
The predicted offset passes through the existing ±5.11 m clip, v2 excess-yaw
|
||||||
|
damping, independent 4 m/s slew and 0.01 m quantization. C1 construction, clip,
|
||||||
|
slew and quantization are unchanged. C2=C3=0. Input sanity, freshness, service
|
||||||
|
health and startup selection gates are unchanged. There are still only two
|
||||||
|
control states (C0 and C1 slew positions), plus three adapter timestamps.
|
||||||
|
The module is 193 total lines, including 125 code lines excluding blanks,
|
||||||
|
comments and docstrings (18 more code lines than v2). No model history, integral
|
||||||
|
or turn state machine is added. The bounds above
|
||||||
|
apply to the prediction target, not arbitrary differences between separately
|
||||||
|
slewed controllers after different histories.
|
||||||
|
|
||||||
|
## Offline results and tradeoff
|
||||||
|
|
||||||
|
All four supplied routes run at their original controls timestamps. Routes90/95
|
||||||
|
also reproduce the archived v1 command construction exactly. The newer routes
|
||||||
|
compare immutable v2 code with v3 using identical measured yaw, selected
|
||||||
|
curvature, exact consumed model and causal carState. Publication times proxy
|
||||||
|
computation time; complete SubMaster health is unavailable. Neither v2 nor v3
|
||||||
|
was driven on these recordings. V2-versus-recorded error is therefore not a
|
||||||
|
reconstruction accuracy measurement.
|
||||||
|
|
||||||
|
| Recorded interval | Command change versus v2 |
|
||||||
|
| --- | --- |
|
||||||
|
| route9e left entry, 173–175.4 s | Mean C0 magnitude +0.143 m; same 2.0 m level reached 0.203 s earlier |
|
||||||
|
| route9e left peak, 175.4–178.3 s | Mean magnitude +0.095 m; prediction also increases some late demand |
|
||||||
|
| route9e reversal, 728–734 s | Peak C0 magnitude 0.24 → 0.22 m |
|
||||||
|
| route9b right exit, 642.7–643.852 s | Mean C0 +0.024 m, partially offsetting v2 damping |
|
||||||
|
| Driver-clean low requests above 8 m/s, routes9b/9e | Mean absolute C0 change ≈0.0015 m; maximum 0.02 m |
|
||||||
|
|
||||||
|
The entry C0 crossings at 0.5, 1.0, 1.5, 2.0 and 2.4 m move earlier by 61, 64,
|
||||||
|
367, 203 and 90 ms respectively. These are command-level crossing times,
|
||||||
|
not measured improvements in wheel response. Several entry/peak windows
|
||||||
|
contain driver input, quantified in the validation record.
|
||||||
|
|
||||||
|
On all 115 earlier right-exit cycles before strong intervention, v3 remains
|
||||||
|
below the driven v1 reconstruction: mean C0 is 0.370 m for v1, 0.290 m for v2,
|
||||||
|
and 0.314 m for v3. This tradeoff is retained explicitly; v3 does not improve
|
||||||
|
every exit command relative to v2. There is no evidence yet that it reduces
|
||||||
|
the late model request or the physical miss.
|
||||||
|
|
||||||
|
Route9b now includes full rlogs 12/13, added after the archived v2 evaluation.
|
||||||
|
Its 14-rlog totals therefore differ from the historical 12-rlog report. The
|
||||||
|
focused segment-10 comparison uses identical timestamps and data.
|
||||||
|
|
||||||
|
Validation passes 372 tests and 26 subtests, including the real extracted
|
||||||
|
controlsd selection/limiter/publication path and downstream CAN builder, with
|
||||||
|
100% controller statement and branch coverage. Four routes cover 299,604
|
||||||
|
original cycles. Randomized testing adds 200,000 core updates plus their
|
||||||
|
mirrors against an independent analytic geometry/damping/slew oracle; boundary
|
||||||
|
and route checks total 817,346 Float32/CAN round trips. These checks establish
|
||||||
|
command construction and retained gates, not closed-loop vehicle behavior.
|
||||||
|
See `ford_model_action_prediction_validation.json` for provenance and counts.
|
||||||
|
|
||||||
|
## Reproduce and select
|
||||||
|
|
||||||
|
To reproduce the archived suite and stress results, use v3 commit
|
||||||
|
`01f8d51c82b3e863f1012d383b5994813ef01b81` with the native dependencies and
|
||||||
|
suite command in the [drive-test guide](ford_model_action_drive_test.md).
|
||||||
|
Current replay tooling can select the immutable v3 source explicitly. New-route comparisons use
|
||||||
|
the deployment opendbc pin `c21a9013700734dd20b09e05aa68329ad8cc20f9`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python -m tools.ford_pscm_lab.damping_replay /path/to/route9e/rlogs --baseline v2 --candidate v3 --window left_entry 173 175.4 --window left_peak 175.4 178.3 --window left_exit 178.3 180.5 --window reversal 728 734 --window final_entry 822 825.5 --output /path/to/separate/route9e-results
|
||||||
|
python -m tools.ford_pscm_lab.damping_replay /path/to/route9b/rlogs --baseline v2 --candidate v3 --window right_entry 637 640 --window right_exit_before_strong_input 642.7 643.852 --output /path/to/separate/route9b-results
|
||||||
|
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260907 --opendbc-revision c21a9013700734dd20b09e05aa68329ad8cc20f9 --output /path/to/stress.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Historical replay loads trusted controller source from immutable local Git
|
||||||
|
commits; those objects must exist in the checkout. The original replay tool
|
||||||
|
still requires its explicit historical opendbc pin. Neither tool downloads
|
||||||
|
code or drives the car.
|
||||||
|
|
||||||
|
The existing default-off Sunnylink **Selected-Action Path Tracking
|
||||||
|
(Experimental)** toggle selects v3 on the CAN FD F-150 Lightning. Updating
|
||||||
|
with that toggle already enabled selects v3 at the next controlsd startup.
|
||||||
|
Diagnostics identify `model-action-c0-c1-prediction-v3` and keep
|
||||||
|
`calibration_approved=false`. No new device installation, hardware build,
|
||||||
|
physical calibration or device boot is part of this offline validation.
|
||||||
@@ -0,0 +1,849 @@
|
|||||||
|
{
|
||||||
|
"date": "2026-09-07",
|
||||||
|
"baseline_commit": "744a97d9bc08d8743b250eceff7c88585b5480de",
|
||||||
|
"deployment_target": {
|
||||||
|
"repository": "sunnypilot/sunnypilot",
|
||||||
|
"branch": "hiimisaac-dev"
|
||||||
|
},
|
||||||
|
"hypothesis": "model-action-c0-c1-prediction-v3",
|
||||||
|
"scope": "Bounded geometric prediction; fixed-input offline evidence only.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"hardware_build_and_device_boot": "not performed",
|
||||||
|
"controller_size": {
|
||||||
|
"total_lines": 193,
|
||||||
|
"code_lines_excluding_blanks_comments_docstrings": 125,
|
||||||
|
"core_persistent_values": 2,
|
||||||
|
"adapter_timestamps": 3
|
||||||
|
},
|
||||||
|
"checks": {
|
||||||
|
"combined_ford_params_sunnylink_suite": "372 passed, 26 subtests passed; no skips",
|
||||||
|
"suite_log_sha256": "b9546bd4cac8349ab9a699f175ba569ed41fc8a9e85e79a0018c79d3af24c11d",
|
||||||
|
"coverage": {
|
||||||
|
"covered_lines": 116,
|
||||||
|
"num_statements": 116,
|
||||||
|
"percent_covered": 100.0,
|
||||||
|
"percent_covered_display": "100",
|
||||||
|
"missing_lines": 0,
|
||||||
|
"excluded_lines": 0,
|
||||||
|
"percent_statements_covered": 100.0,
|
||||||
|
"percent_statements_covered_display": "100",
|
||||||
|
"num_branches": 32,
|
||||||
|
"num_partial_branches": 0,
|
||||||
|
"covered_branches": 32,
|
||||||
|
"missing_branches": 0,
|
||||||
|
"percent_branches_covered": 100.0,
|
||||||
|
"percent_branches_covered_display": "100"
|
||||||
|
},
|
||||||
|
"ruff": "pass",
|
||||||
|
"ty_controller_and_lab": "pass",
|
||||||
|
"settings_compiler_check": "pass",
|
||||||
|
"prediction_red_probe": "Disabling prediction fails all four mirrored developing/flattening-bend cases (4 failed, 34 passed); all 38 pass with prediction.",
|
||||||
|
"standards_review_remaining_findings": 0,
|
||||||
|
"spec_review_remaining_findings": 0,
|
||||||
|
"review_scope": "Tracked and untracked v3 changes since 744a97d9bc08d8743b250eceff7c88585b5480de; final loader unit test checked separately.",
|
||||||
|
"test_portability": "Archive loader unit test mocks Git source retrieval; full offline route runs use the actual immutable commit sources."
|
||||||
|
},
|
||||||
|
"stress": {
|
||||||
|
"seed": 20260907,
|
||||||
|
"random_cycles": 200000,
|
||||||
|
"mirrored_core_updates": 200000,
|
||||||
|
"invalid_or_inactive_resets": 3537,
|
||||||
|
"field_boundary_cases": 18138,
|
||||||
|
"float32_can_round_trips": 218138,
|
||||||
|
"analytic_targets_scalar_slew_and_mirror_checks_pass": true,
|
||||||
|
"bounded_excess_yaw_damping_checked": true,
|
||||||
|
"bounded_geometric_prediction_checked": true,
|
||||||
|
"direct_raw_float32_packing_matches_host_output": true,
|
||||||
|
"max_continuous_step_c0_c1": [
|
||||||
|
0.40000000000015523,
|
||||||
|
0.05000000000000002
|
||||||
|
],
|
||||||
|
"calibration_approved": false,
|
||||||
|
"scope": "Numerical construction only; no PSCM response or closed-loop performance claims.",
|
||||||
|
"opendbc_import_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||||
|
"source_sha256": {
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/stress_model_action.py": "be77689e5327a13b7b4d0e18a3fc1863b57cab76be714339efc609124400489f",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "a872cf4594e364988796394b09f531d18cc3d6732e0c92f45eed9fbf6f5cbee6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routes": {
|
||||||
|
"route90": {
|
||||||
|
"scope": "Command construction and adapter reconstruction only; no counterfactual closed-loop score.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"executes_live_selector": false,
|
||||||
|
"cycles": 78812,
|
||||||
|
"core_active_cycles": 73055,
|
||||||
|
"core_exact_archived_match": true,
|
||||||
|
"cohorts_reproduced": true,
|
||||||
|
"adapter_active_cycles": 73055,
|
||||||
|
"adapter_status_counts": {
|
||||||
|
"inactive": 5757,
|
||||||
|
"active": 73055
|
||||||
|
},
|
||||||
|
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||||
|
"core_active_path_shorter_than_7m_cycles": 0,
|
||||||
|
"adapter_validity_differs_from_archive_cycles": 0,
|
||||||
|
"adapter_command_differs_from_archive_cycles": 27264,
|
||||||
|
"adapter_max_absolute_command_difference_c0_c1": [
|
||||||
|
0.16000000000000014,
|
||||||
|
0.0020000000000000018
|
||||||
|
],
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 157624,
|
||||||
|
"timing": "Original controls publication timestamps proxy computation time; repeated frames and gaps retained. No identified delay.",
|
||||||
|
"eligibility": "Adapter checks recorded services independently; full SubMaster health is unavailable. Core uses archived validity.",
|
||||||
|
"reference": "Recorded controlsState.desiredCurvature, already selected/limited. These two routes have no maneuver publications.",
|
||||||
|
"host_yaw": "Extract cs.yaw equals -carState.yawRate; current adapter uses it for bounded damping.",
|
||||||
|
"archived_core_revision": "5fc16abc7662020706e29f57d31a6d5e2bc1293a",
|
||||||
|
"archived_core_source_sha256": "8f3bc5d68e0051776f614a2ccffae84a88f7898dc95bdc12c23dcfe10dfe676a",
|
||||||
|
"cohorts": {
|
||||||
|
"small_request": {
|
||||||
|
"seconds": 475.24161910000026,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.06125184365338734,
|
||||||
|
0.0057692154738982855
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.057970374224682646,
|
||||||
|
0.007060795713496332
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 475.24161910000026,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.06384544973437024,
|
||||||
|
0.0057692154738982855
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn": {
|
||||||
|
"seconds": 47.85871389900012,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.18725251016049418,
|
||||||
|
0.046449510236699354
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.4773534845264775,
|
||||||
|
0.04315935101392594
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 47.85871389900012,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.20375259142277982,
|
||||||
|
0.046449510236699354
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_2_8": {
|
||||||
|
"seconds": 38.696717235999785,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.209264275431127,
|
||||||
|
0.014019080484620104
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.1593558816527209,
|
||||||
|
0.01823849401244768
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 38.696717235999785,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.21664870054721863,
|
||||||
|
0.014019080484620104
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_2_8": {
|
||||||
|
"seconds": 0.30159887100000304,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
2.090789090165622,
|
||||||
|
0.21878942570759013
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
2.155728831937131,
|
||||||
|
0.38375302254248855
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 0.30159887100000304,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
2.2043400076988204,
|
||||||
|
0.21878942570759013
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_8_15": {
|
||||||
|
"seconds": 79.2599167590001,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.02250905681378238,
|
||||||
|
0.005843904113223639
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.03477379324171293,
|
||||||
|
0.006545440153133947
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 79.2599167590001,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.026080258136122546,
|
||||||
|
0.005843904113223639
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_8_15": {
|
||||||
|
"seconds": 0.022264622000001566,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.20999999999999996,
|
||||||
|
0.04349999999999998
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.25,
|
||||||
|
0.04050000011920929
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 0.022264622000001566,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.20999999999999996,
|
||||||
|
0.04349999999999998
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_15_55": {
|
||||||
|
"seconds": 357.2849851050004,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.011622097035118838,
|
||||||
|
0.003925571829874466
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.03809717934501699,
|
||||||
|
0.0045587590980714285
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 357.2849851050004,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.013693372023947319,
|
||||||
|
0.003925571829874466
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_15_55": {
|
||||||
|
"seconds": 47.53485040600012,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.08686835454709245,
|
||||||
|
0.043216347944464766
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.4471065308273131,
|
||||||
|
0.030663943784303503
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 47.53485040600012,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.10462899220936993,
|
||||||
|
0.043216347944464766
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pose_quiet": {
|
||||||
|
"seconds": 427.99642020700026,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.01261133542366393,
|
||||||
|
0.00422389167351435
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.03724956081568198,
|
||||||
|
0.0046880581807907966
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 427.99642020700026,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.014639447114479471,
|
||||||
|
0.00422389167351435
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workspace_head": "744a97d9bc08d8743b250eceff7c88585b5480de",
|
||||||
|
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||||
|
"opendbc_import_path": "/Users/ibpersonal/.codex/worktrees/b926/sunnypilot/opendbc_repo",
|
||||||
|
"source_sha256": {
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "a872cf4594e364988796394b09f531d18cc3d6732e0c92f45eed9fbf6f5cbee6",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_path.py": "383538fc7cdae3bc28dffb71fe12ac5f3f9866ffbe6adfb7457f3593e9fc903a",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/route.npz": "51e8c26eedde253e171af47d704c1967ba45ae6825d883393bec1fb9e00251c1",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/metadata.json": "742afec55ed629155b22d387f376f878f7b2765221fe94f515a829052bdd916f",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/encoder_comparison.npz": "7a625d3ed5cbd8013d1028aa3bc421740551dcae5c3d60981208bd047af9794c",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/encoder_comparison.json": "64e1cc4be84394ac7ec408d383b99b48ada9b3fd129cdef2fd846e5fac620d66",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/pose_candidate/pose_replay.npz": "4457ccc0868354749da5b72c1dea0faf750f783dfdc87038101288fdcba1e707"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"route95": {
|
||||||
|
"scope": "Command construction and adapter reconstruction only; no counterfactual closed-loop score.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"executes_live_selector": false,
|
||||||
|
"cycles": 54738,
|
||||||
|
"core_active_cycles": 37614,
|
||||||
|
"core_exact_archived_match": true,
|
||||||
|
"cohorts_reproduced": true,
|
||||||
|
"adapter_active_cycles": 37614,
|
||||||
|
"adapter_status_counts": {
|
||||||
|
"inactive": 17124,
|
||||||
|
"active": 37614
|
||||||
|
},
|
||||||
|
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||||
|
"core_active_path_shorter_than_7m_cycles": 44,
|
||||||
|
"adapter_validity_differs_from_archive_cycles": 0,
|
||||||
|
"adapter_command_differs_from_archive_cycles": 20110,
|
||||||
|
"adapter_max_absolute_command_difference_c0_c1": [
|
||||||
|
0.16999999999999993,
|
||||||
|
0.0010000000000000009
|
||||||
|
],
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 109476,
|
||||||
|
"timing": "Original controls publication timestamps proxy computation time; repeated frames and gaps retained. No identified delay.",
|
||||||
|
"eligibility": "Adapter checks recorded services independently; full SubMaster health is unavailable. Core uses archived validity.",
|
||||||
|
"reference": "Recorded controlsState.desiredCurvature, already selected/limited. These two routes have no maneuver publications.",
|
||||||
|
"host_yaw": "Extract cs.yaw equals -carState.yawRate; current adapter uses it for bounded damping.",
|
||||||
|
"archived_core_revision": "5fc16abc7662020706e29f57d31a6d5e2bc1293a",
|
||||||
|
"archived_core_source_sha256": "8f3bc5d68e0051776f614a2ccffae84a88f7898dc95bdc12c23dcfe10dfe676a",
|
||||||
|
"cohorts": {
|
||||||
|
"small_request": {
|
||||||
|
"seconds": 160.93427628100014,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.027283284164810895,
|
||||||
|
0.006054573235641941
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.04189890174428264,
|
||||||
|
0.0075342351227490395
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 160.93427628100014,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.02970169836480558,
|
||||||
|
0.006054573235641941
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn": {
|
||||||
|
"seconds": 58.39837079799993,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.5425346915921071,
|
||||||
|
0.09102985199238507
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.63700479987207,
|
||||||
|
0.09691236120507696
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 58.39837079799993,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.5622074838217578,
|
||||||
|
0.09102985199238507
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_2_8": {
|
||||||
|
"seconds": 8.083969085000064,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.09941192804175551,
|
||||||
|
0.016572084086884566
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.07597383189748476,
|
||||||
|
0.016403469943482765
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 8.083969085000064,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.1039594708778471,
|
||||||
|
0.016572084086884566
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_2_8": {
|
||||||
|
"seconds": 2.9056519089999995,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
2.286598281178436,
|
||||||
|
0.3420570411209625
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
2.4371443775668484,
|
||||||
|
0.37360610108051345
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 2.9056519089999995,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
2.342145227147664,
|
||||||
|
0.3420570411209625
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_8_15": {
|
||||||
|
"seconds": 32.021085868,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.028387860150467858,
|
||||||
|
0.0067602044219821
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.036941169841952046,
|
||||||
|
0.007349671742584858
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 32.021085868,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.032473217223426755,
|
||||||
|
0.0067602044219821
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_8_15": {
|
||||||
|
"seconds": 22.160645306000006,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.28668316323581683,
|
||||||
|
0.0671915746138579
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.3391022553367454,
|
||||||
|
0.07297678510653233
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 22.160645306000006,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.32040564199313326,
|
||||||
|
0.0671915746138579
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"small_speed_15_55": {
|
||||||
|
"seconds": 120.82922132800007,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.010802222799973712,
|
||||||
|
0.004282512931613862
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.03987958989535526,
|
||||||
|
0.0065793876512313535
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 120.82922132800007,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.013133007539405142,
|
||||||
|
0.004282512931613862
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"turn_speed_15_55": {
|
||||||
|
"seconds": 33.332073582999925,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.07257996778378971,
|
||||||
|
0.03628926246224237
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.3416081588451539,
|
||||||
|
0.027322786376822172
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 33.332073582999925,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.08556161843867907,
|
||||||
|
0.03628926246224237
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pose_quiet": {
|
||||||
|
"seconds": 145.24136911700015,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.012566204405760934,
|
||||||
|
0.004614194153565963
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.03855743841849719,
|
||||||
|
0.006621274926345545
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 145.24136911700015,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.014652655970248427,
|
||||||
|
0.004614194153565963
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workspace_head": "744a97d9bc08d8743b250eceff7c88585b5480de",
|
||||||
|
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||||
|
"opendbc_import_path": "/Users/ibpersonal/.codex/worktrees/b926/sunnypilot/opendbc_repo",
|
||||||
|
"source_sha256": {
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "a872cf4594e364988796394b09f531d18cc3d6732e0c92f45eed9fbf6f5cbee6",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_path.py": "383538fc7cdae3bc28dffb71fe12ac5f3f9866ffbe6adfb7457f3593e9fc903a",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/route.npz": "6e5438867ea618e53ca395b60ff4b9b146256f1bf6f07dc8e92d663286074154",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/metadata.json": "f7c95a102e0e084bbcc9938399219080c1d3b26274b4ea3c3a9514ff7dc8ea8d",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/encoder_comparison.npz": "23bd05c4b6400299844acaba1d97051c96c23c682bf17b46e89e7f2cca5fce38",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/encoder_comparison.json": "1ab24a796d14817b8672ea6dfd39711d65cb06744eaaef7dcc02a840c392c4d3",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/pose_candidate/pose_replay.npz": "9cfbb3c6f0b1fdb7e3d38e6b64b8c94cffbcc9fabe41f6344d84a9b657b6af9b"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"route9b": {
|
||||||
|
"scope": "Fixed-input command replay only; no physical improvement or stability claim.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"cycles": 82386,
|
||||||
|
"eligible_cycles": 70703,
|
||||||
|
"status_counts": {
|
||||||
|
"inactive": 11683,
|
||||||
|
"active": 70703
|
||||||
|
},
|
||||||
|
"c1_exactly_unchanged": true,
|
||||||
|
"same_validity": true,
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 164772,
|
||||||
|
"cohorts": {
|
||||||
|
"eligible": {
|
||||||
|
"cycles": 70703,
|
||||||
|
"seconds": 711.571703289,
|
||||||
|
"changed_c0_cycles": 20858,
|
||||||
|
"mean_absolute_c0_change_m": 0.006916798541216091,
|
||||||
|
"max_absolute_c0_change_m": 0.15000000000000124,
|
||||||
|
"increased_absolute_c0_cycles": 17700,
|
||||||
|
"decreased_absolute_c0_cycles": 3157,
|
||||||
|
"driver_input_percent": 3.0297623863274845,
|
||||||
|
"baseline_peak_absolute_c0_m": 3.89,
|
||||||
|
"candidate_peak_absolute_c0_m": 3.96
|
||||||
|
},
|
||||||
|
"driver_clean": {
|
||||||
|
"cycles": 63267,
|
||||||
|
"seconds": 636.7469998759999,
|
||||||
|
"changed_c0_cycles": 16024,
|
||||||
|
"mean_absolute_c0_change_m": 0.002923399405403562,
|
||||||
|
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||||
|
"increased_absolute_c0_cycles": 13587,
|
||||||
|
"decreased_absolute_c0_cycles": 2437,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.16,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.24
|
||||||
|
},
|
||||||
|
"driver_clean_low_request_above_8mps": {
|
||||||
|
"cycles": 40656,
|
||||||
|
"seconds": 409.13067679800054,
|
||||||
|
"changed_c0_cycles": 6007,
|
||||||
|
"mean_absolute_c0_change_m": 0.0014813245503935623,
|
||||||
|
"max_absolute_c0_change_m": 0.020000000000000462,
|
||||||
|
"increased_absolute_c0_cycles": 5099,
|
||||||
|
"decreased_absolute_c0_cycles": 908,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.08999999999999986,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.11000000000000032
|
||||||
|
},
|
||||||
|
"turn": {
|
||||||
|
"cycles": 5459,
|
||||||
|
"seconds": 54.939139939000256,
|
||||||
|
"changed_c0_cycles": 3156,
|
||||||
|
"mean_absolute_c0_change_m": 0.007655026845650452,
|
||||||
|
"max_absolute_c0_change_m": 0.08999999999999986,
|
||||||
|
"increased_absolute_c0_cycles": 2938,
|
||||||
|
"decreased_absolute_c0_cycles": 218,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.16,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.24
|
||||||
|
},
|
||||||
|
"right_entry": {
|
||||||
|
"cycles": 298,
|
||||||
|
"seconds": 2.9932484459999387,
|
||||||
|
"changed_c0_cycles": 298,
|
||||||
|
"mean_absolute_c0_change_m": 0.14871908015683724,
|
||||||
|
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||||
|
"increased_absolute_c0_cycles": 298,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 20.395198628296086,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.8000000000000003,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.95
|
||||||
|
},
|
||||||
|
"right_exit_before_strong_input": {
|
||||||
|
"cycles": 115,
|
||||||
|
"seconds": 1.156770048999988,
|
||||||
|
"changed_c0_cycles": 115,
|
||||||
|
"mean_absolute_c0_change_m": 0.02362929150320746,
|
||||||
|
"max_absolute_c0_change_m": 0.040000000000000036,
|
||||||
|
"increased_absolute_c0_cycles": 115,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.7000000000000002,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.7199999999999998
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"baseline_commands_vs_recorded_publications": {
|
||||||
|
"paired_cycles": 70703,
|
||||||
|
"within_one_quantum_cycles": 69862,
|
||||||
|
"maximum_absolute_error_c0_c1": [
|
||||||
|
0.20999998569488554,
|
||||||
|
0.0005000143051147043
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timing": "Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.",
|
||||||
|
"baseline_version": "v2",
|
||||||
|
"baseline_revision": "744a97d9bc08d8743b250eceff7c88585b5480de",
|
||||||
|
"candidate_version": "current",
|
||||||
|
"candidate_revision": "working_tree",
|
||||||
|
"focus_windows": [
|
||||||
|
[
|
||||||
|
"right_entry",
|
||||||
|
"637",
|
||||||
|
"640"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"right_exit_before_strong_input",
|
||||||
|
"642.7",
|
||||||
|
"643.852"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"baseline_source_sha256": "59d66297a017557f3d4f28b115be3f6220b800566c11935e2284b3814783fb7e",
|
||||||
|
"candidate_source_sha256": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||||
|
"source_rlog_sha256": {
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--0--rlog.zst": "22746f7119109b73ed7f2c26ce8c99f87136e9124fb7fc14c9554409a28a7c3f",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--1--rlog.zst": "4c2e1d7083c31a2b37d0f8dd3be4d330898511b7e02c26f7d40ca9bc2779397d",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--2--rlog.zst": "62f3e049e220cd3681fadf386f2969537bd571998ae2f6ba2d08479428b5a28f",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--3--rlog.zst": "83bf0131b2d36b2ba7e5ba050bbc13c0a3350feb5c9b89dc9c87d3a37abebfb3",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--4--rlog.zst": "430985a80dd6e10f7abeb89457a17022e6bb6978617f415c905f584b1647603e",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--5--rlog.zst": "8c0c5ae6323ec33b3e14f84ca834f70cb56f6b29f471a350f1e3efc06b6ba553",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--6--rlog.zst": "db53dfa8156b9d66792c3eff0b2ce5d31b71ad41cc580dec85f528845593c184",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--7--rlog.zst": "91b0b3be10cb7d7d7f7dd2024d8f9ee99d1e9fd2204203a3a9a2f2f1c6e3fa03",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--8--rlog.zst": "687dbbfc49837efbfe8fa6bc091e40f7fad2908832234d7884f4616d1bc9ccff",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--9--rlog.zst": "a88ec4d25b04cdbf5844686fc77f6b28dca920c9b164e37ebf69844a3ae398fc",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--10--rlog.zst": "fe6b29580a6c94e1c236d13e18db4cd9f31cc1b25d52e1e6e19a5021125c9932",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--11--rlog.zst": "150d31b1944d7a1b8c562f3aee20b66cefa6c4e8d02660ec889907d835142f45",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--12--rlog.zst": "e105e5f703a70f36f1989c66fb46c35a65ff60a265b4332e10fa2e6875a2ced4",
|
||||||
|
"84865544361f55cb_0000009b--e4616dddaa--13--rlog.zst": "69304cf0c81401374f047c5022ca47d257ff9e59e30ec171325a2ee1e7ed13d4"
|
||||||
|
},
|
||||||
|
"opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||||
|
"source_sha256": {
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/damping_replay.py": "663674fa9686291228766c4fd2990dcfc620b155f6d74e2f957bbb33e5593a5d",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"route9e": {
|
||||||
|
"scope": "Fixed-input command replay only; no physical improvement or stability claim.",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"cycles": 83668,
|
||||||
|
"eligible_cycles": 74669,
|
||||||
|
"status_counts": {
|
||||||
|
"inactive": 8999,
|
||||||
|
"active": 74669
|
||||||
|
},
|
||||||
|
"c1_exactly_unchanged": true,
|
||||||
|
"same_validity": true,
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 167336,
|
||||||
|
"cohorts": {
|
||||||
|
"eligible": {
|
||||||
|
"cycles": 74669,
|
||||||
|
"seconds": 751.5479497329998,
|
||||||
|
"changed_c0_cycles": 23282,
|
||||||
|
"mean_absolute_c0_change_m": 0.00617904439256301,
|
||||||
|
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||||
|
"increased_absolute_c0_cycles": 19880,
|
||||||
|
"decreased_absolute_c0_cycles": 3402,
|
||||||
|
"driver_input_percent": 2.7324748055386094,
|
||||||
|
"baseline_peak_absolute_c0_m": 4.33,
|
||||||
|
"candidate_peak_absolute_c0_m": 4.38
|
||||||
|
},
|
||||||
|
"driver_clean": {
|
||||||
|
"cycles": 66321,
|
||||||
|
"seconds": 667.5291971840106,
|
||||||
|
"changed_c0_cycles": 18123,
|
||||||
|
"mean_absolute_c0_change_m": 0.003409158953346422,
|
||||||
|
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||||
|
"increased_absolute_c0_cycles": 16136,
|
||||||
|
"decreased_absolute_c0_cycles": 1987,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.3600000000000003,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.4699999999999998
|
||||||
|
},
|
||||||
|
"driver_clean_low_request_above_8mps": {
|
||||||
|
"cycles": 39635,
|
||||||
|
"seconds": 398.9613925449994,
|
||||||
|
"changed_c0_cycles": 5866,
|
||||||
|
"mean_absolute_c0_change_m": 0.0014778774629256156,
|
||||||
|
"max_absolute_c0_change_m": 0.020000000000000462,
|
||||||
|
"increased_absolute_c0_cycles": 5377,
|
||||||
|
"decreased_absolute_c0_cycles": 489,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.05999999999999961,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.07000000000000028
|
||||||
|
},
|
||||||
|
"turn": {
|
||||||
|
"cycles": 6219,
|
||||||
|
"seconds": 62.59491144700405,
|
||||||
|
"changed_c0_cycles": 5129,
|
||||||
|
"mean_absolute_c0_change_m": 0.011069748090899225,
|
||||||
|
"max_absolute_c0_change_m": 0.10999999999999943,
|
||||||
|
"increased_absolute_c0_cycles": 5098,
|
||||||
|
"decreased_absolute_c0_cycles": 31,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.3600000000000003,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.4699999999999998
|
||||||
|
},
|
||||||
|
"left_entry": {
|
||||||
|
"cycles": 238,
|
||||||
|
"seconds": 2.4048367620016506,
|
||||||
|
"changed_c0_cycles": 238,
|
||||||
|
"mean_absolute_c0_change_m": 0.14261645650107144,
|
||||||
|
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||||
|
"increased_absolute_c0_cycles": 238,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 2.403816920672437,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.46,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.6100000000000003
|
||||||
|
},
|
||||||
|
"left_peak": {
|
||||||
|
"cycles": 288,
|
||||||
|
"seconds": 2.891819394000777,
|
||||||
|
"changed_c0_cycles": 288,
|
||||||
|
"mean_absolute_c0_change_m": 0.095420590363382,
|
||||||
|
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||||
|
"increased_absolute_c0_cycles": 288,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 6.41034524444729,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.79,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.9400000000000004
|
||||||
|
},
|
||||||
|
"left_exit": {
|
||||||
|
"cycles": 219,
|
||||||
|
"seconds": 2.203320298998733,
|
||||||
|
"changed_c0_cycles": 185,
|
||||||
|
"mean_absolute_c0_change_m": 0.0422943841629376,
|
||||||
|
"max_absolute_c0_change_m": 0.09000000000000075,
|
||||||
|
"increased_absolute_c0_cycles": 56,
|
||||||
|
"decreased_absolute_c0_cycles": 129,
|
||||||
|
"driver_input_percent": 9.936619705158035,
|
||||||
|
"baseline_peak_absolute_c0_m": 2.4299999999999997,
|
||||||
|
"candidate_peak_absolute_c0_m": 2.51
|
||||||
|
},
|
||||||
|
"reversal": {
|
||||||
|
"cycles": 554,
|
||||||
|
"seconds": 5.570295188001182,
|
||||||
|
"changed_c0_cycles": 167,
|
||||||
|
"mean_absolute_c0_change_m": 0.007060409634059627,
|
||||||
|
"max_absolute_c0_change_m": 0.040000000000000036,
|
||||||
|
"increased_absolute_c0_cycles": 20,
|
||||||
|
"decreased_absolute_c0_cycles": 147,
|
||||||
|
"driver_input_percent": 0.0,
|
||||||
|
"baseline_peak_absolute_c0_m": 0.2400000000000002,
|
||||||
|
"candidate_peak_absolute_c0_m": 0.21999999999999975
|
||||||
|
},
|
||||||
|
"final_entry": {
|
||||||
|
"cycles": 348,
|
||||||
|
"seconds": 3.4958847329980927,
|
||||||
|
"changed_c0_cycles": 348,
|
||||||
|
"mean_absolute_c0_change_m": 0.11950442609746584,
|
||||||
|
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||||
|
"increased_absolute_c0_cycles": 348,
|
||||||
|
"decreased_absolute_c0_cycles": 0,
|
||||||
|
"driver_input_percent": 3.556656282920533,
|
||||||
|
"baseline_peak_absolute_c0_m": 3.31,
|
||||||
|
"candidate_peak_absolute_c0_m": 3.4400000000000004
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"baseline_commands_vs_recorded_publications": {
|
||||||
|
"paired_cycles": 74669,
|
||||||
|
"within_one_quantum_cycles": 74156,
|
||||||
|
"maximum_absolute_error_c0_c1": [
|
||||||
|
0.29000000834465034,
|
||||||
|
0.0010000016689301061
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timing": "Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.",
|
||||||
|
"baseline_version": "v2",
|
||||||
|
"baseline_revision": "744a97d9bc08d8743b250eceff7c88585b5480de",
|
||||||
|
"candidate_version": "current",
|
||||||
|
"candidate_revision": "working_tree",
|
||||||
|
"focus_windows": [
|
||||||
|
[
|
||||||
|
"left_entry",
|
||||||
|
"173",
|
||||||
|
"175.4"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"left_peak",
|
||||||
|
"175.4",
|
||||||
|
"178.3"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"left_exit",
|
||||||
|
"178.3",
|
||||||
|
"180.5"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"reversal",
|
||||||
|
"728",
|
||||||
|
"734"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"final_entry",
|
||||||
|
"822",
|
||||||
|
"825.5"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"baseline_source_sha256": "59d66297a017557f3d4f28b115be3f6220b800566c11935e2284b3814783fb7e",
|
||||||
|
"candidate_source_sha256": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||||
|
"source_rlog_sha256": {
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--0--rlog.zst": "de63532ae6aedf5dc7fd3ac8e47a2d96a8f065614f4aca79ae2f120ea00390c7",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--1--rlog.zst": "67b58197158b3e8f0581643f6657d2a85c47b0bb75dd3e00306d9ffae790008b",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--2--rlog.zst": "a4840f338f51b5f1864c79ce3a4f2b11dc13d58d52b8459961c9da57237f2cc2",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--3--rlog.zst": "ff3ea8d948006ab19c4dbfeeff59a509de3f193da47b91c88faa27ecd0b3f1fc",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--4--rlog.zst": "5ff1996c2336299128a11e657c32bc21921716b7e3bd401de69a2d49484aa223",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--5--rlog.zst": "238763457e89896933afaf9a5df325469ee8c02ddc53550df252531bc94fc540",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--6--rlog.zst": "2ee86de80cfd762be10cd2dfb2895ddbee6b813706e9c7261460203e09b9bc4d",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--7--rlog.zst": "017801f080861c63d799f87aebe30ab57cdf82f078ac882e6187d3870c403538",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--8--rlog.zst": "3729016bd1f00bb1077613b63fe25b21ba7112822b994e0e0aa2b4cd93bdb940",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--9--rlog.zst": "c40b3c1f6eb9252b85f176fee32cae16c23eaad3db830f6bbf37a730034a5ccf",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--10--rlog.zst": "e0ce8f231798073e5bbc34551fbd9493f169a06a068adc1957ef8cd914b71fbb",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--11--rlog.zst": "90863353f7c862a05618b4d0761dda3fcedd3cf6b4234c21c6dcc2eee3ec20b2",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--12--rlog.zst": "e064f8abde9615b2daf00f469ef36b438cd4b3a1637069467c84de91bee3bdf1",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--13--rlog.zst": "790fc438ce2675e0690fdce3b07bf54fdf6d06b17bbe7f5ff447adfa71189754",
|
||||||
|
"84865544361f55cb_0000009e--592f7dc149--14--rlog.zst": "b98f5a5be660013b1fe11250fdc24ee4fe1d9dd1301b787ddfdff1f2bcd3e27f"
|
||||||
|
},
|
||||||
|
"opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||||
|
"source_sha256": {
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/damping_replay.py": "663674fa9686291228766c4fd2990dcfc620b155f6d74e2f957bbb33e5593a5d",
|
||||||
|
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"focus_comparison": {
|
||||||
|
"scope": "Same recorded inputs; crossing a C0 level earlier does not identify vehicle response time.",
|
||||||
|
"left_entry_crossings": [
|
||||||
|
{
|
||||||
|
"c0_magnitude_m": 0.5,
|
||||||
|
"v2_time_s": 173.04409075799958,
|
||||||
|
"v3_time_s": 172.98298211199835,
|
||||||
|
"earlier_ms": 61.10864600123023
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"c0_magnitude_m": 1.0,
|
||||||
|
"v2_time_s": 173.41054691999852,
|
||||||
|
"v3_time_s": 173.3469171409979,
|
||||||
|
"earlier_ms": 63.629779000621056
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"c0_magnitude_m": 1.5,
|
||||||
|
"v2_time_s": 174.09016323599826,
|
||||||
|
"v3_time_s": 173.7234842099988,
|
||||||
|
"earlier_ms": 366.6790259994741
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"c0_magnitude_m": 2.0,
|
||||||
|
"v2_time_s": 174.69427409499804,
|
||||||
|
"v3_time_s": 174.49167145299907,
|
||||||
|
"earlier_ms": 202.60264199896483
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"c0_magnitude_m": 2.4,
|
||||||
|
"v2_time_s": 175.08411706099832,
|
||||||
|
"v3_time_s": 174.99415714599854,
|
||||||
|
"earlier_ms": 89.95991499978118
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"right_exit": {
|
||||||
|
"mean_c0_v1_m": 0.3704272817752263,
|
||||||
|
"mean_c0_v2_m": 0.29049595970311604,
|
||||||
|
"mean_c0_v3_m": 0.31412525120632345,
|
||||||
|
"v3_below_driven_v1_every_cycle": true,
|
||||||
|
"cycles": 115
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"total_original_route_cycles": 299604,
|
||||||
|
"total_float32_can_round_trips": 817346,
|
||||||
|
"source_sha256": {
|
||||||
|
"openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action.py": "8851104f811757f6a31b5e23ec5933c7bfc18c17012af9a0a78f851217eca525",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "04daed84093330c05ae1669c155ed9c197f95ebb02fd4e7ca1c7a10ce70d03da",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_damping.py": "a7062d2c3c46b3abcd4c3facd97970644ce609fb55285daa0359681950948250",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_prediction.py": "c05ae173c809228a9f4782af32a174b1b928f506a76c454aa89a51ef0ed17bdd",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_selection.py": "f826c6328f0abac2a61f1a0a6f8d119fdbab858e363cb466d84ba9a7783059cd",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_controlsd_logging.py": "fd4095bc4f1363fd014c43a9e0776a2245105e8fb2703d87da367d0f33dc22dc",
|
||||||
|
"tools/ford_pscm_lab/__init__.py": "db4b8b7d2e317ed34ca0ec220bf9d53e7b80a23e766f4e1cb2c8224dada45f2e",
|
||||||
|
"tools/ford_pscm_lab/damping_replay.py": "663674fa9686291228766c4fd2990dcfc620b155f6d74e2f957bbb33e5593a5d",
|
||||||
|
"tools/ford_pscm_lab/model_action_replay.py": "a872cf4594e364988796394b09f531d18cc3d6732e0c92f45eed9fbf6f5cbee6",
|
||||||
|
"tools/ford_pscm_lab/stress_model_action.py": "be77689e5327a13b7b4d0e18a3fc1863b57cab76be714339efc609124400489f",
|
||||||
|
"tools/ford_pscm_lab/test_model_action_replay.py": "d2a3602e145db4f969ecf1402fe66684c3b1f884e3ed429e95f5fb5dcaddefb3",
|
||||||
|
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "b5951a90cb01bfcd79b82705600ad2d165b3cc5f7e33c7404536eb989bc3ce39",
|
||||||
|
"openpilot/sunnypilot/sunnylink/settings_ui.json": "6badb0080d8a999d63d063430c0a320f1a58884dfe0c390774f90a39cbf0e0e2"
|
||||||
|
},
|
||||||
|
"artifact_sha256": {
|
||||||
|
".cache/ford_model_action_prediction/prediction_red.txt": "81e765edd1fc73783855ef30aa3c4aa8f111c001d7e9e0951d5791e18a860f4e",
|
||||||
|
".cache/ford_model_action_prediction/stress.json": "e358a7caafe457a83637857cee7f0b1b2b60e0e4020f64b46630b8462e5edff5",
|
||||||
|
".cache/ford_model_action_prediction/coverage.json": "333ed4414632adbb12a8a1f3ef2f9dc7a62dd9fde08bbc6b8a3d372cf9a6869b",
|
||||||
|
".cache/ford_model_action_prediction/focus_comparison.json": "b0af320c0eb7e535420702ed78df30c43134f8f9e2a977e5f88f765ba0626c69",
|
||||||
|
".cache/ford_model_action_prediction/route90/report.json": "b58f738269d9fe176672670f5bf4116f3226d319f43b5b407ac221c288aba174",
|
||||||
|
".cache/ford_model_action_prediction/route95/report.json": "379a6f5bc9c98d8d1bccf687d61d46c54eda439284508e321d49b5312c1ca9f2",
|
||||||
|
".cache/ford_model_action_prediction/route9b/report.json": "650ce4056b663124e2f72fae94cd1b66423ff74de42611a99501271c2c846bc2",
|
||||||
|
".cache/ford_model_action_prediction/route9e/report.json": "196b3b242e3e056ed531cd6e657a235cc9bf8ed4357ce696b1bec1e5f0a49a39",
|
||||||
|
".cache/ford_model_action_prediction/route9b/commands.npz": "5d82318935253d4fa8a1b8c30cc7f2d465c151ae1cb9109fa09a622d5be49871",
|
||||||
|
".cache/ford_model_action_prediction/route9e/commands.npz": "2917bbe801a78711340fa4eae63d5bf1cb4a4464c45d8538c9a46a77b5511e06",
|
||||||
|
".cache/ford_model_action_prediction/plot_commands.py": "6a5bd97fedeb9bd951925dddc656e508082f6d457dbc1a6c9d4db769afcdd921",
|
||||||
|
"/Users/ibpersonal/.codex/visualizations/2026/09/07/01a07c33-1f6c-7292-8030-ad4b01923034/ford-route9e/prediction_command_comparison.png": "86e68a0497490352065997f73cd5a798ff93dcabc93152028669ccfb36a4fefe",
|
||||||
|
"/Users/ibpersonal/.codex/visualizations/2026/09/07/01a07c33-1f6c-7292-8030-ad4b01923034/ford-route9e/prediction_command_comparison.svg": "2310b2978b60b97619886c2e10ecca88c73b5fde3477452347e6db0a5bbcd5b3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
{
|
||||||
|
"date": "2026-09-07",
|
||||||
|
"baseline_commit": "c4b3c55c826fca1ce09618e418e95f0a24478d96",
|
||||||
|
"calibration_approved": false,
|
||||||
|
"production_selector_changed": false,
|
||||||
|
"vehicle_settings_changed": false,
|
||||||
|
"panda_safety_changed": false,
|
||||||
|
"scope": "Offline command construction, adapter integration, numerical fault probes and Ford regression tests. Physical response remains unvalidated.",
|
||||||
|
"controller_size": {
|
||||||
|
"total_lines": 131,
|
||||||
|
"code_lines_excluding_blanks_comments_docstrings": 86,
|
||||||
|
"core_persistent_values": 2,
|
||||||
|
"adapter_timestamps": 3
|
||||||
|
},
|
||||||
|
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||||
|
"tests": {
|
||||||
|
"ford_suite": "264 passed, 150 subtests passed in 14.29s",
|
||||||
|
"new_core_adapter_tests": 107,
|
||||||
|
"new_replay_validator_tests": 13,
|
||||||
|
"controller_coverage": {
|
||||||
|
"statements": 78,
|
||||||
|
"missing_statements": 0,
|
||||||
|
"branches": 24,
|
||||||
|
"partial_branches": 0,
|
||||||
|
"percent": 100
|
||||||
|
},
|
||||||
|
"ruff": "pass",
|
||||||
|
"ty_controller_and_lab": "pass"
|
||||||
|
},
|
||||||
|
"routes": {
|
||||||
|
"route95": {
|
||||||
|
"cycles": 54738,
|
||||||
|
"core_active_cycles": 37614,
|
||||||
|
"core_exact_archived_match": true,
|
||||||
|
"cohorts_reproduced": true,
|
||||||
|
"adapter_active_cycles": 37614,
|
||||||
|
"adapter_status_counts": {
|
||||||
|
"inactive": 17124,
|
||||||
|
"active": 37614
|
||||||
|
},
|
||||||
|
"adapter_exact_match_with_fresh_engagement_dt": true,
|
||||||
|
"core_active_path_shorter_than_7m_cycles": 44,
|
||||||
|
"adapter_validity_differs_from_archive_cycles": 0,
|
||||||
|
"adapter_command_differs_from_archive_cycles": 59,
|
||||||
|
"adapter_max_absolute_command_difference_c0_c1": [
|
||||||
|
0.010000000000000675,
|
||||||
|
0.0010000000000000009
|
||||||
|
],
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 109476,
|
||||||
|
"turn_speed_15_55": {
|
||||||
|
"seconds": 33.332073582999925,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.07257996778378971,
|
||||||
|
0.03628926246224237
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.3416081588451539,
|
||||||
|
0.027322786376822172
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 33.332073582999925,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.07257996778378971,
|
||||||
|
0.03628926246224237
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"input_sha256": {
|
||||||
|
"route.npz": "6e5438867ea618e53ca395b60ff4b9b146256f1bf6f07dc8e92d663286074154",
|
||||||
|
"encoder_comparison.npz": "23bd05c4b6400299844acaba1d97051c96c23c682bf17b46e89e7f2cca5fce38",
|
||||||
|
"pose_replay.npz": "9cfbb3c6f0b1fdb7e3d38e6b64b8c94cffbcc9fabe41f6344d84a9b657b6af9b"
|
||||||
|
},
|
||||||
|
"report_sha256": "cf4e3804f2ebdb0e50f3c49636bf60a30e3c1180411b582826a115970ab972fc"
|
||||||
|
},
|
||||||
|
"route90": {
|
||||||
|
"cycles": 78812,
|
||||||
|
"core_active_cycles": 73055,
|
||||||
|
"core_exact_archived_match": true,
|
||||||
|
"cohorts_reproduced": true,
|
||||||
|
"adapter_active_cycles": 73055,
|
||||||
|
"adapter_status_counts": {
|
||||||
|
"inactive": 5757,
|
||||||
|
"active": 73055
|
||||||
|
},
|
||||||
|
"adapter_exact_match_with_fresh_engagement_dt": true,
|
||||||
|
"core_active_path_shorter_than_7m_cycles": 0,
|
||||||
|
"adapter_validity_differs_from_archive_cycles": 0,
|
||||||
|
"adapter_command_differs_from_archive_cycles": 19,
|
||||||
|
"adapter_max_absolute_command_difference_c0_c1": [
|
||||||
|
0.020000000000000462,
|
||||||
|
0.0020000000000000018
|
||||||
|
],
|
||||||
|
"field_slew_zero_c2_c3_pass": true,
|
||||||
|
"float32_can_round_trips": 157624,
|
||||||
|
"turn_speed_15_55": {
|
||||||
|
"seconds": 47.53485040600012,
|
||||||
|
"core_c0_c1_rms": [
|
||||||
|
0.08686835454709245,
|
||||||
|
0.043216347944464766
|
||||||
|
],
|
||||||
|
"recorded_v8_c0_c1_rms": [
|
||||||
|
0.4471065308273131,
|
||||||
|
0.030663943784303503
|
||||||
|
],
|
||||||
|
"adapter_eligible_seconds": 47.53485040600012,
|
||||||
|
"adapter_c0_c1_rms": [
|
||||||
|
0.08686835454709245,
|
||||||
|
0.043216347944464766
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"input_sha256": {
|
||||||
|
"route.npz": "51e8c26eedde253e171af47d704c1967ba45ae6825d883393bec1fb9e00251c1",
|
||||||
|
"encoder_comparison.npz": "7a625d3ed5cbd8013d1028aa3bc421740551dcae5c3d60981208bd047af9794c",
|
||||||
|
"pose_replay.npz": "4457ccc0868354749da5b72c1dea0faf750f783dfdc87038101288fdcba1e707"
|
||||||
|
},
|
||||||
|
"report_sha256": "b95247bdf6bbec16e5dc4781eaf7a678aca787418251cd161f6438e3173ad490"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stress": {
|
||||||
|
"seed": 20260907,
|
||||||
|
"random_cycles": 200000,
|
||||||
|
"mirrored_core_updates": 200000,
|
||||||
|
"invalid_or_inactive_resets": 3537,
|
||||||
|
"field_boundary_cases": 18138,
|
||||||
|
"float32_can_round_trips": 218138,
|
||||||
|
"analytic_targets_scalar_slew_and_mirror_checks_pass": true,
|
||||||
|
"direct_raw_float32_packing_matches_host_output": true,
|
||||||
|
"max_continuous_step_c0_c1": [
|
||||||
|
0.40000000000000147,
|
||||||
|
0.05000000000000002
|
||||||
|
],
|
||||||
|
"calibration_approved": false,
|
||||||
|
"scope": "Numerical construction only; no PSCM response or closed-loop performance claims.",
|
||||||
|
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7"
|
||||||
|
},
|
||||||
|
"mutation_checks": {
|
||||||
|
"mutations": [
|
||||||
|
{
|
||||||
|
"mutation": "halve_heading",
|
||||||
|
"detected_by_tests": true,
|
||||||
|
"failed_tests": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mutation": "cap_heading_preview",
|
||||||
|
"detected_by_tests": true,
|
||||||
|
"failed_tests": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mutation": "erase_centering",
|
||||||
|
"detected_by_tests": true,
|
||||||
|
"failed_tests": 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mutation": "slow_c0_to_c1_rate",
|
||||||
|
"detected_by_tests": true,
|
||||||
|
"failed_tests": 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mutation": "retain_invalid_state",
|
||||||
|
"detected_by_tests": true,
|
||||||
|
"failed_tests": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mutation": "ignore_model_freshness",
|
||||||
|
"detected_by_tests": true,
|
||||||
|
"failed_tests": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mutation": "ignore_model_clock_rollback",
|
||||||
|
"detected_by_tests": true,
|
||||||
|
"failed_tests": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mutation": "reverse_c0_wire_sign",
|
||||||
|
"detected_by_tests": true,
|
||||||
|
"failed_tests": 11
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"all_detected": true
|
||||||
|
},
|
||||||
|
"native_test_dependency": {
|
||||||
|
"scope": "Native dependency for inherited Params selection test only; copied existing local build, not rebuilt.",
|
||||||
|
"source_library": "/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/openpilot/common/libparams_c.dylib",
|
||||||
|
"sha256": "ddde738108eab18b75f085c865c43aa197fd79a0384b390b1515ff90116e20e0",
|
||||||
|
"byte_identical_source_files": [
|
||||||
|
"openpilot/common/params.cc",
|
||||||
|
"openpilot/common/params.h",
|
||||||
|
"openpilot/common/params_c.cc",
|
||||||
|
"openpilot/common/params.py",
|
||||||
|
"openpilot/common/params_keys.h",
|
||||||
|
"openpilot/common/queue.h",
|
||||||
|
"openpilot/common/util.cc",
|
||||||
|
"openpilot/common/util.h",
|
||||||
|
"openpilot/common/hardware/hw.h"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"standards_findings": 0,
|
||||||
|
"spec_findings": 0,
|
||||||
|
"method": "Independent parallel read-only reviews; 120 focused tests independently passed."
|
||||||
|
},
|
||||||
|
"source_sha256": {
|
||||||
|
"openpilot/selfdrive/controls/lib/ford_model_action.py": "cb6353f00f2f5c84df4e606c6b7e20650f8c1e908b9fd71890f72aa4a5e42592",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action.py": "c3b971622cc4041575aeec1826d45b76cebab9a2f77b2b9525c85d4184961295",
|
||||||
|
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "5aaa29c1f11080b7df0165fd0202a29b065053809e5f9c4b9ed8abfcce47e41b",
|
||||||
|
"tools/ford_pscm_lab/__init__.py": "db4b8b7d2e317ed34ca0ec220bf9d53e7b80a23e766f4e1cb2c8224dada45f2e",
|
||||||
|
"tools/ford_pscm_lab/model_action_replay.py": "c114c479bd22e4fc61a3e8d3ee7fae5d71d1d80ec4b952faad8f8f3692fb1508",
|
||||||
|
"tools/ford_pscm_lab/stress_model_action.py": "a78a50eed1f801f3b096d694ab8c2fd70804b6c250465b4152f38d83770a982b",
|
||||||
|
"tools/ford_pscm_lab/test_model_action_replay.py": "09d024c59d44b83ec081d6416d0f946a7719a73f22213af1f4ddc03dc4e6f4ac"
|
||||||
|
},
|
||||||
|
"artifacts": {
|
||||||
|
"directory": ".cache/ford_model_action",
|
||||||
|
"route_reports": [
|
||||||
|
"route95/report.json",
|
||||||
|
"route90/report.json"
|
||||||
|
],
|
||||||
|
"stress_report": "stress.json",
|
||||||
|
"mutation_report": "mutations/report.json",
|
||||||
|
"test_log": "ford_suite.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
# Ford C2-free model-pose tracking with measured feedback
|
||||||
|
|
||||||
|
This experiment is retired. Its implementation, setting and dedicated tests
|
||||||
|
were removed from the selected-action drive-test branch. For current setup,
|
||||||
|
see [Ford selected-action drive testing](ford_model_action_drive_test.md).
|
||||||
|
The material below is historical; it does not describe an available toggle.
|
||||||
|
|
||||||
|
Hypothesis `model-pose-c0-c1-feedback-v8` retains the model-pose C0/C1 base
|
||||||
|
and adds two guarded release policies. When measured turning exceeds both
|
||||||
|
current and delayed requests, a separate output guard prevents same-direction
|
||||||
|
C0/C1 growth, including while feedback history rebuilds after driver input.
|
||||||
|
When turning instead falls below both requests and is no longer increasing,
|
||||||
|
bounded C1 tracking can use remaining release-entry command headroom.
|
||||||
|
Existing opposing-bias recovery still stops at zero bias. Geometry, blending,
|
||||||
|
feedback gain, slew rates and field limits are unchanged; C2/C3 remain zero.
|
||||||
|
|
||||||
|
This is an experimental outer controller around the multivariable PSCM.
|
||||||
|
Its geometry does not define a calibrated C0/C1-to-wheel mapping or an angle
|
||||||
|
servo. V8 has offline validation only. Command replay cannot establish the
|
||||||
|
truck's response, closed-loop stability, or an overshoot improvement.
|
||||||
|
|
||||||
|
## Evidence and scope
|
||||||
|
|
||||||
|
Route80 ran v3 and contains both sustained under-response and over-response.
|
||||||
|
Representative eligible windows had median CAN response/request ratios of
|
||||||
|
0.78, 1.77 and 0.69 with a declared 0.2-second comparison interval. These
|
||||||
|
are descriptive tracking ratios, not identified controller gains.
|
||||||
|
|
||||||
|
V4 replaced separate model-heading C1 with selected-curvature C1 and reduced
|
||||||
|
heading demand in several large maneuvers. The user subsequently reported
|
||||||
|
weak turning and steering repeatedly stopping near 85 degrees. Older logs
|
||||||
|
contain larger wheel angles; the inspected host code has no fixed 85-degree
|
||||||
|
wheel stop, although upstream curvature limits depend on speed.
|
||||||
|
|
||||||
|
Route83 had the Sunnylink toggle on, but omitted EPS firmware responses.
|
||||||
|
The former firmware gate selected the default `FordPathController`; replay
|
||||||
|
reproduced its recorded C0/C1/C2 requests. Its favorable turns are evidence
|
||||||
|
for the existing model-pose construction, not validation of v5 or v6.
|
||||||
|
V6 reuses that construction while replacing its remaining C2 request with
|
||||||
|
C0/C1 geometry. Removing C2 changes the request received by the PSCM, so
|
||||||
|
matching large C0/C1 commands does not guarantee matching vehicle motion.
|
||||||
|
|
||||||
|
Route8a ran v6 and was reported as the best drive. Route8e ran v7 throughout
|
||||||
|
with the experiment enabled; it includes entry lag and excessive turning
|
||||||
|
while requests release. Fixed-input v6/v7 replay produced identical commands
|
||||||
|
in the main reversal and over-response examples, so the v7 recovery change
|
||||||
|
does not directly explain their command behavior. In the over-response
|
||||||
|
example, model C0/C1 grew while selected curvature fell and driver resets
|
||||||
|
repeatedly removed feedback history. Another exit remained deficient after
|
||||||
|
opposing bias reached zero. These observations motivate the v8 guards; they
|
||||||
|
do not isolate an EPS transfer function or demonstrate the proposed response.
|
||||||
|
|
||||||
|
## Base request
|
||||||
|
|
||||||
|
controlsd selects valid `lateralManeuverPlan.desiredCurvature`, otherwise
|
||||||
|
`modelV2.action.desiredCurvature`, after the existing curvature limiter.
|
||||||
|
This action already includes upstream delay handling; it receives no extra
|
||||||
|
response advance here.
|
||||||
|
|
||||||
|
The model contribution uses the existing allocator's raw forward pose and
|
||||||
|
bounded short-pose correction. `_model_pose` advances 0.1 seconds, retains
|
||||||
|
the model's remaining forward geometry, and separately corrects the short
|
||||||
|
pose using measured curvature and its recent change. Its offset preview is
|
||||||
|
up to 7 m and its heading preview is up to max(7 m, speed × 1 s), bounded by
|
||||||
|
available path length. This raw pose is not passed through a second model
|
||||||
|
filter. The filtered, ego-aligned reference remains available for comparison
|
||||||
|
and the existing geometry-validity checks.
|
||||||
|
|
||||||
|
```text
|
||||||
|
share(k) = clip((k - 0.006/m) / (0.012/m - 0.006/m), 0, 1)
|
||||||
|
aligned = desired_curvature × model_forward_heading > 0
|
||||||
|
model_share = min(share(abs(desired_curvature)), share(model_curvature_demand))
|
||||||
|
if aligned, otherwise 0
|
||||||
|
model_pair = existing_pose_encoder(model_pose, model_share, C2=0)
|
||||||
|
|
||||||
|
remaining_curvature = desired_curvature × (1 - model_share)
|
||||||
|
L0 = max(8 m, speed × 1 s)
|
||||||
|
L1 = max(7 m, speed × 1 s)
|
||||||
|
curvature_C0 = 0.5 × remaining_curvature × L0²
|
||||||
|
curvature_C1 = remaining_curvature × L1
|
||||||
|
C0_base = clip(model_pair.C0 + curvature_C0, ±5.11 m)
|
||||||
|
C1_base = clip(model_pair.C1 + curvature_C1, ±0.5 rad)
|
||||||
|
```
|
||||||
|
|
||||||
|
`model_curvature_demand` is the larger absolute curvature implied by the
|
||||||
|
forward offset and heading previews. The share uses the existing allocator's
|
||||||
|
0.006–0.012/m thresholds. Both model and action must request a substantial
|
||||||
|
turn in the same direction before model pose supplies the full base.
|
||||||
|
Small, flat, opposed or zero requests use the curvature contribution; zero
|
||||||
|
action produces a zero base. Partial shares combine both contributions.
|
||||||
|
The existing pose encoder retains its quantization and field-allocation rules.
|
||||||
|
The residual-curvature lift is geometric, not a claim of EPS equivalence to C2.
|
||||||
|
|
||||||
|
The inherited pose encoder allocates heading overflow using its asymmetric
|
||||||
|
limits (+0.5235/−0.5 rad), before the symmetric final ±0.5 rad
|
||||||
|
heading bound. On clipped tails, this can leave mirrored C0 requests differing
|
||||||
|
by up to 0.0235 rad × 7 m = 0.1645 m. The favorable comparison anchors lie
|
||||||
|
below that heading cap; full model-base odd symmetry is not claimed.
|
||||||
|
|
||||||
|
## Measured feedback and limits
|
||||||
|
|
||||||
|
```text
|
||||||
|
past_request = selected curvature held at or before (measurement_time - delay)
|
||||||
|
yaw_error = measured_speed × past_request - measured_yaw_rate
|
||||||
|
bias_trial = released_bias + feedback_gain × yaw_error × measurement_dt
|
||||||
|
C1_unconstrained = clip(C1_base + accepted_bias, ±0.5 rad)
|
||||||
|
C1_target = temporary_backoff_ceiling(C1_unconstrained) if backoff_active
|
||||||
|
otherwise C1_unconstrained
|
||||||
|
```
|
||||||
|
|
||||||
|
Measured yaw is negated Ford CAN yaw, matching the control sign convention.
|
||||||
|
The historical request uses zero-order hold; it never interpolates toward a
|
||||||
|
future publication. Nominal comparison delay is `CP.steerActuatorDelay`
|
||||||
|
(0.2 seconds on the source vehicle). Feedback compares against selected
|
||||||
|
curvature, not curvature inferred from the model-pose coefficients.
|
||||||
|
|
||||||
|
| Quantity | Value |
|
||||||
|
|---|---:|
|
||||||
|
| C0 / C1 final bounds | ±5.11 m / ±0.5 rad |
|
||||||
|
| Independent C0 / C1 slew | 4 m/s / 0.5 rad/s |
|
||||||
|
| Feedback integration scale | 1.0 |
|
||||||
|
| Feedback minimum speed | 2 m/s |
|
||||||
|
| Maximum PSCM/core input age | 150 ms |
|
||||||
|
| Allowed timestamp lead | 5 ms |
|
||||||
|
| Release comparison tolerance | one C1 wire quantum, 0.0005 rad |
|
||||||
|
|
||||||
|
The integration scale, preview distances and blend thresholds are effective
|
||||||
|
gains; none establishes stability. No wheel-response gain is fitted.
|
||||||
|
Zero yaw error retains acquired bias while an eligible turn continues.
|
||||||
|
Host anti-windup admits reachable correction within the combined C1 field
|
||||||
|
and slew limits. Feedback overflow is not transferred into C0.
|
||||||
|
|
||||||
|
The release logic scales bias as the bounded base decreases and resets on
|
||||||
|
zero/reversal. When delayed curvature still represents a stronger or opposing
|
||||||
|
request, or PSCM reports LimitReached, new integration is normally frozen.
|
||||||
|
One exception permits measured-error backoff: measured turning must exceed
|
||||||
|
both the delayed and current selected yaw requests in the base's direction,
|
||||||
|
and total heading must still have the base's sign. Exceeding only an older,
|
||||||
|
smaller request during turn-in does not qualify. The accepted increment may
|
||||||
|
only reduce that existing total toward zero; it cannot grow the request or
|
||||||
|
carry it through zero. Existing host field and slew limits still apply.
|
||||||
|
|
||||||
|
The existing release-recovery exception requires fresh valid PSCM status with
|
||||||
|
limit below 2, retained bias opposing the base, and both current and delayed
|
||||||
|
requests aligned with that base. Measured turning must be below both requests
|
||||||
|
in their direction. It then uses the current yaw deficit × the existing
|
||||||
|
feedback gain × measurement interval to unwind only the opposing bias toward
|
||||||
|
zero. The increment is clipped so recovery cannot cross zero bias or create
|
||||||
|
demand beyond the existing base. Common host anti-windup still limits what
|
||||||
|
can be accepted. A separate release-tracking exception is described below;
|
||||||
|
other constrained cases remain frozen. PSCM limit 2 never permits either
|
||||||
|
request-increasing exception.
|
||||||
|
The no-new-bias restriction applies to `release_recovery`. It does not apply
|
||||||
|
to the separate bounded `release_tracking` branch. Once release ends,
|
||||||
|
ordinary eligible integration can add correction beyond the base as before;
|
||||||
|
its existing limits and guards are unchanged.
|
||||||
|
|
||||||
|
`release_recovery` and `feedback_recovery_active=true` indicate that the
|
||||||
|
recovery branch actually changed bias on that update. If host anti-windup
|
||||||
|
blocks the entire increment, the status remains `host_limit` and the flag is
|
||||||
|
false. Recovery is evaluated only on fresh measurements; the flag is false
|
||||||
|
on repeated-measurement updates and after reset.
|
||||||
|
|
||||||
|
Diagnostics distinguish `release_backoff` and `pscm_backoff`; a release takes
|
||||||
|
precedence when both conditions apply. While `feedback_backoff_active` is
|
||||||
|
true, total C1 is also capped at the preceding continuous heading request in
|
||||||
|
the current request direction and at zero in the opposite direction. This
|
||||||
|
ceiling affects the output only: it is not stored or projected into bias.
|
||||||
|
The measured-error increment can still update bias under the normal limits,
|
||||||
|
but a changing model base does not create persistent integral suppression.
|
||||||
|
The ceiling persists between repeated measurements; C1 cannot grow or reverse
|
||||||
|
while it applies. The next fresh measurement clears it unless backoff is
|
||||||
|
again warranted. It does not cap C0, and normal feedback has its own rules
|
||||||
|
outside backoff. Independent slew remains 0.5 rad/s for C1 and 4 m/s for C0.
|
||||||
|
Backoff still compares against the delayed reference, so response lag remains.
|
||||||
|
Reducing a request does not demonstrate that physical overshoot is resolved.
|
||||||
|
|
||||||
|
## V8 release guard and tracking
|
||||||
|
|
||||||
|
`ReleaseGuard` retains selected-request history independently of feedback
|
||||||
|
bias history. Driver-related feedback resets do not erase that reference,
|
||||||
|
but the guard still requires current fresh valid PSCM status, no current
|
||||||
|
driver override, and the existing input and speed eligibility. Invalid core
|
||||||
|
input or disengagement resets its history with the controller.
|
||||||
|
|
||||||
|
During release, measured yaw must exceed both the current and delay-matched
|
||||||
|
requests in the requested turn direction. Only then does the guard cap
|
||||||
|
same-direction C0/C1 growth at each preceding continuous request. Terms
|
||||||
|
already reducing the turn, including an opposing C0 centering offset, remain
|
||||||
|
available. The guard follows base allocation and C1 feedback, so changing
|
||||||
|
model geometry cannot bypass it. Its ceilings affect outputs, never stored
|
||||||
|
bias. No scalar-curvature cap replaces strong model geometry during turn-in
|
||||||
|
or undertracking. Existing independent slew and field limits still apply.
|
||||||
|
|
||||||
|
`release_tracking` addresses an eligible release deficit once bias is zero
|
||||||
|
or already in the base's direction. Both current and delayed requests must
|
||||||
|
align with that base, measured turning must be below both, and measured
|
||||||
|
curvature must not be rising in the turn direction across the response
|
||||||
|
interval by more than one C1 wire quantum after scaling by heading preview.
|
||||||
|
Fresh valid PSCM status with limit below 2 is required. The current yaw deficit
|
||||||
|
uses the existing integration gain and measurement interval;
|
||||||
|
new C1 tracking increments are limited by command headroom captured at
|
||||||
|
release entry, tapered with remaining desired curvature. The allowance is
|
||||||
|
`max(0, entry_command_magnitude - abs(base)) × min(1, abs(desired) / entry_reference)`
|
||||||
|
above the current base; any existing same-direction bias consumes it first.
|
||||||
|
This limits new tracking integration, not the existing model base or bias.
|
||||||
|
Only that additional allowance is tapered; strong model geometry remains
|
||||||
|
available. A brief pause does not reacquire a higher entry
|
||||||
|
ceiling; a full response interval without release ends the retained episode.
|
||||||
|
Common host anti-windup, field and slew bounds still apply. Opposing bias
|
||||||
|
continues through `release_recovery`, which stops at zero, before any separate
|
||||||
|
tracking exception can be considered.
|
||||||
|
|
||||||
|
Neither exception relaxes the PSCM LimitReached growth restriction. The
|
||||||
|
reference delay and finite response time remain; these output policies are
|
||||||
|
command-construction changes, not evidence of improved physical tracking.
|
||||||
|
|
||||||
|
## PSCM status and driver handling
|
||||||
|
|
||||||
|
card publishes `Lane_Assist_Data3_FD1` in `carStateSP.fordPscmStatus`, retaining
|
||||||
|
the original CAN receipt timestamp. Republishing carStateSP or receiving
|
||||||
|
unrelated frames cannot refresh it. The opendbc submodule is unchanged.
|
||||||
|
|
||||||
|
Feedback requires valid fresh status, InProgress lateral state (2), capability
|
||||||
|
LimitedModeAvailable or ExtendedModeAvailable (1 or 2), and no denial.
|
||||||
|
Missing, malformed, stale, backward-timestamped, denied or unavailable status
|
||||||
|
clears feedback bias/history and disables the separate release guard,
|
||||||
|
leaving the base subject to its core validity gates.
|
||||||
|
LimitReached (2) permits only the bounded request-reducing backoff described
|
||||||
|
above and otherwise freezes integration. LimitWithDriverActive (3) clears
|
||||||
|
feedback. Backoff still requires fresh, valid, InProgress status with an
|
||||||
|
available capability and no denial. These generic PSCM reports do not identify
|
||||||
|
a specific torque or rate limit.
|
||||||
|
|
||||||
|
`steeringPressed`, raw torque above the existing Ford driver allowance, or
|
||||||
|
nonfinite torque clear feedback. Below 2 m/s feedback also clears. A fresh
|
||||||
|
feedback reference interval is required after override; the independent
|
||||||
|
release guard can use retained valid request history once its current gates
|
||||||
|
are satisfied. Base requests retain normal
|
||||||
|
PSCM driver arbitration while lateral control remains authorized; an unset
|
||||||
|
override flag cannot rule out subthreshold driver influence.
|
||||||
|
|
||||||
|
## Gates and Sunnylink selection
|
||||||
|
|
||||||
|
Core model/action/car-state freshness, finite-value, clock and speed checks
|
||||||
|
remain in place. Invalid core inputs reset both commands and clear latActive.
|
||||||
|
Raw model geometry is validated on every update, including repeated model
|
||||||
|
timestamps; an invalid raw path cannot reuse the cached valid reference.
|
||||||
|
Missing PSCM status disables feedback, not an otherwise valid base request.
|
||||||
|
|
||||||
|
Vehicle → Ford → **C2-Free Path Tracking (Experimental)** retains the
|
||||||
|
`FordVirtualAngleController` key, default-off setting and offroad/onroad cycle
|
||||||
|
requirement. Enabled selects v8 on Ford CAN FD `FORD_F_150_LIGHTNING_MK1`
|
||||||
|
regardless of missing or different EPS firmware-query results. Other platforms
|
||||||
|
retain their existing controller. V8 takes priority over PSCM Coefficient
|
||||||
|
Observer while selected; disabling and cycling offroad/onroad restores the
|
||||||
|
previous selection. Controller selection does not force lateral engagement.
|
||||||
|
|
||||||
|
The analyzed firmware is `RL38-14D003-AA`; removing the eligibility check
|
||||||
|
is not validation of other firmware. No live device setting is changed.
|
||||||
|
|
||||||
|
## Diagnostics and verification
|
||||||
|
|
||||||
|
The 5 Hz `Ford C2-free path tracking` event keeps its name and identifies v8.
|
||||||
|
`model_offset_base` / `model_heading_base` report the already weighted and
|
||||||
|
encoded model contribution; `curvature_offset_base` / `curvature_heading_base`
|
||||||
|
report the residual-curvature contribution. `model_share` and `base_guard`
|
||||||
|
identify model-pose, blended, curvature-only, opposed-model and zero-request
|
||||||
|
cases. `heading_base` is the bounded pre-feedback C1. `offset_target` and
|
||||||
|
`heading_target` are the final targets after the independent release guard;
|
||||||
|
`offset_target_unguarded` and `heading_target_unguarded` retain the inputs to
|
||||||
|
that guard. The latter C1 already includes its normal feedback/backoff policy.
|
||||||
|
|
||||||
|
The event retains source timestamps, measured curvature/yaw, final commands,
|
||||||
|
slew scales, feedback bias/status/history, raw torque and PSCM status/age.
|
||||||
|
`feedback_backoff_active` records the persistent heading ceiling, including
|
||||||
|
cycles whose feedback status is `no_new_measurement`.
|
||||||
|
`release_guard_active` and `release_guard_reference_curvature` expose the
|
||||||
|
independent C0/C1 guard and its retained delayed reference.
|
||||||
|
`feedback_release_tracking_active`, `feedback_release_ceiling` and
|
||||||
|
`feedback_curvature_delta` identify accepted release
|
||||||
|
tracking, the total-heading threshold used to admit new bias, and the
|
||||||
|
measured-curvature change across the response interval (1/m). The tracking
|
||||||
|
flag is true only when the branch accepts a bias change on a new measurement;
|
||||||
|
it is false on repeated measurements. The ceiling/trend fields can describe
|
||||||
|
an evaluated condition even when no increment is accepted.
|
||||||
|
`feedback_recovery_active` records an accepted recovery increment on this
|
||||||
|
update only; it does not persist between measurements.
|
||||||
|
`feedback_yaw_error` retains its delayed-reference meaning. Recovery instead
|
||||||
|
uses current error, reconstructed from logged `desired_curvature`,
|
||||||
|
synchronized car-state speed and `yaw_rate`; those two errors can differ.
|
||||||
|
During backoff or the independent release guard, `heading_target` can be lower in the request direction than
|
||||||
|
the bounded sum of `heading_base` and `heading_bias`, because the temporary
|
||||||
|
ceiling is not part of the stored bias.
|
||||||
|
`model_heading_target` remains a filtered comparison reference; it is not the
|
||||||
|
weighted model contribution. `angleState.saturated` is not an EPS-limit signal.
|
||||||
|
|
||||||
|
Validation must cover large recorded maneuvers, flat-model centering, both
|
||||||
|
turn directions, model/action disagreement, share transitions, release and
|
||||||
|
reversal, release/limit backoff without growth or zero crossing, status/driver
|
||||||
|
resets, reference causality, bounds, slew and CAN packing with C2/C3 zero.
|
||||||
|
Recovery checks cover both directions, stopping at zero bias, repeated
|
||||||
|
measurements, current-and-delayed agreement, and rejection at PSCM limit 2.
|
||||||
|
Old v3/v4 command-equality expectations do not define
|
||||||
|
v8 success. Guard checks also cover driver reset/history rebuilding,
|
||||||
|
same-direction growth, opposing coefficients, repeated measurements,
|
||||||
|
undertracking and invalid-status inhibition. Tracking checks cover delayed
|
||||||
|
curvature trends and tapered release-entry headroom. Historical v5–v7 replay
|
||||||
|
results remain historical observations.
|
||||||
|
|
||||||
|
The v8 recorded-input fixture contains 15,273 cycles with 4,879 selected
|
||||||
|
evidence samples. Base allocation and output eligibility match v7. In the
|
||||||
|
clean deficient exit, median absolute C1 changes from 0.0665 to 0.0845 rad
|
||||||
|
while C0 stays unchanged. The growth guard also acts while feedback history
|
||||||
|
rebuilds; the largest over-growth witness includes nearby driver input and
|
||||||
|
is excluded from the strict autonomous tracking score. Both good comparison
|
||||||
|
curves in that fixture retain their median requests, and the older large-turn
|
||||||
|
fixtures retain their required command scale.
|
||||||
|
|
||||||
|
On the earlier good drive, one comparison curve retains extra C1 after
|
||||||
|
eligible release tracking: median magnitude changes from 0.121 to 0.128 rad.
|
||||||
|
In its 103–110 s interval, tracking increments occur only while measured
|
||||||
|
turning falls short, with a median current response/request ratio of 0.895.
|
||||||
|
Acquired bias can persist after matching, as with ordinary integral feedback.
|
||||||
|
This collateral command change remains a reason to compare new vehicle logs.
|
||||||
|
Replay fixes recorded motion and planner outputs, so enabled vehicle logs
|
||||||
|
are still required to assess tracking error, oscillation and interventions.
|
||||||
@@ -30,7 +30,7 @@ Each car brand is supported by a standard interface structure in `opendbc/car/[b
|
|||||||
|
|
||||||
For historical reasons, openpilot still contains a small amount of car-specific logic. This will eventually be migrated to opendbc or otherwise removed.
|
For historical reasons, openpilot still contains a small amount of car-specific logic. This will eventually be migrated to opendbc or otherwise removed.
|
||||||
|
|
||||||
* `selfdrive/car/car_specific.py`: Brand-specific event logic
|
* `openpilot/selfdrive/car/car_specific.py`: Brand-specific event logic
|
||||||
|
|
||||||
# How do I port car?
|
# How do I port car?
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Replaying is a critical tool for openpilot development and debugging.
|
|||||||
## Replaying a route
|
## Replaying a route
|
||||||
*Hardware required: none*
|
*Hardware required: none*
|
||||||
|
|
||||||
Just run `tools/replay/replay --demo`.
|
Just run `openpilot/tools/replay/replay --demo`.
|
||||||
|
|
||||||
## Replaying CAN data
|
## Replaying CAN data
|
||||||
*Hardware required: jungle and comma four*
|
*Hardware required: jungle and comma four*
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ source .venv/bin/activate
|
|||||||
|
|
||||||
Then, compile openpilot:
|
Then, compile openpilot:
|
||||||
```bash
|
```bash
|
||||||
scons -j$(nproc)
|
scons
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. Run replay
|
## 2. Run replay
|
||||||
@@ -28,10 +28,10 @@ scons -j$(nproc)
|
|||||||
We'll run the `replay` tool with the demo route to get data streaming for testing our UI changes.
|
We'll run the `replay` tool with the demo route to get data streaming for testing our UI changes.
|
||||||
```bash
|
```bash
|
||||||
# in terminal 1
|
# in terminal 1
|
||||||
tools/replay/replay --demo
|
openpilot/tools/replay/replay --demo
|
||||||
|
|
||||||
# in terminal 2
|
# in terminal 2
|
||||||
./selfdrive/ui/ui.py
|
./openpilot/selfdrive/ui/ui.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The openpilot UI should launch and show a replay of the demo route.
|
The openpilot UI should launch and show a replay of the demo route.
|
||||||
@@ -45,10 +45,10 @@ Now let’s update the speed display color in the UI.
|
|||||||
|
|
||||||
Search for the function responsible for rendering the current speed:
|
Search for the function responsible for rendering the current speed:
|
||||||
```bash
|
```bash
|
||||||
git grep "_draw_current_speed" selfdrive/ui/onroad/hud_renderer.py
|
git grep "_draw_current_speed" openpilot/selfdrive/ui/onroad/hud_renderer.py
|
||||||
```
|
```
|
||||||
|
|
||||||
You'll find the relevant code inside `selfdrive/ui/onroad/hud_renderer.py`, in this function:
|
You'll find the relevant code inside `openpilot/selfdrive/ui/onroad/hud_renderer.py`, in this function:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def _draw_current_speed(self, rect: rl.Rectangle) -> None:
|
def _draw_current_speed(self, rect: rl.Rectangle) -> None:
|
||||||
@@ -72,7 +72,7 @@ Change `COLORS.white` to make it **blue** instead of white. A nice soft blue is
|
|||||||
|
|
||||||
After making changes, re-run the UI to see your new UI:
|
After making changes, re-run the UI to see your new UI:
|
||||||
```bash
|
```bash
|
||||||
./selfdrive/ui/ui.py
|
./openpilot/selfdrive/ui/ui.py
|
||||||
```
|
```
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
+555
@@ -0,0 +1,555 @@
|
|||||||
|
import argparse
|
||||||
|
import functools
|
||||||
|
import html
|
||||||
|
import http.server
|
||||||
|
import json
|
||||||
|
import posixpath
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import urllib.parse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
DOCS_DIR = Path(__file__).resolve().parent
|
||||||
|
SITE_DIR = DOCS_DIR / "_site"
|
||||||
|
TEMPLATE_FILE = DOCS_DIR / "template.html"
|
||||||
|
EXCLUDE_DIRS = {"_site", "__pycache__"}
|
||||||
|
|
||||||
|
REPO_URL = "https://github.com/commaai/openpilot/"
|
||||||
|
|
||||||
|
# (title, target) pairs. target is a page path or an absolute URL.
|
||||||
|
# A None target marks a section header.
|
||||||
|
NAV: list[tuple[str, str | None]] = [
|
||||||
|
("What is openpilot?", "index.md"),
|
||||||
|
("How-to", None),
|
||||||
|
("Turn the speed blue", "how-to/turn-the-speed-blue.md"),
|
||||||
|
("Connect to a comma 3X or four", "how-to/connect-to-comma.md"),
|
||||||
|
("Add support for a car", "how-to/car-port.md"),
|
||||||
|
("Concepts", None),
|
||||||
|
("Logs", "concepts/logs.md"),
|
||||||
|
("Safety", "concepts/safety.md"),
|
||||||
|
("Glossary", "concepts/glossary.md"),
|
||||||
|
("Contributing", None),
|
||||||
|
("Feedback", "contributing/feedback.md"),
|
||||||
|
("Roadmap", "contributing/roadmap.md"),
|
||||||
|
("Contributing Guide →", "https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md"),
|
||||||
|
("Links", None),
|
||||||
|
("Blog →", "https://blog.comma.ai"),
|
||||||
|
("Bounties →", "https://comma.ai/bounties"),
|
||||||
|
("GitHub →", "https://github.com/commaai"),
|
||||||
|
("Discord →", "https://discord.comma.ai"),
|
||||||
|
("X →", "https://x.com/comma_ai"),
|
||||||
|
]
|
||||||
|
|
||||||
|
GLOSSARY_DESCRIPTIONS = {
|
||||||
|
"onroad": "openpilot's system state while ignition is on.",
|
||||||
|
"offroad": "openpilot's system state while ignition is off.",
|
||||||
|
"route": "A route is a recording of an onroad session.",
|
||||||
|
"segment": "Routes are split into one minute chunks called segments.",
|
||||||
|
"comma connect": "The web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai).",
|
||||||
|
"panda": "The secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda).",
|
||||||
|
"comma four": "The latest hardware by comma.ai for running openpilot. More info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four).",
|
||||||
|
}
|
||||||
|
GLOSSARY_PAGE = "concepts/glossary.md"
|
||||||
|
GLOSSARY_ROUTE = GLOSSARY_PAGE.removesuffix(".md")
|
||||||
|
GLOSSARY_SKIP = frozenset("a code h1 h2 h3 h4 h5 h6 kbd pre script style".split())
|
||||||
|
|
||||||
|
_ENTITY = re.compile(r"&(?:#x?[0-9a-fA-F]+|[a-zA-Z]+);")
|
||||||
|
_LIST = re.compile(r"^(\s*)([*+-]|\d+\.)\s+(.*)$")
|
||||||
|
_HEADING = re.compile(r"^(#{1,6})\s+(.*)$")
|
||||||
|
_HR = re.compile(r"^(-{3,}|\*{3,}|_{3,})$")
|
||||||
|
_ATTR_URL = re.compile(r"""(?P<pre>\b(?:href|src)=(?P<q>["']))(?P<url>.*?)(?P=q)""")
|
||||||
|
_VOID = frozenset("br img hr meta link input".split())
|
||||||
|
_URL = re.compile(r"https?://[^\s<>\[\]\"']+")
|
||||||
|
_ADMONITION = re.compile(r"^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]$", re.I)
|
||||||
|
|
||||||
|
|
||||||
|
def page_route(path: str) -> str:
|
||||||
|
path = path.removesuffix(".md")
|
||||||
|
return posixpath.dirname(path) or "." if posixpath.basename(path) == "index" else path
|
||||||
|
|
||||||
|
|
||||||
|
def page_href(current: str, target: str) -> str:
|
||||||
|
route = posixpath.relpath(page_route(target), page_route(current))
|
||||||
|
return ("." if route == "." else route) + "/"
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite_relative_url(value: str, page: str) -> str | None:
|
||||||
|
url = urllib.parse.urlparse(value)
|
||||||
|
if value.startswith(("#", "/")) or url.scheme or url.netloc or not url.path:
|
||||||
|
return None
|
||||||
|
target = posixpath.normpath(posixpath.join(posixpath.dirname(page), url.path))
|
||||||
|
if target == ".." or target.startswith("../"):
|
||||||
|
return None
|
||||||
|
path = page_href(page, target) if target.endswith(".md") else posixpath.relpath(target, page_route(page))
|
||||||
|
return url._replace(path=path).geturl()
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite_html_urls(fragment: str, page: str) -> str:
|
||||||
|
def repl(m: re.Match[str]) -> str:
|
||||||
|
r = rewrite_relative_url(m.group("url"), page)
|
||||||
|
return m.group(0) if r is None else f'{m.group("pre")}{r}{m.group("q")}'
|
||||||
|
|
||||||
|
return _ATTR_URL.sub(repl, fragment)
|
||||||
|
|
||||||
|
|
||||||
|
def esc(text: str, attr: bool = False) -> str:
|
||||||
|
held: list[str] = []
|
||||||
|
|
||||||
|
def hold(m: re.Match[str]) -> str:
|
||||||
|
held.append(m.group(0))
|
||||||
|
return f"\0{len(held) - 1}\0"
|
||||||
|
|
||||||
|
return re.sub(r"\0(\d+)\0", lambda m: held[int(m.group(1))], html.escape(_ENTITY.sub(hold, text), quote=attr))
|
||||||
|
|
||||||
|
|
||||||
|
def clean_tooltip(description: str) -> str:
|
||||||
|
text = re.sub(r"\[([^\]]+)]\([^)]+\)", r"\1", description)
|
||||||
|
return re.sub(r"\s+", " ", re.sub(r"[*_~]", "", re.sub(r"`([^`]+)`", r"\1", text))).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def glossary_slug(label: str) -> str:
|
||||||
|
return label.replace(" ", "-").replace("_", "-").lower()
|
||||||
|
|
||||||
|
|
||||||
|
GLOSSARY_TERMS = [(glossary_slug(l), re.compile(rf"(?<!\w){re.escape(l)}(?!\w)", re.I), clean_tooltip(d)) for l, d in GLOSSARY_DESCRIPTIONS.items()]
|
||||||
|
GLOSSARY_DEFINITIONS = "\n".join(f'* <span id="{glossary_slug(l)}"></span>**{l}**: {d}' for l, d in GLOSSARY_DESCRIPTIONS.items())
|
||||||
|
|
||||||
|
|
||||||
|
def inject_glossary(body: str, page: str) -> str:
|
||||||
|
if page == GLOSSARY_PAGE:
|
||||||
|
return body
|
||||||
|
route = "." if page == "index.md" else page.removesuffix(".md")
|
||||||
|
base, seen, out, skip, depth = f"{posixpath.relpath(GLOSSARY_ROUTE, route)}/#", set(), [], None, 0
|
||||||
|
for part in re.split(r"(<[^>]+>)", body):
|
||||||
|
if not part:
|
||||||
|
continue
|
||||||
|
if part.startswith("<"):
|
||||||
|
out.append(part)
|
||||||
|
if part.startswith("<!") or not (m := re.match(r"</?\s*([a-zA-Z0-9]+)", part)):
|
||||||
|
continue
|
||||||
|
tag = m.group(1).lower()
|
||||||
|
if tag not in GLOSSARY_SKIP:
|
||||||
|
continue
|
||||||
|
closing, void = part.startswith("</"), part.endswith("/>") or tag in _VOID
|
||||||
|
if closing and skip == tag and depth:
|
||||||
|
depth -= 1
|
||||||
|
skip = None if not depth else skip
|
||||||
|
elif not closing and not void:
|
||||||
|
skip, depth = (tag, 1) if skip is None else (skip, depth + (skip == tag))
|
||||||
|
continue
|
||||||
|
if depth:
|
||||||
|
out.append(part)
|
||||||
|
continue
|
||||||
|
cur, text = 0, part
|
||||||
|
while True:
|
||||||
|
best = None
|
||||||
|
for order, (slug, pat, tip) in enumerate(GLOSSARY_TERMS):
|
||||||
|
if slug in seen or (found := pat.search(text, cur)) is None:
|
||||||
|
continue
|
||||||
|
cand = (found.start(), found.start() - found.end(), order, slug, tip, found.end(), found.group(0))
|
||||||
|
if best is None or cand[:3] < best[:3]:
|
||||||
|
best = cand
|
||||||
|
if best is None:
|
||||||
|
out.append(text[cur:])
|
||||||
|
break
|
||||||
|
start, _, _, slug, tip, end, matched = best
|
||||||
|
out.append(text[cur:start])
|
||||||
|
out.append(
|
||||||
|
f'<a class="glossary-term" data-glossary-term="" href="{base}{slug}">'
|
||||||
|
+ f'<span class="glossary-term__label">{matched}</span>'
|
||||||
|
+ f'<span class="glossary-term__tooltip" data-search-exclude="">{esc(tip)}</span></a>'
|
||||||
|
)
|
||||||
|
seen.add(slug)
|
||||||
|
cur = end
|
||||||
|
return "".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def slugify(text: str) -> str:
|
||||||
|
text = html.unescape(re.sub(r"<[^>]+>", "", text)).lower()
|
||||||
|
return re.sub(r"[-\s]+", "-", re.sub(r"[^\w\s-]", "", text, flags=re.UNICODE)).strip("-")
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_link(text: str, start: int) -> tuple[str, str, int] | None:
|
||||||
|
if start >= len(text) or text[start] != "[":
|
||||||
|
return None
|
||||||
|
depth, i = 0, start
|
||||||
|
while i < len(text):
|
||||||
|
depth += (text[i] == "[") - (text[i] == "]")
|
||||||
|
if text[i] == "]" and depth == 0:
|
||||||
|
label = text[start + 1 : i]
|
||||||
|
if i + 1 >= len(text) or text[i + 1] != "(":
|
||||||
|
return None
|
||||||
|
j, dp = i + 2, 1
|
||||||
|
while j < len(text) and dp:
|
||||||
|
dp += (text[j] == "(") - (text[j] == ")")
|
||||||
|
j += 1
|
||||||
|
return None if dp else (label, text[i + 2 : j - 1], j)
|
||||||
|
i += 1
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def autolink_plain(text: str) -> str:
|
||||||
|
parts: list[str] = []
|
||||||
|
last = 0
|
||||||
|
for m in _URL.finditer(text):
|
||||||
|
start = m.start()
|
||||||
|
if start > 0 and text[start - 1].isalnum():
|
||||||
|
continue
|
||||||
|
parts.append(esc(text[last:start]))
|
||||||
|
url = m.group(0).rstrip(".,;:!?)]")
|
||||||
|
parts.append(f'<a href="{esc(url, True)}">{esc(url)}</a>')
|
||||||
|
last = start + len(url)
|
||||||
|
parts.append(esc(text[last:]))
|
||||||
|
return "".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def render_inline(text: str, page: str) -> str:
|
||||||
|
out, i, n = [], 0, len(text)
|
||||||
|
while i < n:
|
||||||
|
if text[i] == "\n" and i >= 2 and text[i - 2 : i] == " " and out and out[-1].endswith(" "):
|
||||||
|
out[-1] = out[-1][:-2]
|
||||||
|
out.append("<br>\n")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if text[i] == "`" and (end := text.find("`", i + 1)) != -1:
|
||||||
|
out.append(f"<code>{esc(text[i + 1 : end])}</code>")
|
||||||
|
i = end + 1
|
||||||
|
continue
|
||||||
|
if text[i] == "!" and i + 1 < n and text[i + 1] == "[" and (p := _parse_link(text, i + 1)):
|
||||||
|
label, url, end = p
|
||||||
|
src = rewrite_relative_url(url, page) or url
|
||||||
|
out.append(f'<img alt="{esc(label, True)}" src="{esc(src, True)}">')
|
||||||
|
i = end
|
||||||
|
continue
|
||||||
|
if text[i] == "[" and (p := _parse_link(text, i)):
|
||||||
|
label, url, end = p
|
||||||
|
href = rewrite_relative_url(url, page) or url
|
||||||
|
out.append(f'<a href="{esc(href, True)}">{render_inline(label, page)}</a>')
|
||||||
|
i = end
|
||||||
|
continue
|
||||||
|
if text[i] == "<":
|
||||||
|
if text.startswith("<!--", i):
|
||||||
|
end = text.find("-->", i + 4)
|
||||||
|
end = n if end < 0 else end + 3
|
||||||
|
out.append(rewrite_html_urls(text[i:end], page))
|
||||||
|
i = end
|
||||||
|
continue
|
||||||
|
if m := re.match(r"<[^>]+>", text[i:]):
|
||||||
|
out.append(rewrite_html_urls(m.group(0), page))
|
||||||
|
i += len(m.group(0))
|
||||||
|
continue
|
||||||
|
if (text.startswith("**", i) or text.startswith("__", i)) and (end := text.find(text[i : i + 2], i + 2)) != -1:
|
||||||
|
out.append(f"<strong>{render_inline(text[i + 2 : end], page)}</strong>")
|
||||||
|
i = end + 2
|
||||||
|
continue
|
||||||
|
if text[i] in "*_" and i + 1 < n and text[i + 1] not in " \t\n" and (end := text.find(text[i], i + 1)) > i + 1:
|
||||||
|
out.append(f"<em>{render_inline(text[i + 1 : end], page)}</em>")
|
||||||
|
i = end + 1
|
||||||
|
continue
|
||||||
|
j = i + 1
|
||||||
|
while j < n and text[j] not in "`[<!*_":
|
||||||
|
j += 1
|
||||||
|
out.append(autolink_plain(text[i:j]))
|
||||||
|
i = j
|
||||||
|
return "".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def _trow(line: str) -> list[str]:
|
||||||
|
return [c.strip() for c in line.strip().removeprefix("|").removesuffix("|").split("|")]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_sep(line: str) -> bool:
|
||||||
|
return "|" in line and all(re.fullmatch(r":?-{3,}:?", c.strip()) for c in _trow(line))
|
||||||
|
|
||||||
|
|
||||||
|
def _align(sep: str) -> str:
|
||||||
|
s = sep.strip()
|
||||||
|
if s.startswith(":") and s.endswith(":"):
|
||||||
|
return ' style="text-align: center;"'
|
||||||
|
if s.endswith(":"):
|
||||||
|
return ' style="text-align: right;"'
|
||||||
|
if s.startswith(":"):
|
||||||
|
return ' style="text-align: left;"'
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _list_info(line: str) -> tuple[int, str, str] | None:
|
||||||
|
m = _LIST.match(line)
|
||||||
|
return None if not m else (len(m.group(1)) // 4, "ol" if m.group(2)[-1] == "." else "ul", m.group(3))
|
||||||
|
|
||||||
|
|
||||||
|
def _render_blocks(text: str, page: str) -> str:
|
||||||
|
lines, out, i, n = text.splitlines(), [], 0, 0
|
||||||
|
n = len(lines)
|
||||||
|
while i < n:
|
||||||
|
line, s = lines[i], lines[i].strip()
|
||||||
|
if not s:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if s.startswith("```"):
|
||||||
|
lang, body = s[3:].strip(), []
|
||||||
|
i += 1
|
||||||
|
while i < n and not lines[i].strip().startswith("```"):
|
||||||
|
body.append(lines[i])
|
||||||
|
i += 1
|
||||||
|
if i < n:
|
||||||
|
i += 1
|
||||||
|
code = html.escape("\n".join(body) + ("\n" if body else ""))
|
||||||
|
cls = f' class="language-{html.escape(lang)}"' if lang else ""
|
||||||
|
out.append(f"<pre><code{cls}>{code}</code></pre>")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if m := _HEADING.match(s):
|
||||||
|
content, level = m.group(2).rstrip("#").strip(), len(m.group(1))
|
||||||
|
sid = slugify(content)
|
||||||
|
out.append(f'<h{level} id="{sid}">{render_inline(content, page)}<a class="headerlink" href="#{sid}" title="Permanent link">#</a></h{level}>')
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if _HR.fullmatch(s):
|
||||||
|
out.append("<hr>")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "|" in line and i + 1 < n and _is_sep(lines[i + 1]):
|
||||||
|
headers, aligns = _trow(line), [_align(c) for c in _trow(lines[i + 1])]
|
||||||
|
i += 2
|
||||||
|
rows = []
|
||||||
|
while i < n and "|" in lines[i] and lines[i].strip():
|
||||||
|
rows.append(_trow(lines[i]))
|
||||||
|
i += 1
|
||||||
|
parts = (
|
||||||
|
["<table>", "<thead>", "<tr>"]
|
||||||
|
+ [f"<th{aligns[j] if j < len(aligns) else ''}>{render_inline(h, page)}</th>" for j, h in enumerate(headers)]
|
||||||
|
+ ["</tr>", "</thead>", "<tbody>"]
|
||||||
|
)
|
||||||
|
for row in rows:
|
||||||
|
parts.append("<tr>")
|
||||||
|
for j in range(len(headers)):
|
||||||
|
parts.append(f"<td{aligns[j] if j < len(aligns) else ''}>{render_inline(row[j] if j < len(row) else '', page)}</td>")
|
||||||
|
parts.append("</tr>")
|
||||||
|
out.append("\n".join(parts + ["</tbody>", "</table>"]))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if _list_info(line):
|
||||||
|
items: list[tuple[int, str, list[str]]] = []
|
||||||
|
while i < n:
|
||||||
|
if not lines[i].strip():
|
||||||
|
if i + 1 < n and _list_info(lines[i + 1]):
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
info = _list_info(lines[i])
|
||||||
|
if not info:
|
||||||
|
break
|
||||||
|
level, kind, body = info
|
||||||
|
chunk = [body]
|
||||||
|
i += 1
|
||||||
|
while i < n and lines[i].strip() and _list_info(lines[i]) is None:
|
||||||
|
t = lines[i].strip()
|
||||||
|
if t.startswith(("```", ">")) or _HEADING.match(t) or _HR.fullmatch(t):
|
||||||
|
break
|
||||||
|
chunk.append(lines[i])
|
||||||
|
i += 1
|
||||||
|
items.append((level, kind, chunk))
|
||||||
|
|
||||||
|
def render_list(items: list[tuple[int, str, list[str]]], start: int, min_level: int) -> tuple[str, int]:
|
||||||
|
if start >= len(items) or items[start][0] < min_level:
|
||||||
|
return "", start
|
||||||
|
kind, chunks, idx = items[start][1], [f"<{items[start][1]}>"], start
|
||||||
|
while idx < len(items) and items[idx][0] >= min_level:
|
||||||
|
level, ikind, body_lines = items[idx]
|
||||||
|
if level > min_level:
|
||||||
|
nested, idx = render_list(items, idx, level)
|
||||||
|
chunks[-1] = (chunks[-1][:-5] + nested + "</li>") if chunks[-1].endswith("</li>") else chunks[-1] + nested
|
||||||
|
continue
|
||||||
|
if ikind != kind:
|
||||||
|
chunks += [f"</{kind}>", f"<{ikind}>"]
|
||||||
|
kind = ikind
|
||||||
|
idx += 1
|
||||||
|
body = render_inline("\n".join(body_lines), page)
|
||||||
|
nested = ""
|
||||||
|
if idx < len(items) and items[idx][0] > min_level:
|
||||||
|
nested, idx = render_list(items, idx, min_level + 1)
|
||||||
|
chunks.append(f"<li>{body}{nested}\n</li>" if nested else f"<li>{body}</li>")
|
||||||
|
chunks.append(f"</{kind}>")
|
||||||
|
return "\n".join(chunks), idx
|
||||||
|
|
||||||
|
out.append(render_list(items, 0, items[0][0])[0])
|
||||||
|
continue
|
||||||
|
|
||||||
|
if s.startswith(">"):
|
||||||
|
q = []
|
||||||
|
while i < n and lines[i].strip().startswith(">"):
|
||||||
|
q.append(re.sub(r"^>\s?", "", lines[i].strip()))
|
||||||
|
i += 1
|
||||||
|
m = _ADMONITION.match(q[0].strip()) if q else None
|
||||||
|
if m:
|
||||||
|
kind = m.group(1).lower()
|
||||||
|
title = m.group(1).capitalize()
|
||||||
|
body = _render_blocks("\n".join(q[1:]), page)
|
||||||
|
out.append(f'<div class="admonition {kind}">\n<p class="admonition-title">{title}</p>\n{body}\n</div>')
|
||||||
|
else:
|
||||||
|
out.append(f"<blockquote>\n<p>{render_inline(chr(10).join(q), page)}</p>\n</blockquote>")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if s.startswith("<!--"):
|
||||||
|
out.append(rewrite_html_urls(line, page))
|
||||||
|
i += 1
|
||||||
|
# Preserve a blank line after HTML comments (python-markdown does).
|
||||||
|
if i < n and not lines[i].strip():
|
||||||
|
out.append("")
|
||||||
|
while i < n and not lines[i].strip():
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
buf = [line]
|
||||||
|
i += 1
|
||||||
|
while i < n and lines[i].strip():
|
||||||
|
t = lines[i].strip()
|
||||||
|
if t.startswith(("```", ">")) or _HEADING.match(t) or _HR.fullmatch(t):
|
||||||
|
break
|
||||||
|
if "|" in lines[i] and i + 1 < n and _is_sep(lines[i + 1]):
|
||||||
|
break
|
||||||
|
buf.append(lines[i])
|
||||||
|
i += 1
|
||||||
|
out.append(f"<p>{render_inline(chr(10).join(buf), page)}</p>")
|
||||||
|
|
||||||
|
return "\n".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(text: str, page: str) -> str:
|
||||||
|
return inject_glossary(_render_blocks(text, page), page)
|
||||||
|
|
||||||
|
|
||||||
|
def page_title(source: str) -> str:
|
||||||
|
for line in source.splitlines():
|
||||||
|
if line.startswith("# "):
|
||||||
|
return line[2:].strip()
|
||||||
|
return "openpilot docs"
|
||||||
|
|
||||||
|
|
||||||
|
def write_html_redirect(rel: Path) -> None:
|
||||||
|
if rel.name == "index.md":
|
||||||
|
return
|
||||||
|
target = f"{rel.stem}/"
|
||||||
|
out = SITE_DIR / rel.with_suffix(".html")
|
||||||
|
out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
out.write_text(
|
||||||
|
"\n".join(
|
||||||
|
[
|
||||||
|
"<!doctype html>",
|
||||||
|
f'<meta http-equiv="refresh" content="0; url={html.escape(target)}">',
|
||||||
|
f'<link rel="canonical" href="{html.escape(target)}">',
|
||||||
|
f"<script>location.replace({json.dumps(target)} + location.search + location.hash)</script>",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_assets() -> None:
|
||||||
|
for src in DOCS_DIR.rglob("*"):
|
||||||
|
if not src.is_file():
|
||||||
|
continue
|
||||||
|
rel = src.relative_to(DOCS_DIR)
|
||||||
|
if any(part in EXCLUDE_DIRS for part in rel.parts):
|
||||||
|
continue
|
||||||
|
if src.suffix == ".md" or src in (Path(__file__).resolve(), TEMPLATE_FILE):
|
||||||
|
continue
|
||||||
|
dest = SITE_DIR / rel
|
||||||
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(src, dest)
|
||||||
|
|
||||||
|
|
||||||
|
def render_nav_html(current_page: str) -> str:
|
||||||
|
parts: list[str] = []
|
||||||
|
for title, target in NAV:
|
||||||
|
if target is None:
|
||||||
|
parts.append(f'<div class="nav-section">{html.escape(title)}</div>')
|
||||||
|
elif target.startswith(("http://", "https://")):
|
||||||
|
parts.append(f'<a href="{html.escape(target)}">{html.escape(title)}</a>')
|
||||||
|
else:
|
||||||
|
active = ' class="active"' if target == current_page else ""
|
||||||
|
parts.append(f'<a href="{html.escape(page_href(current_page, target))}"{active}>{html.escape(title)}</a>')
|
||||||
|
return "\n".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def build() -> None:
|
||||||
|
template = TEMPLATE_FILE.read_text()
|
||||||
|
pages = [
|
||||||
|
(path.relative_to(DOCS_DIR), path.read_text())
|
||||||
|
for path in sorted(DOCS_DIR.rglob("*.md"))
|
||||||
|
if path != DOCS_DIR / "README.md" and not any(part in EXCLUDE_DIRS for part in path.relative_to(DOCS_DIR).parts)
|
||||||
|
]
|
||||||
|
pages.append((Path(GLOSSARY_PAGE), f"# openpilot glossary\n\n{GLOSSARY_DEFINITIONS}"))
|
||||||
|
pages.sort()
|
||||||
|
|
||||||
|
if SITE_DIR.exists():
|
||||||
|
shutil.rmtree(SITE_DIR)
|
||||||
|
SITE_DIR.mkdir(parents=True)
|
||||||
|
copy_assets()
|
||||||
|
|
||||||
|
for rel_path, source in pages:
|
||||||
|
rel = rel_path.as_posix()
|
||||||
|
body = render_markdown(source, rel)
|
||||||
|
title = page_title(source)
|
||||||
|
route = page_route(rel)
|
||||||
|
root = "../" * (0 if route == "." else len(route.split("/")))
|
||||||
|
edit_path = "serve.py" if rel == GLOSSARY_PAGE else rel
|
||||||
|
page_html = template
|
||||||
|
for name, value in {
|
||||||
|
"TITLE": html.escape(title),
|
||||||
|
"ROOT": root,
|
||||||
|
"HOME_HREF": page_href(rel, "index.md"),
|
||||||
|
"NAV": render_nav_html(rel),
|
||||||
|
"BODY": body,
|
||||||
|
"EDIT_URL": html.escape(f"{REPO_URL}blob/master/docs/{edit_path}"),
|
||||||
|
}.items():
|
||||||
|
page_html = page_html.replace(f"{{{{{name}}}}}", value)
|
||||||
|
out = SITE_DIR / ("" if route == "." else route) / "index.html"
|
||||||
|
out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
out.write_text(page_html)
|
||||||
|
write_html_redirect(rel_path)
|
||||||
|
|
||||||
|
print(f"docs: built {len(pages)} pages into {SITE_DIR}")
|
||||||
|
|
||||||
|
|
||||||
|
def _watched_files() -> list[Path]:
|
||||||
|
return [p for p in DOCS_DIR.rglob("*") if p.is_file() and not any(part in EXCLUDE_DIRS for part in p.relative_to(DOCS_DIR).parts)]
|
||||||
|
|
||||||
|
|
||||||
|
def serve() -> None:
|
||||||
|
build()
|
||||||
|
mtimes = {p: p.stat().st_mtime for p in _watched_files()}
|
||||||
|
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(SITE_DIR))
|
||||||
|
httpd = http.server.ThreadingHTTPServer(("", 0), handler)
|
||||||
|
print(f"docs: serving on http://localhost:{httpd.server_port}/ (watching for changes)")
|
||||||
|
try:
|
||||||
|
threading.Thread(target=httpd.serve_forever, daemon=True).start()
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
new_mtimes = {p: p.stat().st_mtime for p in _watched_files()}
|
||||||
|
if new_mtimes != mtimes:
|
||||||
|
mtimes = new_mtimes
|
||||||
|
print("docs: change detected, rebuilding...")
|
||||||
|
try:
|
||||||
|
build()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"docs: build failed: {e}")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
httpd.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="Build or serve the openpilot documentation site.")
|
||||||
|
parser.add_argument("--build", action="store_true", help="Build the site and exit.")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.build:
|
||||||
|
build()
|
||||||
|
else:
|
||||||
|
serve()
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
.md-logo img {
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glossary-term {
|
|
||||||
position: relative;
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glossary-term__label {
|
|
||||||
border-bottom: 1px dotted currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glossary-term__tooltip {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% + 0.4rem);
|
|
||||||
left: 50%;
|
|
||||||
width: max-content;
|
|
||||||
max-width: min(30rem, 80vw);
|
|
||||||
padding: 0.65rem 0.8rem;
|
|
||||||
border-radius: 0.6rem;
|
|
||||||
background: rgb(26 26 26 / 96%);
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 0.6rem 1.8rem rgb(0 0 0 / 22%);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
line-height: 1.45;
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
transform: translateX(-50%) translateY(-0.15rem);
|
|
||||||
transition: opacity 120ms ease, transform 120ms ease;
|
|
||||||
visibility: hidden;
|
|
||||||
z-index: 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glossary-term:hover .glossary-term__tooltip,
|
|
||||||
.glossary-term:focus-visible .glossary-term__tooltip,
|
|
||||||
.glossary-term:focus-within .glossary-term__tooltip {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(-50%) translateY(0);
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{TITLE}} · openpilot docs</title>
|
||||||
|
<link rel="icon" href="{{ROOT}}assets/favicon.svg">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #fff;
|
||||||
|
--bg-elev: #f5f5f5;
|
||||||
|
--bg-hover: #eef0ff;
|
||||||
|
--fg: #262626;
|
||||||
|
--fg-dim: #666;
|
||||||
|
--accent: #4051b5;
|
||||||
|
--border: #e5e5e5;
|
||||||
|
--max-width: 76rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
html { scrollbar-gutter: stable; }
|
||||||
|
html, body { margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a { color: var(--accent); text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
header.site {
|
||||||
|
display: flex; align-items: center; gap: 0.75rem;
|
||||||
|
min-height: 3rem;
|
||||||
|
padding: 0.5rem max(1.25rem, calc((100% - var(--max-width)) / 2));
|
||||||
|
background: var(--bg);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
position: sticky; top: 0; z-index: 10;
|
||||||
|
}
|
||||||
|
header.site img { display: block; height: 24px; filter: invert(1); }
|
||||||
|
header.site .brand { display: flex; align-items: center; gap: 0.75rem; color: var(--fg); }
|
||||||
|
header.site .brand:hover { text-decoration: none; }
|
||||||
|
header.site .site-name { font-weight: 600; font-size: 1.05rem; }
|
||||||
|
header.site .spacer { flex: 1; }
|
||||||
|
header.site .social { display: flex; gap: 1rem; }
|
||||||
|
header.site .social a { display: flex; color: var(--fg); }
|
||||||
|
header.site .social a:hover { color: var(--accent); }
|
||||||
|
header.site .social svg { width: 1.75rem; height: 1.75rem; fill: currentColor; }
|
||||||
|
header.site .social a[aria-label="Discord"] svg { transform: scale(1.15); }
|
||||||
|
|
||||||
|
.layout { display: flex; width: 100%; max-width: var(--max-width); margin: 0 auto; }
|
||||||
|
|
||||||
|
nav.sidebar {
|
||||||
|
width: 14rem; flex-shrink: 0;
|
||||||
|
padding: 3.3rem 1.25rem 2rem 0;
|
||||||
|
position: sticky; top: 3rem; align-self: start;
|
||||||
|
max-height: calc(100vh - 3rem); overflow-y: auto;
|
||||||
|
}
|
||||||
|
nav.sidebar .nav-section { font-weight: 600; margin: 1.15rem 0.8rem 0.45rem; }
|
||||||
|
nav.sidebar a {
|
||||||
|
display: block; padding: 0.28rem 0.8rem; border-radius: 0.45rem;
|
||||||
|
color: var(--fg); line-height: 1.35;
|
||||||
|
}
|
||||||
|
nav.sidebar a:hover { background: var(--bg-hover); text-decoration: none; }
|
||||||
|
nav.sidebar a.active { background: var(--bg-hover); color: var(--accent); font-weight: 600; }
|
||||||
|
|
||||||
|
main.content { width: min(100%, 46.5rem); min-width: 0; padding: 2.7rem 2rem 5rem; }
|
||||||
|
main.content h1, main.content h2, main.content h3 { line-height: 1.25; margin-top: 1.8rem; }
|
||||||
|
main.content h1 { margin-top: 0; }
|
||||||
|
main.content img { max-width: 100%; }
|
||||||
|
main.content table { border-collapse: collapse; display: block; overflow-x: auto; }
|
||||||
|
main.content th, main.content td { border: 1px solid var(--border); padding: 0.4rem 0.6rem; text-align: left; }
|
||||||
|
main.content th { background: var(--bg-elev); }
|
||||||
|
main.content pre {
|
||||||
|
position: relative;
|
||||||
|
background: var(--bg-elev);
|
||||||
|
padding: 0.9rem 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
main.content code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
||||||
|
main.content :not(pre) > code { background: var(--bg-elev); padding: 0.1rem 0.35rem; border-radius: 0.2rem; font-size: 0.88em; }
|
||||||
|
main.content blockquote { border-left: 3px solid var(--border); margin: 1rem 0; padding: 0.2rem 1rem; color: var(--fg-dim); }
|
||||||
|
main.content .admonition {
|
||||||
|
margin: 1rem 0; padding: 0.5rem 1rem; border-radius: 0.3rem; border-left: 3px solid var(--border);
|
||||||
|
background: var(--bg-elev);
|
||||||
|
}
|
||||||
|
main.content .admonition .admonition-title { margin: 0.2rem 0 0.4rem; font-weight: 600; }
|
||||||
|
main.content .admonition.note { border-left-color: #0969da; }
|
||||||
|
main.content .admonition.note .admonition-title { color: #0969da; }
|
||||||
|
main.content .admonition.tip { border-left-color: #1a7f37; }
|
||||||
|
main.content .admonition.tip .admonition-title { color: #1a7f37; }
|
||||||
|
main.content .admonition.important { border-left-color: #8250df; }
|
||||||
|
main.content .admonition.important .admonition-title { color: #8250df; }
|
||||||
|
main.content .admonition.warning { border-left-color: #9a6700; }
|
||||||
|
main.content .admonition.warning .admonition-title { color: #9a6700; }
|
||||||
|
main.content .admonition.caution { border-left-color: #cf222e; }
|
||||||
|
main.content .admonition.caution .admonition-title { color: #cf222e; }
|
||||||
|
main.content details { margin: 0.5rem 0; }
|
||||||
|
main.content hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
|
||||||
|
.headerlink { margin-left: 0.25rem; opacity: 0; font-size: 0.7em; }
|
||||||
|
h1:hover .headerlink, h2:hover .headerlink, h3:hover .headerlink { opacity: 1; }
|
||||||
|
|
||||||
|
.edit-link { margin-top: 3rem; font-size: 0.85rem; }
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
position: absolute; top: 0.4rem; right: 0.4rem;
|
||||||
|
background: var(--bg); color: var(--fg-dim);
|
||||||
|
border: 1px solid var(--border); border-radius: 0.35rem;
|
||||||
|
padding: 0.2rem 0.5rem; font-size: 0.78rem; cursor: pointer;
|
||||||
|
opacity: 0; transition: opacity 120ms;
|
||||||
|
}
|
||||||
|
main.content pre:hover .copy-btn { opacity: 1; }
|
||||||
|
|
||||||
|
@media (max-width: 48rem) {
|
||||||
|
nav.sidebar { display: none; }
|
||||||
|
main.content { padding: 2rem 1.25rem 4rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-term {
|
||||||
|
position: relative;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-term__label {
|
||||||
|
border-bottom: 1px dotted currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-term__tooltip {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 0.4rem);
|
||||||
|
left: 50%;
|
||||||
|
width: max-content;
|
||||||
|
max-width: min(30rem, 80vw);
|
||||||
|
padding: 0.65rem 0.8rem;
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
background: rgb(26 26 26 / 96%);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 0.6rem 1.8rem rgb(0 0 0 / 22%);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateX(-50%) translateY(-0.15rem);
|
||||||
|
transition: opacity 120ms ease, transform 120ms ease;
|
||||||
|
visibility: hidden;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-term:hover .glossary-term__tooltip,
|
||||||
|
.glossary-term:focus-visible .glossary-term__tooltip,
|
||||||
|
.glossary-term:focus-within .glossary-term__tooltip {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site">
|
||||||
|
<a class="brand" href="{{HOME_HREF}}">
|
||||||
|
<img src="{{ROOT}}assets/comma-logo.png" alt="">
|
||||||
|
<span class="site-name">openpilot docs</span>
|
||||||
|
</a>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<div class="social">
|
||||||
|
<a href="https://github.com/commaai" aria-label="GitHub">
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
|
||||||
|
M12 .7A11.5 11.5 0 0 0 8.4 23c.6.1.8-.2.8-.5v-2c-3.3.7-4-1.4-4-1.4-.5-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7
|
||||||
|
1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-5.7 0-1.3.5-2.3 1.2-3.1
|
||||||
|
-.1-.3-.5-1.6.1-3.1 0 0 1-.3 3.2 1.2a11 11 0 0 1 5.8 0C15.8 6 16.8 6.3 16.8 6.3c.6 1.5.2 2.8.1 3.1
|
||||||
|
.8.8 1.2 1.8 1.2 3.1 0 4.4-2.8 5.4-5.5 5.7.4.4.8 1.1.8 2.2v2.1c0 .3.2.6.8.5A11.5 11.5 0 0 0 12 .7Z"/></svg>
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.comma.ai" aria-label="Discord">
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
|
||||||
|
M20.3 4.4A16 16 0 0 0 16.3 3l-.5 1.1a15 15 0 0 0-7.6 0L7.7 3a16 16 0 0 0-4 1.4C1.1 8.2.4 11.9.8 15.5
|
||||||
|
a16 16 0 0 0 4.9 2.6l1.2-1.7-1.8-.9.4-.3c3.5 1.6 9.4 1.6 13 0l.4.3-1.8.9 1.2 1.7a16 16 0 0 0 4.9-2.6
|
||||||
|
c.5-4.2-.8-7.8-2.9-11.1ZM8.3 13.3c-1.1 0-1.9-1-1.9-2.2 0-1.3.8-2.3 1.9-2.3s2 1 1.9 2.3c0 1.2-.8 2.2-1.9 2.2Z
|
||||||
|
m7.4 0c-1.1 0-1.9-1-1.9-2.2 0-1.3.8-2.3 1.9-2.3s2 1 1.9 2.3c0 1.2-.8 2.2-1.9 2.2Z"/></svg>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/comma_ai" aria-label="X">
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
|
||||||
|
M18.2 2.3h3.3l-7.2 8.3 8.5 11.2h-6.7l-5.2-6.8-6 6.8H1.6l7.8-8.9L1.2 2.3h6.8l4.7 6.2 5.5-6.2Z
|
||||||
|
m-1.2 17.5h1.8L7 4.2H5Z"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="layout">
|
||||||
|
<nav class="sidebar">
|
||||||
|
{{NAV}}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="content">
|
||||||
|
{{BODY}}
|
||||||
|
<div class="edit-link">
|
||||||
|
<a href="{{EDIT_URL}}">Edit this page on GitHub</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
document.querySelectorAll('pre').forEach(function (el) {
|
||||||
|
var btn = document.createElement('button');
|
||||||
|
btn.className = 'copy-btn';
|
||||||
|
btn.textContent = 'copy';
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
var code = el.querySelector('code');
|
||||||
|
if (!code) return;
|
||||||
|
navigator.clipboard.writeText(code.innerText).then(function () {
|
||||||
|
btn.textContent = 'copied';
|
||||||
|
setTimeout(function () { btn.textContent = 'copy'; }, 1200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
el.appendChild(btn);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+14
-4
@@ -19,12 +19,14 @@ function agnos_init {
|
|||||||
|
|
||||||
# Check if AGNOS update is required
|
# Check if AGNOS update is required
|
||||||
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
|
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
|
||||||
AGNOS_PY="$DIR/system/hardware/tici/agnos.py"
|
AGNOS_PY="$DIR/openpilot/common/hardware/comma/agnos.py"
|
||||||
MANIFEST="$DIR/system/hardware/tici/agnos.json"
|
MANIFEST="$DIR/openpilot/system/hardware/comma/agnos.json"
|
||||||
if $AGNOS_PY --verify $MANIFEST; then
|
if $AGNOS_PY --verify $MANIFEST; then
|
||||||
sudo reboot
|
sudo reboot
|
||||||
fi
|
fi
|
||||||
$DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST
|
while true; do
|
||||||
|
$DIR/openpilot/common/hardware/comma/updater $AGNOS_PY $MANIFEST
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +72,14 @@ function launch {
|
|||||||
ln -sfn $(pwd) /data/pythonpath
|
ln -sfn $(pwd) /data/pythonpath
|
||||||
export PYTHONPATH="$PWD"
|
export PYTHONPATH="$PWD"
|
||||||
|
|
||||||
|
# submodule package symlinks for PYTHONPATH imports on device.
|
||||||
|
# on PC these come from editable installs via pyproject.toml / uv.
|
||||||
|
ln -sfn msgq_repo/msgq msgq
|
||||||
|
ln -sfn opendbc_repo/opendbc opendbc
|
||||||
|
ln -sfn rednose_repo/rednose rednose
|
||||||
|
ln -sfn teleoprtc_repo/teleoprtc teleoprtc
|
||||||
|
ln -sfn tinygrad_repo/tinygrad tinygrad
|
||||||
|
|
||||||
# hardware specific init
|
# hardware specific init
|
||||||
if [ -f /AGNOS ]; then
|
if [ -f /AGNOS ]; then
|
||||||
agnos_init
|
agnos_init
|
||||||
@@ -79,7 +89,7 @@ function launch {
|
|||||||
tmux capture-pane -pq -S-1000 > /tmp/launch_log
|
tmux capture-pane -pq -S-1000 > /tmp/launch_log
|
||||||
|
|
||||||
# start manager
|
# start manager
|
||||||
cd system/manager
|
cd openpilot/system/manager
|
||||||
if [ ! -f $DIR/prebuilt ]; then
|
if [ ! -f $DIR/prebuilt ]; then
|
||||||
./build.py
|
./build.py
|
||||||
fi
|
fi
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
|
|||||||
export QCOM_PRIORITY=12
|
export QCOM_PRIORITY=12
|
||||||
|
|
||||||
if [ -z "$AGNOS_VERSION" ]; then
|
if [ -z "$AGNOS_VERSION" ]; then
|
||||||
export AGNOS_VERSION="17.2"
|
export AGNOS_VERSION="19.7"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export STAGING_ROOT="/data/safe_staging"
|
export STAGING_ROOT="/data/safe_staging"
|
||||||
|
|||||||
@@ -1,20 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
|
||||||
IFS=$'\n\t'
|
|
||||||
|
|
||||||
# On any failure, run the fallback launcher
|
|
||||||
trap 'exec ./launch_chffrplus.sh' ERR
|
|
||||||
C3_LAUNCH_SH="./sunnypilot/system/hardware/c3/launch_chffrplus.sh"
|
|
||||||
|
|
||||||
MODEL="$(tr -d '\0' < "/sys/firmware/devicetree/base/model")"
|
|
||||||
export MODEL
|
|
||||||
|
|
||||||
if [ "$MODEL" = "comma tici" ]; then
|
|
||||||
# Force a failure if the launcher doesn't exist
|
|
||||||
[ -x "$C3_LAUNCH_SH" ] || false
|
|
||||||
|
|
||||||
# If it exists, run it
|
|
||||||
exec "$C3_LAUNCH_SH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec ./launch_chffrplus.sh
|
exec ./launch_chffrplus.sh
|
||||||
|
|||||||
+1
-1
Submodule msgq_repo updated: b7688b9bd7...e7396e76da
+1
-1
Submodule opendbc_repo updated: df807f8be3...c21a901370
@@ -24,17 +24,17 @@ things are not. Read more details [here](https://capnproto.org/language.html).
|
|||||||
### Custom forks
|
### Custom forks
|
||||||
|
|
||||||
Forks of [openpilot](https://github.com/commaai/openpilot) might want to add things to the messaging
|
Forks of [openpilot](https://github.com/commaai/openpilot) might want to add things to the messaging
|
||||||
spec, however this could conflict with future changes made in mainline cereal/openpilot. Rebasing against mainline openpilot
|
spec, however this could conflict with future changes made in mainline openpilot's cereal spec. Rebasing against mainline openpilot
|
||||||
then means breaking backwards-compatibility with all old logs of your fork. So we added reserved events in
|
then means breaking backwards-compatibility with all old logs of your fork. So we added reserved events in
|
||||||
[custom.capnp](custom.capnp) that we will leave empty in mainline cereal/openpilot. **If you only modify those, you can ensure your
|
[custom.capnp](custom.capnp) that we will leave empty in mainline openpilot's cereal spec. **If you only modify those, you can ensure your
|
||||||
fork will remain backwards-compatible with all versions of mainline openpilot and your fork.**
|
fork will remain backwards-compatible with all versions of mainline openpilot and your fork.**
|
||||||
|
|
||||||
An example of compatible changes:
|
An example of compatible changes:
|
||||||
```diff
|
```diff
|
||||||
diff --git a/cereal/custom.capnp b/cereal/custom.capnp
|
diff --git a/openpilot/cereal/custom.capnp b/openpilot/cereal/custom.capnp
|
||||||
index 3348e859e..3365c7b98 100644
|
index 3348e859e..3365c7b98 100644
|
||||||
--- a/cereal/custom.capnp
|
--- a/openpilot/cereal/custom.capnp
|
||||||
+++ b/cereal/custom.capnp
|
+++ b/openpilot/cereal/custom.capnp
|
||||||
@@ -10,7 +10,11 @@ $Cxx.namespace("cereal");
|
@@ -10,7 +10,11 @@ $Cxx.namespace("cereal");
|
||||||
# DO rename the structs
|
# DO rename the structs
|
||||||
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
||||||
@@ -48,10 +48,10 @@ index 3348e859e..3365c7b98 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct CustomReserved1 @0xaedffd8f31e7b55d {
|
struct CustomReserved1 @0xaedffd8f31e7b55d {
|
||||||
diff --git a/cereal/log.capnp b/cereal/log.capnp
|
diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp
|
||||||
index 1209f3fd9..b189f58b6 100644
|
index 1209f3fd9..b189f58b6 100644
|
||||||
--- a/cereal/log.capnp
|
--- a/openpilot/cereal/log.capnp
|
||||||
+++ b/cereal/log.capnp
|
+++ b/openpilot/cereal/log.capnp
|
||||||
@@ -2558,14 +2558,14 @@ struct Event {
|
@@ -2558,14 +2558,14 @@ struct Event {
|
||||||
|
|
||||||
# DO change the name of the field
|
# DO change the name of the field
|
||||||
@@ -76,7 +76,7 @@ index 1209f3fd9..b189f58b6 100644
|
|||||||
Example
|
Example
|
||||||
---
|
---
|
||||||
```python
|
```python
|
||||||
import cereal.messaging as messaging
|
import openpilot.cereal.messaging as messaging
|
||||||
|
|
||||||
# in subscriber
|
# in subscriber
|
||||||
sm = messaging.SubMaster(['sensorEvents'])
|
sm = messaging.SubMaster(['sensorEvents'])
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
Import('env', 'common', 'msgq')
|
||||||
|
|
||||||
|
cereal_dir = Dir('.')
|
||||||
|
gen_dir = Dir('gen')
|
||||||
|
|
||||||
|
# Build cereal
|
||||||
|
schema_files = ['log.capnp', 'deprecated.capnp', 'custom.capnp']
|
||||||
|
car_capnp = '#opendbc_repo/opendbc/car/car.capnp'
|
||||||
|
all_output = schema_files + ['car.capnp']
|
||||||
|
env.Command([f'gen/cpp/{s}.c++' for s in all_output] + [f'gen/cpp/{s}.h' for s in all_output],
|
||||||
|
schema_files + [car_capnp],
|
||||||
|
f"capnpc --src-prefix={cereal_dir.path} --src-prefix=opendbc_repo/opendbc/car --import-path=opendbc_repo/opendbc/car $SOURCES -o c++:{gen_dir.path}/cpp/")
|
||||||
|
|
||||||
|
cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in all_output])
|
||||||
|
|
||||||
|
# Build messaging
|
||||||
|
services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET')
|
||||||
|
env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc', 'messaging/bridge_zmq.cc'], LIBS=[msgq, common, 'pthread'])
|
||||||
|
|
||||||
|
socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc'])
|
||||||
|
|
||||||
|
Export('cereal', 'socketmaster')
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import os
|
||||||
|
import capnp
|
||||||
|
from importlib.resources import as_file, files
|
||||||
|
|
||||||
|
capnp.remove_import_hook()
|
||||||
|
|
||||||
|
with as_file(files("openpilot.cereal")) as fspath, as_file(files("opendbc")) as opendbc_path:
|
||||||
|
CEREAL_PATH = fspath.as_posix()
|
||||||
|
opendbc_import_path = os.path.join(os.path.realpath(opendbc_path.as_posix()), 'car')
|
||||||
|
log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"), imports=[opendbc_import_path])
|
||||||
|
custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp"), imports=[opendbc_import_path])
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Cxx = import "./include/c++.capnp";
|
using Cxx = import "/include/c++.capnp";
|
||||||
$Cxx.namespace("cereal");
|
$Cxx.namespace("cereal");
|
||||||
|
|
||||||
@0xb526ba661d550a59;
|
@0xb526ba661d550a59;
|
||||||
@@ -69,6 +69,8 @@ struct LeadData {
|
|||||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||||
mads @0 :ModularAssistiveDrivingSystem;
|
mads @0 :ModularAssistiveDrivingSystem;
|
||||||
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
||||||
|
buttonsPressed @2 :UInt16;
|
||||||
|
buttonsReleaseToggle @3 :UInt16;
|
||||||
|
|
||||||
enum AudibleAlert {
|
enum AudibleAlert {
|
||||||
none @0;
|
none @0;
|
||||||
@@ -129,6 +131,7 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
|||||||
downloaded @2;
|
downloaded @2;
|
||||||
cached @3;
|
cached @3;
|
||||||
failed @4;
|
failed @4;
|
||||||
|
verifying @5;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DownloadProgress {
|
struct DownloadProgress {
|
||||||
@@ -137,10 +140,16 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
|||||||
eta @2 :UInt32;
|
eta @2 :UInt32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Chunk {
|
||||||
|
fileName @0 :Text;
|
||||||
|
sha256 @1 :Text;
|
||||||
|
}
|
||||||
|
|
||||||
struct Artifact {
|
struct Artifact {
|
||||||
fileName @0 :Text;
|
fileName @0 :Text;
|
||||||
downloadUri @1 :DownloadUri;
|
downloadUri @1 :DownloadUri;
|
||||||
downloadProgress @2 :DownloadProgress;
|
downloadProgress @2 :DownloadProgress;
|
||||||
|
chunks @3 :List(Chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Model {
|
struct Model {
|
||||||
@@ -155,6 +164,7 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
|||||||
policy @3;
|
policy @3;
|
||||||
offPolicy @4;
|
offPolicy @4;
|
||||||
onPolicy @5;
|
onPolicy @5;
|
||||||
|
chunked @6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,6 +352,8 @@ struct OnroadEventSP @0xda96579883444c35 {
|
|||||||
speedLimitChanged @21;
|
speedLimitChanged @21;
|
||||||
speedLimitPending @22;
|
speedLimitPending @22;
|
||||||
e2eChime @23;
|
e2eChime @23;
|
||||||
|
laneChangeRoadEdge @24;
|
||||||
|
bigModelReady @25;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,6 +383,7 @@ struct CarControlSP @0xa5cd762cd951a455 {
|
|||||||
leadOne @2 :LeadData;
|
leadOne @2 :LeadData;
|
||||||
leadTwo @3 :LeadData;
|
leadTwo @3 :LeadData;
|
||||||
intelligentCruiseButtonManagement @4 :IntelligentCruiseButtonManagement;
|
intelligentCruiseButtonManagement @4 :IntelligentCruiseButtonManagement;
|
||||||
|
fordLateralPath @5 :FordLateralPath;
|
||||||
|
|
||||||
struct Param {
|
struct Param {
|
||||||
key @0 :Text;
|
key @0 :Text;
|
||||||
@@ -391,6 +404,14 @@ struct CarControlSP @0xa5cd762cd951a455 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FordLateralPath {
|
||||||
|
pathOffset @0 :Float32; # c0 [m]
|
||||||
|
pathAngle @1 :Float32; # c1 [rad]
|
||||||
|
curvature @2 :Float32; # c2 [1/m]
|
||||||
|
curvatureRate @3 :Float32; # c3 [1/m^2]
|
||||||
|
valid @4 :Bool;
|
||||||
|
}
|
||||||
|
|
||||||
struct BackupManagerSP @0xf98d843bfd7004a3 {
|
struct BackupManagerSP @0xf98d843bfd7004a3 {
|
||||||
backupStatus @0 :Status;
|
backupStatus @0 :Status;
|
||||||
restoreStatus @1 :Status;
|
restoreStatus @1 :Status;
|
||||||
@@ -435,6 +456,16 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
|
|||||||
|
|
||||||
struct CarStateSP @0xb86e6369214c01c8 {
|
struct CarStateSP @0xb86e6369214c01c8 {
|
||||||
speedLimit @0 :Float32;
|
speedLimit @0 :Float32;
|
||||||
|
fordPscmStatus @1 :FordPscmStatus;
|
||||||
|
|
||||||
|
struct FordPscmStatus {
|
||||||
|
valid @0 :Bool;
|
||||||
|
canMonoTime @1 :UInt64; # Last accepted Lane_Assist_Data3_FD1 CAN receipt, not carStateSP publication time.
|
||||||
|
lateralState @2 :UInt8; # LatCtlSte_D_Stat
|
||||||
|
limit @3 :UInt8; # LatCtlLim_D_Stat: generic lateral limit, not a torque/rate diagnosis.
|
||||||
|
capability @4 :UInt8; # LatCtlCpblty_D_Stat
|
||||||
|
denied @5 :Bool; # LaActDeny_B_Actl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LiveMapDataSP @0xf416ec09499d9d19 {
|
struct LiveMapDataSP @0xf416ec09499d9d19 {
|
||||||
@@ -448,6 +479,8 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
|
|||||||
|
|
||||||
struct ModelDataV2SP @0xa1680744031fdb2d {
|
struct ModelDataV2SP @0xa1680744031fdb2d {
|
||||||
laneTurnDirection @0 :TurnDirection;
|
laneTurnDirection @0 :TurnDirection;
|
||||||
|
leftLaneChangeEdgeBlock @1 :Bool;
|
||||||
|
rightLaneChangeEdgeBlock @2 :Bool;
|
||||||
|
|
||||||
enum TurnDirection {
|
enum TurnDirection {
|
||||||
none @0;
|
none @0;
|
||||||
@@ -456,7 +489,30 @@ struct ModelDataV2SP @0xa1680744031fdb2d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
struct AssistedDrivingMilestoneState @0xcb9fd56c7057593a {
|
||||||
|
enabled @0 :Bool;
|
||||||
|
madsDistanceMeters @1 :Float64;
|
||||||
|
fullAssistDistanceMeters @2 :Float64;
|
||||||
|
event @3 :Event;
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
id @0 :UInt64;
|
||||||
|
category @1 :Category;
|
||||||
|
distanceMeters @2 :Float64;
|
||||||
|
previousDistanceMeters @3 :Float64;
|
||||||
|
unit @4 :Unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Category {
|
||||||
|
none @0;
|
||||||
|
mads @1;
|
||||||
|
fullAssist @2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Unit {
|
||||||
|
imperial @0;
|
||||||
|
metric @1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CustomReserved11 @0xc2243c65e0340384 {
|
struct CustomReserved11 @0xc2243c65e0340384 {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Cxx = import "./include/c++.capnp";
|
using Cxx = import "/include/c++.capnp";
|
||||||
$Cxx.namespace("cereal");
|
$Cxx.namespace("cereal");
|
||||||
|
|
||||||
@0x80ef1ec4889c2a63;
|
@0x80ef1ec4889c2a63;
|
||||||
@@ -760,18 +760,6 @@ struct LateralLQRState @0x9024e2d790c82ade {
|
|||||||
steeringAngleDesiredDeg @6 :Float32;
|
steeringAngleDesiredDeg @6 :Float32;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LateralCurvatureState @0xad9d8095c06f7c61 {
|
|
||||||
active @0 :Bool;
|
|
||||||
actualCurvature @1 :Float32;
|
|
||||||
desiredCurvature @2 :Float32;
|
|
||||||
error @3 :Float32;
|
|
||||||
p @4 :Float32;
|
|
||||||
i @5 :Float32;
|
|
||||||
f @6 :Float32;
|
|
||||||
output @7 :Float32;
|
|
||||||
saturated @8 :Bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LateralPlannerSolution @0x84caeca5a6b4acfe {
|
struct LateralPlannerSolution @0x84caeca5a6b4acfe {
|
||||||
x @0 :List(Float32);
|
x @0 :List(Float32);
|
||||||
y @1 :List(Float32);
|
y @1 :List(Float32);
|
||||||
@@ -787,3 +775,13 @@ struct GpsTrajectory @0x8cfeb072f5301000 {
|
|||||||
x @0 :List(Float32);
|
x @0 :List(Float32);
|
||||||
y @1 :List(Float32);
|
y @1 :List(Float32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AudioFeedbackDEPRECATED @0xed47e3c075be372a {
|
||||||
|
audio @0 :AudioData;
|
||||||
|
blockNum @1 :UInt16;
|
||||||
|
|
||||||
|
struct AudioData {
|
||||||
|
data @0 :Data;
|
||||||
|
sampleRate @1 :UInt32;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Cxx = import "./include/c++.capnp";
|
using Cxx = import "/include/c++.capnp";
|
||||||
$Cxx.namespace("cereal");
|
$Cxx.namespace("cereal");
|
||||||
|
|
||||||
using Car = import "car.capnp";
|
using Car = import "/car.capnp";
|
||||||
using Deprecated = import "deprecated.capnp";
|
using Deprecated = import "deprecated.capnp";
|
||||||
using Custom = import "custom.capnp";
|
using Custom = import "custom.capnp";
|
||||||
|
|
||||||
@@ -75,7 +75,6 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
|||||||
driverUnresponsive2 @37;
|
driverUnresponsive2 @37;
|
||||||
driverUnresponsive3 @38;
|
driverUnresponsive3 @38;
|
||||||
belowSteerSpeed @39;
|
belowSteerSpeed @39;
|
||||||
lowBattery @40;
|
|
||||||
accFaulted @41;
|
accFaulted @41;
|
||||||
sensorDataInvalid @42;
|
sensorDataInvalid @42;
|
||||||
commIssue @43;
|
commIssue @43;
|
||||||
@@ -107,18 +106,17 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
|||||||
noGps @68;
|
noGps @68;
|
||||||
wrongCruiseMode @69;
|
wrongCruiseMode @69;
|
||||||
modeldLagging @70;
|
modeldLagging @70;
|
||||||
deviceFalling @71;
|
|
||||||
fanMalfunction @72;
|
fanMalfunction @72;
|
||||||
cameraMalfunction @73;
|
cameraMalfunction @73;
|
||||||
cameraFrameRate @74;
|
cameraFrameRate @74;
|
||||||
processNotRunning @75;
|
processNotRunning @75;
|
||||||
dashcamMode @76;
|
dashcamMode @76;
|
||||||
selfdriveInitializing @77;
|
selfdriveInitializing @77;
|
||||||
usbError @78;
|
|
||||||
cruiseMismatch @79;
|
cruiseMismatch @79;
|
||||||
canBusMissing @80;
|
canBusMissing @80;
|
||||||
selfdrivedLagging @81;
|
selfdrivedLagging @81;
|
||||||
resumeBlocked @82;
|
resumeBlocked @82;
|
||||||
|
carNotReady @103;
|
||||||
steerTimeLimit @83;
|
steerTimeLimit @83;
|
||||||
vehicleSensorsInvalid @84;
|
vehicleSensorsInvalid @84;
|
||||||
locationdTemporaryError @85;
|
locationdTemporaryError @85;
|
||||||
@@ -131,9 +129,15 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
|||||||
aeb @92;
|
aeb @92;
|
||||||
userBookmark @95;
|
userBookmark @95;
|
||||||
excessiveActuation @96;
|
excessiveActuation @96;
|
||||||
audioFeedback @97;
|
bigModelLoading @100;
|
||||||
|
bigModelFailed @102;
|
||||||
|
|
||||||
|
lowBatteryDEPRECATED @40;
|
||||||
soundsUnavailableDEPRECATED @47;
|
soundsUnavailableDEPRECATED @47;
|
||||||
|
deviceFallingDEPRECATED @71;
|
||||||
|
usbErrorDEPRECATED @78;
|
||||||
|
audioFeedbackDEPRECATED @97;
|
||||||
|
bigModelReadyDEPRECATED @101;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,13 +180,13 @@ struct InitData {
|
|||||||
|
|
||||||
enum DeviceType {
|
enum DeviceType {
|
||||||
unknown @0;
|
unknown @0;
|
||||||
neo @1;
|
neo @1; # NEO, EON, & comma two
|
||||||
chffrAndroid @2;
|
chffrAndroid @2;
|
||||||
chffrIos @3;
|
chffrIos @3;
|
||||||
tici @4;
|
tici @4; # comma three
|
||||||
pc @5;
|
pc @5;
|
||||||
tizi @6;
|
tizi @6; # comma 3X
|
||||||
mici @7;
|
mici @7; # comma four
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PandaInfo {
|
struct PandaInfo {
|
||||||
@@ -273,11 +277,7 @@ struct GPSNMEAData {
|
|||||||
nmea @2 :Text;
|
nmea @2 :Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
# android sensor_event_t
|
|
||||||
struct SensorEventData {
|
struct SensorEventData {
|
||||||
version @0 :Int32;
|
|
||||||
sensor @1 :Int32;
|
|
||||||
type @2 :Int32;
|
|
||||||
timestamp @3 :Int64;
|
timestamp @3 :Int64;
|
||||||
|
|
||||||
union {
|
union {
|
||||||
@@ -296,7 +296,10 @@ struct SensorEventData {
|
|||||||
|
|
||||||
struct SensorVec {
|
struct SensorVec {
|
||||||
v @0 :List(Float32);
|
v @0 :List(Float32);
|
||||||
status @1 :Int8;
|
|
||||||
|
deprecated :group {
|
||||||
|
status @1 :Int8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SensorSource {
|
enum SensorSource {
|
||||||
@@ -314,7 +317,11 @@ struct SensorEventData {
|
|||||||
mmc5603nj @11;
|
mmc5603nj @11;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# formerly based on android sensor_event_t
|
||||||
deprecated :group {
|
deprecated :group {
|
||||||
|
version @0 :Int32;
|
||||||
|
sensor @1 :Int32;
|
||||||
|
type @2 :Int32;
|
||||||
uncalibrated @10 :Bool;
|
uncalibrated @10 :Bool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,6 +418,10 @@ struct CanData {
|
|||||||
struct DeviceState @0xa4d8b5af2aa492eb {
|
struct DeviceState @0xa4d8b5af2aa492eb {
|
||||||
deviceType @45 :InitData.DeviceType;
|
deviceType @45 :InitData.DeviceType;
|
||||||
|
|
||||||
|
# usb
|
||||||
|
chestnutPresent @51 :Bool;
|
||||||
|
usbState @52 :UsbState;
|
||||||
|
|
||||||
networkType @22 :NetworkType;
|
networkType @22 :NetworkType;
|
||||||
networkInfo @31 :NetworkInfo;
|
networkInfo @31 :NetworkInfo;
|
||||||
networkStrength @24 :NetworkStrength;
|
networkStrength @24 :NetworkStrength;
|
||||||
@@ -457,10 +468,10 @@ struct DeviceState @0xa4d8b5af2aa492eb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ThermalStatus {
|
enum ThermalStatus {
|
||||||
green @0;
|
ok @0;
|
||||||
yellow @1;
|
warmDEPRECATED @1;
|
||||||
red @2;
|
overheated @2;
|
||||||
danger @3;
|
critical @3;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NetworkType {
|
enum NetworkType {
|
||||||
@@ -681,33 +692,68 @@ struct PeripheralState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct UsbState {
|
||||||
|
devices @0 :List(Device);
|
||||||
|
|
||||||
|
struct Device {
|
||||||
|
busnum @0 :UInt8;
|
||||||
|
devnum @1 :UInt8;
|
||||||
|
vendorId @2 :UInt16;
|
||||||
|
productId @3 :UInt16;
|
||||||
|
speedMbps @4 :UInt16;
|
||||||
|
manufacturer @6 :Text;
|
||||||
|
product @5 :Text;
|
||||||
|
linkErrorCount @7 :UInt16;
|
||||||
|
usb3Lane @8 :Usb3Lane;
|
||||||
|
|
||||||
|
enum Usb3Lane {
|
||||||
|
unknown @0;
|
||||||
|
a @1;
|
||||||
|
b @2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ChestnutState {
|
||||||
|
tempC @0 :Float32;
|
||||||
|
memoryTempC @1 :Float32;
|
||||||
|
powerDrawW @2 :Float32;
|
||||||
|
powerLimitW @3 :Float32;
|
||||||
|
gpuUsagePercent @4 :UInt8;
|
||||||
|
gpuClockMhz @5 :UInt16;
|
||||||
|
fanSpeedRpm @6 :UInt16;
|
||||||
|
pcieLtssm @7 :UInt8;
|
||||||
|
supplyVoltage @8 :UInt16; # mV
|
||||||
|
supplyCurrent @9 :Int16; # mA
|
||||||
|
supplyFault @10 :Bool;
|
||||||
|
}
|
||||||
|
|
||||||
struct RadarState @0x9a185389d6fdd05f {
|
struct RadarState @0x9a185389d6fdd05f {
|
||||||
mdMonoTime @6 :UInt64;
|
mdMonoTime @6 :UInt64; # for debugging
|
||||||
carStateMonoTime @11 :UInt64;
|
|
||||||
radarErrors @13 :Car.RadarData.Error;
|
radarErrors @13 :Car.RadarData.Error;
|
||||||
|
|
||||||
leadOne @3 :LeadData;
|
leadOne @3 :LeadData;
|
||||||
leadTwo @4 :LeadData;
|
leadTwo @4 :LeadData;
|
||||||
|
|
||||||
struct LeadData {
|
struct LeadData {
|
||||||
dRel @0 :Float32;
|
dRel @0 :Float32; # m from the front bumper of the car
|
||||||
yRel @1 :Float32;
|
yRel @1 :Float32; # m in car frame, left positive
|
||||||
vRel @2 :Float32;
|
vRel @2 :Float32; # m/s relative longitudinal speed
|
||||||
aRel @3 :Float32;
|
vLead @4 :Float32; # m/s absolute lead speed
|
||||||
vLead @4 :Float32;
|
vLeadK @8 :Float32; # kalman-filtered lead speed
|
||||||
dPath @6 :Float32;
|
aLeadK @9 :Float32; # kalman-filtered lead accel
|
||||||
vLat @7 :Float32;
|
present @11 :Bool; # true if a lead is present
|
||||||
vLeadK @8 :Float32;
|
aLeadTau @12 :Float32; # lead accel time constant
|
||||||
aLeadK @9 :Float32;
|
modelProb @13 :Float32; # vision model lead probability
|
||||||
fcw @10 :Bool;
|
radar @14 :Bool; # true if lead is radar-matched (vs vision-only)
|
||||||
status @11 :Bool;
|
radarTrackId @15 :Int32 = -1; # for debugging
|
||||||
aLeadTau @12 :Float32;
|
|
||||||
modelProb @13 :Float32;
|
|
||||||
radar @14 :Bool;
|
|
||||||
radarTrackId @15 :Int32 = -1;
|
|
||||||
|
|
||||||
deprecated :group {
|
deprecated :group {
|
||||||
|
aRel @3 :Float32;
|
||||||
aLead @5 :Float32;
|
aLead @5 :Float32;
|
||||||
|
dPath @6 :Float32;
|
||||||
|
vLat @7 :Float32;
|
||||||
|
fcw @10 :Bool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,11 +766,12 @@ struct RadarState @0x9a185389d6fdd05f {
|
|||||||
calPerc @9 :Int8;
|
calPerc @9 :Int8;
|
||||||
canMonoTimes @10 :List(UInt64);
|
canMonoTimes @10 :List(UInt64);
|
||||||
cumLagMs @5 :Float32;
|
cumLagMs @5 :Float32;
|
||||||
|
carStateMonoTime @11 :UInt64;
|
||||||
radarErrors @12 :List(Car.RadarData.ErrorDEPRECATED);
|
radarErrors @12 :List(Car.RadarData.ErrorDEPRECATED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LiveCalibrationData {
|
struct ExtrinsicsCalibration @0x96df70754d8390bc {
|
||||||
calStatus @11 :Status;
|
calStatus @11 :Status;
|
||||||
calCycle @2 :Int32;
|
calCycle @2 :Int32;
|
||||||
calPerc @3 :Int8;
|
calPerc @3 :Int8;
|
||||||
@@ -768,13 +815,31 @@ struct SelfdriveState {
|
|||||||
alertStatus @5 :AlertStatus;
|
alertStatus @5 :AlertStatus;
|
||||||
alertSize @6 :AlertSize;
|
alertSize @6 :AlertSize;
|
||||||
alertType @7 :Text;
|
alertType @7 :Text;
|
||||||
alertSound @8 :Car.CarControl.HUDControl.AudibleAlert;
|
alertSound @13 :AudibleAlert;
|
||||||
alertHudVisual @12 :Car.CarControl.HUDControl.VisualAlert;
|
alertHudVisual @12 :Car.CarControl.HUDControl.VisualAlert;
|
||||||
|
|
||||||
# configurable driving settings
|
# configurable driving settings
|
||||||
experimentalMode @10 :Bool;
|
experimentalMode @10 :Bool;
|
||||||
personality @11 :LongitudinalPersonality;
|
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;
|
||||||
|
complete @10;
|
||||||
|
}
|
||||||
|
|
||||||
enum OpenpilotState @0xdbe58b96d2d1ac61 {
|
enum OpenpilotState @0xdbe58b96d2d1ac61 {
|
||||||
disabled @0;
|
disabled @0;
|
||||||
preEnabled @1;
|
preEnabled @1;
|
||||||
@@ -795,6 +860,10 @@ struct SelfdriveState {
|
|||||||
mid @2;
|
mid @2;
|
||||||
full @3;
|
full @3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deprecated :group {
|
||||||
|
alertSound @8 :Car.CarControl.HUDControl.AudibleAlert;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ControlsState @0x97ff69c53601abf1 {
|
struct ControlsState @0x97ff69c53601abf1 {
|
||||||
@@ -815,7 +884,7 @@ struct ControlsState @0x97ff69c53601abf1 {
|
|||||||
debugState @59 :LateralDebugState;
|
debugState @59 :LateralDebugState;
|
||||||
torqueState @60 :LateralTorqueState;
|
torqueState @60 :LateralTorqueState;
|
||||||
|
|
||||||
curvatureStateDEPRECATED @65 :Deprecated.LateralCurvatureState;
|
curvatureState @65 :LateralCurvatureState;
|
||||||
lqrStateDEPRECATED @55 :Deprecated.LateralLQRState;
|
lqrStateDEPRECATED @55 :Deprecated.LateralLQRState;
|
||||||
indiStateDEPRECATED @52 :Deprecated.LateralINDIState;
|
indiStateDEPRECATED @52 :Deprecated.LateralINDIState;
|
||||||
}
|
}
|
||||||
@@ -864,6 +933,18 @@ struct ControlsState @0x97ff69c53601abf1 {
|
|||||||
saturated @3 :Bool;
|
saturated @3 :Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LateralCurvatureState @0xad9d8095c06f7c61 {
|
||||||
|
active @0 :Bool;
|
||||||
|
actualCurvature @1 :Float32;
|
||||||
|
desiredCurvature @2 :Float32;
|
||||||
|
error @3 :Float32;
|
||||||
|
p @4 :Float32;
|
||||||
|
i @5 :Float32;
|
||||||
|
f @6 :Float32;
|
||||||
|
output @7 :Float32;
|
||||||
|
saturated @8 :Bool;
|
||||||
|
}
|
||||||
|
|
||||||
deprecated :group {
|
deprecated :group {
|
||||||
vEgo @0 :Float32;
|
vEgo @0 :Float32;
|
||||||
vEgoRaw @32 :Float32;
|
vEgoRaw @32 :Float32;
|
||||||
@@ -903,7 +984,7 @@ struct ControlsState @0x97ff69c53601abf1 {
|
|||||||
alertStatus @38 :SelfdriveState.AlertStatus;
|
alertStatus @38 :SelfdriveState.AlertStatus;
|
||||||
alertSize @39 :SelfdriveState.AlertSize;
|
alertSize @39 :SelfdriveState.AlertSize;
|
||||||
alertType @44 :Text;
|
alertType @44 :Text;
|
||||||
alertSound2 @56 :Car.CarControl.HUDControl.AudibleAlert;
|
alertSound2 @56 :SelfdriveState.AudibleAlert;
|
||||||
engageable @41 :Bool; # can OP be engaged?
|
engageable @41 :Bool; # can OP be engaged?
|
||||||
state @31 :SelfdriveState.OpenpilotState;
|
state @31 :SelfdriveState.OpenpilotState;
|
||||||
enabled @19 :Bool;
|
enabled @19 :Bool;
|
||||||
@@ -924,6 +1005,7 @@ struct DrivingModelData {
|
|||||||
frameIdExtra @1 :UInt32;
|
frameIdExtra @1 :UInt32;
|
||||||
frameDropPerc @6 :Float32;
|
frameDropPerc @6 :Float32;
|
||||||
modelExecutionTime @7 :Float32;
|
modelExecutionTime @7 :Float32;
|
||||||
|
big @8 :Bool;
|
||||||
|
|
||||||
action @2 :ModelDataV2.Action;
|
action @2 :ModelDataV2.Action;
|
||||||
|
|
||||||
@@ -970,6 +1052,7 @@ struct ModelDataV2 {
|
|||||||
timestampEof @3 :UInt64;
|
timestampEof @3 :UInt64;
|
||||||
modelExecutionTime @15 :Float32;
|
modelExecutionTime @15 :Float32;
|
||||||
rawPredictions @16 :Data;
|
rawPredictions @16 :Data;
|
||||||
|
big @27 :Bool;
|
||||||
|
|
||||||
# predicted future position, orientation, etc..
|
# predicted future position, orientation, etc..
|
||||||
position @4 :XYZTData;
|
position @4 :XYZTData;
|
||||||
@@ -986,7 +1069,6 @@ struct ModelDataV2 {
|
|||||||
roadEdgeStds @14 :List(Float32);
|
roadEdgeStds @14 :List(Float32);
|
||||||
|
|
||||||
# predicted lead cars
|
# predicted lead cars
|
||||||
leads @11 :List(LeadDataV2);
|
|
||||||
leadsV3 @18 :List(LeadDataV3);
|
leadsV3 @18 :List(LeadDataV3);
|
||||||
|
|
||||||
meta @12 :MetaData;
|
meta @12 :MetaData;
|
||||||
@@ -996,8 +1078,9 @@ struct ModelDataV2 {
|
|||||||
action @26: Action;
|
action @26: Action;
|
||||||
|
|
||||||
lateralPlannerSolutionDEPRECATED @25: Deprecated.LateralPlannerSolution;
|
lateralPlannerSolutionDEPRECATED @25: Deprecated.LateralPlannerSolution;
|
||||||
|
leadsDEPRECATED @11 :List(LeadDataV2DEPRECATED);
|
||||||
|
|
||||||
struct LeadDataV2 {
|
struct LeadDataV2DEPRECATED {
|
||||||
prob @0 :Float32; # probability that car is your lead at time t
|
prob @0 :Float32; # probability that car is your lead at time t
|
||||||
t @1 :Float32;
|
t @1 :Float32;
|
||||||
|
|
||||||
@@ -1117,7 +1200,7 @@ struct EncodeIndex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AndroidLogEntry {
|
struct OperatingSystemLogEntry {
|
||||||
id @0 :UInt8;
|
id @0 :UInt8;
|
||||||
ts @1 :UInt64;
|
ts @1 :UInt64;
|
||||||
priority @2 :UInt8;
|
priority @2 :UInt8;
|
||||||
@@ -1255,7 +1338,7 @@ struct LateralPlan @0xe1e9318e2ae8b51e {
|
|||||||
struct LiveLocationKalman {
|
struct LiveLocationKalman {
|
||||||
|
|
||||||
# More info on reference frames:
|
# More info on reference frames:
|
||||||
# https://github.com/commaai/openpilot/tree/master/common/transformations
|
# https://github.com/commaai/openpilot/tree/master/openpilot/common/transformations
|
||||||
|
|
||||||
positionECEF @0 : Measurement;
|
positionECEF @0 : Measurement;
|
||||||
positionGeodetic @1 : Measurement;
|
positionGeodetic @1 : Measurement;
|
||||||
@@ -1310,9 +1393,9 @@ struct LiveLocationKalman {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct LivePose {
|
struct DeviceMotion @0xc24ca2b57206b44d {
|
||||||
# More info on reference frames:
|
# More info on reference frames:
|
||||||
# https://github.com/commaai/openpilot/tree/master/common/transformations
|
# https://github.com/commaai/openpilot/tree/master/openpilot/common/transformations
|
||||||
orientationNED @0 :XYZMeasurement;
|
orientationNED @0 :XYZMeasurement;
|
||||||
velocityDevice @1 :XYZMeasurement;
|
velocityDevice @1 :XYZMeasurement;
|
||||||
accelerationDevice @2 :XYZMeasurement;
|
accelerationDevice @2 :XYZMeasurement;
|
||||||
@@ -2054,16 +2137,15 @@ struct DriverStateV2 {
|
|||||||
facePosition @2 :List(Float32);
|
facePosition @2 :List(Float32);
|
||||||
facePositionStd @3 :List(Float32);
|
facePositionStd @3 :List(Float32);
|
||||||
faceProb @4 :Float32;
|
faceProb @4 :Float32;
|
||||||
eyesVisibleProb @14 :Float32;
|
leftEyeProb @5 :Float32;
|
||||||
eyesClosedProb @15 :Float32;
|
rightEyeProb @6 :Float32;
|
||||||
|
leftBlinkProb @7 :Float32;
|
||||||
|
rightBlinkProb @8 :Float32;
|
||||||
|
sunglassesProb @9 :Float32;
|
||||||
phoneProb @13 :Float32;
|
phoneProb @13 :Float32;
|
||||||
|
sleepProb @14 :Float32;
|
||||||
|
|
||||||
deprecated :group {
|
deprecated :group {
|
||||||
leftEyeProb @5 :Float32;
|
|
||||||
rightEyeProb @6 :Float32;
|
|
||||||
leftBlinkProb @7 :Float32;
|
|
||||||
rightBlinkProb @8 :Float32;
|
|
||||||
sunglassesProb @9 :Float32;
|
|
||||||
notReadyProb @12 :List(Float32);
|
notReadyProb @12 :List(Float32);
|
||||||
occludedProb @10 :Float32;
|
occludedProb @10 :Float32;
|
||||||
readyProb @11 :List(Float32);
|
readyProb @11 :List(Float32);
|
||||||
@@ -2076,7 +2158,7 @@ struct DriverStateV2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DriverMonitoringState @0xb83cda094a1da284 {
|
struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 {
|
||||||
events @18 :List(OnroadEvent);
|
events @18 :List(OnroadEvent);
|
||||||
faceDetected @1 :Bool;
|
faceDetected @1 :Bool;
|
||||||
isDistracted @2 :Bool;
|
isDistracted @2 :Bool;
|
||||||
@@ -2104,6 +2186,83 @@ struct DriverMonitoringState @0xb83cda094a1da284 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
struct Boot {
|
||||||
wallTimeNanos @0 :UInt64;
|
wallTimeNanos @0 :UInt64;
|
||||||
pstore @4 :Map(Text, Data);
|
pstore @4 :Map(Text, Data);
|
||||||
@@ -2116,7 +2275,7 @@ struct Boot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LiveParametersData {
|
struct VehicleParameters @0xd9058dcb967c2753 {
|
||||||
valid @0 :Bool;
|
valid @0 :Bool;
|
||||||
gyroBias @1 :Float32;
|
gyroBias @1 :Float32;
|
||||||
angleOffsetDeg @2 :Float32;
|
angleOffsetDeg @2 :Float32;
|
||||||
@@ -2150,8 +2309,8 @@ struct LiveParametersData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LiveTorqueParametersData {
|
struct LateralTorqueParameters @0xe61690eb0b091692 {
|
||||||
liveValid @0 :Bool;
|
valid @0 :Bool;
|
||||||
latAccelFactorRaw @1 :Float32;
|
latAccelFactorRaw @1 :Float32;
|
||||||
latAccelOffsetRaw @2 :Float32;
|
latAccelOffsetRaw @2 :Float32;
|
||||||
frictionCoefficientRaw @3 :Float32;
|
frictionCoefficientRaw @3 :Float32;
|
||||||
@@ -2167,7 +2326,7 @@ struct LiveTorqueParametersData {
|
|||||||
calPerc @13 :Int8;
|
calPerc @13 :Int8;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LiveDelayData {
|
struct LateralDelay @0x98dfdb22c44df8d4 {
|
||||||
lateralDelay @0 :Float32;
|
lateralDelay @0 :Float32;
|
||||||
validBlocks @1 :Int32;
|
validBlocks @1 :Int32;
|
||||||
status @2 :Status;
|
status @2 :Status;
|
||||||
@@ -2176,6 +2335,7 @@ struct LiveDelayData {
|
|||||||
lateralDelayEstimateStd @5 :Float32;
|
lateralDelayEstimateStd @5 :Float32;
|
||||||
points @4 :List(Float32);
|
points @4 :List(Float32);
|
||||||
calPerc @6 :Int8;
|
calPerc @6 :Int8;
|
||||||
|
version @7 :Int32;
|
||||||
|
|
||||||
enum Status {
|
enum Status {
|
||||||
unestimated @0;
|
unestimated @0;
|
||||||
@@ -2229,7 +2389,8 @@ struct Sentinel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct UIDebug {
|
struct UIDebug {
|
||||||
drawTimeMillis @0 :Float32;
|
cpuTimeMillis @0 :Float32;
|
||||||
|
frameTimeMillis @1 :Float32;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ManagerState {
|
struct ManagerState {
|
||||||
@@ -2351,11 +2512,6 @@ struct AudioData {
|
|||||||
sampleRate @1 :UInt32;
|
sampleRate @1 :UInt32;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AudioFeedback {
|
|
||||||
audio @0 :AudioData;
|
|
||||||
blockNum @1 :UInt16;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Touch {
|
struct Touch {
|
||||||
sec @0 :Int64;
|
sec @0 :Int64;
|
||||||
usec @1 :Int64;
|
usec @1 :Int64;
|
||||||
@@ -2377,7 +2533,6 @@ struct Event {
|
|||||||
boot @60 :Boot;
|
boot @60 :Boot;
|
||||||
|
|
||||||
# ********** openpilot daemon msgs **********
|
# ********** openpilot daemon msgs **********
|
||||||
gpsNMEA @3 :GPSNMEAData;
|
|
||||||
can @5 :List(CanData);
|
can @5 :List(CanData);
|
||||||
controlsState @7 :ControlsState;
|
controlsState @7 :ControlsState;
|
||||||
selfdriveState @130 :SelfdriveState;
|
selfdriveState @130 :SelfdriveState;
|
||||||
@@ -2389,9 +2544,9 @@ struct Event {
|
|||||||
pandaStates @81 :List(PandaState);
|
pandaStates @81 :List(PandaState);
|
||||||
peripheralState @80 :PeripheralState;
|
peripheralState @80 :PeripheralState;
|
||||||
radarState @13 :RadarState;
|
radarState @13 :RadarState;
|
||||||
liveTracks @131 :Car.RadarData;
|
radarTracks @131 :Car.RadarData;
|
||||||
sendcan @17 :List(CanData);
|
sendcan @17 :List(CanData);
|
||||||
liveCalibration @19 :LiveCalibrationData;
|
extrinsicsCalibration @19 :ExtrinsicsCalibration;
|
||||||
carState @22 :Car.CarState;
|
carState @22 :Car.CarState;
|
||||||
carControl @23 :Car.CarControl;
|
carControl @23 :Car.CarControl;
|
||||||
carOutput @127 :Car.CarOutput;
|
carOutput @127 :Car.CarOutput;
|
||||||
@@ -2402,77 +2557,69 @@ struct Event {
|
|||||||
qcomGnss @31 :QcomGnss;
|
qcomGnss @31 :QcomGnss;
|
||||||
gpsLocationExternal @48 :GpsLocationData;
|
gpsLocationExternal @48 :GpsLocationData;
|
||||||
gpsLocation @21 :GpsLocationData;
|
gpsLocation @21 :GpsLocationData;
|
||||||
gnssMeasurements @91 :GnssMeasurements;
|
vehicleParameters @61 :VehicleParameters;
|
||||||
liveParameters @61 :LiveParametersData;
|
lateralTorqueParameters @94 :LateralTorqueParameters;
|
||||||
liveTorqueParameters @94 :LiveTorqueParametersData;
|
lateralDelay @146 : LateralDelay;
|
||||||
liveDelay @146 : LiveDelayData;
|
|
||||||
cameraOdometry @63 :CameraOdometry;
|
cameraOdometry @63 :CameraOdometry;
|
||||||
thumbnail @66: Thumbnail;
|
thumbnail @66: Thumbnail;
|
||||||
onroadEvents @134: List(OnroadEvent);
|
onroadEvents @134: List(OnroadEvent);
|
||||||
carParams @69: Car.CarParams;
|
carParams @69: Car.CarParams;
|
||||||
driverMonitoringState @71: DriverMonitoringState;
|
driverMonitoringState @151 :DriverMonitoringState;
|
||||||
livePose @129 :LivePose;
|
deviceMotion @129 :DeviceMotion;
|
||||||
modelV2 @75 :ModelDataV2;
|
modelV2 @75 :ModelDataV2;
|
||||||
drivingModelData @128 :DrivingModelData;
|
drivingModelData @128 :DrivingModelData;
|
||||||
driverStateV2 @92 :DriverStateV2;
|
driverStateV2 @92 :DriverStateV2;
|
||||||
|
|
||||||
# camera stuff, each camera state has a matching encode idx
|
# camera stuff, each camera state has a matching encode idx
|
||||||
roadCameraState @2 :FrameData;
|
narrowRoadCameraState @2 :FrameData;
|
||||||
driverCameraState @70: FrameData;
|
cabinCameraState @70: FrameData;
|
||||||
wideRoadCameraState @74: FrameData;
|
wideRoadCameraState @74: FrameData;
|
||||||
roadEncodeIdx @15 :EncodeIndex;
|
narrowRoadEncodeIdx @15 :EncodeIndex;
|
||||||
driverEncodeIdx @76 :EncodeIndex;
|
cabinEncodeIdx @76 :EncodeIndex;
|
||||||
wideRoadEncodeIdx @77 :EncodeIndex;
|
wideRoadEncodeIdx @77 :EncodeIndex;
|
||||||
qRoadEncodeIdx @90 :EncodeIndex;
|
qNarrowRoadEncodeIdx @90 :EncodeIndex;
|
||||||
|
|
||||||
livestreamRoadEncodeIdx @117 :EncodeIndex;
|
livestreamNarrowRoadEncodeIdx @117 :EncodeIndex;
|
||||||
livestreamWideRoadEncodeIdx @118 :EncodeIndex;
|
livestreamWideRoadEncodeIdx @118 :EncodeIndex;
|
||||||
livestreamDriverEncodeIdx @119 :EncodeIndex;
|
livestreamCabinEncodeIdx @119 :EncodeIndex;
|
||||||
|
|
||||||
# microphone data
|
# microphone data
|
||||||
soundPressure @103 :SoundPressure;
|
soundPressure @103 :SoundPressure;
|
||||||
rawAudioData @147 :AudioData;
|
rawAudioData @147 :AudioData;
|
||||||
|
|
||||||
# systems stuff
|
# systems stuff
|
||||||
androidLog @20 :AndroidLogEntry;
|
operatingSystemLog @20 :OperatingSystemLogEntry;
|
||||||
managerState @78 :ManagerState;
|
managerState @78 :ManagerState;
|
||||||
uploaderState @79 :UploaderState;
|
|
||||||
procLog @33 :ProcLog;
|
procLog @33 :ProcLog;
|
||||||
clocks @35 :Clocks;
|
clocks @35 :Clocks;
|
||||||
deviceState @6 :DeviceState;
|
deviceState @6 :DeviceState;
|
||||||
|
chestnutState @152 :ChestnutState;
|
||||||
logMessage @18 :Text;
|
logMessage @18 :Text;
|
||||||
errorLogMessage @85 :Text;
|
errorLogMessage @85 :Text;
|
||||||
|
|
||||||
# touch frame
|
# touch frame
|
||||||
touch @135 :List(Touch);
|
touch @135 :List(Touch);
|
||||||
|
|
||||||
# navigation
|
|
||||||
navInstruction @82 :NavInstruction;
|
|
||||||
navRoute @83 :NavRoute;
|
|
||||||
navThumbnail @84: Thumbnail;
|
|
||||||
mapRenderState @105: MapRenderState;
|
|
||||||
|
|
||||||
# UI services
|
# UI services
|
||||||
uiDebug @102 :UIDebug;
|
uiDebug @102 :UIDebug;
|
||||||
|
|
||||||
# driving feedback
|
# driving feedback
|
||||||
userBookmark @93 :UserBookmark;
|
userBookmark @93 :UserBookmark;
|
||||||
bookmarkButton @148 :UserBookmark;
|
bookmarkButton @148 :UserBookmark;
|
||||||
audioFeedback @149 :AudioFeedback;
|
|
||||||
|
|
||||||
lateralManeuverPlan @150 :LateralManeuverPlan;
|
lateralManeuverPlan @150 :LateralManeuverPlan;
|
||||||
|
|
||||||
# *********** debug ***********
|
# *********** debug ***********
|
||||||
testJoystick @52 :Joystick;
|
testJoystick @52 :Joystick;
|
||||||
roadEncodeData @86 :EncodeData;
|
narrowRoadEncodeData @86 :EncodeData;
|
||||||
driverEncodeData @87 :EncodeData;
|
cabinEncodeData @87 :EncodeData;
|
||||||
wideRoadEncodeData @88 :EncodeData;
|
wideRoadEncodeData @88 :EncodeData;
|
||||||
qRoadEncodeData @89 :EncodeData;
|
qNarrowRoadEncodeData @89 :EncodeData;
|
||||||
alertDebug @133 :DebugAlert;
|
alertDebug @133 :DebugAlert;
|
||||||
|
|
||||||
livestreamRoadEncodeData @120 :EncodeData;
|
livestreamNarrowRoadEncodeData @120 :EncodeData;
|
||||||
livestreamWideRoadEncodeData @121 :EncodeData;
|
livestreamWideRoadEncodeData @121 :EncodeData;
|
||||||
livestreamDriverEncodeData @122 :EncodeData;
|
livestreamCabinEncodeData @122 :EncodeData;
|
||||||
|
|
||||||
# *********** Custom: reserved for forks ***********
|
# *********** Custom: reserved for forks ***********
|
||||||
|
|
||||||
@@ -2495,7 +2642,7 @@ struct Event {
|
|||||||
carStateSP @114 :Custom.CarStateSP;
|
carStateSP @114 :Custom.CarStateSP;
|
||||||
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
||||||
modelDataV2SP @116 :Custom.ModelDataV2SP;
|
modelDataV2SP @116 :Custom.ModelDataV2SP;
|
||||||
customReserved10 @136 :Custom.CustomReserved10;
|
assistedDrivingMilestoneState @136 :Custom.AssistedDrivingMilestoneState;
|
||||||
customReserved11 @137 :Custom.CustomReserved11;
|
customReserved11 @137 :Custom.CustomReserved11;
|
||||||
customReserved12 @138 :Custom.CustomReserved12;
|
customReserved12 @138 :Custom.CustomReserved12;
|
||||||
customReserved13 @139 :Custom.CustomReserved13;
|
customReserved13 @139 :Custom.CustomReserved13;
|
||||||
@@ -2508,6 +2655,7 @@ struct Event {
|
|||||||
|
|
||||||
# *********** legacy + deprecated ***********
|
# *********** legacy + deprecated ***********
|
||||||
model @9 :Deprecated.ModelData; # TODO: rename modelV2 and mark this as deprecated
|
model @9 :Deprecated.ModelData; # TODO: rename modelV2 and mark this as deprecated
|
||||||
|
audioFeedbackDEPRECATED @149 :Deprecated.AudioFeedbackDEPRECATED;
|
||||||
liveMpcDEPRECATED @36 :Deprecated.LiveMpcData;
|
liveMpcDEPRECATED @36 :Deprecated.LiveMpcData;
|
||||||
liveLongitudinalMpcDEPRECATED @37 :Deprecated.LiveLongitudinalMpcData;
|
liveLongitudinalMpcDEPRECATED @37 :Deprecated.LiveLongitudinalMpcData;
|
||||||
liveLocationKalmanDeprecatedDEPRECATED @51 :Deprecated.LiveLocationData;
|
liveLocationKalmanDeprecatedDEPRECATED @51 :Deprecated.LiveLocationData;
|
||||||
@@ -2553,5 +2701,13 @@ struct Event {
|
|||||||
gyroscope2DEPRECATED @100 :SensorEventData;
|
gyroscope2DEPRECATED @100 :SensorEventData;
|
||||||
accelerometer2DEPRECATED @101 :SensorEventData;
|
accelerometer2DEPRECATED @101 :SensorEventData;
|
||||||
temperatureSensor2DEPRECATED @123 :SensorEventData;
|
temperatureSensor2DEPRECATED @123 :SensorEventData;
|
||||||
|
driverMonitoringStateDEPRECATED @71 :DriverMonitoringStateDEPRECATED;
|
||||||
|
gpsNMEADEPRECATED @3 :GPSNMEAData;
|
||||||
|
uploaderStateDEPRECATED @79 :UploaderState;
|
||||||
|
navInstructionDEPRECATED @82 :NavInstruction;
|
||||||
|
navRouteDEPRECATED @83 :NavRoute;
|
||||||
|
navThumbnailDEPRECATED @84 :Thumbnail;
|
||||||
|
gnssMeasurementsDEPRECATED @91 :GnssMeasurements;
|
||||||
|
mapRenderStateDEPRECATED @105: MapRenderState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,12 +7,43 @@ import os
|
|||||||
import capnp
|
import capnp
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from typing import Optional, List, Union, Dict
|
from typing import Union
|
||||||
|
|
||||||
from cereal import log
|
from openpilot.cereal import log
|
||||||
from cereal.services import SERVICE_LIST
|
from openpilot.cereal.services import SERVICE_LIST
|
||||||
from openpilot.common.utils import MovingAverage
|
from openpilot.common.utils import MovingAverage
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"NO_TRAVERSAL_LIMIT",
|
||||||
|
"Context",
|
||||||
|
"FrequencyTracker",
|
||||||
|
"IpcError",
|
||||||
|
"MultiplePublishersError",
|
||||||
|
"Poller",
|
||||||
|
"PubMaster",
|
||||||
|
"PubSocket",
|
||||||
|
"SocketEventHandle",
|
||||||
|
"SubMaster",
|
||||||
|
"SubSocket",
|
||||||
|
"delete_fake_prefix",
|
||||||
|
"drain_sock",
|
||||||
|
"drain_sock_raw",
|
||||||
|
"fake_event_handle",
|
||||||
|
"get_fake_prefix",
|
||||||
|
"log_from_bytes",
|
||||||
|
"new_message",
|
||||||
|
"pub_sock",
|
||||||
|
"recv_one",
|
||||||
|
"recv_one_or_none",
|
||||||
|
"recv_one_retry",
|
||||||
|
"recv_sock",
|
||||||
|
"reset_context",
|
||||||
|
"set_fake_prefix",
|
||||||
|
"sub_sock",
|
||||||
|
"toggle_fake_events",
|
||||||
|
"wait_for_one_event",
|
||||||
|
)
|
||||||
|
|
||||||
NO_TRAVERSAL_LIMIT = 2**64-1
|
NO_TRAVERSAL_LIMIT = 2**64-1
|
||||||
|
|
||||||
|
|
||||||
@@ -22,8 +53,8 @@ def pub_sock(endpoint: str) -> PubSocket:
|
|||||||
return msgq.pub_sock(endpoint, segment_size)
|
return msgq.pub_sock(endpoint, segment_size)
|
||||||
|
|
||||||
|
|
||||||
def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
|
def sub_sock(endpoint: str, poller: Poller | None = None, addr: str = "127.0.0.1",
|
||||||
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
|
conflate: bool = False, timeout: int | None = None) -> SubSocket:
|
||||||
service = SERVICE_LIST.get(endpoint)
|
service = SERVICE_LIST.get(endpoint)
|
||||||
segment_size = service.queue_size if service else 0
|
segment_size = service.queue_size if service else 0
|
||||||
return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate,
|
return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate,
|
||||||
@@ -39,7 +70,7 @@ def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
|
def new_message(service: str | None, size: int | None = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||||
args = {
|
args = {
|
||||||
'valid': False,
|
'valid': False,
|
||||||
'logMonoTime': int(time.monotonic() * 1e9),
|
'logMonoTime': int(time.monotonic() * 1e9),
|
||||||
@@ -54,14 +85,14 @@ def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) ->
|
|||||||
return dat
|
return dat
|
||||||
|
|
||||||
|
|
||||||
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]:
|
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> list[capnp.lib.capnp._DynamicStructReader]:
|
||||||
"""Receive all message currently available on the queue"""
|
"""Receive all message currently available on the queue"""
|
||||||
msgs = drain_sock_raw(sock, wait_for_one=wait_for_one)
|
msgs = drain_sock_raw(sock, wait_for_one=wait_for_one)
|
||||||
return [log_from_bytes(m) for m in msgs]
|
return [log_from_bytes(m) for m in msgs]
|
||||||
|
|
||||||
|
|
||||||
# TODO: print when we drop packets?
|
# TODO: print when we drop packets?
|
||||||
def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
def recv_sock(sock: SubSocket, wait: bool = False) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||||
"""Same as drain sock, but only returns latest message. Consider using conflate instead."""
|
"""Same as drain sock, but only returns latest message. Consider using conflate instead."""
|
||||||
dat = None
|
dat = None
|
||||||
|
|
||||||
@@ -82,14 +113,14 @@ def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._
|
|||||||
return dat
|
return dat
|
||||||
|
|
||||||
|
|
||||||
def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
def recv_one(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||||
dat = sock.receive()
|
dat = sock.receive()
|
||||||
if dat is not None:
|
if dat is not None:
|
||||||
dat = log_from_bytes(dat)
|
dat = log_from_bytes(dat)
|
||||||
return dat
|
return dat
|
||||||
|
|
||||||
|
|
||||||
def recv_one_or_none(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
def recv_one_or_none(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||||
dat = sock.receive(non_blocking=True)
|
dat = sock.receive(non_blocking=True)
|
||||||
if dat is not None:
|
if dat is not None:
|
||||||
dat = log_from_bytes(dat)
|
dat = log_from_bytes(dat)
|
||||||
@@ -148,27 +179,27 @@ class FrequencyTracker:
|
|||||||
|
|
||||||
|
|
||||||
class SubMaster:
|
class SubMaster:
|
||||||
def __init__(self, services: List[str], poll: Optional[str] = None,
|
def __init__(self, services: list[str], poll: str | None = None,
|
||||||
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
|
ignore_alive: list[str] | None = None, ignore_avg_freq: list[str] | None = None,
|
||||||
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
|
ignore_valid: list[str] | None = None, addr: str = "127.0.0.1", frequency: float | None = None):
|
||||||
self.frame = -1
|
self.frame = -1
|
||||||
self.services = services
|
self.services = services
|
||||||
self.seen = {s: False for s in services}
|
self.seen = dict.fromkeys(services, False)
|
||||||
self.updated = {s: False for s in services}
|
self.updated = dict.fromkeys(services, False)
|
||||||
self.recv_time = {s: 0. for s in services}
|
self.recv_time = dict.fromkeys(services, 0.0)
|
||||||
self.recv_frame = {s: 0 for s in services}
|
self.recv_frame = dict.fromkeys(services, 0)
|
||||||
self.sock = {}
|
self.sock = {}
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.logMonoTime = {s: 0 for s in services}
|
self.logMonoTime = dict.fromkeys(services, 0)
|
||||||
|
|
||||||
# zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks
|
# zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks
|
||||||
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services}
|
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services}
|
||||||
self.static_freq_services = set(s for s in services if not on_demand[s])
|
self.static_freq_services = {s for s in services if not on_demand[s]}
|
||||||
self.alive = {s: on_demand[s] for s in services}
|
self.alive = {s: on_demand[s] for s in services}
|
||||||
self.freq_ok = {s: on_demand[s] for s in services}
|
self.freq_ok = {s: on_demand[s] for s in services}
|
||||||
self.valid = {s: on_demand[s] for s in services}
|
self.valid = {s: on_demand[s] for s in services}
|
||||||
|
|
||||||
self.freq_tracker: Dict[str, FrequencyTracker] = {}
|
self.freq_tracker: dict[str, FrequencyTracker] = {}
|
||||||
self.poller = Poller()
|
self.poller = Poller()
|
||||||
polled_services = set([poll, ] if poll is not None else services)
|
polled_services = set([poll, ] if poll is not None else services)
|
||||||
self.non_polled_services = set(services) - polled_services
|
self.non_polled_services = set(services) - polled_services
|
||||||
@@ -211,7 +242,7 @@ class SubMaster:
|
|||||||
msgs.append(recv_one_or_none(self.sock[s]))
|
msgs.append(recv_one_or_none(self.sock[s]))
|
||||||
self.update_msgs(time.monotonic(), msgs)
|
self.update_msgs(time.monotonic(), msgs)
|
||||||
|
|
||||||
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
|
def update_msgs(self, cur_time: float, msgs: list[capnp.lib.capnp._DynamicStructReader]) -> None:
|
||||||
self.frame += 1
|
self.frame += 1
|
||||||
self.updated = dict.fromkeys(self.services, False)
|
self.updated = dict.fromkeys(self.services, False)
|
||||||
for msg in msgs:
|
for msg in msgs:
|
||||||
@@ -234,21 +265,21 @@ class SubMaster:
|
|||||||
self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation)
|
self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation)
|
||||||
self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation
|
self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation
|
||||||
|
|
||||||
def all_alive(self, service_list: Optional[List[str]] = None) -> bool:
|
def all_alive(self, service_list: list[str] | None = None) -> bool:
|
||||||
return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive)
|
return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive)
|
||||||
|
|
||||||
def all_freq_ok(self, service_list: Optional[List[str]] = None) -> bool:
|
def all_freq_ok(self, service_list: list[str] | None = None) -> bool:
|
||||||
return all(self.freq_ok[s] for s in (service_list or self.services) if self._check_avg_freq(s))
|
return all(self.freq_ok[s] for s in (service_list or self.services) if self._check_avg_freq(s))
|
||||||
|
|
||||||
def all_valid(self, service_list: Optional[List[str]] = None) -> bool:
|
def all_valid(self, service_list: list[str] | None = None) -> bool:
|
||||||
return all(self.valid[s] for s in (service_list or self.services) if s not in self.ignore_valid)
|
return all(self.valid[s] for s in (service_list or self.services) if s not in self.ignore_valid)
|
||||||
|
|
||||||
def all_checks(self, service_list: Optional[List[str]] = None) -> bool:
|
def all_checks(self, service_list: list[str] | None = None) -> bool:
|
||||||
return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list)
|
return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list)
|
||||||
|
|
||||||
|
|
||||||
class PubMaster:
|
class PubMaster:
|
||||||
def __init__(self, services: List[str]):
|
def __init__(self, services: list[str]):
|
||||||
self.sock = {}
|
self.sock = {}
|
||||||
for s in services:
|
for s in services:
|
||||||
self.sock[s] = pub_sock(s)
|
self.sock[s] = pub_sock(s)
|
||||||
@@ -259,11 +290,11 @@ class PubMaster:
|
|||||||
self.sock[s].send(dat)
|
self.sock[s].send(dat)
|
||||||
|
|
||||||
def wait_for_readers_to_update(self, s: str, timeout: int, dt: float = 0.05) -> bool:
|
def wait_for_readers_to_update(self, s: str, timeout: int, dt: float = 0.05) -> bool:
|
||||||
for _ in range(int(timeout*(1./dt))):
|
try:
|
||||||
if self.sock[s].all_readers_updated():
|
self.sock[s].wait_for_readers(timeout=timeout, interval=dt)
|
||||||
return True
|
return True
|
||||||
time.sleep(dt)
|
except TimeoutError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def all_readers_updated(self, s: str) -> bool:
|
def all_readers_updated(self, s: str) -> bool:
|
||||||
return self.sock[s].all_readers_updated() # type: ignore
|
return self.sock[s].all_readers_updated()
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user