mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-08 13:53:41 +08:00
Compare commits
615 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 61b6d52b01 | |||
| 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 | |||
| 7abe0205fd | |||
| ef3ec30fe2 | |||
| b0600a4225 | |||
| cc110d1eb1 | |||
| bfd8d4868d | |||
| 14a00e0cc8 | |||
| 268126f376 | |||
| 900a896c63 | |||
| c01b8be968 | |||
| 094591793a | |||
| f02d134f40 | |||
| 5b3d5f74ed | |||
| 866cd01f32 | |||
| d09a411cb4 |
@@ -1,18 +0,0 @@
|
|||||||
**/.git
|
|
||||||
.DS_Store
|
|
||||||
*.dylib
|
|
||||||
*.DSYM
|
|
||||||
*.d
|
|
||||||
*.pyc
|
|
||||||
*.pyo
|
|
||||||
.*.swp
|
|
||||||
.*.swo
|
|
||||||
.*.un~
|
|
||||||
*.tmp
|
|
||||||
*.o
|
|
||||||
*.o-*
|
|
||||||
*.os
|
|
||||||
*.os-*
|
|
||||||
|
|
||||||
venv/
|
|
||||||
.venv/
|
|
||||||
+3
-2
@@ -9,6 +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 filter=lfs diff=lfs merge=lfs -text
|
openpilot/common/hardware/comma/updater 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
|
|
||||||
@@ -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
|
||||||
@@ -123,25 +116,30 @@ jobs:
|
|||||||
is_20hz: ${{ matrix.model.is_20hz }}
|
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
|
||||||
@@ -149,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) }}
|
||||||
@@ -161,146 +159,9 @@ jobs:
|
|||||||
is_20hz: ${{ matrix.model.is_20hz }}
|
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:
|
||||||
@@ -29,11 +29,24 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
bypass_push:
|
target_hardware:
|
||||||
description: 'Bypass pushing to GitLab for build-all'
|
description: 'Hardware target to compile for (qcom or chestnut)'
|
||||||
required: false
|
required: false
|
||||||
default: true
|
type: string
|
||||||
type: boolean
|
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:
|
||||||
@@ -63,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)'
|
||||||
@@ -82,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:
|
||||||
@@ -94,38 +119,20 @@ jobs:
|
|||||||
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
||||||
is_20hz: ${{ inputs.is_20hz }}
|
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:
|
||||||
@@ -134,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
|
||||||
@@ -163,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
|
||||||
|
|
||||||
@@ -221,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:
|
||||||
@@ -30,11 +30,25 @@ jobs:
|
|||||||
- name: Checkout sunnypilot cereal
|
- name: Checkout sunnypilot cereal
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
sparse-checkout: cereal
|
sparse-checkout: |
|
||||||
|
openpilot/cereal
|
||||||
|
uv.lock
|
||||||
|
submodules: false
|
||||||
|
|
||||||
- name: Init sunnypilot opendbc submodule
|
- name: Init sunnypilot opendbc submodule
|
||||||
run: git submodule update --init --depth 1 opendbc_repo
|
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:
|
||||||
@@ -65,20 +79,19 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "import_args=$IMPORT_ARGS" >> "$GITHUB_OUTPUT"
|
echo "import_args=$IMPORT_ARGS" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install pycapnp
|
||||||
run: pip install uv
|
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
|
- name: Generate sunnypilot schema
|
||||||
run: |
|
run: |
|
||||||
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
|
python3 openpilot/cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
||||||
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
|
-g -f /tmp/sp_schema.json --cereal-dir openpilot/cereal \
|
||||||
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
${{ steps.locate-sp-capnp.outputs.import_args }}
|
||||||
-g -f /tmp/sp_schema.json --cereal-dir cereal
|
|
||||||
|
|
||||||
- name: Validate against upstream
|
- name: Validate against upstream
|
||||||
run: |
|
run: |
|
||||||
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
|
python3 openpilot/cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
||||||
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
|
|
||||||
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
|
||||||
-r -f /tmp/sp_schema.json --cereal-dir ${{ steps.locate-capnp.outputs.cereal_dir }} \
|
-r -f /tmp/sp_schema.json --cereal-dir ${{ steps.locate-capnp.outputs.cereal_dir }} \
|
||||||
${{ steps.locate-capnp.outputs.import_args }}
|
${{ 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,41 +162,71 @@ 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
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}:${{ github.workspace }}"
|
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}:${{ github.workspace }}"
|
||||||
|
|
||||||
COMPILE_MODELD="${{ github.workspace }}/sunnypilot/modeld_v2/compile_modeld.py"
|
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]}')")
|
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}')")
|
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="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
|
||||||
|
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
|
# 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
|
||||||
echo "Generating metadata: $onnx_file"
|
echo "Generating metadata: $onnx_file"
|
||||||
env ${TG_FLAGS} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
env ${TG_FLAGS_QCOM} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Detect model type and build compile args
|
# Detect model type and build compile args
|
||||||
VISION_ONNX="${{ env.MODELS_DIR }}/driving_vision.onnx"
|
VISION_ONNX=""
|
||||||
POLICY_ONNX="${{ env.MODELS_DIR }}/driving_policy.onnx"
|
for f in "${{ env.MODELS_DIR }}/driving_vision.onnx" "${{ env.MODELS_DIR }}/big_driving_vision.onnx"; do
|
||||||
OFF_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_off_policy.onnx"
|
[ -f "$f" ] && VISION_ONNX="$f" && break
|
||||||
ON_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_on_policy.onnx"
|
done
|
||||||
SUPERCOMBO_ONNX="${{ env.MODELS_DIR }}/supercombo.onnx"
|
|
||||||
|
POLICY_ONNX=""
|
||||||
|
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=""
|
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
|
||||||
if [ -f "$VISION_ONNX" ]; then
|
if [ -f "$VISION_ONNX" ]; then
|
||||||
@@ -203,24 +247,15 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$MODEL_TYPE" ]; then
|
if [ -n "$MODEL_TYPE" ]; then
|
||||||
echo "Detected: $MODEL_TYPE -> driving_tinygrad.pkl"
|
echo "Detected: $MODEL_TYPE -> $OUTPUT_PKL"
|
||||||
env ${TG_FLAGS} python3 "$COMPILE_MODELD" \
|
env ${TG_FLAGS} python3 "$COMPILE_MODELD" \
|
||||||
--model-type $MODEL_TYPE \
|
--model-type $MODEL_TYPE \
|
||||||
--model-size $MODEL_SIZE \
|
--model-size $MODEL_SIZE \
|
||||||
--camera-resolutions $CAMERA_RES \
|
--camera-resolutions $CAMERA_RES \
|
||||||
$ONNX_ARGS \
|
$ONNX_ARGS \
|
||||||
--output "${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
|
--output "$OUTPUT_PKL"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Validate Model Outputs
|
|
||||||
run: |
|
|
||||||
source /etc/profile
|
|
||||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
|
||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
|
||||||
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
|
|
||||||
--validate-only \
|
|
||||||
--model-dir "${{ env.MODELS_DIR }}"
|
|
||||||
|
|
||||||
- name: Prepare Output
|
- name: Prepare Output
|
||||||
run: |
|
run: |
|
||||||
sudo rm -rf ${{ env.OUTPUT_DIR }}
|
sudo rm -rf ${{ env.OUTPUT_DIR }}
|
||||||
@@ -229,10 +264,8 @@ jobs:
|
|||||||
# Copy the model files
|
# Copy the model files
|
||||||
rsync -avm \
|
rsync -avm \
|
||||||
--include='*.dlc' \
|
--include='*.dlc' \
|
||||||
--include='*.pkl' \
|
|
||||||
--include='*.chunk*' \
|
--include='*.chunk*' \
|
||||||
--include='*.chunkmanifest' \
|
--include='*.chunkmanifest' \
|
||||||
--include='*.onnx' \
|
|
||||||
--exclude='*' \
|
--exclude='*' \
|
||||||
--delete-excluded \
|
--delete-excluded \
|
||||||
--chown=comma:comma \
|
--chown=comma:comma \
|
||||||
@@ -264,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/*.onnx*' \
|
|
||||||
--exclude='sunnypilot/modeld*/models/*.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
|
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,68 +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
|
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: Prepare 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"
|
||||||
- name: Push refs
|
- name: Push refs
|
||||||
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master'
|
||||||
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: cd ${{ github.workspace }}/ci-artifacts && git push origin process-replay --force
|
command: cd ${{ github.workspace }}/ci-artifacts && git push origin process-replay --force
|
||||||
- name: Run regen
|
|
||||||
if: false
|
|
||||||
timeout-minutes: 4
|
|
||||||
env:
|
|
||||||
ONNXCPU: 1
|
|
||||||
run: $PYTEST selfdrive/test/process_replay/test_regen.py
|
|
||||||
|
|
||||||
simulator_driving:
|
simulator_driving:
|
||||||
name: simulator driving
|
name: simulator driving
|
||||||
@@ -215,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
|
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
|
||||||
@@ -236,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
|
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_input_devices.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
-5
@@ -17,10 +17,5 @@
|
|||||||
"**/.git",
|
"**/.git",
|
||||||
"**/.venv",
|
"**/.venv",
|
||||||
"**/__pycache__",
|
"**/__pycache__",
|
||||||
// exclude directories that should be using the symlinked version
|
|
||||||
"common/**",
|
|
||||||
"selfdrive/**",
|
|
||||||
"system/**",
|
|
||||||
"tools/**",
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
+31
-28
@@ -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]
|
||||||
@@ -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,release-mici-staging $SOURCE_DIR/release/build_release.sh"),
|
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,51 +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"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+12
-4
@@ -1,6 +1,14 @@
|
|||||||
Version 0.11.2 (2026-06-15)
|
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)
|
Version 0.11.1 (2026-05-18)
|
||||||
========================
|
========================
|
||||||
@@ -1104,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
|
||||||
|
|||||||
+92
-41
@@ -10,7 +10,7 @@ import numpy as np
|
|||||||
import SCons.Errors
|
import SCons.Errors
|
||||||
from SCons.Defaults import _stripixes
|
from SCons.Defaults import _stripixes
|
||||||
|
|
||||||
TICI = os.path.isfile('/TICI')
|
COMMA_HARDWARE = os.path.isfile('/AGNOS')
|
||||||
|
|
||||||
SCons.Warnings.warningAsException(True)
|
SCons.Warnings.warningAsException(True)
|
||||||
|
|
||||||
@@ -24,25 +24,55 @@ release = not os.path.exists(File('#.gitattributes').abspath) # file absent on r
|
|||||||
AddOption('--minimal',
|
AddOption('--minimal',
|
||||||
action='store_false',
|
action='store_false',
|
||||||
dest='extras',
|
dest='extras',
|
||||||
default=(not TICI and not release),
|
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 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 = ['acados', 'bzip2', 'capnproto', 'catch2', 'eigen', 'ffmpeg', 'json11', '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')]
|
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_dirs = [
|
||||||
acados.INCLUDE_DIR,
|
acados.INCLUDE_DIR,
|
||||||
os.path.join(acados.INCLUDE_DIR, "blasfeo", "include"),
|
os.path.join(acados.INCLUDE_DIR, "blasfeo", "include"),
|
||||||
@@ -91,7 +121,7 @@ def _libflags(target, source, env, for_signature):
|
|||||||
env = Environment(
|
env = Environment(
|
||||||
ENV={
|
ENV={
|
||||||
"PATH": os.environ['PATH'],
|
"PATH": os.environ['PATH'],
|
||||||
"PYTHONPATH": Dir("#").abspath,
|
"PYTHONPATH": os.pathsep.join(submodule_python_paths),
|
||||||
"ACADOS_SOURCE_DIR": acados.DIR,
|
"ACADOS_SOURCE_DIR": acados.DIR,
|
||||||
"ACADOS_PYTHON_INTERFACE_PATH": acados.TEMPLATE_DIR,
|
"ACADOS_PYTHON_INTERFACE_PATH": acados.TEMPLATE_DIR,
|
||||||
"TERA_PATH": acados.TERA_PATH
|
"TERA_PATH": acados.TERA_PATH
|
||||||
@@ -99,10 +129,11 @@ env = Environment(
|
|||||||
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",
|
||||||
@@ -112,36 +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/..."
|
||||||
|
"#opendbc_repo", # #include "opendbc/..."
|
||||||
|
"#rednose_repo", # #include "rednose/..."
|
||||||
|
"#rednose_repo/rednose", # #include "logger/..." (rednose package root)
|
||||||
|
"#openpilot/cereal/gen/cpp",
|
||||||
acados_include_dirs,
|
acados_include_dirs,
|
||||||
[x.INCLUDE_DIR for x in pkgs],
|
[x.INCLUDE_DIR for x in pkgs],
|
||||||
|
"#",
|
||||||
],
|
],
|
||||||
LIBPATH=[
|
LIBPATH=[
|
||||||
"#common",
|
"#openpilot/common",
|
||||||
"#msgq_repo",
|
"#msgq_repo",
|
||||||
"#selfdrive/pandad",
|
"#openpilot/selfdrive/pandad",
|
||||||
"#rednose/helpers",
|
"#rednose_repo/rednose/helpers",
|
||||||
[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":
|
||||||
@@ -190,10 +231,10 @@ 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', 'acados', 'release')
|
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
|
cache_size_limit = 4e9 if "CI" in os.environ else 2e9
|
||||||
CacheDir(cache_dir)
|
CacheDir(cache_dir)
|
||||||
@@ -211,7 +252,7 @@ def prune_cache_dir(target=None, source=None, env=None):
|
|||||||
# ********** 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')
|
||||||
@@ -222,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',]
|
||||||
@@ -233,34 +274,33 @@ 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 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 desktop-only tools
|
# Build desktop-only tools
|
||||||
if GetOption('extras') and 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',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@@ -276,6 +316,8 @@ def count_scons_nodes(nodes):
|
|||||||
if node in seen:
|
if node in seen:
|
||||||
continue
|
continue
|
||||||
seen.add(node)
|
seen.add(node)
|
||||||
|
if hasattr(node, 'has_builder') and node.has_builder():
|
||||||
|
build_product_nodes.add(node)
|
||||||
executor = node.get_executor()
|
executor = node.get_executor()
|
||||||
if executor is not None:
|
if executor is not None:
|
||||||
stack += executor.get_all_prerequisites() + executor.get_all_children()
|
stack += executor.get_all_prerequisites() + executor.get_all_children()
|
||||||
@@ -284,6 +326,7 @@ def count_scons_nodes(nodes):
|
|||||||
|
|
||||||
progress_interval = 5
|
progress_interval = 5
|
||||||
progress_count = 0
|
progress_count = 0
|
||||||
|
build_product_nodes = set()
|
||||||
progress_total = max(1, count_scons_nodes(env.arg2nodes(BUILD_TARGETS or [Dir('.')], env.fs.Entry)))
|
progress_total = max(1, count_scons_nodes(env.arg2nodes(BUILD_TARGETS or [Dir('.')], env.fs.Entry)))
|
||||||
|
|
||||||
def progress_function(node):
|
def progress_function(node):
|
||||||
@@ -299,3 +342,11 @@ def progress_function(node):
|
|||||||
|
|
||||||
Progress(progress_function, interval=progress_interval)
|
Progress(progress_function, interval=progress_interval)
|
||||||
AddPostAction(BUILD_TARGETS or [Dir('.')], prune_cache_dir)
|
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,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,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,54 +0,0 @@
|
|||||||
import re
|
|
||||||
import sys
|
|
||||||
import pytest
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
|
|
||||||
def _to_safe_name(s):
|
|
||||||
return re.sub(r"[^a-zA-Z0-9_]+", "_", str(s)).strip("_")
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
|
||||||
# append sanitized string param values so pytest -k can filter by them
|
|
||||||
suffix = "_".join(filter(None, (_to_safe_name(v) for v in params.values() if isinstance(v, str))))
|
|
||||||
name = f"{cls.__name__}_{i}" + (f"_{suffix}" if suffix else "")
|
|
||||||
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, block=True)
|
|
||||||
elif len(sys.argv) == 2:
|
|
||||||
print(f"GET: {key} = {params.get(key)}")
|
|
||||||
@@ -1,192 +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, bool block = False):
|
|
||||||
"""
|
|
||||||
Warning: block=True blocks until the param is written to disk!
|
|
||||||
In very rare cases this can take over a second, and your code will hang.
|
|
||||||
Use block=False 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:
|
|
||||||
if block:
|
|
||||||
self.p.put(k, dat_bytes)
|
|
||||||
else:
|
|
||||||
self.p.putNonBlocking(k, dat_bytes)
|
|
||||||
|
|
||||||
def put_bool(self, key, bool val, bool block = False):
|
|
||||||
cdef string k = self.check_key(key)
|
|
||||||
with nogil:
|
|
||||||
if block:
|
|
||||||
self.p.putBool(k, val)
|
|
||||||
else:
|
|
||||||
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 "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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-93
@@ -1,93 +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
|
|
||||||
|
|
||||||
# these are heavy CI-only tests, invoked explicitly in .github/workflows/tests.yaml
|
|
||||||
collect_ignore = [
|
|
||||||
"selfdrive/test/process_replay/test_processes.py",
|
|
||||||
"selfdrive/test/process_replay/test_regen.py",
|
|
||||||
# tinygrad JIT has process-global state. Other test files import modeld → tinygrad,
|
|
||||||
# which corrupts JIT captures for test_warp.py in the same process. Run separately in CI.
|
|
||||||
"sunnypilot/modeld_v2/tests/test_warp.py",
|
|
||||||
]
|
|
||||||
collect_ignore_glob = [
|
|
||||||
"selfdrive/debug/*.py",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@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))
|
|
||||||
@@ -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.
|
||||||
+21
-17
@@ -1,10 +1,10 @@
|
|||||||
<!--- 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.
|
||||||
|
|
||||||
# 341 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|
|
||||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
@@ -34,6 +34,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|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>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)|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>|||
|
||||||
@@ -78,11 +79,11 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|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 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,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 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,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">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,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 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,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-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,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 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,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 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,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 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>||
|
||||||
@@ -158,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>|||
|
||||||
@@ -187,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>||
|
||||||
@@ -230,31 +231,32 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|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>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>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>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>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>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>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)|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>|||
|
|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>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>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>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>|||
|
|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>7</sup>](#footnotes)|openpilot available[<sup>1,8</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>7</sup>](#footnotes)|openpilot available[<sup>1,8</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>7</sup>](#footnotes)|openpilot available[<sup>1,8</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>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 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>7</sup>](#footnotes)|openpilot available[<sup>1,8</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>7</sup>](#footnotes)|openpilot available[<sup>1,8</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>7</sup>](#footnotes)|openpilot available[<sup>1,8</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>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 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>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|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>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 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>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 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>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|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>7</sup>](#footnotes)|openpilot available[<sup>1,8</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>7</sup>](#footnotes)|openpilot available[<sup>1,8</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>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|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>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|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>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>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>|||
|
||||||
@@ -333,6 +335,8 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|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>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>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>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>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>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>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>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[<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>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>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>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|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|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>|||
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
+2
-2
@@ -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 LinksTreeprocessor
|
|
||||||
|
|
||||||
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, LinksTreeprocessor):
|
|
||||||
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,84 @@
|
|||||||
|
# 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 4 uses
|
||||||
|
[full geometric path prediction](ford_model_action_full_prediction.md) while retaining
|
||||||
|
[excess-yaw offset damping](ford_model_action_damping.md). 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-v4` 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_full_prediction_validation.json`;
|
||||||
|
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,83 @@
|
|||||||
|
# Experimental Ford full path prediction, v4
|
||||||
|
|
||||||
|
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,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*
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ scons
|
|||||||
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="18.4"
|
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: 9beb84af67...e7396e76da
+1
-1
Submodule opendbc_repo updated: b9712d20ef...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 {
|
||||||
@@ -414,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;
|
||||||
@@ -684,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,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;
|
||||||
@@ -771,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;
|
||||||
@@ -798,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 {
|
||||||
@@ -818,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;
|
||||||
}
|
}
|
||||||
@@ -867,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;
|
||||||
@@ -906,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;
|
||||||
@@ -927,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;
|
||||||
|
|
||||||
@@ -973,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;
|
||||||
@@ -989,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;
|
||||||
@@ -999,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;
|
||||||
|
|
||||||
@@ -1120,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;
|
||||||
@@ -1258,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;
|
||||||
@@ -1313,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;
|
||||||
@@ -2108,8 +2188,11 @@ struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 {
|
|||||||
|
|
||||||
struct DriverMonitoringState {
|
struct DriverMonitoringState {
|
||||||
lockout @0 :Bool;
|
lockout @0 :Bool;
|
||||||
alertCountLockoutPercent @1 :Int8;
|
lockoutCount @15 :Int8;
|
||||||
alertTimeLockoutPercent @2 :Int8;
|
lockoutMinutesRemaining @11 :Int8;
|
||||||
|
alert3Count @12 :Int8;
|
||||||
|
noResponseCount @13 :Int8;
|
||||||
|
noResponseForceDecel @14 :Bool;
|
||||||
|
|
||||||
alwaysOn @3 :Bool;
|
alwaysOn @3 :Bool;
|
||||||
alwaysOnLockout @4 :Bool;
|
alwaysOnLockout @4 :Bool;
|
||||||
@@ -2173,6 +2256,11 @@ struct DriverMonitoringState {
|
|||||||
calibratedPercent @0 :Int8;
|
calibratedPercent @0 :Int8;
|
||||||
offset @1 :Float32;
|
offset @1 :Float32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deprecated :group {
|
||||||
|
alertCountLockoutPercent @1 :Int8;
|
||||||
|
alertTimeLockoutPercent @2 :Int8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Boot {
|
struct Boot {
|
||||||
@@ -2187,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;
|
||||||
@@ -2221,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;
|
||||||
@@ -2238,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;
|
||||||
@@ -2247,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;
|
||||||
@@ -2423,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;
|
||||||
@@ -2460,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;
|
||||||
@@ -2473,42 +2557,43 @@ struct Event {
|
|||||||
qcomGnss @31 :QcomGnss;
|
qcomGnss @31 :QcomGnss;
|
||||||
gpsLocationExternal @48 :GpsLocationData;
|
gpsLocationExternal @48 :GpsLocationData;
|
||||||
gpsLocation @21 :GpsLocationData;
|
gpsLocation @21 :GpsLocationData;
|
||||||
liveParameters @61 :LiveParametersData;
|
vehicleParameters @61 :VehicleParameters;
|
||||||
liveTorqueParameters @94 :LiveTorqueParametersData;
|
lateralTorqueParameters @94 :LateralTorqueParameters;
|
||||||
liveDelay @146 : LiveDelayData;
|
lateralDelay @146 : LateralDelay;
|
||||||
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 @151 :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;
|
||||||
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;
|
||||||
|
|
||||||
@@ -2521,21 +2606,20 @@ struct Event {
|
|||||||
# 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 ***********
|
||||||
|
|
||||||
@@ -2558,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;
|
||||||
@@ -2571,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;
|
||||||
@@ -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)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "cereal/messaging/msgq_to_zmq.h"
|
#include "openpilot/cereal/messaging/msgq_to_zmq.h"
|
||||||
#include "cereal/services.h"
|
#include "openpilot/cereal/services.h"
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
|
|
||||||
ExitHandler do_exit;
|
ExitHandler do_exit;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "cereal/messaging/bridge_zmq.h"
|
#include "openpilot/cereal/messaging/bridge_zmq.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include <capnp/serialize.h>
|
#include <capnp/serialize.h>
|
||||||
|
|
||||||
#include "cereal/gen/cpp/log.capnp.h"
|
#include "openpilot/cereal/gen/cpp/log.capnp.h"
|
||||||
#include "common/timing.h"
|
#include "common/timing.h"
|
||||||
#include "msgq/ipc.h"
|
#include "msgq/ipc.h"
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "cereal/messaging/msgq_to_zmq.h"
|
#include "openpilot/cereal/messaging/msgq_to_zmq.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "cereal/services.h"
|
#include "openpilot/cereal/services.h"
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
|
|
||||||
extern ExitHandler do_exit;
|
extern ExitHandler do_exit;
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "msgq/impl_msgq.h"
|
#include "msgq/impl_msgq.h"
|
||||||
#include "cereal/messaging/bridge_zmq.h"
|
#include "openpilot/cereal/messaging/bridge_zmq.h"
|
||||||
|
|
||||||
class MsgqToZmq {
|
class MsgqToZmq {
|
||||||
public:
|
public:
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "cereal/services.h"
|
#include "openpilot/cereal/services.h"
|
||||||
#include "cereal/messaging/messaging.h"
|
#include "openpilot/cereal/messaging/messaging.h"
|
||||||
|
|
||||||
const bool SIMULATION = (getenv("SIMULATION") != nullptr) && (std::string(getenv("SIMULATION")) == "1");
|
const bool SIMULATION = (getenv("SIMULATION") != nullptr) && (std::string(getenv("SIMULATION")) == "1");
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user