mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-26 02:33:46 +08:00
Compare commits
633 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b384ea101 | |||
| 8369e11417 | |||
| 26bccbdcc8 | |||
| db336329c5 | |||
| 7a7f343978 | |||
| 49d82d6ac1 | |||
| 0aa4867be4 | |||
| f370bf5ba6 | |||
| d6074e554d | |||
| b205dd6954 | |||
| 9e7fb4680d | |||
| 40982f18ab | |||
| 4d47e1243d | |||
| 9ff1fb48de | |||
| 51aa86da12 | |||
| 6e00ef601d | |||
| db7ea2a599 | |||
| 682dc5ddad | |||
| 3b62a21fa7 | |||
| ffe062c774 | |||
| 4fd2b0e730 | |||
| 2d659de09c | |||
| 35bd9d9fb3 | |||
| f878928731 | |||
| 771a8b7a55 | |||
| fcd5a66252 | |||
| 23bbf52578 | |||
| abfe469d95 | |||
| a39e33b9d0 | |||
| c9c66ce21a | |||
| ddee718536 | |||
| e0ab166ed3 | |||
| 7e908db510 | |||
| eb0dff8438 | |||
| d1aff96611 | |||
| 5bfae8d806 | |||
| d692982601 | |||
| cc6358d983 | |||
| a5c3340c8d | |||
| fa98a83b06 | |||
| d1ad7f3fe1 | |||
| 72427d8cc4 | |||
| 9397e18742 | |||
| 0e793367de | |||
| a3690e4034 | |||
| 29f108de10 | |||
| a091133e80 | |||
| 42e0d13389 | |||
| 98c8888cf7 | |||
| c534346d70 | |||
| 4bf377cfef | |||
| 332cb82886 | |||
| ab3557e107 | |||
| b3e67e28e2 | |||
| 6b8726ef75 | |||
| 2924e495ce | |||
| 1d7d11f5dd | |||
| a47feb0817 | |||
| dc61915529 | |||
| d25c617595 | |||
| b5807aab22 | |||
| 7827a7cdba | |||
| 21f4245444 | |||
| 69df34bf0f | |||
| 305037fc1a | |||
| 4ea182a1b1 | |||
| 7a69c82d65 | |||
| 60d3364233 | |||
| 7119634b5b | |||
| 1e1de64a1e | |||
| 67ab1c5b2e | |||
| 460a8e3189 | |||
| 354d14515f | |||
| 5c359f93ac | |||
| f60fe79999 | |||
| 21572ce67b | |||
| 8f62890de4 | |||
| adf25bcb5e | |||
| 7c25bb6950 | |||
| 8da8b6135c | |||
| 77e450db57 | |||
| 25d18afad5 | |||
| b0b7a768f0 | |||
| af301ad7b5 | |||
| ab415aa5fa | |||
| 9504037aa7 | |||
| 0440535f64 | |||
| a2ae18d1db | |||
| 8f78b61a47 | |||
| 69fb8b312f | |||
| e0d7a55eab | |||
| a4b4513149 | |||
| c025b96e8a | |||
| 683b6151ce | |||
| 261fb00e11 | |||
| df875819f5 | |||
| bc7b9b38ae | |||
| e3b21173a5 | |||
| 391eb0b74b | |||
| 7be62e781d | |||
| 02c4ade948 | |||
| 84ef9d50b7 | |||
| 812f93a5a5 | |||
| 1e1065874b | |||
| d4eb5a6eaf | |||
| 3f4695de53 | |||
| d20896ced5 | |||
| a649732568 | |||
| 3c6d8846ac | |||
| 29642139f0 | |||
| d7f0b402a8 | |||
| 67c4121f62 | |||
| b4f4b92ecf | |||
| 22023ebd58 | |||
| 97a289e6a9 | |||
| 047924cb35 | |||
| 38e7d54d5d | |||
| fe0ed57f45 | |||
| affe1c3d2c | |||
| 98d2907a35 | |||
| 2a77b02bd8 | |||
| d2a92fcbe3 | |||
| 7a2addf782 | |||
| 23dfde1ce7 | |||
| d953b6a7aa | |||
| 26dd7e27c8 | |||
| 70d17cd69b | |||
| 069e337bea | |||
| 6b62dd2308 | |||
| 4ff7da61e0 | |||
| 686d4bbdc2 | |||
| 08d86aa7d7 | |||
| cf80f7a28b | |||
| 4f4a90117b | |||
| 3a04a69cbe | |||
| 38faf7f8a4 | |||
| f2de9784f8 | |||
| f66527d0b1 | |||
| d17745fb5e | |||
| 1bc51d6b47 | |||
| 6edc36181a | |||
| a0b41e6cf9 | |||
| 47e87baa60 | |||
| 5cfc0be5cb | |||
| 41d0a8957a | |||
| 58f376002e | |||
| b0563a5968 | |||
| 61229779e4 | |||
| d2b3ed0ec7 | |||
| af4f9f1f31 | |||
| 876256a268 | |||
| 4dc51cfea6 | |||
| f274a8e3b0 | |||
| df46400d6d | |||
| da42760e55 | |||
| f6531a9d08 | |||
| c73464f67f | |||
| 4d5d709bea | |||
| d5f9caa82d | |||
| cb0ee932c4 | |||
| 6a61788682 | |||
| 9955b3c806 | |||
| ead59a5a61 | |||
| a1ec1c089e | |||
| a1f069c6d8 | |||
| 095ef5f9f6 | |||
| fd71fe698c | |||
| d5242c5b82 | |||
| 1f1893a170 | |||
| 5808958fb2 | |||
| e90c41c576 | |||
| f448d357e0 | |||
| aa1b61eb8e | |||
| 30bb73d527 | |||
| 98cd6147c3 | |||
| d22636b194 | |||
| 4808de10d6 | |||
| a440425ef8 | |||
| 61bbb52172 | |||
| cf5c4aeacb | |||
| 94053536b4 | |||
| b539ae3e2d | |||
| cd98235644 | |||
| 1ff59baea7 | |||
| c8b4633cd1 | |||
| 813d5b9982 | |||
| 53413fa019 | |||
| 63da1abe2c | |||
| be28530ee4 | |||
| 13bdfcdd95 | |||
| 9675794969 | |||
| 151a504507 | |||
| 03e764bcb3 | |||
| 16eb74250c | |||
| 4a48ef8dbc | |||
| 76ab558ca6 | |||
| 60a20537c5 | |||
| be020bdedb | |||
| 1e77f2482c | |||
| d8da18ed54 | |||
| 6abd80f116 | |||
| f4b258a082 | |||
| 8a9ed94f5f | |||
| ae44a57565 | |||
| 78e4e4ea23 | |||
| 1c6164e11c | |||
| b93f77ea0e | |||
| dba8e01e54 | |||
| 983120bfed | |||
| 27803e787b | |||
| 24e096d9f0 | |||
| 6651d50c81 | |||
| f64c4df8c8 | |||
| 1883bd6135 | |||
| 1ba6b0004d | |||
| 68b86c7ca8 | |||
| 36881b6410 | |||
| fe7e5cf5df | |||
| d8b1588937 | |||
| a2f4d6b5ad | |||
| 16d0c4a3e7 | |||
| dd34ccfe28 | |||
| 7ccae06bce | |||
| 59bd6b8837 | |||
| 53c4b90ffc | |||
| f91df07d3f | |||
| 0829b0a767 | |||
| 9a143c5ab2 | |||
| 1e8098c140 | |||
| b5a88f5700 | |||
| c0a3e48d94 | |||
| 65e1342e41 | |||
| 7ada2abca0 | |||
| 9278fad15c | |||
| 0aa41e348e | |||
| 64a6e9776c | |||
| 56b2945de4 | |||
| b686ca87d3 | |||
| 1b3b260b4d | |||
| 25d43fe15e | |||
| cbc73e55a2 | |||
| 9b31da0985 | |||
| 3f9059fea8 | |||
| 99c81d291f | |||
| 530b637d27 | |||
| 52e55eb244 | |||
| ec36bc3a39 | |||
| 98797fb24e | |||
| 127998ca8b | |||
| 1da59216b0 | |||
| 184ba93833 | |||
| a321836788 | |||
| f5044670fa | |||
| 340e0f4a4c | |||
| d8b1e99d77 | |||
| 2eebe49940 | |||
| 9dae0bfac4 | |||
| 2f92d577f9 | |||
| 684e7b9f9e | |||
| 790732bea3 | |||
| 3c25760cc9 | |||
| 7834995df4 | |||
| 0a7fed9437 | |||
| a201322160 | |||
| d64025cca4 | |||
| 58d645cd18 | |||
| 76dfb135ca | |||
| 27ef9f2236 | |||
| 185375ed64 | |||
| 7978afabe5 | |||
| e89fd1236f | |||
| bdf6585650 | |||
| 4662cfc8d7 | |||
| dd32343342 | |||
| 1efa3f0eba | |||
| ca2f30989d | |||
| 03f09105c6 | |||
| 5a9d89ed42 | |||
| f74a201edc | |||
| 342bb13bff | |||
| be5c2aef3a | |||
| 6aa1048d15 | |||
| ede869e34a | |||
| 2a0f066426 | |||
| 113d4c1b70 | |||
| 27e0adc434 | |||
| 4697568e67 | |||
| 21a24446db | |||
| 26cc816c1b | |||
| 6c1f516bb3 | |||
| 737f2ace69 | |||
| f0203614ee | |||
| 8b7dfb647f | |||
| 15fa6664c0 | |||
| 5c8aa7295d | |||
| 0207a97040 | |||
| 9a79df8a8a | |||
| fdb04d9f69 | |||
| 603f3f1a34 | |||
| 27f405bb6f | |||
| 26da755a1e | |||
| a25e2153a0 | |||
| 88246af4c7 | |||
| 03f13e614b | |||
| 23512ba932 | |||
| ee35b905d7 | |||
| 2c9d5afd75 | |||
| 860623f157 | |||
| 73c1df714d | |||
| b967da5fc1 | |||
| ea35a0043c | |||
| f364a3ee8f | |||
| d21c6591c1 | |||
| ece9cf9480 | |||
| d5f8643e7c | |||
| 9cf542d7f7 | |||
| 9eb1666a52 | |||
| b78ed77f1d | |||
| d84e64b315 | |||
| 0fded2c08b | |||
| 67e7f6dc3d | |||
| 4d2ff103d6 | |||
| 58e716ff48 | |||
| f41cb3b4ec | |||
| b5d8230c3d | |||
| 853bcda352 | |||
| 4610a6e4d4 | |||
| 067f1bfe9e | |||
| e3c934bcc1 | |||
| 210db686bb | |||
| 30ec405890 | |||
| 573a6915fc | |||
| 0002cc7c3a | |||
| dc331df1fc | |||
| 5c4ae7a2fc | |||
| 29bea62ac1 | |||
| 024870d16a | |||
| 68e56c8017 | |||
| 9d76070ab7 | |||
| 6ec0d23b7a | |||
| c9dd7fe0e6 | |||
| e32463ee96 | |||
| 53c6ca6589 | |||
| e4ed48928b | |||
| b94d598c2d | |||
| 860a48765d | |||
| efa97f23a5 | |||
| 92c596544d | |||
| 2f0b0fb15e | |||
| 3e78efacca | |||
| c10a755516 | |||
| 3b744f4fba | |||
| b4dd562c43 | |||
| 892e14aa92 | |||
| 8b7c726bea | |||
| db24b1e838 | |||
| 7db592d3b8 | |||
| b7c029c92c | |||
| fdbf1c5938 | |||
| 00429e6bbb | |||
| 2f8034b2ec | |||
| 5501541aa2 | |||
| 3d05cca678 | |||
| 195139999b | |||
| c9fa92b377 | |||
| dfa7757c40 | |||
| 8f3539a27b | |||
| d0715b6562 | |||
| e8ae37e36e | |||
| a2c76acf3b | |||
| a51a60b598 | |||
| 7ed5c6554d | |||
| 5641fc986d | |||
| c499aa549c | |||
| 0a99fe3baa | |||
| 8cc32df779 | |||
| 8291f35701 | |||
| 0f885c87a5 | |||
| a422246dc3 | |||
| 906b55a5bd | |||
| e5b2ec4f01 | |||
| 5c0935d253 | |||
| 97f1ee4a0d | |||
| 2d450bc3a8 | |||
| 96c923875b | |||
| 14b7eadf63 | |||
| 402faabd42 | |||
| de79a07745 | |||
| 4b2c137489 | |||
| b3654cbcc9 | |||
| d2b8c4f0cb | |||
| 50d9c446cf | |||
| 6eb1247426 | |||
| faea9a42c4 | |||
| 86813e6d37 | |||
| 012727ef60 | |||
| 9653f9d6a6 | |||
| 285c52eb69 | |||
| 75db5e854e | |||
| 8f6e36f426 | |||
| c4bba32347 | |||
| f0c5ca7227 | |||
| 8970cc8d70 | |||
| 54f3c2b373 | |||
| 2c4e1fd4fa | |||
| 401c4026ac | |||
| 22f0a89cf8 | |||
| b942ab58e1 | |||
| c29b311583 | |||
| ed72759a48 | |||
| 0129a8a4ff | |||
| 6839a11685 | |||
| db96b4b912 | |||
| c812915765 | |||
| b70d75d1d0 | |||
| 6a02547b42 | |||
| 33a75c3506 | |||
| 2cfdc67610 | |||
| 6f3d10a4c4 | |||
| fe9ce203ee | |||
| 589b6187a1 | |||
| 6b1efbf185 | |||
| 76494ab0be | |||
| b63d51d3ef | |||
| e07853bb5e | |||
| 866711ef2e | |||
| ef8e6fb39f | |||
| 4568504c41 | |||
| 18c6482545 | |||
| b0c83bb9a6 | |||
| ce57ce4898 | |||
| 7d9f8c55e0 | |||
| 04c26981f0 | |||
| 7f6ea03066 | |||
| de33bc4645 | |||
| 87fcd4537f | |||
| e9ad7793f0 | |||
| 4bdf3b95f3 | |||
| 7fca1ba2f4 | |||
| 504d00353d | |||
| ce67c75f1f | |||
| 95509a58cd | |||
| 548608490a | |||
| 21b3f5321a | |||
| 7953078a31 | |||
| b75d606c91 | |||
| ca3e1ce9b0 | |||
| 58744df1c5 | |||
| 1181a00fe9 | |||
| 71f547219c | |||
| 35b08e1e3d | |||
| fd2bead226 | |||
| ae5cb7a0da | |||
| e425c1cf59 | |||
| e3ab9d6460 | |||
| c210011c84 | |||
| d5b884f824 | |||
| 2751d87d01 | |||
| 8849aa02a3 | |||
| f49e9f4f09 | |||
| f0a6db351e | |||
| 53b177b3c1 | |||
| c6df34f55b | |||
| ea6c19638c | |||
| 28e3543ec4 | |||
| 752df1681a | |||
| 7865525451 | |||
| dcacbf606a | |||
| 4a5019d925 | |||
| 1b7b3b4e66 | |||
| 37285038d3 | |||
| c32fb85dbd | |||
| 91a731f2ae | |||
| bb2587664a | |||
| e41761eb1b | |||
| 9a9ff839a9 | |||
| f66eb5a0a3 | |||
| 4f7336f0e4 | |||
| e6e6ad2e1f | |||
| 94a27e351f | |||
| 28b8043c5b | |||
| 78df63a6af | |||
| a69dfe7b20 | |||
| d0c9cd28d1 | |||
| 9e1cabde36 | |||
| c7cd8b4459 | |||
| 3d628a6fe2 | |||
| 51fad4a6c6 | |||
| 1a8883fb63 | |||
| a70a821d28 | |||
| a8d110ad74 | |||
| 013351a2ee | |||
| ad3f0a348a | |||
| 9a411ebf32 | |||
| 4188b54c28 | |||
| 19010d3766 | |||
| 02968cda63 | |||
| 098e304118 | |||
| 650c45dcc5 | |||
| 93f55f3ccf | |||
| 569db3c1d2 | |||
| 26d97d3374 | |||
| 3acfa1b39b | |||
| bbc67f8506 | |||
| 8343b56870 | |||
| 008d900f81 | |||
| 32e5d6cd15 | |||
| 28c0797d30 | |||
| 7ae657880d | |||
| a701aa7292 | |||
| db67cffb4d | |||
| 433f934783 | |||
| 0dc6778548 | |||
| c86b52698b | |||
| e5e5aa7ded | |||
| 4474b9b371 | |||
| 26e966852c | |||
| 852166f5cc | |||
| 75ac92b90f | |||
| 96f8e5158e | |||
| b68f3f7781 | |||
| 36bef17224 | |||
| 715771bcff | |||
| 8e264baaa2 | |||
| 8ff147de6d | |||
| 71057c586f | |||
| fac22f4038 | |||
| 40094779d7 | |||
| 2552aa45c3 | |||
| c2972a80f9 | |||
| 791a440709 | |||
| 13145dd9a7 | |||
| e9c477dcf2 | |||
| 8eebd19884 | |||
| a54b35a4d6 | |||
| 8a6f7a5e78 | |||
| 6752959d4a | |||
| da52d065a4 | |||
| 255e45ca58 | |||
| b731b7cf12 | |||
| 5f014635e1 | |||
| ef3d8314d3 | |||
| 7ef3fd567f | |||
| b773e27ad1 | |||
| a77c0a1098 | |||
| 57b9ddf20e | |||
| c7b5fb9116 | |||
| ba97d0e838 | |||
| b8085e2c42 | |||
| c251b312d8 | |||
| 14fb17e22f | |||
| a34c87ab46 | |||
| 1ad9cc8c67 | |||
| 5627d0d7fd | |||
| 2cfdbefde8 | |||
| 7dabcdace8 | |||
| 187a70f760 | |||
| 48303589e9 | |||
| 8385b27cad | |||
| abd75aedd7 | |||
| daf54ad54d | |||
| 94fe677f91 | |||
| 3de85098e5 | |||
| 5524dc8773 | |||
| 19dd5f3e32 | |||
| 99cb610b12 | |||
| 9d3963559a | |||
| 1b8c44b506 | |||
| 6f46f988d9 | |||
| 68485aa4e4 | |||
| 1581fdc198 | |||
| 317ae0fb37 | |||
| 5bf4196aed | |||
| 38aa03e0f7 | |||
| 9a9dc3ab23 | |||
| e4aa959e2c | |||
| 2aa9a56f40 | |||
| 721ed4ec0e | |||
| 70be4ceab1 | |||
| 5cf91d0496 | |||
| 6fee0bdb2d | |||
| e40c161125 | |||
| 97eb55cc55 | |||
| 65134be0d1 | |||
| dbf71a23aa | |||
| 7fec3db1d6 | |||
| 26b573c1d0 | |||
| 5ec1e7307e | |||
| 8bc36b7f21 | |||
| 3b909eb693 | |||
| 57e39c4472 | |||
| ff7672339c | |||
| 6e824a2c22 | |||
| 32fa49e093 | |||
| 2250eac58f | |||
| 97be6b3a0e | |||
| 942655c947 | |||
| 615db3f7fd | |||
| 0bb75c5389 | |||
| 7b5ee81d2d | |||
| 7fe46f1e1d | |||
| 50c0d1c9da | |||
| 6dbf544d06 | |||
| 41e3a0f699 | |||
| c5d8aec28b | |||
| 4653a9aef0 | |||
| ab3492bb90 | |||
| ed7cbb3866 | |||
| a30626cfe3 | |||
| d2c087b3e2 | |||
| 0a747f991d | |||
| f5a1e86d85 | |||
| 693bcb0f83 | |||
| 0d0daed86e | |||
| 95a349abcc | |||
| c6ba5dc539 | |||
| 6c3afeec0f | |||
| 29c58b4588 | |||
| ecc565aa3f | |||
| db61810f98 | |||
| 48f203ad5b | |||
| 6ab4ac2dfb | |||
| 9cb3c7b6e6 | |||
| adaa4ed350 | |||
| a64b9aa9b8 | |||
| 0138eca61d | |||
| 139a40de29 | |||
| 17d9becd3c | |||
| 449b482cc3 | |||
| a7e099c946 | |||
| 610462be5a | |||
| 207d32668f | |||
| e94a30bec0 |
@@ -1,3 +0,0 @@
|
||||
((c++-mode (flycheck-gcc-language-standard . "c++11")
|
||||
(flycheck-clang-language-standard . "c++11")
|
||||
))
|
||||
@@ -1,24 +0,0 @@
|
||||
.git
|
||||
.DS_Store
|
||||
notebooks
|
||||
phone
|
||||
massivemap
|
||||
neos
|
||||
installer
|
||||
chffr/app2
|
||||
chffr/backend/env
|
||||
selfdrive/nav
|
||||
selfdrive/baseui
|
||||
chffr/lib/vidindex/vidindex
|
||||
chffr/lib/index_log/index_log
|
||||
selfdrive/test/simulator2
|
||||
**/cache_data
|
||||
xx/chffr/lib/index_log/index_log
|
||||
xx/chffr/lib/vidindex/vidindex
|
||||
xx/plus
|
||||
xx/community
|
||||
xx/projects
|
||||
!xx/projects/eon_testing_master
|
||||
!xx/projects/map3d
|
||||
xx/ops
|
||||
xx/junk
|
||||
@@ -1,11 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.py, *.pyx, *pxd}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -1,27 +0,0 @@
|
||||
*.keras filter=lfs diff=lfs merge=lfs -text
|
||||
*.dlc filter=lfs diff=lfs merge=lfs -text
|
||||
*.pb filter=lfs diff=lfs merge=lfs -text
|
||||
*.bin filter=lfs diff=lfs merge=lfs -text
|
||||
*.apk filter=lfs diff=lfs merge=lfs -text
|
||||
*.ipynb filter=nbstripout -diff
|
||||
external/ffmpeg/bin/ffmpeg_cuda filter=lfs diff=lfs merge=lfs -text
|
||||
models/segnet.keras filter=lfs diff=lfs merge=lfs -text
|
||||
external/zmq/lib/libzmq.a filter=lfs diff=lfs merge=lfs -text
|
||||
external/zmq/lib/libczmq.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/x64/lib/libacado_toolkit.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/x64/lib/libacado_toolkit_s.so.1.2.2beta filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/x64/lib/libacado_casadi.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/x64/lib/libacado_csparse.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/x64/lib/libacado_qpoases.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/aarch64/lib/libacado_toolkit.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/aarch64/lib/libacado_toolkit_s.so.1.2.2beta filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/aarch64/lib/libacado_casadi.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/aarch64/lib/libacado_csparse.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/aarch64/lib/libacado_qpoases.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/fastcv/aarch64/libfastcvopt.so filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/fastcv/aarch64/libfastcvadsp_stub.so filter=lfs diff=lfs merge=lfs -text
|
||||
external/mac/MP4Box filter=lfs diff=lfs merge=lfs -text
|
||||
models/segnet2.keras filter=lfs diff=lfs merge=lfs -text
|
||||
external/opencl/*.deb filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/zmq/aarch64-linux/lib/libzmq.a filter=lfs diff=lfs merge=lfs -text
|
||||
external/azcopy/azcopy filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve openpilot
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**How to reproduce or log data**
|
||||
Steps to reproduce the behavior, or a explorer/cabana link to the exact drive and timestamp of when the bug occurred.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Device/Version information (please complete the following information):**
|
||||
- Device: [e.g. EON/EON Gold]
|
||||
- Version: [e.g. 0.6.4], or commit hash when on devel
|
||||
- Car make/model [e.g. Toyota Prius 2016]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -1,125 +0,0 @@
|
||||
name: Openpilot Tests
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
RUN: docker run --shm-size 1G --rm tmppilot /bin/sh -c
|
||||
LOAD: docker load -i tmppilot.tar.gz/tmppilot.tar.gz
|
||||
UNIT_TEST: cd /tmp/openpilot && python -m unittest discover
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodules
|
||||
run: |
|
||||
git submodule update --init
|
||||
- run: |
|
||||
docker pull $(grep -ioP '(?<=^from)\s+\S+' Dockerfile.openpilot) || true
|
||||
docker pull docker.io/commaai/openpilot:latest || true
|
||||
|
||||
docker build --cache-from docker.io/commaai/openpilot:latest -t tmppilot -f Dockerfile.openpilot .
|
||||
docker save tmppilot:latest | gzip > tmppilot.tar.gz
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
path: tmppilot.tar.gz
|
||||
|
||||
linter:
|
||||
name: linter
|
||||
runs-on: ubuntu-16.04
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
- name: Load image
|
||||
run: $LOAD
|
||||
- name: flake8
|
||||
run: $RUN "cd /tmp/openpilot/ && ./flake8_openpilot.sh"
|
||||
- name: pylint
|
||||
run: $RUN "cd /tmp/openpilot/ && ./pylint_openpilot.sh"
|
||||
|
||||
unit_tests:
|
||||
name: unit tests
|
||||
runs-on: ubuntu-16.04
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
- name: Load image
|
||||
run: $LOAD
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
$RUN cd /tmp/openpilot/selfdrive/test/test_fingerprints.py
|
||||
$RUN "$UNIT_TEST common"
|
||||
$RUN "$UNIT_TEST opendbc/can"
|
||||
$RUN "$UNIT_TEST selfdrive/boardd"
|
||||
$RUN "$UNIT_TEST selfdrive/controls"
|
||||
$RUN "$UNIT_TEST selfdrive/loggerd"
|
||||
$RUN "$UNIT_TEST selfdrive/car"
|
||||
$RUN "$UNIT_TEST selfdrive/locationd"
|
||||
$RUN "$UNIT_TEST selfdrive/athena"
|
||||
|
||||
process_replay:
|
||||
name: process replay
|
||||
runs-on: ubuntu-16.04
|
||||
needs: build
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
- name: Load image
|
||||
run: $LOAD
|
||||
- name: Run replay
|
||||
run: |
|
||||
CONTAINER_NAME="tmppilot_${GITHUB_SHA}"
|
||||
docker run --shm-size 1G --name ${CONTAINER_NAME} tmppilot /bin/sh -c "cd /tmp/openpilot/selfdrive/test/process_replay && CI=1 ./test_processes.py"
|
||||
docker cp $CONTAINER_NAME:/tmp/openpilot/selfdrive/test/process_replay/diff.txt diff.txt
|
||||
docker rm $CONTAINER_NAME
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: always()
|
||||
with:
|
||||
name: process_replay_diff.txt
|
||||
path: diff.txt
|
||||
|
||||
test_longitudinal:
|
||||
name: longitudinal
|
||||
runs-on: ubuntu-16.04
|
||||
needs: build
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
- name: Load image
|
||||
run: $LOAD
|
||||
- name: Test longitudinal
|
||||
run: |
|
||||
CONTAINER_NAME="tmppilot_${GITHUB_SHA}"
|
||||
docker run --shm-size 1G --name ${CONTAINER_NAME} tmppilot /bin/sh -c "cd /tmp/openpilot/selfdrive/test/longitudinal_maneuvers && OPTEST=1 ./test_longitudinal.py"
|
||||
mkdir out
|
||||
docker cp $CONTAINER_NAME:/tmp/openpilot/selfdrive/test/longitudinal_maneuvers/out/longitudinal/ out/
|
||||
docker rm $CONTAINER_NAME
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: always()
|
||||
with:
|
||||
name: longitudinal
|
||||
path: out
|
||||
|
||||
test_car_models:
|
||||
name: test car models
|
||||
runs-on: ubuntu-16.04
|
||||
needs: build
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
- name: Load image
|
||||
run: $LOAD
|
||||
- name: Test car models
|
||||
run: $RUN "mkdir -p /data/params && cd /tmp/openpilot/selfdrive/test/ && ./test_car_models.py"
|
||||
@@ -20,7 +20,9 @@ a.out
|
||||
.*.un~
|
||||
*.tmp
|
||||
*.o
|
||||
*.o-*
|
||||
*.os
|
||||
*.os-*
|
||||
*.so
|
||||
*.a
|
||||
*.clb
|
||||
@@ -52,8 +54,15 @@ openpilot
|
||||
notebooks
|
||||
xx
|
||||
panda_jungle
|
||||
apks
|
||||
openpilot-apks
|
||||
|
||||
.coverage*
|
||||
coverage.xml
|
||||
htmlcov
|
||||
pandaextra
|
||||
|
||||
.mypy_cache/
|
||||
flycheck_*
|
||||
|
||||
cppcheck_report.txt
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
[submodule "panda"]
|
||||
path = panda
|
||||
url = ../../commaai/panda.git
|
||||
[submodule "opendbc"]
|
||||
path = opendbc
|
||||
url = ../../commaai/opendbc.git
|
||||
[submodule "laika_repo"]
|
||||
path = laika_repo
|
||||
url = ../../commaai/laika.git
|
||||
[submodule "apks"]
|
||||
path = apks
|
||||
url = ../../commaai/openpilot-apks.git
|
||||
[submodule "cereal"]
|
||||
path = cereal
|
||||
url = ../../commaai/cereal.git
|
||||
|
||||
@@ -1,585 +0,0 @@
|
||||
[MASTER]
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code
|
||||
extension-pkg-whitelist=scipy
|
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not
|
||||
# paths.
|
||||
ignore=CVS
|
||||
|
||||
# Add files or directories matching the regex patterns to the blacklist. The
|
||||
# regex matches against base names, not paths.
|
||||
ignore-patterns=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Use multiple processes to speed up Pylint.
|
||||
jobs=4
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
||||
# user-friendly hints instead of false-positive error messages
|
||||
suggestion-mode=yes
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||
confidence=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=print-statement,
|
||||
parameter-unpacking,
|
||||
unpacking-in-except,
|
||||
old-raise-syntax,
|
||||
backtick,
|
||||
long-suffix,
|
||||
old-ne-operator,
|
||||
old-octal-literal,
|
||||
import-star-module-level,
|
||||
non-ascii-bytes-literal,
|
||||
raw-checker-failed,
|
||||
bad-inline-option,
|
||||
locally-disabled,
|
||||
locally-enabled,
|
||||
file-ignored,
|
||||
suppressed-message,
|
||||
useless-suppression,
|
||||
deprecated-pragma,
|
||||
apply-builtin,
|
||||
basestring-builtin,
|
||||
buffer-builtin,
|
||||
cmp-builtin,
|
||||
coerce-builtin,
|
||||
execfile-builtin,
|
||||
file-builtin,
|
||||
long-builtin,
|
||||
raw_input-builtin,
|
||||
reduce-builtin,
|
||||
standarderror-builtin,
|
||||
unicode-builtin,
|
||||
xrange-builtin,
|
||||
coerce-method,
|
||||
delslice-method,
|
||||
getslice-method,
|
||||
setslice-method,
|
||||
no-absolute-import,
|
||||
old-division,
|
||||
dict-iter-method,
|
||||
dict-view-method,
|
||||
next-method-called,
|
||||
metaclass-assignment,
|
||||
indexing-exception,
|
||||
raising-string,
|
||||
reload-builtin,
|
||||
oct-method,
|
||||
hex-method,
|
||||
nonzero-method,
|
||||
cmp-method,
|
||||
input-builtin,
|
||||
round-builtin,
|
||||
intern-builtin,
|
||||
unichr-builtin,
|
||||
map-builtin-not-iterating,
|
||||
zip-builtin-not-iterating,
|
||||
range-builtin-not-iterating,
|
||||
filter-builtin-not-iterating,
|
||||
using-cmp-argument,
|
||||
eq-without-hash,
|
||||
div-method,
|
||||
idiv-method,
|
||||
rdiv-method,
|
||||
exception-message-attribute,
|
||||
invalid-str-codec,
|
||||
sys-max-int,
|
||||
bad-python3-import,
|
||||
deprecated-string-function,
|
||||
deprecated-str-translate-call,
|
||||
deprecated-itertools-function,
|
||||
deprecated-types-field,
|
||||
next-method-defined,
|
||||
dict-items-not-iterating,
|
||||
dict-keys-not-iterating,
|
||||
dict-values-not-iterating,
|
||||
bad-indentation,
|
||||
line-too-long,
|
||||
missing-docstring,
|
||||
multiple-statements,
|
||||
bad-continuation,
|
||||
invalid-name,
|
||||
too-many-arguments,
|
||||
too-many-locals,
|
||||
superfluous-parens,
|
||||
bad-whitespace,
|
||||
too-many-instance-attributes,
|
||||
wrong-import-position,
|
||||
ungrouped-imports,
|
||||
wrong-import-order,
|
||||
protected-access,
|
||||
trailing-whitespace,
|
||||
too-many-branches,
|
||||
too-few-public-methods,
|
||||
too-many-statements,
|
||||
trailing-newlines,
|
||||
attribute-defined-outside-init,
|
||||
too-many-return-statements,
|
||||
too-many-public-methods,
|
||||
unused-argument,
|
||||
old-style-class,
|
||||
no-init,
|
||||
len-as-condition,
|
||||
unneeded-not,
|
||||
no-self-use,
|
||||
multiple-imports,
|
||||
no-else-return,
|
||||
logging-not-lazy,
|
||||
fixme,
|
||||
redefined-outer-name,
|
||||
unused-variable,
|
||||
unsubscriptable-object,
|
||||
expression-not-assigned,
|
||||
too-many-boolean-expressions,
|
||||
consider-using-ternary,
|
||||
invalid-unary-operand-type,
|
||||
relative-import,
|
||||
deprecated-lambda
|
||||
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once). See also the "--disable" option for examples.
|
||||
enable=c-extension-no-member
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
#msg-template=
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, json
|
||||
# and msvs (visual studio).You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=no
|
||||
|
||||
# Activate the evaluation score.
|
||||
score=yes
|
||||
|
||||
|
||||
[REFACTORING]
|
||||
|
||||
# Maximum number of nested blocks for function / method body
|
||||
max-nested-blocks=5
|
||||
|
||||
# Complete name of functions that never returns. When checking for
|
||||
# inconsistent-return-statements if a never returning function is called then
|
||||
# it will be considered as an explicit return statement and no message will be
|
||||
# printed.
|
||||
never-returning-functions=optparse.Values,sys.exit
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format
|
||||
logging-modules=logging
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Limits count of emitted suggestions for spelling mistakes
|
||||
max-spelling-suggestions=4
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working
|
||||
# install python-enchant package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in
|
||||
# --spelling-private-dict-file option instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,
|
||||
XXX,
|
||||
TODO
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# List of decorators that produce context managers, such as
|
||||
# contextlib.contextmanager. Add to this list to register other decorators that
|
||||
# produce valid context managers.
|
||||
contextmanager-decorators=contextlib.contextmanager
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=capnp.* cereal.* pygame.* zmq.* setproctitle.* smbus2.* usb1.* serial.* cv2.*
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# This flag controls whether pylint should warn about no-member and similar
|
||||
# checks whenever an opaque object is returned when inferring. The inference
|
||||
# can return multiple potential results while evaluating a Python object, but
|
||||
# some branches might not be evaluated, which results in partial inference. In
|
||||
# that case, it might be useful to still emit no-member and other checks for
|
||||
# the rest of the inferred objects.
|
||||
ignore-on-opaque-inference=yes
|
||||
|
||||
# List of class names for which member attributes should not be checked (useful
|
||||
# for classes with dynamically set attributes). This supports the use of
|
||||
# qualified names.
|
||||
ignored-classes=optparse.Values,thread._local,_thread._local
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis. It
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=flask setproctitle usb1 flask.ext.socketio smbus2 usb1.*
|
||||
|
||||
# Show a hint with possible names when a member name was not found. The aspect
|
||||
# of finding the hint is based on edit distance.
|
||||
missing-member-hint=yes
|
||||
|
||||
# The minimum edit distance a name should have in order to be considered a
|
||||
# similar match for a missing member name.
|
||||
missing-member-hint-distance=1
|
||||
|
||||
# The total number of similar names that should be taken in consideration when
|
||||
# showing a hint for a missing member.
|
||||
missing-member-max-choices=1
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# Tells whether unused global variables should be treated as a violation.
|
||||
allow-global-unused-variables=yes
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,
|
||||
_cb
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||
# not used).
|
||||
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore
|
||||
ignored-argument-names=_.*|^ignored_|^unused_
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# List of qualified module names which can have objects that can redefine
|
||||
# builtins.
|
||||
redefining-builtins-modules=six.moves,past.builtins,future.builtins
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=100
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
|
||||
# List of optional constructs for which whitespace checking is disabled. `dict-
|
||||
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
|
||||
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
|
||||
# `empty-line` allows space-only lines.
|
||||
no-space-check=trailing-comma,
|
||||
dict-separator
|
||||
|
||||
# Allow the body of a class to be on the same line as the declaration if body
|
||||
# contains single statement.
|
||||
single-line-class-stmt=no
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Naming style matching correct argument names
|
||||
argument-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct argument names. Overrides argument-
|
||||
# naming-style
|
||||
#argument-rgx=
|
||||
|
||||
# Naming style matching correct attribute names
|
||||
attr-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct attribute names. Overrides attr-naming-
|
||||
# style
|
||||
#attr-rgx=
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=foo,
|
||||
bar,
|
||||
baz,
|
||||
toto,
|
||||
tutu,
|
||||
tata
|
||||
|
||||
# Naming style matching correct class attribute names
|
||||
class-attribute-naming-style=any
|
||||
|
||||
# Regular expression matching correct class attribute names. Overrides class-
|
||||
# attribute-naming-style
|
||||
#class-attribute-rgx=
|
||||
|
||||
# Naming style matching correct class names
|
||||
class-naming-style=PascalCase
|
||||
|
||||
# Regular expression matching correct class names. Overrides class-naming-style
|
||||
#class-rgx=
|
||||
|
||||
# Naming style matching correct constant names
|
||||
const-naming-style=UPPER_CASE
|
||||
|
||||
# Regular expression matching correct constant names. Overrides const-naming-
|
||||
# style
|
||||
#const-rgx=
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
# Naming style matching correct function names
|
||||
function-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct function names. Overrides function-
|
||||
# naming-style
|
||||
#function-rgx=
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=i,
|
||||
j,
|
||||
k,
|
||||
ex,
|
||||
Run,
|
||||
_
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name
|
||||
include-naming-hint=no
|
||||
|
||||
# Naming style matching correct inline iteration names
|
||||
inlinevar-naming-style=any
|
||||
|
||||
# Regular expression matching correct inline iteration names. Overrides
|
||||
# inlinevar-naming-style
|
||||
#inlinevar-rgx=
|
||||
|
||||
# Naming style matching correct method names
|
||||
method-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct method names. Overrides method-naming-
|
||||
# style
|
||||
#method-rgx=
|
||||
|
||||
# Naming style matching correct module names
|
||||
module-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct module names. Overrides module-naming-
|
||||
# style
|
||||
#module-rgx=
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=^_
|
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
||||
# to this list to register other decorators that produce valid properties.
|
||||
property-classes=abc.abstractproperty
|
||||
|
||||
# Naming style matching correct variable names
|
||||
variable-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct variable names. Overrides variable-
|
||||
# naming-style
|
||||
#variable-rgx=
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=5
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Maximum number of boolean expressions in a if statement
|
||||
max-bool-expr=5
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=50
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,
|
||||
__new__,
|
||||
setUp
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,
|
||||
_fields,
|
||||
_replace,
|
||||
_source,
|
||||
_make
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Allow wildcard imports from modules that define __all__.
|
||||
allow-wildcard-with-all=no
|
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
||||
# 3 compatible code, which means that the block might have code that exists
|
||||
# only in one or another interpreter, leading to false positives when analysed.
|
||||
analyse-fallback-blocks=no
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,
|
||||
TERMIOS,
|
||||
Bastion,
|
||||
rexec
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
# Force import order to recognize a module as part of the standard
|
||||
# compatibility libraries.
|
||||
known-standard-library=
|
||||
|
||||
# Force import order to recognize a module as part of a third party library.
|
||||
known-third-party=enchant
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=Exception
|
||||
+18
-7
@@ -2,7 +2,7 @@
|
||||
|
||||
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.
|
||||
|
||||
Most open source development activity is coordinated through our [Discord](https://discord.comma.ai). A lot of documentation is available on our [medium](https://medium.com/@comma_ai/)
|
||||
Most open source development activity is coordinated through our [Discord](https://discord.comma.ai). A lot of documentation is available on our [medium](https://medium.com/@comma_ai/).
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -14,27 +14,38 @@ Most open source development activity is coordinated through our [Discord](https
|
||||
|
||||
### Local Testing
|
||||
|
||||
You can test your changes on your machine by running `run_docker_tests.sh`. This will run some automated tests in docker against your code.
|
||||
You can test your changes on your machine by running `run_docker_tests.sh`. This will run some automated tests in docker against your code.
|
||||
|
||||
### Automated Testing
|
||||
|
||||
All PRs are automatically checked by travis. Check out `.travis.yml` for what travis runs. Any new tests sould be added to travis.
|
||||
All PRs and commits are automatically checked by Github Actions. Check out `.github/workflows/` for what Github Actions runs. Any new tests should be added to Github Actions.
|
||||
|
||||
### Code Style and Linting
|
||||
|
||||
Code is automatically check for style by travis as part of the automated tests. You can also run these yourself by running `check_code_quality.sh`.
|
||||
Code is automatically checked for style by Github Actions as part of the automated tests. You can also run these tests yourself by running `pre-commit run --all`.
|
||||
|
||||
## Car Ports (openpilot)
|
||||
|
||||
We've released a [Model Port guide](https://medium.com/@comma_ai/openpilot-port-guide-for-toyota-models-e5467f4b5fe6) for porting to Toyota/Lexus models.
|
||||
|
||||
If you port openpilot to a substantially new car brand, see this more generic [Brand Port guide](https://medium.com/@comma_ai/how-to-write-a-car-port-for-openpilot-7ce0785eda84). You might also be eligible for a bounty. See our bounties at [comma.ai/bounties.html](https://comma.ai/bounties.html)
|
||||
If you port openpilot to a substantially new car brand, see this more generic [Brand Port guide](https://medium.com/@comma_ai/how-to-write-a-car-port-for-openpilot-7ce0785eda84). You might also be eligible for a bounty.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Pull requests should be against the master branch. Before running master on in-car hardware, you'll need to run
|
||||
Pull requests should be against the master branch. Before running master on in-car hardware, you'll need to clone the submodules too. That can be done by recursively cloning the repository:
|
||||
```
|
||||
git clone https://github.com/commaai/openpilot.git --recursive
|
||||
```
|
||||
Or alternatively, when on the master branch:
|
||||
```
|
||||
git submodule init
|
||||
git submodule update
|
||||
```
|
||||
in order to pull down the submodules, such as `panda` and `opendbc`.
|
||||
The reasons for having submodules on a dedicated repository and our new development philosophy can be found in our [post about externalization](https://medium.com/@comma_ai/a-2020-theme-externalization-13b33326d8b3).
|
||||
Modules that are in seperate repositories include:
|
||||
* apks
|
||||
* cereal
|
||||
* laika
|
||||
* opendbc
|
||||
* panda
|
||||
* rednose
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
FROM ubuntu:16.04
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
autoconf \
|
||||
build-essential \
|
||||
bzip2 \
|
||||
clang \
|
||||
cmake \
|
||||
curl \
|
||||
ffmpeg \
|
||||
git \
|
||||
libarchive-dev \
|
||||
libbz2-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libeigen3-dev \
|
||||
libffi-dev \
|
||||
libglew-dev \
|
||||
libgles2-mesa-dev \
|
||||
libglfw3-dev \
|
||||
libglib2.0-0 \
|
||||
liblzma-dev \
|
||||
libmysqlclient-dev \
|
||||
libomp-dev \
|
||||
libopencv-dev \
|
||||
libssl-dev \
|
||||
libsqlite3-dev \
|
||||
libtool \
|
||||
libusb-1.0-0-dev \
|
||||
libzmq5-dev \
|
||||
locales \
|
||||
ocl-icd-libopencl1 \
|
||||
ocl-icd-opencl-dev \
|
||||
opencl-headers \
|
||||
python-dev \
|
||||
python-pip \
|
||||
screen \
|
||||
sudo \
|
||||
vim \
|
||||
wget
|
||||
|
||||
|
||||
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
|
||||
|
||||
RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
|
||||
|
||||
ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}"
|
||||
RUN pyenv install 3.7.3
|
||||
RUN pyenv global 3.7.3
|
||||
RUN pyenv rehash
|
||||
|
||||
RUN pip install pipenv==2018.11.26
|
||||
|
||||
COPY Pipfile /tmp/
|
||||
COPY Pipfile.lock /tmp/
|
||||
|
||||
RUN python --version
|
||||
RUN cd /tmp && pipenv install --system --deploy
|
||||
|
||||
# Install subset of dev dependencies needed for CI
|
||||
RUN pip install matplotlib==3.1.1 dictdiffer==0.8.0 fastcluster==1.1.25 aenum==2.2.1 scipy==1.3.1 lru-dict==1.1.6 tenacity==5.1.1 azure-common==1.1.23 azure-nspkg==3.0.2 azure-storage-blob==2.1.0 azure-storage-common==2.1.0 azure-storage-nspkg==3.1.0 pycurl==7.43.0.3
|
||||
|
||||
ENV PATH="/tmp/openpilot/external/bin:${PATH}"
|
||||
ENV PYTHONPATH /tmp/openpilot:${PYTHONPATH}
|
||||
|
||||
RUN mkdir -p /tmp/openpilot
|
||||
COPY ./flake8_openpilot.sh /tmp/openpilot/
|
||||
COPY ./pylint_openpilot.sh /tmp/openpilot/
|
||||
COPY ./.pylintrc /tmp/openpilot/
|
||||
|
||||
COPY ./release /tmp/openpilot/release
|
||||
COPY ./common /tmp/openpilot/common
|
||||
COPY ./cereal /tmp/openpilot/cereal
|
||||
COPY ./opendbc /tmp/openpilot/opendbc
|
||||
COPY ./selfdrive /tmp/openpilot/selfdrive
|
||||
COPY ./phonelibs /tmp/openpilot/phonelibs
|
||||
COPY ./pyextra /tmp/openpilot/pyextra
|
||||
COPY ./panda /tmp/openpilot/panda
|
||||
COPY ./external /tmp/openpilot/external
|
||||
COPY ./tools /tmp/openpilot/tools
|
||||
|
||||
COPY SConstruct /tmp/openpilot/SConstruct
|
||||
|
||||
RUN mkdir -p /tmp/openpilot/selfdrive/test/out
|
||||
RUN cd /tmp/openpilot && scons -j$(nproc)
|
||||
Vendored
+149
-12
@@ -1,22 +1,159 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'python:3.7.3'
|
||||
args '--user=root'
|
||||
}
|
||||
def phone(String ip, String step_label, String cmd) {
|
||||
def ci_env = "CI=1 TEST_DIR=${env.TEST_DIR} GIT_BRANCH=${env.GIT_BRANCH} GIT_COMMIT=${env.GIT_COMMIT}"
|
||||
|
||||
withCredentials([file(credentialsId: 'id_rsa_public', variable: 'key_file')]) {
|
||||
sh label: step_label,
|
||||
script: """
|
||||
ssh -tt -o StrictHostKeyChecking=no -i ${key_file} -p 8022 root@${ip} '${ci_env} /usr/bin/bash -le' <<'EOF'
|
||||
echo \$\$ > /dev/cpuset/app/tasks || true
|
||||
echo \$PPID > /dev/cpuset/app/tasks || true
|
||||
mkdir -p /dev/shm
|
||||
chmod 777 /dev/shm
|
||||
cd ${env.TEST_DIR} || true
|
||||
${cmd}
|
||||
exit 0
|
||||
EOF"""
|
||||
}
|
||||
}
|
||||
|
||||
def phone_steps(String device_type, steps) {
|
||||
lock(resource: "", label: device_type, inversePrecedence: true, variable: 'device_ip', quantity: 1) {
|
||||
timeout(time: 60, unit: 'MINUTES') {
|
||||
phone(device_ip, "kill old processes", "pkill -f comma || true")
|
||||
phone(device_ip, "git checkout", readFile("selfdrive/test/setup_device_ci.sh"),)
|
||||
steps.each { item ->
|
||||
phone(device_ip, item[0], item[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pipeline {
|
||||
agent none
|
||||
environment {
|
||||
COMMA_JWT = credentials('athena-test-jwt')
|
||||
TEST_DIR = "/data/openpilot"
|
||||
}
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('EON Build/Test') {
|
||||
|
||||
stage('Release Build') {
|
||||
agent {
|
||||
docker {
|
||||
image 'python:3.7.3'
|
||||
args '--user=root'
|
||||
}
|
||||
}
|
||||
when {
|
||||
branch 'devel-staging'
|
||||
}
|
||||
steps {
|
||||
lock(resource: "", label: 'eon', inversePrecedence: true, variable: 'eon_name', quantity: 1){
|
||||
timeout(time: 30, unit: 'MINUTES') {
|
||||
dir(path: 'release') {
|
||||
sh 'pip install paramiko'
|
||||
sh 'python remote_build.py'
|
||||
phone_steps("eon-build", [
|
||||
["build release2-staging and dashcam-staging", "cd release && PUSH=1 ./build_release2.sh"],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
stage('openpilot tests') {
|
||||
when {
|
||||
not {
|
||||
anyOf {
|
||||
branch 'master-ci'; branch 'devel'; branch 'devel-staging'; branch 'release2'; branch 'release2-staging'; branch 'dashcam'; branch 'dashcam-staging'; branch 'testing-closet*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stages {
|
||||
|
||||
/*
|
||||
stage('PC tests') {
|
||||
agent {
|
||||
dockerfile {
|
||||
filename 'Dockerfile.openpilotci'
|
||||
args '--privileged --shm-size=1G --user=root'
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'scons -j$(nproc)'
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
// fix permissions since docker runs as another user
|
||||
sh "chmod -R 777 ."
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
stage('On-device Tests') {
|
||||
agent {
|
||||
docker {
|
||||
image 'python:3.7.3'
|
||||
args '--user=root'
|
||||
}
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('parallel tests') {
|
||||
parallel {
|
||||
|
||||
stage('Devel Build') {
|
||||
environment {
|
||||
CI_PUSH = "${env.BRANCH_NAME == 'master' ? 'master-ci' : ' '}"
|
||||
}
|
||||
steps {
|
||||
phone_steps("eon", [
|
||||
["build devel", "cd release && CI_PUSH=${env.CI_PUSH} ./build_devel.sh"],
|
||||
["test openpilot", "nosetests -s selfdrive/test/test_openpilot.py"],
|
||||
["test cpu usage", "cd selfdrive/test/ && ./test_cpu_usage.py"],
|
||||
["test car interfaces", "cd selfdrive/car/tests/ && ./test_car_interfaces.py"],
|
||||
["test spinner build", "cd selfdrive/ui/spinner && make clean && make"],
|
||||
["test text window build", "cd selfdrive/ui/text && make clean && make"],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
stage('Replay Tests') {
|
||||
steps {
|
||||
phone_steps("eon2", [
|
||||
["camerad/modeld replay", "cd selfdrive/test/process_replay && ./camera_replay.py"],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
stage('HW + Unit Tests') {
|
||||
steps {
|
||||
phone_steps("eon", [
|
||||
["build cereal", "SCONS_CACHE=1 scons -j4 cereal/"],
|
||||
["test sounds", "nosetests -s selfdrive/test/test_sounds.py"],
|
||||
["test boardd loopback", "nosetests -s selfdrive/boardd/tests/test_boardd_loopback.py"],
|
||||
["test loggerd", "CI=1 python selfdrive/loggerd/tests/test_loggerd.py"],
|
||||
//["test camerad", "CI=1 python selfdrive/camerad/test/test_camerad.py"], // wait for shelf refactor
|
||||
//["test updater", "python installer/updater/test_updater.py"],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
cleanWs()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
opencv-python= "==3.4.2.17"
|
||||
PyQt5 = "*"
|
||||
ipython = "*"
|
||||
networkx = "==2.3"
|
||||
azure-core = "==1.1.1"
|
||||
azure-common = "==1.1.24"
|
||||
azure-nspkg = "==3.0.2"
|
||||
azure-storage-blob = "==2.1.0"
|
||||
azure-storage-common = "==2.1.0"
|
||||
azure-storage-nspkg = "==3.1.0"
|
||||
boto = "*"
|
||||
"boto3" = "*"
|
||||
control = "*"
|
||||
datadog = "*"
|
||||
dlib = "*"
|
||||
elasticsearch = "*"
|
||||
future = "*"
|
||||
futures = "*"
|
||||
pycocotools = {git = "https://github.com/cocodataset/cocoapi.git",subdirectory = "PythonAPI"}
|
||||
gunicorn = "*"
|
||||
"h5py" = "*"
|
||||
hexdump = "*"
|
||||
"html5lib" = "*"
|
||||
imageio = "*"
|
||||
ipykernel = "*"
|
||||
joblib = "*"
|
||||
json-logging-py = "*"
|
||||
jupyter = "*"
|
||||
libarchive = "*"
|
||||
lru-dict = "*"
|
||||
"mpld3" = "*"
|
||||
msgpack-python = "*"
|
||||
nbstripout = "*"
|
||||
numpy = "*"
|
||||
osmium = "*"
|
||||
pbr = "*"
|
||||
percache = "*"
|
||||
pprofile = "*"
|
||||
pycurl = "*"
|
||||
git-pylint-commit-hook = "*"
|
||||
pymongo = "*"
|
||||
"pynmea2" = "*"
|
||||
pypolyline = "*"
|
||||
python-logstash = "*"
|
||||
redis = "*"
|
||||
"s2sphere" = "*"
|
||||
scikit-image = "*"
|
||||
"subprocess32" = "*"
|
||||
tenacity = "*"
|
||||
tensorflow-gpu = "==2.0"
|
||||
PyJWT = "==1.4.1"
|
||||
PyMySQL = "==0.9.2"
|
||||
Werkzeug = "*"
|
||||
"backports.lzma" = "*"
|
||||
Flask-Cors = "*"
|
||||
Flask-SocketIO = "*"
|
||||
"GeoAlchemy2" = "*"
|
||||
Pygments = "*"
|
||||
PyNaCl = "*"
|
||||
"PySDL2" = "*"
|
||||
reverse_geocoder = "*"
|
||||
Shapely = "*"
|
||||
SQLAlchemy = "*"
|
||||
scipy = "*"
|
||||
fastcluster = "*"
|
||||
backports-abc = "*"
|
||||
pygame = "*"
|
||||
simplejson = "*"
|
||||
python-logstash-async = "*"
|
||||
seaborn = "*"
|
||||
pyproj = "*"
|
||||
mock = "*"
|
||||
matplotlib = "*"
|
||||
dictdiffer = "*"
|
||||
aenum = "*"
|
||||
coverage = "*"
|
||||
azure-cli-core = "*"
|
||||
paramiko = "*"
|
||||
aiohttp = "*"
|
||||
|
||||
[packages]
|
||||
overpy = {git = "https://github.com/commaai/python-overpy.git",ref = "f86529af402d4642e1faeb146671c40284007323"}
|
||||
atomicwrites = "*"
|
||||
cffi = "*"
|
||||
crcmod = "*"
|
||||
hexdump = "*"
|
||||
libusb1 = "*"
|
||||
numpy = "*"
|
||||
psutil = "*"
|
||||
pycapnp = "*"
|
||||
cryptography = "*"
|
||||
pyserial = "*"
|
||||
python-dateutil = "*"
|
||||
pyzmq = "*"
|
||||
raven = "*"
|
||||
requests = "*"
|
||||
setproctitle = "*"
|
||||
six = "*"
|
||||
smbus2 = "*"
|
||||
sympy = "*"
|
||||
tqdm = "*"
|
||||
Cython = "*"
|
||||
PyYAML = "*"
|
||||
websocket_client = "*"
|
||||
Logentries = {git = "https://github.com/commaai/le_python.git",ref = "feaeacb48f7f4bdb02c0a8fc092326d4e101b7f2"}
|
||||
urllib3 = "*"
|
||||
chardet = "*"
|
||||
idna = "*"
|
||||
gunicorn = "*"
|
||||
utm = "*"
|
||||
json-rpc = "*"
|
||||
Flask = "*"
|
||||
PyJWT = "*"
|
||||
"Jinja2" = "*"
|
||||
nose = "*"
|
||||
flake8 = "*"
|
||||
pylint = "*"
|
||||
pycryptodome = "*"
|
||||
pillow = "*"
|
||||
scons = "*"
|
||||
cysignals = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7.3"
|
||||
Generated
-2970
File diff suppressed because it is too large
Load Diff
@@ -57,102 +57,129 @@ openpilot should preserve all other vehicle's stock features, including, but are
|
||||
Supported Hardware
|
||||
------
|
||||
|
||||
At the moment, openpilot supports the [EON DevKit](https://comma.ai/shop/products/eon-dashcam-devkit) and the [comma two](https://comma.ai/shop/products/comma-two-devkit). A [car harness](https://comma.ai/shop/products/car-harness) is recommended to connect the EON or comma two to the car. In the future, we'd like to support other platforms as well, like gaming PCs.
|
||||
At the moment, openpilot supports the [EON DevKit](https://comma.ai/shop/products/eon-dashcam-devkit) and the [comma two](https://comma.ai/shop/products/comma-two-devkit). A [car harness](https://comma.ai/shop/products/car-harness) is recommended to connect the EON or comma two to the car. For experimental purposes, openpilot can also run on an Ubuntu computer with external [webcams](https://github.com/commaai/openpilot/tree/master/tools/webcam).
|
||||
|
||||
Supported Cars
|
||||
------
|
||||
|
||||
| Make | Model (US Market Reference) | Supported Package | ACC | No ACC accel below | No ALC below |
|
||||
| ----------| ------------------------------| ------------------| -----------------| -------------------| -------------|
|
||||
| Acura | ILX 2016-18 | AcuraWatch Plus | openpilot | 25mph<sup>5</sup> | 25mph |
|
||||
| Acura | RDX 2016-18 | AcuraWatch Plus | openpilot | 25mph<sup>5</sup> | 12mph |
|
||||
| Chrysler | Pacifica 2017-18 | Adaptive Cruise | Stock | 0mph | 9mph |
|
||||
| Chrysler | Pacifica Hybrid 2017-18 | Adaptive Cruise | Stock | 0mph | 9mph |
|
||||
| Chrysler | Pacifica Hybrid 2019-20 | Adaptive Cruise | Stock | 0mph | 39mph |
|
||||
| Honda | Accord 2018-19 | All | Stock | 0mph | 3mph |
|
||||
| Honda | Accord Hybrid 2018-19 | All | Stock | 0mph | 3mph |
|
||||
| Honda | Civic Hatchback 2017-19 | Honda Sensing | Stock | 0mph | 12mph |
|
||||
| Honda | Civic Sedan/Coupe 2016-18 | Honda Sensing | openpilot | 0mph | 12mph |
|
||||
| Honda | Civic Sedan/Coupe 2019 | Honda Sensing | Stock | 0mph | 2mph |
|
||||
| Honda | CR-V 2015-16 | Touring | openpilot | 25mph<sup>5</sup> | 12mph |
|
||||
| Honda | CR-V 2017-19 | Honda Sensing | Stock | 0mph | 12mph |
|
||||
| Honda | CR-V Hybrid 2017-2019 | Honda Sensing | Stock | 0mph | 12mph |
|
||||
| Honda | Fit 2018-19 | Honda Sensing | openpilot | 25mph<sup>5</sup> | 12mph |
|
||||
| Honda | Odyssey 2018-20 | Honda Sensing | openpilot | 25mph<sup>5</sup> | 0mph |
|
||||
| Honda | Passport 2019 | All | openpilot | 25mph<sup>5</sup> | 12mph |
|
||||
| Honda | Pilot 2016-18 | Honda Sensing | openpilot | 25mph<sup>5</sup> | 12mph |
|
||||
| Honda | Pilot 2019 | All | openpilot | 25mph<sup>5</sup> | 12mph |
|
||||
| Honda | Ridgeline 2017-19 | Honda Sensing | openpilot | 25mph<sup>5</sup> | 12mph |
|
||||
| Hyundai | Elantra 2017-19<sup>1</sup> | SCC + LKAS | Stock | 19mph | 34mph |
|
||||
| Hyundai | Genesis 2018<sup>1</sup> | All | Stock | 19mph | 34mph |
|
||||
| Hyundai | Santa Fe 2019<sup>1</sup> | All | Stock | 0mph | 0mph |
|
||||
| Jeep | Grand Cherokee 2016-18 | Adaptive Cruise | Stock | 0mph | 9mph |
|
||||
| Jeep | Grand Cherokee 2019 | Adaptive Cruise | Stock | 0mph | 39mph |
|
||||
| Kia | Optima 2019<sup>1</sup> | SCC + LKAS | Stock | 0mph | 0mph |
|
||||
| Kia | Sorento 2018<sup>1</sup> | All | Stock | 0mph | 0mph |
|
||||
| Kia | Stinger 2018<sup>1</sup> | SCC + LKAS | Stock | 0mph | 0mph |
|
||||
| Lexus | CT Hybrid 2017-18 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Lexus | ES 2019 | All | openpilot | 0mph | 0mph |
|
||||
| Lexus | ES Hybrid 2019 | All | openpilot | 0mph | 0mph |
|
||||
| Lexus | IS 2017-2019 | All | Stock | 22mph | 0mph |
|
||||
| Lexus | IS Hybrid 2017 | All | Stock | 0mph | 0mph |
|
||||
| Lexus | RX 2016-17 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Lexus | RX 2020 | All | openpilot | 0mph | 0mph |
|
||||
| Lexus | RX Hybrid 2016-19 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Subaru | Crosstrek 2018-19 | EyeSight | Stock | 0mph | 0mph |
|
||||
| Subaru | Impreza 2019-20 | EyeSight | Stock | 0mph | 0mph |
|
||||
| Toyota | Avalon 2016 | TSS-P | Stock<sup>4</sup>| 20mph<sup>5</sup> | 0mph |
|
||||
| Toyota | Avalon 2017-18 | All | Stock<sup>4</sup>| 20mph<sup>5</sup> | 0mph |
|
||||
| Toyota | Camry 2018-19 | All | Stock | 0mph<sup>2</sup> | 0mph |
|
||||
| Toyota | Camry Hybrid 2018-19 | All | Stock | 0mph<sup>2</sup> | 0mph |
|
||||
| Toyota | C-HR 2017-19 | All | Stock | 0mph | 0mph |
|
||||
| Toyota | C-HR Hybrid 2017-19 | All | Stock | 0mph | 0mph |
|
||||
| Toyota | Corolla 2017-19 | All | Stock<sup>4</sup>| 20mph<sup>5</sup> | 0mph |
|
||||
| Toyota | Corolla 2020 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Corolla Hatchback 2019-20 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Corolla Hybrid 2020 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Highlander 2017-19 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Highlander Hybrid 2017-19 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Prius 2016 | TSS-P | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Prius 2017-19 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Prius Prime 2017-20 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Rav4 2016 | TSS-P | Stock<sup>4</sup>| 20mph<sup>5</sup> | 0mph |
|
||||
| Toyota | Rav4 2017-18 | All | Stock<sup>4</sup>| 20mph<sup>5</sup> | 0mph |
|
||||
| Toyota | Rav4 2019 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Rav4 Hybrid 2016 | TSS-P | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Rav4 Hybrid 2017-18 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Rav4 Hybrid 2019-20 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Sienna 2018 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Volkswagen| Golf 2016-19<sup>3</sup> | Driver Assistance | Stock | 0mph | 0mph |
|
||||
| Make | Model (US Market Reference) | Supported Package | ACC | No ACC accel below | No ALC below |
|
||||
| ----------| ------------------------------| ------------------| -----------------| -------------------| ------------------|
|
||||
| Acura | ILX 2016-18 | AcuraWatch Plus | openpilot | 25mph<sup>1</sup> | 25mph |
|
||||
| Acura | RDX 2016-18 | AcuraWatch Plus | openpilot | 25mph<sup>1</sup> | 12mph |
|
||||
| Acura | RDX 2020 | All | Stock | 0mph | 3mph |
|
||||
| Honda | Accord 2018-20 | All | Stock | 0mph | 3mph |
|
||||
| Honda | Accord Hybrid 2018-20 | All | Stock | 0mph | 3mph |
|
||||
| Honda | Civic Hatchback 2017-19 | Honda Sensing | Stock | 0mph | 12mph |
|
||||
| Honda | Civic Sedan/Coupe 2016-18 | Honda Sensing | openpilot | 0mph | 12mph |
|
||||
| Honda | Civic Sedan/Coupe 2019-20 | All | Stock | 0mph | 2mph<sup>2</sup> |
|
||||
| Honda | CR-V 2015-16 | Touring | openpilot | 25mph<sup>1</sup> | 12mph |
|
||||
| Honda | CR-V 2017-20 | Honda Sensing | Stock | 0mph | 12mph |
|
||||
| Honda | CR-V Hybrid 2017-2019 | Honda Sensing | Stock | 0mph | 12mph |
|
||||
| Honda | Fit 2018-19 | Honda Sensing | openpilot | 25mph<sup>1</sup> | 12mph |
|
||||
| Honda | HR-V 2019 | Honda Sensing | openpilot | 25mph<sup>1</sup> | 12mph |
|
||||
| Honda | Insight 2019-20 | All | Stock | 0mph | 3mph |
|
||||
| Honda | Inspire 2018 | All | Stock | 0mph | 3mph |
|
||||
| Honda | Odyssey 2018-20 | Honda Sensing | openpilot | 25mph<sup>1</sup> | 0mph |
|
||||
| Honda | Passport 2019 | All | openpilot | 25mph<sup>1</sup> | 12mph |
|
||||
| Honda | Pilot 2016-19 | Honda Sensing | openpilot | 25mph<sup>1</sup> | 12mph |
|
||||
| Honda | Ridgeline 2017-20 | Honda Sensing | openpilot | 25mph<sup>1</sup> | 12mph |
|
||||
| Hyundai | Palisade 2020 | All | Stock | 0mph | 0mph |
|
||||
| Hyundai | Sonata 2020 | All | Stock | 0mph | 0mph |
|
||||
| Lexus | CT Hybrid 2017-18 | LSS | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
| Lexus | ES 2019-20 | All | openpilot | 0mph | 0mph |
|
||||
| Lexus | ES Hybrid 2019 | All | openpilot | 0mph | 0mph |
|
||||
| Lexus | IS 2017-2019 | All | Stock | 22mph | 0mph |
|
||||
| Lexus | IS Hybrid 2017 | All | Stock | 0mph | 0mph |
|
||||
| Lexus | NX 2018 | All | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
| Lexus | NX Hybrid 2018 | All | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
| Lexus | RX 2016-18 | All | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
| Lexus | RX 2020 | All | openpilot | 0mph | 0mph |
|
||||
| Lexus | RX Hybrid 2016-19 | All | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
| Lexus | RX Hybrid 2020 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Avalon 2016-18 | TSS-P | Stock<sup>3</sup>| 20mph<sup>1</sup> | 0mph |
|
||||
| Toyota | Camry 2018-20 | All | Stock | 0mph<sup>4</sup> | 0mph |
|
||||
| Toyota | Camry Hybrid 2018-20 | All | Stock | 0mph<sup>4</sup> | 0mph |
|
||||
| Toyota | C-HR 2017-19 | All | Stock | 0mph | 0mph |
|
||||
| Toyota | C-HR Hybrid 2017-19 | All | Stock | 0mph | 0mph |
|
||||
| Toyota | Corolla 2017-19 | All | Stock<sup>3</sup>| 20mph<sup>1</sup> | 0mph |
|
||||
| Toyota | Corolla 2020-21 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Corolla Hatchback 2019-20 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Corolla Hybrid 2020-21 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Highlander 2017-19 | All | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
| Toyota | Highlander 2020 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Highlander Hybrid 2017-19 | All | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
| Toyota | Highlander Hybrid 2020 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Prius 2016-20 | TSS-P | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
| Toyota | Prius 2021 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Prius Prime 2017-20 | All | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
| Toyota | Rav4 2016-18 | TSS-P | Stock<sup>3</sup>| 20mph<sup>1</sup> | 0mph |
|
||||
| Toyota | Rav4 2019-21 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Rav4 Hybrid 2016-18 | TSS-P | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
| Toyota | Rav4 Hybrid 2019-21 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Sienna 2018-20 | All | Stock<sup>3</sup>| 0mph | 0mph |
|
||||
|
||||
<sup>1</sup>Requires a [panda](https://comma.ai/shop/products/panda-obd-ii-dongle) and open sourced [Hyundai giraffe](https://github.com/commaai/neo/tree/master/giraffe/hyundai), designed for the 2019 Sante Fe; pinout may differ for other Hyundai and Kia models. <br />
|
||||
<sup>2</sup>28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
|
||||
<sup>3</sup>Requires a [custom connector](https://community.comma.ai/wiki/index.php/Volkswagen#Integration_at_R242_Camera) for the [car harness](https://comma.ai/shop/products/car-harness) <br />
|
||||
<sup>1</sup>[Comma Pedal](https://github.com/commaai/openpilot/wiki/comma-pedal) is used to provide stop-and-go capability to some of the openpilot-supported cars that don't currently support stop-and-go. ***NOTE: The Comma Pedal is not officially supported by [comma](https://comma.ai).*** <br />
|
||||
<sup>2</sup>2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph. <br />
|
||||
<sup>3</sup>When disconnecting the Driver Support Unit (DSU), openpilot ACC will replace stock ACC. ***NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).*** <br />
|
||||
<sup>4</sup>28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
|
||||
|
||||
Community Maintained Cars and Features
|
||||
------
|
||||
|
||||
| Make | Model (US Market Reference) | Supported Package | ACC | No ACC accel below | No ALC below |
|
||||
| ----------| ------------------------------| ------------------| -----------------| -------------------| -------------|
|
||||
| Buick | Regal 2018<sup>6</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Cadillac | ATS 2018<sup>6</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Chevrolet | Malibu 2017<sup>6</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Chevrolet | Volt 2017-18<sup>6</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| GMC | Acadia Denali 2018<sup>6</sup>| Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Holden | Astra 2017<sup>6</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Buick | Regal 2018<sup>1</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Cadillac | ATS 2018<sup>1</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Chevrolet | Malibu 2017<sup>1</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Chevrolet | Volt 2017-18<sup>1</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Chrysler | Pacifica 2017-18 | Adaptive Cruise | Stock | 0mph | 9mph |
|
||||
| Chrysler | Pacifica 2020 | Adaptive Cruise | Stock | 0mph | 39mph |
|
||||
| Chrysler | Pacifica Hybrid 2017-18 | Adaptive Cruise | Stock | 0mph | 9mph |
|
||||
| Chrysler | Pacifica Hybrid 2019-20 | Adaptive Cruise | Stock | 0mph | 39mph |
|
||||
| Genesis | G70 2018 | All | Stock | 0mph | 0mph |
|
||||
| Genesis | G80 2018 | All | Stock | 0mph | 0mph |
|
||||
| Genesis | G90 2018 | All | Stock | 0mph | 0mph |
|
||||
| GMC | Acadia 2018<sup>1</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Holden | Astra 2017<sup>1</sup> | Adaptive Cruise | openpilot | 0mph | 7mph |
|
||||
| Hyundai | Elantra 2017-19 | SCC + LKAS | Stock | 19mph | 34mph |
|
||||
| Hyundai | Genesis 2015-16 | SCC + LKAS | Stock | 19mph | 37mph |
|
||||
| Hyundai | Ioniq Electric 2019-20 | SCC + LKAS | Stock | 0mph | 32mph |
|
||||
| Hyundai | Kona 2020 | SCC + LKAS | Stock | 0mph | 0mph |
|
||||
| Hyundai | Kona EV 2019 | SCC + LKAS | Stock | 0mph | 0mph |
|
||||
| Hyundai | Santa Fe 2019 | All | Stock | 0mph | 0mph |
|
||||
| Hyundai | Sonata 2019 | SCC + LKAS | Stock | 0mph | 0mph |
|
||||
| Hyundai | Veloster 2019 | SCC + LKAS | Stock | 5mph | 0mph |
|
||||
| Jeep | Grand Cherokee 2016-18 | Adaptive Cruise | Stock | 0mph | 9mph |
|
||||
| Jeep | Grand Cherokee 2019-20 | Adaptive Cruise | Stock | 0mph | 39mph |
|
||||
| Kia | Forte 2018-19 | SCC + LKAS | Stock | 0mph | 0mph |
|
||||
| Kia | Niro EV 2020 | SCC + LKAS | Stock | 0mph | 0mph |
|
||||
| Kia | Optima 2017 | SCC + LKAS | Stock | 0mph | 32mph |
|
||||
| Kia | Optima 2019 | SCC + LKAS | Stock | 0mph | 0mph |
|
||||
| Kia | Sorento 2018 | SCC + LKAS | Stock | 0mph | 0mph |
|
||||
| Kia | Stinger 2018 | SCC + LKAS | Stock | 0mph | 0mph |
|
||||
| Nissan | Leaf 2018-20 | ProPILOT | Stock | 0mph | 0mph |
|
||||
| Nissan | Rogue 2018-19 | ProPILOT | Stock | 0mph | 0mph |
|
||||
| Nissan | X-Trail 2017 | ProPILOT | Stock | 0mph | 0mph |
|
||||
| Subaru | Ascent 2019 | EyeSight | Stock | 0mph | 0mph |
|
||||
| Subaru | Crosstrek 2018-19 | EyeSight | Stock | 0mph | 0mph |
|
||||
| Subaru | Forester 2019 | EyeSight | Stock | 0mph | 0mph |
|
||||
| Subaru | Impreza 2017-19 | EyeSight | Stock | 0mph | 0mph |
|
||||
| Volkswagen| Golf 2015-19 | Driver Assistance | Stock | 0mph | 0mph |
|
||||
|
||||
<sup>4</sup>When disconnecting the Driver Support Unit (DSU), openpilot ACC will replace stock ACC. For DSU locations, see [Toyota Wiki page](https://community.comma.ai/wiki/index.php/Toyota). ***NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).*** <br />
|
||||
<sup>5</sup>[Comma Pedal](https://community.comma.ai/wiki/index.php/Comma_Pedal) is used to provide stop-and-go capability to some of the openpilot-supported cars that don't currently support stop-and-go. Here is how to [build a Comma Pedal](https://medium.com/@jfrux/comma-pedal-building-with-macrofab-6328bea791e8). ***NOTE: The Comma Pedal is not officially supported by [comma](https://comma.ai).*** <br />
|
||||
<sup>6</sup>Requires a [panda](https://comma.ai/shop/products/panda-obd-ii-dongle) and [community built giraffe](https://zoneos.com/volt/). ***NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).*** <br />
|
||||
<sup>1</sup>Requires an [OBD-II car harness](https://comma.ai/shop/products/comma-car-harness) and [community built ASCM harness](https://github.com/commaai/openpilot/wiki/GM#hardware). ***NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).*** <br />
|
||||
|
||||
Although it's not upstream, there's a community of people getting openpilot to run on Tesla's [here](https://tinkla.us/)
|
||||
|
||||
Community Maintained Cars and Features are not verified by comma to meet our [safety model](SAFETY.md). Be extra cautious using them. They are only available after enabling the toggle in `Settings->Developer->Enable Community Features`.
|
||||
|
||||
To promote a car from community maintained, it must meet a few requirements. We must own one from the brand, we must sell the harness for it, has full ISO26262 in both panda and openpilot, there must be a path forward for longitudinal control, it must have AEB still enabled, and it must support fingerprinting 2.0
|
||||
|
||||
Installation Instructions
|
||||
------
|
||||
|
||||
Install openpilot on a EON by entering ``https://openpilot.comma.ai`` during the installer setup.
|
||||
Install openpilot on an EON or comma two by entering ``https://openpilot.comma.ai`` during the installer setup.
|
||||
|
||||
Follow this [video instructions](https://youtu.be/3nlkomHathI) to properly mount the EON on the windshield. Note: openpilot features an automatic pose calibration routine and openpilot performance should not be affected by small pitch and yaw misalignments caused by imprecise EON mounting.
|
||||
Follow these [video instructions](https://youtu.be/lcjqxCymins) to properly mount the device on the windshield. Note: openpilot features an automatic pose calibration routine and openpilot performance should not be affected by small pitch and yaw misalignments caused by imprecise device mounting.
|
||||
|
||||
Before placing the device on your windshield, check the state and local laws and ordinances where you drive. Some state laws prohibit or restrict the placement of objects on the windshield of a motor vehicle.
|
||||
|
||||
@@ -170,7 +197,7 @@ Many factors can impact the performance of openpilot ALC and openpilot LDW, caus
|
||||
* Poor visibility (heavy rain, snow, fog, etc.) or weather conditions that may interfere with sensor operation.
|
||||
* The road facing camera is obstructed, covered or damaged by mud, ice, snow, etc.
|
||||
* Obstruction caused by applying excessive paint or adhesive products (such as wraps, stickers, rubber coating, etc.) onto the vehicle.
|
||||
* The EON is mounted incorrectly.
|
||||
* The device is mounted incorrectly.
|
||||
* When in sharp curves, like on-off ramps, intersections etc...; openpilot is designed to be limited in the amount of steering torque it can produce.
|
||||
* In the presence of restricted lanes or construction zones.
|
||||
* When driving on highly banked roads or in presence of strong cross-wind.
|
||||
@@ -190,7 +217,7 @@ Many factors can impact the performance of openpilot ACC and openpilot FCW, caus
|
||||
* Poor visibility (heavy rain, snow, fog, etc.) or weather conditions that may interfere with sensor operation.
|
||||
* The road facing camera or radar are obstructed, covered, or damaged by mud, ice, snow, etc.
|
||||
* Obstruction caused by applying excessive paint or adhesive products (such as wraps, stickers, rubber coating, etc.) onto the vehicle.
|
||||
* The EON is mounted incorrectly.
|
||||
* The device is mounted incorrectly.
|
||||
* Approaching a toll booth, a bridge or a large metal plate.
|
||||
* When driving on roads with pedestrians, cyclists, etc...
|
||||
* In presence of traffic signs or stop lights, which are not detected by openpilot at this time.
|
||||
@@ -208,14 +235,13 @@ The list above does not represent an exhaustive list of situations that may inte
|
||||
Limitations of openpilot DM
|
||||
------
|
||||
|
||||
openpilot DM should not be considered an exact measurements of the status of alertness of the driver.
|
||||
openpilot DM should not be considered an exact measurement of the alertness of the driver.
|
||||
|
||||
Many factors can impact the performance of openpilot DM, causing it to be unable to function as intended. These include, but are not limited to:
|
||||
|
||||
* Low light conditions, such as driving at night or in dark tunnels.
|
||||
* Bright light (due to oncoming headlights, direct sunlight, etc.).
|
||||
* The driver face is partially or completely outside field of view of the driver facing camera.
|
||||
* Right hand driving vehicles.
|
||||
* The driver's face is partially or completely outside field of view of the driver facing camera.
|
||||
* The driver facing 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.
|
||||
@@ -236,7 +262,7 @@ Safety and Testing
|
||||
----
|
||||
|
||||
* openpilot observes ISO26262 guidelines, see [SAFETY.md](SAFETY.md) for more detail.
|
||||
* openpilot has software in the loop [tests](run_docker_tests.sh) that run on every commit.
|
||||
* openpilot has software in the loop [tests](.github/workflows/test.yaml) that run on every commit.
|
||||
* The safety model code lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details.
|
||||
* panda has software in the loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety).
|
||||
* Internally, we have a hardware in the loop Jenkins test suite that builds and unit tests the various processes.
|
||||
@@ -245,8 +271,23 @@ Safety and Testing
|
||||
|
||||
Testing on PC
|
||||
------
|
||||
For simplified development and experimentation, openpilot can be run in the CARLA driving simulator, which allows you to develop openpilot without a car. The whole setup should only take a few minutes.
|
||||
|
||||
Steps:
|
||||
1) Start the CARLA server on first terminal
|
||||
```
|
||||
bash -c "$(curl https://raw.githubusercontent.com/commaai/openpilot/master/tools/sim/start_carla.sh)"
|
||||
```
|
||||
2) Start openpilot on second terminal
|
||||
```
|
||||
bash -c "$(curl https://raw.githubusercontent.com/commaai/openpilot/master/tools/sim/start_openpilot_docker.sh)"
|
||||
```
|
||||
3) Press 1 to engage openpilot
|
||||
|
||||
See the full [README](tools/sim/README.md)
|
||||
|
||||
You should also take a look at the tools directory in master: lots of tools you can use to replay driving data, test, and develop openpilot from your PC.
|
||||
|
||||
Check out the tools directory in master: lots of tools you can use to replay driving data, test and develop openpilot from your pc.
|
||||
|
||||
Community and Contributing
|
||||
------
|
||||
@@ -255,7 +296,7 @@ openpilot is developed by [comma](https://comma.ai/) and by users like you. We w
|
||||
|
||||
You can add support for your car by following guides we have written for [Brand](https://medium.com/@comma_ai/how-to-write-a-car-port-for-openpilot-7ce0785eda84) and [Model](https://medium.com/@comma_ai/openpilot-port-guide-for-toyota-models-e5467f4b5fe6) ports. Generally, a car with adaptive cruise control and lane keep assist is a good candidate. [Join our Discord](https://discord.comma.ai) to discuss car ports: most car makes have a dedicated channel.
|
||||
|
||||
Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs/). We also have a [bounty program](https://comma.ai/bounties.html).
|
||||
Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs/).
|
||||
|
||||
And [follow us on Twitter](https://twitter.com/comma_ai).
|
||||
|
||||
@@ -263,15 +304,15 @@ Directory Structure
|
||||
------
|
||||
.
|
||||
├── apk # The apk files used for the UI
|
||||
├── cereal # The messaging spec and libs used for all logs on EON
|
||||
├── cereal # The messaging spec and libs used for all logs
|
||||
├── common # Library like functionality we've developed here
|
||||
├── installer/updater # Manages auto-updates of openpilot
|
||||
├── opendbc # Files showing how to interpret data from cars
|
||||
├── panda # Code used to communicate on CAN
|
||||
├── phonelibs # Libraries used on EON
|
||||
├── pyextra # Libraries used on EON
|
||||
├── phonelibs # Libraries used on NEOS devices
|
||||
├── pyextra # Libraries used on NEOS devices
|
||||
└── selfdrive # Code needed to drive the car
|
||||
├── assets # Fonts and images for UI
|
||||
├── assets # Fonts, images and sounds for UI
|
||||
├── athena # Allows communication with the app
|
||||
├── boardd # Daemon to talk to the board
|
||||
├── camerad # Driver to capture images from the camera sensors
|
||||
@@ -285,7 +326,7 @@ Directory Structure
|
||||
├── modeld # Driving and monitoring model runners
|
||||
├── proclogd # Logs information from proc
|
||||
├── sensord # IMU / GPS interface code
|
||||
├── tests # Unit tests, system tests and a car simulator
|
||||
├── test # Unit tests, system tests and a car simulator
|
||||
└── ui # The UI
|
||||
|
||||
To understand how the services interact, see `cereal/service_list.yaml`.
|
||||
@@ -304,3 +345,9 @@ NO WARRANTY EXPRESSED OR IMPLIED.**
|
||||
---
|
||||
|
||||
<img src="https://d1qb2nb5cznatu.cloudfront.net/startups/i/1061157-bc7e9bf3b246ece7322e6ffe653f6af8-medium_jpg.jpg?buster=1458363130" width="75"></img> <img src="https://cdn-images-1.medium.com/max/1600/1*C87EjxGeMPrkTuVRVWVg4w.png" width="225"></img>
|
||||
|
||||
[](https://github.com/commaai/openpilot/actions)
|
||||
[](https://lgtm.com/projects/g/commaai/openpilot/alerts/)
|
||||
[](https://lgtm.com/projects/g/commaai/openpilot/context:python)
|
||||
[](https://lgtm.com/projects/g/commaai/openpilot/context:cpp)
|
||||
[](https://codecov.io/gh/commaai/openpilot)
|
||||
|
||||
+141
-51
@@ -1,3 +1,93 @@
|
||||
Version 0.8.0 (2020-11-30)
|
||||
========================
|
||||
* New driving model: fully 3D and improved cut-in detection
|
||||
* UI draws 2 road edges, 4 lanelines and paths in 3D
|
||||
* Major fixes to cut-in detection for openpilot longitudinal
|
||||
* Grey panda is no longer supported, upgrade to comma two or black panda
|
||||
* Lexus NX 2018 support thanks to matt12eagles!
|
||||
* Kia Niro EV 2020 support thanks to nickn17!
|
||||
* Toyota Prius 2021 support thanks to rav4kumar!
|
||||
* Improved lane positioning with uncertain lanelines, wide lanes and exits
|
||||
* Improved lateral control for Prius and Subaru
|
||||
|
||||
Version 0.7.10 (2020-10-29)
|
||||
========================
|
||||
* Grey panda is deprecated, upgrade to comma two or black panda
|
||||
* NEOS update: update to Python 3.8.2 and lower CPU frequency
|
||||
* Improved thermals due to reduced CPU frequency
|
||||
* Update SNPE to 1.41.0
|
||||
* Reduced offroad power consumption
|
||||
* Various system stability improvements
|
||||
* Acura RDX 2020 support thanks to csouers!
|
||||
|
||||
Version 0.7.9 (2020-10-09)
|
||||
========================
|
||||
* Improved car battery power management
|
||||
* Improved updater robustness
|
||||
* Improved realtime performance
|
||||
* Reduced UI and modeld lags
|
||||
* Increased torque on 2020 Hyundai Sonata and Palisade
|
||||
|
||||
Version 0.7.8 (2020-08-19)
|
||||
========================
|
||||
* New driver monitoring model: improved face detection and better compatibility with sunglasses
|
||||
* Download NEOS operating system updates in the background
|
||||
* Improved updater reliability and responsiveness
|
||||
* Hyundai Kona 2020, Veloster 2019, and Genesis G70 2018 support thanks to xps-genesis!
|
||||
|
||||
Version 0.7.7 (2020-07-20)
|
||||
========================
|
||||
* White panda is no longer supported, upgrade to comma two or black panda
|
||||
* Improved vehicle model estimation using high precision localizer
|
||||
* Improved thermal management on comma two
|
||||
* Improved autofocus for road-facing camera
|
||||
* Improved noise performance for driver-facing camera
|
||||
* Block lane change start using blindspot monitor on select Toyota, Hyundai, and Subaru
|
||||
* Fix GM ignition detection
|
||||
* Code cleanup and smaller release sizes
|
||||
* Hyundai Sonata 2020 promoted to officially supported car
|
||||
* Hyundai Ioniq Electric Limited 2019 and Ioniq SE 2020 support thanks to baldwalker!
|
||||
* Subaru Forester 2019 and Ascent 2019 support thanks to martinl!
|
||||
|
||||
Version 0.7.6.1 (2020-06-16)
|
||||
========================
|
||||
* Hotfix: update kernel on some comma twos (orders #8570-#8680)
|
||||
|
||||
Version 0.7.6 (2020-06-05)
|
||||
========================
|
||||
* White panda is deprecated, upgrade to comma two or black panda
|
||||
* 2017 Nissan X-Trail, 2018-19 Leaf and 2019 Rogue support thanks to avolmensky!
|
||||
* 2017 Mazda CX-5 support in dashcam mode thanks to Jafaral!
|
||||
* Huge CPU savings in modeld by using thneed!
|
||||
* Lots of code cleanup and refactors
|
||||
|
||||
Version 0.7.5 (2020-05-13)
|
||||
========================
|
||||
* Right-Hand Drive support for both driving and driver monitoring!
|
||||
* New driving model: improved at sharp turns and lead speed estimation
|
||||
* New driver monitoring model: overall improvement on comma two
|
||||
* Driver camera preview in settings to improve mounting position
|
||||
* Added support for many Hyundai, Kia, Genesis models thanks to xx979xx!
|
||||
* Improved lateral tuning for 2020 Toyota Rav 4 (hybrid)
|
||||
|
||||
Version 0.7.4 (2020-03-20)
|
||||
========================
|
||||
* New driving model: improved lane changes and lead car detection
|
||||
* Improved driver monitoring model: improve eye detection
|
||||
* Improved calibration stability
|
||||
* Improved lateral control on some 2019 and 2020 Toyota Prius
|
||||
* Improved lateral control on VW Golf: 20% more steering torque
|
||||
* Fixed bug where some 2017 and 2018 Toyota C-HR would use the wrong steering angle sensor
|
||||
* Support for Honda Insight thanks to theantihero!
|
||||
* Code cleanup in car abstraction layers and ui
|
||||
|
||||
Version 0.7.3 (2020-02-21)
|
||||
========================
|
||||
* Support for 2020 Highlander thanks to che220!
|
||||
* Support for 2018 Lexus NX 300h thanks to kengggg!
|
||||
* Speed up ECU firmware query
|
||||
* Fix bug where manager would sometimes hang after shutting down the car
|
||||
|
||||
Version 0.7.2 (2020-02-07)
|
||||
========================
|
||||
* ECU firmware version based fingerprinting for Honda & Toyota
|
||||
@@ -414,96 +504,96 @@ Version 0.3.4 (2017-07-28)
|
||||
|
||||
Version 0.3.3 (2017-06-28)
|
||||
===========================
|
||||
* Improved model trained on more data
|
||||
* Alpha CR-V support thanks to energee and johnnwvs!
|
||||
* Using the opendbc project for DBC files
|
||||
* Minor performance improvements
|
||||
* UI update thanks to pjlao307
|
||||
* Power off button
|
||||
* 6% more torque on the Civic
|
||||
* Improved model trained on more data
|
||||
* Alpha CR-V support thanks to energee and johnnwvs!
|
||||
* Using the opendbc project for DBC files
|
||||
* Minor performance improvements
|
||||
* UI update thanks to pjlao307
|
||||
* Power off button
|
||||
* 6% more torque on the Civic
|
||||
|
||||
Version 0.3.2 (2017-05-22)
|
||||
===========================
|
||||
* Minor stability bugfixes
|
||||
* Added metrics and rear view mirror disable to settings
|
||||
* Update model with more crowdsourced data
|
||||
* Minor stability bugfixes
|
||||
* Added metrics and rear view mirror disable to settings
|
||||
* Update model with more crowdsourced data
|
||||
|
||||
Version 0.3.1 (2017-05-17)
|
||||
===========================
|
||||
* visiond stability bugfix
|
||||
* Add logging for angle and flashing
|
||||
* visiond stability bugfix
|
||||
* Add logging for angle and flashing
|
||||
|
||||
Version 0.3.0 (2017-05-12)
|
||||
===========================
|
||||
* Add CarParams struct to improve the abstraction layer
|
||||
* Refactor visiond IPC to support multiple clients
|
||||
* Add raw GPS and beginning support for navigation
|
||||
* Improve model in visiond using crowdsourced data
|
||||
* Add improved system logging to diagnose instability
|
||||
* Rewrite baseui in React Native
|
||||
* Moved calibration to the cloud
|
||||
* Add CarParams struct to improve the abstraction layer
|
||||
* Refactor visiond IPC to support multiple clients
|
||||
* Add raw GPS and beginning support for navigation
|
||||
* Improve model in visiond using crowdsourced data
|
||||
* Add improved system logging to diagnose instability
|
||||
* Rewrite baseui in React Native
|
||||
* Moved calibration to the cloud
|
||||
|
||||
Version 0.2.9 (2017-03-01)
|
||||
===========================
|
||||
* Retain compatibility with NEOS v1
|
||||
* Retain compatibility with NEOS v1
|
||||
|
||||
Version 0.2.8 (2017-02-27)
|
||||
===========================
|
||||
* Fix bug where frames were being dropped in minute 71
|
||||
* Fix bug where frames were being dropped in minute 71
|
||||
|
||||
Version 0.2.7 (2017-02-08)
|
||||
===========================
|
||||
* Better performance and pictures at night
|
||||
* Fix ptr alignment issue in boardd
|
||||
* Fix brake error light, fix crash if too cold
|
||||
* Better performance and pictures at night
|
||||
* Fix ptr alignment issue in boardd
|
||||
* Fix brake error light, fix crash if too cold
|
||||
|
||||
Version 0.2.6 (2017-01-31)
|
||||
===========================
|
||||
* Fix bug in visiond model execution
|
||||
* Fix bug in visiond model execution
|
||||
|
||||
Version 0.2.5 (2017-01-30)
|
||||
===========================
|
||||
* Fix race condition in manager
|
||||
* Fix race condition in manager
|
||||
|
||||
Version 0.2.4 (2017-01-27)
|
||||
===========================
|
||||
* OnePlus 3T support
|
||||
* Enable installation as NEOS app
|
||||
* Various minor bugfixes
|
||||
* OnePlus 3T support
|
||||
* Enable installation as NEOS app
|
||||
* Various minor bugfixes
|
||||
|
||||
Version 0.2.3 (2017-01-11)
|
||||
===========================
|
||||
* Reduce space usage by 80%
|
||||
* Add better logging
|
||||
* Add Travis CI
|
||||
* Reduce space usage by 80%
|
||||
* Add better logging
|
||||
* Add Travis CI
|
||||
|
||||
Version 0.2.2 (2017-01-10)
|
||||
===========================
|
||||
* Board triggers started signal on CAN messages
|
||||
* Improved autoexposure
|
||||
* Handle out of space, improve upload status
|
||||
* Board triggers started signal on CAN messages
|
||||
* Improved autoexposure
|
||||
* Handle out of space, improve upload status
|
||||
|
||||
Version 0.2.1 (2016-12-14)
|
||||
===========================
|
||||
* Performance improvements, removal of more numpy
|
||||
* Fix boardd process priority
|
||||
* Make counter timer reset on use of steering wheel
|
||||
* Performance improvements, removal of more numpy
|
||||
* Fix boardd process priority
|
||||
* Make counter timer reset on use of steering wheel
|
||||
|
||||
Version 0.2 (2016-12-12)
|
||||
=========================
|
||||
* Car/Radar abstraction layers have shipped, see cereal/car.capnp
|
||||
* controlsd has been refactored
|
||||
* Shipped plant model and testing maneuvers
|
||||
* visiond exits more gracefully now
|
||||
* Hardware encoder in visiond should always init
|
||||
* ui now turns off the screen after 30 seconds
|
||||
* Switch to openpilot release branch for future releases
|
||||
* Added preliminary Docker container to run tests on PC
|
||||
* Car/Radar abstraction layers have shipped, see cereal/car.capnp
|
||||
* controlsd has been refactored
|
||||
* Shipped plant model and testing maneuvers
|
||||
* visiond exits more gracefully now
|
||||
* Hardware encoder in visiond should always init
|
||||
* ui now turns off the screen after 30 seconds
|
||||
* Switch to openpilot release branch for future releases
|
||||
* Added preliminary Docker container to run tests on PC
|
||||
|
||||
Version 0.1 (2016-11-29)
|
||||
=========================
|
||||
* Initial release of openpilot
|
||||
* Adaptive cruise control is working
|
||||
* Lane keep assist is working
|
||||
* Support for Acura ILX 2016 with AcuraWatch Plus
|
||||
* Support for Honda Civic 2016 Touring Edition
|
||||
* Initial release of openpilot
|
||||
* Adaptive cruise control is working
|
||||
* Lane keep assist is working
|
||||
* Support for Acura ILX 2016 with AcuraWatch Plus
|
||||
* Support for Honda Civic 2016 Touring Edition
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openpilot Safety
|
||||
======
|
||||
|
||||
openpilot is an Adaptive Cruise Control (ACC) and Automated Lane Centering (ALC) system.
|
||||
openpilot is an Adaptive Cruise Control (ACC) and Automated Lane Centering (ALC) system.
|
||||
Like other ACC and ALC systems, openpilot is a failsafe passive system and it requires the
|
||||
driver to be alert and to pay attention at all times.
|
||||
|
||||
@@ -22,7 +22,7 @@ hardware-in-the-loop and in-vehicle tests before each software release.
|
||||
Following Hazard and Risk Analysis and FMEA, at a very high level, we have designed openpilot
|
||||
ensuring two main safety requirements.
|
||||
|
||||
1. The driver must always be capable to immediately retake manual control of the vehicle,
|
||||
1. The driver must always be capable to immediately retake manual control of the vehicle,
|
||||
by stepping on either pedal or by pressing the cancel button.
|
||||
2. The vehicle must not alter its trajectory too quickly for the driver to safely
|
||||
react. This means that while the system is engaged, the actuators are constrained
|
||||
|
||||
+137
-56
@@ -1,7 +1,15 @@
|
||||
import Cython
|
||||
import distutils
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import platform
|
||||
import numpy as np
|
||||
from sysconfig import get_paths
|
||||
|
||||
TICI = os.path.isfile('/TICI')
|
||||
Decider('MD5-timestamp')
|
||||
|
||||
AddOption('--test',
|
||||
action='store_true',
|
||||
@@ -11,64 +19,90 @@ AddOption('--asan',
|
||||
action='store_true',
|
||||
help='turn on ASAN')
|
||||
|
||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||
# Rebuild cython extensions if python, distutils, or cython change
|
||||
cython_dependencies = [Value(v) for v in (sys.version, distutils.__version__, Cython.__version__)]
|
||||
Export('cython_dependencies')
|
||||
|
||||
real_arch = arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||
if platform.system() == "Darwin":
|
||||
arch = "Darwin"
|
||||
|
||||
if arch == "aarch64":
|
||||
if arch == "aarch64" and TICI:
|
||||
arch = "larch64"
|
||||
|
||||
USE_WEBCAM = os.getenv("USE_WEBCAM") is not None
|
||||
QCOM_REPLAY = arch == "aarch64" and os.getenv("QCOM_REPLAY") is not None
|
||||
|
||||
if arch == "aarch64" or arch == "larch64":
|
||||
lenv = {
|
||||
"LD_LIBRARY_PATH": '/data/data/com.termux/files/usr/lib',
|
||||
"PATH": os.environ['PATH'],
|
||||
"ANDROID_DATA": os.environ['ANDROID_DATA'],
|
||||
"ANDROID_ROOT": os.environ['ANDROID_ROOT'],
|
||||
}
|
||||
|
||||
if arch == "aarch64":
|
||||
# android
|
||||
lenv["ANDROID_DATA"] = os.environ['ANDROID_DATA']
|
||||
lenv["ANDROID_ROOT"] = os.environ['ANDROID_ROOT']
|
||||
|
||||
cpppath = [
|
||||
"#phonelibs/opencl/include",
|
||||
]
|
||||
|
||||
libpath = [
|
||||
"#phonelibs/snpe/aarch64-android-clang3.8",
|
||||
"/usr/lib",
|
||||
"/data/data/com.termux/files/usr/lib",
|
||||
"/system/vendor/lib64",
|
||||
"/system/comma/usr/lib",
|
||||
"#phonelibs/nanovg",
|
||||
"#phonelibs/libyuv/lib",
|
||||
]
|
||||
|
||||
cflags = ["-DQCOM", "-mcpu=cortex-a57"]
|
||||
cxxflags = ["-DQCOM", "-mcpu=cortex-a57"]
|
||||
if arch == "larch64":
|
||||
libpath += [
|
||||
"#phonelibs/snpe/larch64",
|
||||
"#phonelibs/libyuv/larch64/lib",
|
||||
"/usr/lib/aarch64-linux-gnu"
|
||||
]
|
||||
cflags = ["-DQCOM2", "-mcpu=cortex-a57"]
|
||||
cxxflags = ["-DQCOM2", "-mcpu=cortex-a57"]
|
||||
rpath = ["/usr/local/lib"]
|
||||
else:
|
||||
libpath += [
|
||||
"#phonelibs/snpe/aarch64",
|
||||
"#phonelibs/libyuv/lib",
|
||||
"/system/vendor/lib64"
|
||||
]
|
||||
cflags = ["-DQCOM", "-mcpu=cortex-a57"]
|
||||
cxxflags = ["-DQCOM", "-mcpu=cortex-a57"]
|
||||
rpath = []
|
||||
|
||||
if QCOM_REPLAY:
|
||||
cflags += ["-DQCOM_REPLAY"]
|
||||
cxxflags += ["-DQCOM_REPLAY"]
|
||||
|
||||
rpath = ["/system/vendor/lib64"]
|
||||
else:
|
||||
cflags = []
|
||||
cxxflags = []
|
||||
|
||||
lenv = {
|
||||
"PATH": "#external/bin:" + os.environ['PATH'],
|
||||
}
|
||||
cpppath = [
|
||||
"#phonelibs/capnp-cpp/include",
|
||||
"#phonelibs/capnp-c/include",
|
||||
"#phonelibs/zmq/x64/include",
|
||||
"#external/tensorflow/include",
|
||||
]
|
||||
|
||||
if arch == "Darwin":
|
||||
libpath = [
|
||||
"#phonelibs/capnp-cpp/mac/lib",
|
||||
"#phonelibs/capnp-c/mac/lib",
|
||||
"#phonelibs/libyuv/mac/lib",
|
||||
"#cereal",
|
||||
"#selfdrive/common",
|
||||
"/usr/local/lib",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Libraries",
|
||||
]
|
||||
cflags += ["-DGL_SILENCE_DEPRECATION"]
|
||||
cxxflags += ["-DGL_SILENCE_DEPRECATION"]
|
||||
else:
|
||||
libpath = [
|
||||
"#phonelibs/capnp-cpp/x64/lib",
|
||||
"#phonelibs/capnp-c/x64/lib",
|
||||
"#phonelibs/snpe/x86_64-linux-clang",
|
||||
"#phonelibs/zmq/x64/lib",
|
||||
"#phonelibs/libyuv/x64/lib",
|
||||
"#external/zmq/lib",
|
||||
"#external/tensorflow/lib",
|
||||
"#cereal",
|
||||
"#selfdrive/common",
|
||||
@@ -76,35 +110,44 @@ else:
|
||||
"/usr/local/lib",
|
||||
]
|
||||
|
||||
rpath = ["phonelibs/capnp-cpp/x64/lib",
|
||||
"phonelibs/zmq/x64/lib",
|
||||
"external/tensorflow/lib",
|
||||
"cereal",
|
||||
"selfdrive/common"]
|
||||
rpath = [
|
||||
"phonelibs/snpe/x86_64-linux-clang",
|
||||
"external/tensorflow/lib",
|
||||
"cereal",
|
||||
"selfdrive/common"
|
||||
]
|
||||
|
||||
# allows shared libraries to work globally
|
||||
rpath = [os.path.join(os.getcwd(), x) for x in rpath]
|
||||
|
||||
cflags = []
|
||||
cxxflags = []
|
||||
|
||||
ccflags_asan = ["-fsanitize=address", "-fno-omit-frame-pointer"] if GetOption('asan') else []
|
||||
ldflags_asan = ["-fsanitize=address"] if GetOption('asan') else []
|
||||
if GetOption('asan'):
|
||||
ccflags_asan = ["-fsanitize=address", "-fno-omit-frame-pointer"]
|
||||
ldflags_asan = ["-fsanitize=address"]
|
||||
else:
|
||||
ccflags_asan = []
|
||||
ldflags_asan = []
|
||||
|
||||
# change pythonpath to this
|
||||
lenv["PYTHONPATH"] = Dir("#").path
|
||||
|
||||
#Get the path for Python.h for cython linking
|
||||
python_path = get_paths()['include']
|
||||
numpy_path = np.get_include()
|
||||
|
||||
env = Environment(
|
||||
ENV=lenv,
|
||||
CCFLAGS=[
|
||||
"-g",
|
||||
"-fPIC",
|
||||
"-O2",
|
||||
"-Werror=implicit-function-declaration",
|
||||
"-Werror=incompatible-pointer-types",
|
||||
"-Werror=int-conversion",
|
||||
"-Werror=return-type",
|
||||
"-Werror=format-extra-args",
|
||||
"-Wunused",
|
||||
"-Werror",
|
||||
"-Wno-unknown-warning-option",
|
||||
"-Wno-deprecated-register",
|
||||
"-Wno-register",
|
||||
"-Wno-inconsistent-missing-override",
|
||||
"-Wno-c99-designator",
|
||||
"-Wno-reorder-init-list",
|
||||
] + cflags + ccflags_asan,
|
||||
|
||||
CPPPATH=cpppath + [
|
||||
@@ -113,11 +156,8 @@ env = Environment(
|
||||
"#phonelibs/bzip2",
|
||||
"#phonelibs/libyuv/include",
|
||||
"#phonelibs/openmax/include",
|
||||
"#phonelibs/json/src",
|
||||
"#phonelibs/json11",
|
||||
"#phonelibs/eigen",
|
||||
"#phonelibs/curl/include",
|
||||
"#phonelibs/opencv/include",
|
||||
"#phonelibs/libgralloc/include",
|
||||
"#phonelibs/android_frameworks_native/include",
|
||||
"#phonelibs/android_hardware_libhardware/include",
|
||||
@@ -125,11 +165,14 @@ env = Environment(
|
||||
"#phonelibs/linux/include",
|
||||
"#phonelibs/snpe/include",
|
||||
"#phonelibs/nanovg",
|
||||
"#selfdrive/boardd",
|
||||
"#selfdrive/common",
|
||||
"#selfdrive/camerad",
|
||||
"#selfdrive/camerad/include",
|
||||
"#selfdrive/loggerd/include",
|
||||
"#selfdrive/modeld",
|
||||
"#selfdrive/sensord",
|
||||
"#selfdrive/ui",
|
||||
"#cereal/messaging",
|
||||
"#cereal",
|
||||
"#opendbc/can",
|
||||
@@ -142,17 +185,34 @@ env = Environment(
|
||||
RPATH=rpath,
|
||||
|
||||
CFLAGS=["-std=gnu11"] + cflags,
|
||||
CXXFLAGS=["-std=c++14"] + cxxflags,
|
||||
LIBPATH=libpath +
|
||||
[
|
||||
CXXFLAGS=["-std=c++1z"] + cxxflags,
|
||||
LIBPATH=libpath + [
|
||||
"#cereal",
|
||||
"#selfdrive/boardd",
|
||||
"#selfdrive/common",
|
||||
"#phonelibs",
|
||||
]
|
||||
],
|
||||
CYTHONCFILESUFFIX=".cpp",
|
||||
tools=["default", "cython"]
|
||||
)
|
||||
|
||||
if os.environ.get('SCONS_CACHE'):
|
||||
CacheDir('/tmp/scons_cache')
|
||||
cache_dir = '/tmp/scons_cache'
|
||||
|
||||
if os.getenv('CI'):
|
||||
branch = os.getenv('GIT_BRANCH')
|
||||
|
||||
if QCOM_REPLAY:
|
||||
cache_dir = '/tmp/scons_cache_qcom_replay'
|
||||
elif branch is not None and branch != 'master':
|
||||
cache_dir_branch = '/tmp/scons_cache_' + branch
|
||||
if not os.path.isdir(cache_dir_branch) and os.path.isdir(cache_dir):
|
||||
shutil.copytree(cache_dir, cache_dir_branch)
|
||||
cache_dir = cache_dir_branch
|
||||
elif TICI:
|
||||
cache_dir = '/data/scons_cache'
|
||||
|
||||
CacheDir(cache_dir)
|
||||
|
||||
node_interval = 5
|
||||
node_count = 0
|
||||
@@ -175,10 +235,28 @@ def abspath(x):
|
||||
# rpath works elsewhere
|
||||
return x[0].path.rsplit("/", 1)[1][:-3]
|
||||
|
||||
#zmq = 'zmq'
|
||||
#Cython build enviroment
|
||||
envCython = env.Clone()
|
||||
envCython["CPPPATH"] += [python_path, numpy_path]
|
||||
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-deprecated-declarations"]
|
||||
|
||||
python_libs = []
|
||||
if arch == "Darwin":
|
||||
envCython["LINKFLAGS"]=["-bundle", "-undefined", "dynamic_lookup"]
|
||||
elif arch == "aarch64":
|
||||
envCython["LINKFLAGS"]=["-shared"]
|
||||
|
||||
python_libs.append(os.path.basename(python_path))
|
||||
else:
|
||||
envCython["LINKFLAGS"]=["-pthread", "-shared"]
|
||||
|
||||
envCython["LIBS"] = python_libs
|
||||
|
||||
Export('envCython')
|
||||
|
||||
# still needed for apks
|
||||
zmq = FindFile("libzmq.a", libpath)
|
||||
Export('env', 'arch', 'zmq', 'SHARED')
|
||||
zmq = 'zmq'
|
||||
Export('env', 'arch', 'real_arch', 'zmq', 'SHARED', 'USE_WEBCAM', 'QCOM_REPLAY')
|
||||
|
||||
# cereal and messaging are shared with the system
|
||||
SConscript(['cereal/SConscript'])
|
||||
@@ -196,7 +274,7 @@ Import('_common', '_visionipc', '_gpucommon', '_gpu_libs')
|
||||
if SHARED:
|
||||
common, visionipc, gpucommon = abspath(common), abspath(visionipc), abspath(gpucommon)
|
||||
else:
|
||||
common = [_common, 'json']
|
||||
common = [_common, 'json11']
|
||||
visionipc = _visionipc
|
||||
gpucommon = [_gpucommon] + _gpu_libs
|
||||
|
||||
@@ -206,27 +284,30 @@ SConscript(['opendbc/can/SConscript'])
|
||||
|
||||
SConscript(['common/SConscript'])
|
||||
SConscript(['common/kalman/SConscript'])
|
||||
SConscript(['common/transformations/SConscript'])
|
||||
SConscript(['phonelibs/SConscript'])
|
||||
|
||||
if arch != "Darwin":
|
||||
SConscript(['selfdrive/camerad/SConscript'])
|
||||
SConscript(['selfdrive/modeld/SConscript'])
|
||||
SConscript(['selfdrive/camerad/SConscript'])
|
||||
SConscript(['selfdrive/modeld/SConscript'])
|
||||
|
||||
SConscript(['selfdrive/controls/lib/cluster/SConscript'])
|
||||
SConscript(['selfdrive/controls/lib/lateral_mpc/SConscript'])
|
||||
SConscript(['selfdrive/controls/lib/longitudinal_mpc/SConscript'])
|
||||
SConscript(['selfdrive/controls/lib/longitudinal_mpc_model/SConscript'])
|
||||
|
||||
SConscript(['selfdrive/boardd/SConscript'])
|
||||
SConscript(['selfdrive/proclogd/SConscript'])
|
||||
SConscript(['selfdrive/clocksd/SConscript'])
|
||||
|
||||
SConscript(['selfdrive/ui/SConscript'])
|
||||
SConscript(['selfdrive/loggerd/SConscript'])
|
||||
|
||||
if arch == "aarch64":
|
||||
SConscript(['selfdrive/logcatd/SConscript'])
|
||||
SConscript(['selfdrive/sensord/SConscript'])
|
||||
SConscript(['selfdrive/clocksd/SConscript'])
|
||||
|
||||
SConscript(['selfdrive/locationd/SConscript'])
|
||||
SConscript(['selfdrive/locationd/models/SConscript'])
|
||||
SConscript(['selfdrive/sensord/SConscript'])
|
||||
SConscript(['selfdrive/ui/SConscript'])
|
||||
|
||||
# TODO: finish cereal, dbcbuilder, MPC
|
||||
if arch != "Darwin":
|
||||
SConscript(['selfdrive/logcatd/SConscript'])
|
||||
|
||||
if arch == "x86_64":
|
||||
SConscript(['tools/lib/index_log/SConscript'])
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Place apks here that you want to be installed with the name <app>.apk
|
||||
|
||||
These won't be committed into one, but for now baseui will ship with openpilot.
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:309b46b7c38f10da52b18b0340eb3c57b633558a9a27c3ca4116474969ebb456
|
||||
size 84675
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d563bc9d43408f78d859538b50999cd7703487bdbe1fba1b11a3810205cd26c4
|
||||
size 2856327
|
||||
Binary file not shown.
-1
Submodule apks deleted from d4f4f07a15
-1
Submodule cereal deleted from ab32956aaf
@@ -0,0 +1,14 @@
|
||||
gen
|
||||
node_modules
|
||||
package-lock.json
|
||||
*.pyc
|
||||
__pycache__
|
||||
.*.swp
|
||||
.*.swo
|
||||
libcereal*.a
|
||||
libmessaging.*
|
||||
libmessaging_shared.*
|
||||
services.h
|
||||
.sconsign.dblite
|
||||
libcereal_shared.*
|
||||
.mypy_cache/
|
||||
@@ -0,0 +1,65 @@
|
||||
Import('env', 'arch', 'zmq', 'cython_dependencies')
|
||||
|
||||
import shutil
|
||||
|
||||
cereal_dir = Dir('.')
|
||||
gen_dir = Dir('gen')
|
||||
messaging_dir = Dir('messaging')
|
||||
|
||||
# TODO: remove src-prefix and cereal from command string. can we set working directory?
|
||||
env.Command(["gen/c/include/c++.capnp.h", "gen/c/include/java.capnp.h"], [], "mkdir -p " + gen_dir.path + "/c/include && touch $TARGETS")
|
||||
env.Command(['gen/cpp/car.capnp.c++', 'gen/cpp/log.capnp.c++', 'gen/cpp/car.capnp.h', 'gen/cpp/log.capnp.h'],
|
||||
['car.capnp', 'log.capnp'],
|
||||
f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/")
|
||||
|
||||
if shutil.which('capnpc-java'):
|
||||
env.Command(['gen/java/Car.java', 'gen/java/Log.java'],
|
||||
['car.capnp', 'log.capnp'],
|
||||
f"capnpc $SOURCES --src-prefix={cereal_dir.path} -o java:{gen_dir.path}/java/")
|
||||
|
||||
# TODO: remove non shared cereal and messaging
|
||||
cereal_objects = env.SharedObject([
|
||||
'gen/cpp/car.capnp.c++',
|
||||
'gen/cpp/log.capnp.c++',
|
||||
])
|
||||
|
||||
env.Library('cereal', cereal_objects)
|
||||
env.SharedLibrary('cereal_shared', cereal_objects)
|
||||
|
||||
cereal_dir = Dir('.')
|
||||
services_h = env.Command(['services.h'],
|
||||
['service_list.yaml', 'services.py'],
|
||||
'python3 ' + cereal_dir.path + '/services.py > $TARGET')
|
||||
|
||||
messaging_objects = env.SharedObject([
|
||||
'messaging/messaging.cc',
|
||||
'messaging/impl_zmq.cc',
|
||||
'messaging/impl_msgq.cc',
|
||||
'messaging/msgq.cc',
|
||||
'messaging/socketmaster.cc',
|
||||
])
|
||||
|
||||
messaging_lib = env.Library('messaging', messaging_objects)
|
||||
Depends('messaging/impl_zmq.cc', services_h)
|
||||
|
||||
# note, this rebuilds the deps shared, zmq is statically linked to make APK happy
|
||||
# TODO: get APK to load system zmq to remove the static link
|
||||
if arch == "aarch64":
|
||||
zmq_static = FindFile("libzmq.a", "/usr/lib")
|
||||
shared_lib_shared_lib = [zmq_static, 'm', 'stdc++', "gnustl_shared", "kj", "capnp"]
|
||||
env.SharedLibrary('messaging_shared', messaging_objects, LIBS=shared_lib_shared_lib)
|
||||
|
||||
env.Program('messaging/bridge', ['messaging/bridge.cc'], LIBS=[messaging_lib, 'zmq'])
|
||||
Depends('messaging/bridge.cc', services_h)
|
||||
|
||||
# different target?
|
||||
#env.Program('messaging/demo', ['messaging/demo.cc'], LIBS=[messaging_lib, 'zmq'])
|
||||
|
||||
|
||||
env.Command(['messaging/messaging_pyx.so', 'messaging/messaging_pyx.cpp'],
|
||||
cython_dependencies + [messaging_lib, 'messaging/messaging_pyx_setup.py', 'messaging/messaging_pyx.pyx', 'messaging/messaging.pxd'],
|
||||
"cd " + messaging_dir.path + " && python3 messaging_pyx_setup.py build_ext --inplace")
|
||||
|
||||
|
||||
if GetOption('test'):
|
||||
env.Program('messaging/test_runner', ['messaging/test_runner.cc', 'messaging/msgq_tests.cc'], LIBS=[messaging_lib])
|
||||
@@ -0,0 +1,9 @@
|
||||
# pylint: skip-file
|
||||
import os
|
||||
import capnp
|
||||
|
||||
CEREAL_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
capnp.remove_import_hook()
|
||||
|
||||
log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"))
|
||||
car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp"))
|
||||
@@ -0,0 +1,538 @@
|
||||
using Cxx = import "./include/c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
using Java = import "./include/java.capnp";
|
||||
$Java.package("ai.comma.openpilot.cereal");
|
||||
$Java.outerClassname("Car");
|
||||
|
||||
@0x8e2af1e708af8b8d;
|
||||
|
||||
# ******* events causing controls state machine transition *******
|
||||
|
||||
struct CarEvent @0x9b1657f34caf3ad3 {
|
||||
name @0 :EventName;
|
||||
enable @1 :Bool;
|
||||
noEntry @2 :Bool;
|
||||
warning @3 :Bool; # alerts presented only when enabled or soft disabling
|
||||
userDisable @4 :Bool;
|
||||
softDisable @5 :Bool;
|
||||
immediateDisable @6 :Bool;
|
||||
preEnable @7 :Bool;
|
||||
permanent @8 :Bool; # alerts presented regardless of openpilot state
|
||||
|
||||
enum EventName @0xbaa8c5d505f727de {
|
||||
# TODO: copy from error list
|
||||
canError @0;
|
||||
steerUnavailable @1;
|
||||
brakeUnavailable @2;
|
||||
wrongGear @4;
|
||||
doorOpen @5;
|
||||
seatbeltNotLatched @6;
|
||||
espDisabled @7;
|
||||
wrongCarMode @8;
|
||||
steerTempUnavailable @9;
|
||||
reverseGear @10;
|
||||
buttonCancel @11;
|
||||
buttonEnable @12;
|
||||
pedalPressed @13;
|
||||
cruiseDisabled @14;
|
||||
radarCanError @15;
|
||||
speedTooLow @17;
|
||||
outOfSpace @18;
|
||||
overheat @19;
|
||||
calibrationIncomplete @20;
|
||||
calibrationInvalid @21;
|
||||
controlsMismatch @22;
|
||||
pcmEnable @23;
|
||||
pcmDisable @24;
|
||||
noTarget @25;
|
||||
radarFault @26;
|
||||
brakeHold @28;
|
||||
parkBrake @29;
|
||||
manualRestart @30;
|
||||
lowSpeedLockout @31;
|
||||
plannerError @32;
|
||||
debugAlert @34;
|
||||
steerTempUnavailableMute @35;
|
||||
resumeRequired @36;
|
||||
preDriverDistracted @37;
|
||||
promptDriverDistracted @38;
|
||||
driverDistracted @39;
|
||||
preDriverUnresponsive @43;
|
||||
promptDriverUnresponsive @44;
|
||||
driverUnresponsive @45;
|
||||
belowSteerSpeed @46;
|
||||
lowBattery @48;
|
||||
vehicleModelInvalid @50;
|
||||
controlsFailed @51;
|
||||
sensorDataInvalid @52;
|
||||
commIssue @53;
|
||||
tooDistracted @54;
|
||||
posenetInvalid @55;
|
||||
soundsUnavailable @56;
|
||||
preLaneChangeLeft @57;
|
||||
preLaneChangeRight @58;
|
||||
laneChange @59;
|
||||
internetConnectivityNeeded @61;
|
||||
communityFeatureDisallowed @62;
|
||||
lowMemory @63;
|
||||
stockAeb @64;
|
||||
ldw @65;
|
||||
carUnrecognized @66;
|
||||
radarCommIssue @67;
|
||||
driverMonitorLowAcc @68;
|
||||
invalidLkasSetting @69;
|
||||
speedTooHigh @70;
|
||||
laneChangeBlocked @71;
|
||||
relayMalfunction @72;
|
||||
gasPressed @73;
|
||||
stockFcw @74;
|
||||
startup @75;
|
||||
startupNoCar @76;
|
||||
startupNoControl @77;
|
||||
startupMaster @78;
|
||||
fcw @79;
|
||||
steerSaturated @80;
|
||||
belowEngageSpeed @84;
|
||||
noGps @85;
|
||||
wrongCruiseMode @87;
|
||||
modeldLagging @89;
|
||||
deviceFalling @90;
|
||||
fanMalfunction @91;
|
||||
cameraMalfunction @92;
|
||||
|
||||
gasUnavailableDEPRECATED @3;
|
||||
dataNeededDEPRECATED @16;
|
||||
modelCommIssueDEPRECATED @27;
|
||||
ipasOverrideDEPRECATED @33;
|
||||
geofenceDEPRECATED @40;
|
||||
driverMonitorOnDEPRECATED @41;
|
||||
driverMonitorOffDEPRECATED @42;
|
||||
calibrationProgressDEPRECATED @47;
|
||||
invalidGiraffeHondaDEPRECATED @49;
|
||||
invalidGiraffeToyotaDEPRECATED @60;
|
||||
whitePandaUnsupportedDEPRECATED @81;
|
||||
startupGreyPandaDEPRECATED @82;
|
||||
canErrorPersistentDEPRECATED @83;
|
||||
focusRecoverActiveDEPRECATED @86;
|
||||
neosUpdateRequiredDEPRECATED @88;
|
||||
modelLagWarningDEPRECATED @93;
|
||||
}
|
||||
}
|
||||
|
||||
# ******* main car state @ 100hz *******
|
||||
# all speeds in m/s
|
||||
|
||||
struct CarState {
|
||||
errorsDEPRECATED @0 :List(CarEvent.EventName);
|
||||
events @13 :List(CarEvent);
|
||||
|
||||
# car speed
|
||||
vEgo @1 :Float32; # best estimate of speed
|
||||
aEgo @16 :Float32; # best estimate of acceleration
|
||||
vEgoRaw @17 :Float32; # unfiltered speed from CAN sensors
|
||||
yawRate @22 :Float32; # best estimate of yaw rate
|
||||
standstill @18 :Bool;
|
||||
wheelSpeeds @2 :WheelSpeeds;
|
||||
|
||||
# gas pedal, 0.0-1.0
|
||||
gas @3 :Float32; # this is user + computer
|
||||
gasPressed @4 :Bool; # this is user pedal only
|
||||
|
||||
# brake pedal, 0.0-1.0
|
||||
brake @5 :Float32; # this is user pedal only
|
||||
brakePressed @6 :Bool; # this is user pedal only
|
||||
brakeLights @19 :Bool;
|
||||
|
||||
# steering wheel
|
||||
steeringAngle @7 :Float32; # deg
|
||||
steeringRate @15 :Float32; # deg/s
|
||||
steeringTorque @8 :Float32; # TODO: standardize units
|
||||
steeringTorqueEps @27 :Float32; # TODO: standardize units
|
||||
steeringPressed @9 :Bool; # if the user is using the steering wheel
|
||||
steeringRateLimited @29 :Bool; # if the torque is limited by the rate limiter
|
||||
steerWarning @35 :Bool; # temporary steer unavailble
|
||||
steerError @36 :Bool; # permanent steer error
|
||||
stockAeb @30 :Bool;
|
||||
stockFcw @31 :Bool;
|
||||
espDisabled @32 :Bool;
|
||||
|
||||
# cruise state
|
||||
cruiseState @10 :CruiseState;
|
||||
|
||||
# gear
|
||||
gearShifter @14 :GearShifter;
|
||||
|
||||
# button presses
|
||||
buttonEvents @11 :List(ButtonEvent);
|
||||
leftBlinker @20 :Bool;
|
||||
rightBlinker @21 :Bool;
|
||||
genericToggle @23 :Bool;
|
||||
|
||||
# lock info
|
||||
doorOpen @24 :Bool;
|
||||
seatbeltUnlatched @25 :Bool;
|
||||
canValid @26 :Bool;
|
||||
|
||||
# clutch (manual transmission only)
|
||||
clutchPressed @28 :Bool;
|
||||
|
||||
# which packets this state came from
|
||||
canMonoTimes @12: List(UInt64);
|
||||
|
||||
# blindspot sensors
|
||||
leftBlindspot @33 :Bool; # Is there something blocking the left lane change
|
||||
rightBlindspot @34 :Bool; # Is there something blocking the right lane change
|
||||
|
||||
struct WheelSpeeds {
|
||||
# optional wheel speeds
|
||||
fl @0 :Float32;
|
||||
fr @1 :Float32;
|
||||
rl @2 :Float32;
|
||||
rr @3 :Float32;
|
||||
}
|
||||
|
||||
struct CruiseState {
|
||||
enabled @0 :Bool;
|
||||
speed @1 :Float32;
|
||||
available @2 :Bool;
|
||||
speedOffset @3 :Float32;
|
||||
standstill @4 :Bool;
|
||||
nonAdaptive @5 :Bool;
|
||||
}
|
||||
|
||||
enum GearShifter {
|
||||
unknown @0;
|
||||
park @1;
|
||||
drive @2;
|
||||
neutral @3;
|
||||
reverse @4;
|
||||
sport @5;
|
||||
low @6;
|
||||
brake @7;
|
||||
eco @8;
|
||||
manumatic @9;
|
||||
}
|
||||
|
||||
# send on change
|
||||
struct ButtonEvent {
|
||||
pressed @0 :Bool;
|
||||
type @1 :Type;
|
||||
|
||||
enum Type {
|
||||
unknown @0;
|
||||
leftBlinker @1;
|
||||
rightBlinker @2;
|
||||
accelCruise @3;
|
||||
decelCruise @4;
|
||||
cancel @5;
|
||||
altButton1 @6;
|
||||
altButton2 @7;
|
||||
altButton3 @8;
|
||||
setCruise @9;
|
||||
resumeCruise @10;
|
||||
gapAdjustCruise @11;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ******* radar state @ 20hz *******
|
||||
|
||||
struct RadarData @0x888ad6581cf0aacb {
|
||||
errors @0 :List(Error);
|
||||
points @1 :List(RadarPoint);
|
||||
|
||||
# which packets this state came from
|
||||
canMonoTimes @2 :List(UInt64);
|
||||
|
||||
enum Error {
|
||||
canError @0;
|
||||
fault @1;
|
||||
wrongConfig @2;
|
||||
}
|
||||
|
||||
# similar to LiveTracks
|
||||
# is one timestamp valid for all? I think so
|
||||
struct RadarPoint {
|
||||
trackId @0 :UInt64; # no trackId reuse
|
||||
|
||||
# these 3 are the minimum required
|
||||
dRel @1 :Float32; # m from the front bumper of the car
|
||||
yRel @2 :Float32; # m
|
||||
vRel @3 :Float32; # m/s
|
||||
|
||||
# these are optional and valid if they are not NaN
|
||||
aRel @4 :Float32; # m/s^2
|
||||
yvRel @5 :Float32; # m/s
|
||||
|
||||
# some radars flag measurements VS estimates
|
||||
measured @6 :Bool;
|
||||
}
|
||||
}
|
||||
|
||||
# ******* car controls @ 100hz *******
|
||||
|
||||
struct CarControl {
|
||||
# must be true for any actuator commands to work
|
||||
enabled @0 :Bool;
|
||||
active @7 :Bool;
|
||||
|
||||
gasDEPRECATED @1 :Float32;
|
||||
brakeDEPRECATED @2 :Float32;
|
||||
steeringTorqueDEPRECATED @3 :Float32;
|
||||
|
||||
actuators @6 :Actuators;
|
||||
|
||||
cruiseControl @4 :CruiseControl;
|
||||
hudControl @5 :HUDControl;
|
||||
|
||||
struct Actuators {
|
||||
# range from 0.0 - 1.0
|
||||
gas @0: Float32;
|
||||
brake @1: Float32;
|
||||
# range from -1.0 - 1.0
|
||||
steer @2: Float32;
|
||||
steerAngle @3: Float32;
|
||||
}
|
||||
|
||||
struct CruiseControl {
|
||||
cancel @0: Bool;
|
||||
override @1: Bool;
|
||||
speedOverride @2: Float32;
|
||||
accelOverride @3: Float32;
|
||||
}
|
||||
|
||||
struct HUDControl {
|
||||
speedVisible @0: Bool;
|
||||
setSpeed @1: Float32;
|
||||
lanesVisible @2: Bool;
|
||||
leadVisible @3: Bool;
|
||||
visualAlert @4: VisualAlert;
|
||||
audibleAlert @5: AudibleAlert;
|
||||
rightLaneVisible @6: Bool;
|
||||
leftLaneVisible @7: Bool;
|
||||
rightLaneDepart @8: Bool;
|
||||
leftLaneDepart @9: Bool;
|
||||
|
||||
enum VisualAlert {
|
||||
# these are the choices from the Honda
|
||||
# map as good as you can for your car
|
||||
none @0;
|
||||
fcw @1;
|
||||
steerRequired @2;
|
||||
brakePressed @3;
|
||||
wrongGear @4;
|
||||
seatbeltUnbuckled @5;
|
||||
speedTooHigh @6;
|
||||
ldw @7;
|
||||
}
|
||||
|
||||
enum AudibleAlert {
|
||||
# these are the choices from the Honda
|
||||
# map as good as you can for your car
|
||||
none @0;
|
||||
chimeEngage @1;
|
||||
chimeDisengage @2;
|
||||
chimeError @3;
|
||||
chimeWarning1 @4;
|
||||
chimeWarning2 @5;
|
||||
chimeWarningRepeat @6;
|
||||
chimePrompt @7;
|
||||
chimeWarning2Repeat @8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ****** car param ******
|
||||
|
||||
struct CarParams {
|
||||
carName @0 :Text;
|
||||
carFingerprint @1 :Text;
|
||||
|
||||
enableGasInterceptor @2 :Bool;
|
||||
enableCruise @3 :Bool;
|
||||
enableCamera @4 :Bool;
|
||||
enableDsu @5 :Bool; # driving support unit
|
||||
enableApgs @6 :Bool; # advanced parking guidance system
|
||||
|
||||
minEnableSpeed @7 :Float32;
|
||||
minSteerSpeed @8 :Float32;
|
||||
safetyModel @9 :SafetyModel;
|
||||
safetyModelPassive @42 :SafetyModel = silent;
|
||||
safetyParam @10 :Int16;
|
||||
|
||||
steerMaxBP @11 :List(Float32);
|
||||
steerMaxV @12 :List(Float32);
|
||||
gasMaxBP @13 :List(Float32);
|
||||
gasMaxV @14 :List(Float32);
|
||||
brakeMaxBP @15 :List(Float32);
|
||||
brakeMaxV @16 :List(Float32);
|
||||
|
||||
# things about the car in the manual
|
||||
mass @17 :Float32; # [kg] running weight
|
||||
wheelbase @18 :Float32; # [m] distance from rear to front axle
|
||||
centerToFront @19 :Float32; # [m] GC distance to front axle
|
||||
steerRatio @20 :Float32; # [] ratio between front wheels and steering wheel angles
|
||||
steerRatioRear @21 :Float32; # [] rear steering ratio wrt front steering (usually 0)
|
||||
|
||||
# things we can derive
|
||||
rotationalInertia @22 :Float32; # [kg*m2] body rotational inertia
|
||||
tireStiffnessFront @23 :Float32; # [N/rad] front tire coeff of stiff
|
||||
tireStiffnessRear @24 :Float32; # [N/rad] rear tire coeff of stiff
|
||||
|
||||
longitudinalTuning @25 :LongitudinalPIDTuning;
|
||||
lateralParams @48 :LateralParams;
|
||||
lateralTuning :union {
|
||||
pid @26 :LateralPIDTuning;
|
||||
indi @27 :LateralINDITuning;
|
||||
lqr @40 :LateralLQRTuning;
|
||||
}
|
||||
|
||||
steerLimitAlert @28 :Bool;
|
||||
steerLimitTimer @47 :Float32; # time before steerLimitAlert is issued
|
||||
|
||||
vEgoStopping @29 :Float32; # Speed at which the car goes into stopping state
|
||||
directAccelControl @30 :Bool; # Does the car have direct accel control or just gas/brake
|
||||
stoppingControl @31 :Bool; # Does the car allows full control even at lows speeds when stopping
|
||||
startAccel @32 :Float32; # Required acceleraton to overcome creep braking
|
||||
steerRateCost @33 :Float32; # Lateral MPC cost on steering rate
|
||||
steerControlType @34 :SteerControlType;
|
||||
radarOffCan @35 :Bool; # True when radar objects aren't visible on CAN
|
||||
|
||||
steerActuatorDelay @36 :Float32; # Steering wheel actuator delay in seconds
|
||||
openpilotLongitudinalControl @37 :Bool; # is openpilot doing the longitudinal control?
|
||||
carVin @38 :Text; # VIN number queried during fingerprinting
|
||||
isPandaBlack @39: Bool;
|
||||
dashcamOnly @41: Bool;
|
||||
transmissionType @43 :TransmissionType;
|
||||
carFw @44 :List(CarFw);
|
||||
radarTimeStep @45: Float32 = 0.05; # time delta between radar updates, 20Hz is very standard
|
||||
communityFeature @46: Bool; # true if a community maintained feature is detected
|
||||
fingerprintSource @49: FingerprintSource;
|
||||
networkLocation @50 :NetworkLocation; # Where Panda/C2 is integrated into the car's CAN network
|
||||
|
||||
struct LateralParams {
|
||||
torqueBP @0 :List(Int32);
|
||||
torqueV @1 :List(Int32);
|
||||
}
|
||||
|
||||
struct LateralPIDTuning {
|
||||
kpBP @0 :List(Float32);
|
||||
kpV @1 :List(Float32);
|
||||
kiBP @2 :List(Float32);
|
||||
kiV @3 :List(Float32);
|
||||
kf @4 :Float32;
|
||||
}
|
||||
|
||||
struct LongitudinalPIDTuning {
|
||||
kpBP @0 :List(Float32);
|
||||
kpV @1 :List(Float32);
|
||||
kiBP @2 :List(Float32);
|
||||
kiV @3 :List(Float32);
|
||||
deadzoneBP @4 :List(Float32);
|
||||
deadzoneV @5 :List(Float32);
|
||||
}
|
||||
|
||||
struct LateralINDITuning {
|
||||
outerLoopGain @0 :Float32;
|
||||
innerLoopGain @1 :Float32;
|
||||
timeConstant @2 :Float32;
|
||||
actuatorEffectiveness @3 :Float32;
|
||||
}
|
||||
|
||||
struct LateralLQRTuning {
|
||||
scale @0 :Float32;
|
||||
ki @1 :Float32;
|
||||
dcGain @2 :Float32;
|
||||
|
||||
# State space system
|
||||
a @3 :List(Float32);
|
||||
b @4 :List(Float32);
|
||||
c @5 :List(Float32);
|
||||
|
||||
k @6 :List(Float32); # LQR gain
|
||||
l @7 :List(Float32); # Kalman gain
|
||||
}
|
||||
|
||||
enum SafetyModel {
|
||||
silent @0;
|
||||
hondaNidec @1;
|
||||
toyota @2;
|
||||
elm327 @3;
|
||||
gm @4;
|
||||
hondaBoschGiraffe @5;
|
||||
ford @6;
|
||||
cadillac @7;
|
||||
hyundai @8;
|
||||
chrysler @9;
|
||||
tesla @10;
|
||||
subaru @11;
|
||||
gmPassive @12;
|
||||
mazda @13;
|
||||
nissan @14;
|
||||
volkswagen @15;
|
||||
toyotaIpas @16;
|
||||
allOutput @17;
|
||||
gmAscm @18;
|
||||
noOutput @19; # like silent but without silent CAN TXs
|
||||
hondaBoschHarness @20;
|
||||
volkswagenPq @21;
|
||||
subaruLegacy @22; # pre-Global platform
|
||||
hyundaiLegacy @23;
|
||||
hyundaiCommunity @24;
|
||||
}
|
||||
|
||||
enum SteerControlType {
|
||||
torque @0;
|
||||
angle @1;
|
||||
}
|
||||
|
||||
enum TransmissionType {
|
||||
unknown @0;
|
||||
automatic @1; # Traditional auto, including DSG
|
||||
manual @2; # True "stick shift" only
|
||||
direct @3; # Electric vehicle or other direct drive
|
||||
}
|
||||
|
||||
struct CarFw {
|
||||
ecu @0 :Ecu;
|
||||
fwVersion @1 :Data;
|
||||
address @2: UInt32;
|
||||
subAddress @3: UInt8;
|
||||
}
|
||||
|
||||
enum Ecu {
|
||||
eps @0;
|
||||
esp @1;
|
||||
fwdRadar @2;
|
||||
fwdCamera @3;
|
||||
engine @4;
|
||||
unknown @5;
|
||||
transmission @8; # Transmission Control Module
|
||||
srs @9; # airbag
|
||||
gateway @10; # can gateway
|
||||
hud @11; # heads up display
|
||||
combinationMeter @12; # instrument cluster
|
||||
|
||||
# Toyota only
|
||||
dsu @6;
|
||||
apgs @7;
|
||||
|
||||
# Honda only
|
||||
vsa @13; # Vehicle Stability Assist
|
||||
programmedFuelInjection @14;
|
||||
electricBrakeBooster @15;
|
||||
shiftByWire @16;
|
||||
}
|
||||
|
||||
enum FingerprintSource {
|
||||
can @0;
|
||||
fw @1;
|
||||
fixed @2;
|
||||
}
|
||||
|
||||
enum NetworkLocation {
|
||||
fwdCamera @0; # Standard/default integration at LKAS camera
|
||||
gateway @1; # Integration at vehicle's CAN gateway
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2013-2015 Sandstorm Development Group, Inc. and contributors
|
||||
# Licensed under the MIT License:
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
@0xc5f1af96651f70ea;
|
||||
|
||||
annotation package @0x9ee4c8f803b3b596 (file) : Text;
|
||||
# Name of the package, such as "org.example.foo", in which the generated code will reside.
|
||||
|
||||
annotation outerClassname @0x9b066bb4881f7cd3 (file) : Text;
|
||||
# Name of the outer class that will wrap the generated code.
|
||||
+2142
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
demo
|
||||
bridge
|
||||
test_runner
|
||||
*.o
|
||||
*.os
|
||||
*.d
|
||||
*.a
|
||||
*.so
|
||||
messaging_pyx.cpp
|
||||
build/
|
||||
@@ -0,0 +1,225 @@
|
||||
# must be build with scons
|
||||
from .messaging_pyx import Context, Poller, SubSocket, PubSocket # pylint: disable=no-name-in-module, import-error
|
||||
from .messaging_pyx import MultiplePublishersError, MessagingError # pylint: disable=no-name-in-module, import-error
|
||||
import capnp
|
||||
|
||||
from typing import Optional, List, Union
|
||||
|
||||
from cereal import log
|
||||
from cereal.services import service_list
|
||||
|
||||
assert MultiplePublishersError
|
||||
assert MessagingError
|
||||
|
||||
# sec_since_boot is faster, but allow to run standalone too
|
||||
try:
|
||||
from common.realtime import sec_since_boot
|
||||
except ImportError:
|
||||
import time
|
||||
sec_since_boot = time.time
|
||||
print("Warning, using python time.time() instead of faster sec_since_boot")
|
||||
|
||||
context = Context()
|
||||
|
||||
def new_message(service: Optional[str] = None, size: Optional[int] = None) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||
dat = log.Event.new_message()
|
||||
dat.logMonoTime = int(sec_since_boot() * 1e9)
|
||||
dat.valid = True
|
||||
if service is not None:
|
||||
if size is None:
|
||||
dat.init(service)
|
||||
else:
|
||||
dat.init(service, size)
|
||||
return dat
|
||||
|
||||
def pub_sock(endpoint: str) -> PubSocket:
|
||||
sock = PubSocket()
|
||||
sock.connect(context, endpoint)
|
||||
return sock
|
||||
|
||||
def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
|
||||
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
|
||||
sock = SubSocket()
|
||||
sock.connect(context, endpoint, addr.encode('utf8'), conflate)
|
||||
|
||||
if timeout is not None:
|
||||
sock.setTimeout(timeout)
|
||||
|
||||
if poller is not None:
|
||||
poller.registerSocket(sock)
|
||||
return sock
|
||||
|
||||
|
||||
def drain_sock_raw(sock: SubSocket, wait_for_one: bool = False) -> List[bytes]:
|
||||
"""Receive all message currently available on the queue"""
|
||||
ret: List[bytes] = []
|
||||
while 1:
|
||||
if wait_for_one and len(ret) == 0:
|
||||
dat = sock.receive()
|
||||
else:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
|
||||
if dat is None:
|
||||
break
|
||||
|
||||
ret.append(dat)
|
||||
|
||||
return ret
|
||||
|
||||
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]:
|
||||
"""Receive all message currently available on the queue"""
|
||||
ret: List[capnp.lib.capnp._DynamicStructReader] = []
|
||||
while 1:
|
||||
if wait_for_one and len(ret) == 0:
|
||||
dat = sock.receive()
|
||||
else:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
|
||||
if dat is None: # Timeout hit
|
||||
break
|
||||
|
||||
dat = log.Event.from_bytes(dat)
|
||||
ret.append(dat)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# TODO: print when we drop packets?
|
||||
def recv_sock(sock: SubSocket, wait: bool = False) -> Union[None, capnp.lib.capnp._DynamicStructReader]:
|
||||
"""Same as drain sock, but only returns latest message. Consider using conflate instead."""
|
||||
dat = None
|
||||
|
||||
while 1:
|
||||
if wait and dat is None:
|
||||
rcv = sock.receive()
|
||||
else:
|
||||
rcv = sock.receive(non_blocking=True)
|
||||
|
||||
if rcv is None: # Timeout hit
|
||||
break
|
||||
|
||||
dat = rcv
|
||||
|
||||
if dat is not None:
|
||||
dat = log.Event.from_bytes(dat)
|
||||
|
||||
return dat
|
||||
|
||||
def recv_one(sock: SubSocket) -> Union[None, capnp.lib.capnp._DynamicStructReader]:
|
||||
dat = sock.receive()
|
||||
if dat is not None:
|
||||
dat = log.Event.from_bytes(dat)
|
||||
return dat
|
||||
|
||||
def recv_one_or_none(sock: SubSocket) -> Union[None, capnp.lib.capnp._DynamicStructReader]:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
if dat is not None:
|
||||
dat = log.Event.from_bytes(dat)
|
||||
return dat
|
||||
|
||||
def recv_one_retry(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader:
|
||||
"""Keep receiving until we get a message"""
|
||||
while True:
|
||||
dat = sock.receive()
|
||||
if dat is not None:
|
||||
return log.Event.from_bytes(dat)
|
||||
|
||||
class SubMaster():
|
||||
def __init__(self, services: List[str], poll: Optional[List[str]] = None,
|
||||
ignore_alive: Optional[List[str]] = None, addr:str ="127.0.0.1"):
|
||||
self.frame = -1
|
||||
self.updated = {s: False for s in services}
|
||||
self.rcv_time = {s: 0. for s in services}
|
||||
self.rcv_frame = {s: 0 for s in services}
|
||||
self.alive = {s: False for s in services}
|
||||
self.sock = {}
|
||||
self.freq = {}
|
||||
self.data = {}
|
||||
self.valid = {}
|
||||
self.logMonoTime = {}
|
||||
|
||||
self.poller = Poller()
|
||||
self.non_polled_services = [s for s in services if poll is not None and
|
||||
len(poll) and s not in poll]
|
||||
|
||||
if ignore_alive is not None:
|
||||
self.ignore_alive = ignore_alive
|
||||
else:
|
||||
self.ignore_alive = []
|
||||
|
||||
for s in services:
|
||||
if addr is not None:
|
||||
p = self.poller if s not in self.non_polled_services else None
|
||||
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=True)
|
||||
self.freq[s] = service_list[s].frequency
|
||||
|
||||
try:
|
||||
data = new_message(s)
|
||||
except capnp.lib.capnp.KjException: # pylint: disable=c-extension-no-member
|
||||
data = new_message(s, 0) # lists
|
||||
|
||||
self.data[s] = getattr(data, s)
|
||||
self.logMonoTime[s] = 0
|
||||
self.valid[s] = data.valid
|
||||
|
||||
def __getitem__(self, s: str) -> capnp.lib.capnp._DynamicStructReader:
|
||||
return self.data[s]
|
||||
|
||||
def update(self, timeout: int = 1000) -> None:
|
||||
msgs = []
|
||||
for sock in self.poller.poll(timeout):
|
||||
msgs.append(recv_one_or_none(sock))
|
||||
|
||||
# non-blocking receive for non-polled sockets
|
||||
for s in self.non_polled_services:
|
||||
msgs.append(recv_one_or_none(self.sock[s]))
|
||||
self.update_msgs(sec_since_boot(), msgs)
|
||||
|
||||
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
|
||||
self.frame += 1
|
||||
self.updated = dict.fromkeys(self.updated, False)
|
||||
for msg in msgs:
|
||||
if msg is None:
|
||||
continue
|
||||
|
||||
s = msg.which()
|
||||
self.updated[s] = True
|
||||
self.rcv_time[s] = cur_time
|
||||
self.rcv_frame[s] = self.frame
|
||||
self.data[s] = getattr(msg, s)
|
||||
self.logMonoTime[s] = msg.logMonoTime
|
||||
self.valid[s] = msg.valid
|
||||
|
||||
for s in self.data:
|
||||
# arbitrary small number to avoid float comparison. If freq is 0, we can skip the check
|
||||
if self.freq[s] > 1e-5:
|
||||
# alive if delay is within 10x the expected frequency
|
||||
self.alive[s] = (cur_time - self.rcv_time[s]) < (10. / self.freq[s])
|
||||
else:
|
||||
self.alive[s] = True
|
||||
|
||||
def all_alive(self, service_list=None) -> bool:
|
||||
if service_list is None: # check all
|
||||
service_list = self.alive.keys()
|
||||
return all(self.alive[s] for s in service_list if s not in self.ignore_alive)
|
||||
|
||||
def all_valid(self, service_list=None) -> bool:
|
||||
if service_list is None: # check all
|
||||
service_list = self.valid.keys()
|
||||
return all(self.valid[s] for s in service_list)
|
||||
|
||||
def all_alive_and_valid(self, service_list=None) -> bool:
|
||||
if service_list is None: # check all
|
||||
service_list = self.alive.keys()
|
||||
return self.all_alive(service_list=service_list) and self.all_valid(service_list=service_list)
|
||||
|
||||
class PubMaster():
|
||||
def __init__(self, services: List[str]):
|
||||
self.sock = {}
|
||||
for s in services:
|
||||
self.sock[s] = pub_sock(s)
|
||||
|
||||
def send(self, s: str, dat: Union[bytes, capnp.lib.capnp._DynamicStructBuilder]) -> None:
|
||||
if not isinstance(dat, bytes):
|
||||
dat = dat.to_bytes()
|
||||
self.sock[s].send(dat)
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include <csignal>
|
||||
#include <map>
|
||||
|
||||
typedef void (*sighandler_t)(int sig);
|
||||
|
||||
#include "services.h"
|
||||
|
||||
#include "impl_msgq.hpp"
|
||||
#include "impl_zmq.hpp"
|
||||
|
||||
void sigpipe_handler(int sig) {
|
||||
assert(sig == SIGPIPE);
|
||||
std::cout << "SIGPIPE received" << std::endl;
|
||||
}
|
||||
|
||||
static std::vector<std::string> get_services() {
|
||||
std::vector<std::string> name_list;
|
||||
|
||||
for (const auto& it : services) {
|
||||
std::string name = it.name;
|
||||
if (name == "plusFrame" || name == "uiLayoutState") continue;
|
||||
name_list.push_back(name);
|
||||
}
|
||||
|
||||
return name_list;
|
||||
}
|
||||
|
||||
|
||||
int main(void){
|
||||
signal(SIGPIPE, (sighandler_t)sigpipe_handler);
|
||||
|
||||
auto endpoints = get_services();
|
||||
|
||||
std::map<SubSocket*, PubSocket*> sub2pub;
|
||||
|
||||
Context *zmq_context = new ZMQContext();
|
||||
Context *msgq_context = new MSGQContext();
|
||||
Poller *poller = new MSGQPoller();
|
||||
|
||||
for (auto endpoint: endpoints){
|
||||
SubSocket * msgq_sock = new MSGQSubSocket();
|
||||
msgq_sock->connect(msgq_context, endpoint, "127.0.0.1", false);
|
||||
poller->registerSocket(msgq_sock);
|
||||
|
||||
PubSocket * zmq_sock = new ZMQPubSocket();
|
||||
zmq_sock->connect(zmq_context, endpoint);
|
||||
|
||||
sub2pub[msgq_sock] = zmq_sock;
|
||||
}
|
||||
|
||||
|
||||
while (true){
|
||||
for (auto sub_sock : poller->poll(100)){
|
||||
Message * msg = sub_sock->receive();
|
||||
if (msg == NULL) continue;
|
||||
sub2pub[sub_sock]->sendMessage(msg);
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
|
||||
|
||||
#include "impl_msgq.hpp"
|
||||
|
||||
volatile sig_atomic_t msgq_do_exit = 0;
|
||||
|
||||
void sig_handler(int signal) {
|
||||
assert(signal == SIGINT || signal == SIGTERM);
|
||||
msgq_do_exit = 1;
|
||||
}
|
||||
|
||||
static size_t get_size(std::string endpoint){
|
||||
size_t sz = DEFAULT_SEGMENT_SIZE;
|
||||
|
||||
#if !defined(QCOM) && !defined(QCOM2)
|
||||
if (endpoint == "frame" || endpoint == "frontFrame" || endpoint == "wideFrame"){
|
||||
sz *= 10;
|
||||
}
|
||||
#endif
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
|
||||
MSGQContext::MSGQContext() {
|
||||
}
|
||||
|
||||
MSGQContext::~MSGQContext() {
|
||||
}
|
||||
|
||||
void MSGQMessage::init(size_t sz) {
|
||||
size = sz;
|
||||
data = new char[size];
|
||||
}
|
||||
|
||||
void MSGQMessage::init(char * d, size_t sz) {
|
||||
size = sz;
|
||||
data = new char[size];
|
||||
memcpy(data, d, size);
|
||||
}
|
||||
|
||||
void MSGQMessage::takeOwnership(char * d, size_t sz) {
|
||||
size = sz;
|
||||
data = d;
|
||||
}
|
||||
|
||||
void MSGQMessage::close() {
|
||||
if (size > 0){
|
||||
delete[] data;
|
||||
}
|
||||
size = 0;
|
||||
}
|
||||
|
||||
MSGQMessage::~MSGQMessage() {
|
||||
this->close();
|
||||
}
|
||||
|
||||
int MSGQSubSocket::connect(Context *context, std::string endpoint, std::string address, bool conflate){
|
||||
assert(context);
|
||||
assert(address == "127.0.0.1");
|
||||
|
||||
q = new msgq_queue_t;
|
||||
int r = msgq_new_queue(q, endpoint.c_str(), get_size(endpoint));
|
||||
if (r != 0){
|
||||
return r;
|
||||
}
|
||||
|
||||
msgq_init_subscriber(q);
|
||||
|
||||
if (conflate){
|
||||
q->read_conflate = true;
|
||||
}
|
||||
|
||||
timeout = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Message * MSGQSubSocket::receive(bool non_blocking){
|
||||
msgq_do_exit = 0;
|
||||
|
||||
void (*prev_handler_sigint)(int);
|
||||
void (*prev_handler_sigterm)(int);
|
||||
if (!non_blocking){
|
||||
prev_handler_sigint = std::signal(SIGINT, sig_handler);
|
||||
prev_handler_sigterm = std::signal(SIGTERM, sig_handler);
|
||||
}
|
||||
|
||||
msgq_msg_t msg;
|
||||
|
||||
MSGQMessage *r = NULL;
|
||||
|
||||
int rc = msgq_msg_recv(&msg, q);
|
||||
|
||||
// Hack to implement blocking read with a poller. Don't use this
|
||||
while (!non_blocking && rc == 0 && msgq_do_exit == 0){
|
||||
msgq_pollitem_t items[1];
|
||||
items[0].q = q;
|
||||
|
||||
int t = (timeout != -1) ? timeout : 100;
|
||||
|
||||
int n = msgq_poll(items, 1, t);
|
||||
rc = msgq_msg_recv(&msg, q);
|
||||
|
||||
// The poll indicated a message was ready, but the receive failed. Try again
|
||||
if (n == 1 && rc == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timeout != -1){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!non_blocking){
|
||||
std::signal(SIGINT, prev_handler_sigint);
|
||||
std::signal(SIGTERM, prev_handler_sigterm);
|
||||
}
|
||||
|
||||
errno = msgq_do_exit ? EINTR : 0;
|
||||
|
||||
if (rc > 0){
|
||||
if (msgq_do_exit){
|
||||
msgq_msg_close(&msg); // Free unused message on exit
|
||||
} else {
|
||||
r = new MSGQMessage;
|
||||
r->takeOwnership(msg.data, msg.size);
|
||||
}
|
||||
}
|
||||
|
||||
return (Message*)r;
|
||||
}
|
||||
|
||||
void MSGQSubSocket::setTimeout(int t){
|
||||
timeout = t;
|
||||
}
|
||||
|
||||
MSGQSubSocket::~MSGQSubSocket(){
|
||||
if (q != NULL){
|
||||
msgq_close_queue(q);
|
||||
delete q;
|
||||
}
|
||||
}
|
||||
|
||||
int MSGQPubSocket::connect(Context *context, std::string endpoint){
|
||||
assert(context);
|
||||
|
||||
q = new msgq_queue_t;
|
||||
int r = msgq_new_queue(q, endpoint.c_str(), get_size(endpoint));
|
||||
if (r != 0){
|
||||
return r;
|
||||
}
|
||||
|
||||
msgq_init_publisher(q);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MSGQPubSocket::sendMessage(Message *message){
|
||||
msgq_msg_t msg;
|
||||
msg.data = message->getData();
|
||||
msg.size = message->getSize();
|
||||
|
||||
return msgq_msg_send(&msg, q);
|
||||
}
|
||||
|
||||
int MSGQPubSocket::send(char *data, size_t size){
|
||||
msgq_msg_t msg;
|
||||
msg.data = data;
|
||||
msg.size = size;
|
||||
|
||||
return msgq_msg_send(&msg, q);
|
||||
}
|
||||
|
||||
MSGQPubSocket::~MSGQPubSocket(){
|
||||
if (q != NULL){
|
||||
msgq_close_queue(q);
|
||||
delete q;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MSGQPoller::registerSocket(SubSocket * socket){
|
||||
assert(num_polls + 1 < MAX_POLLERS);
|
||||
polls[num_polls].q = (msgq_queue_t*)socket->getRawSocket();
|
||||
|
||||
sockets.push_back(socket);
|
||||
num_polls++;
|
||||
}
|
||||
|
||||
std::vector<SubSocket*> MSGQPoller::poll(int timeout){
|
||||
std::vector<SubSocket*> r;
|
||||
|
||||
msgq_poll(polls, num_polls, timeout);
|
||||
for (size_t i = 0; i < num_polls; i++){
|
||||
if (polls[i].revents){
|
||||
r.push_back(sockets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include "messaging.hpp"
|
||||
#include "msgq.hpp"
|
||||
#include <zmq.h>
|
||||
#include <string>
|
||||
|
||||
#define MAX_POLLERS 128
|
||||
|
||||
class MSGQContext : public Context {
|
||||
private:
|
||||
void * context = NULL;
|
||||
public:
|
||||
MSGQContext();
|
||||
void * getRawContext() {return context;}
|
||||
~MSGQContext();
|
||||
};
|
||||
|
||||
class MSGQMessage : public Message {
|
||||
private:
|
||||
char * data;
|
||||
size_t size;
|
||||
public:
|
||||
void init(size_t size);
|
||||
void init(char *data, size_t size);
|
||||
void takeOwnership(char *data, size_t size);
|
||||
size_t getSize(){return size;}
|
||||
char * getData(){return data;}
|
||||
void close();
|
||||
~MSGQMessage();
|
||||
};
|
||||
|
||||
class MSGQSubSocket : public SubSocket {
|
||||
private:
|
||||
msgq_queue_t * q = NULL;
|
||||
int timeout;
|
||||
public:
|
||||
int connect(Context *context, std::string endpoint, std::string address, bool conflate=false);
|
||||
void setTimeout(int timeout);
|
||||
void * getRawSocket() {return (void*)q;}
|
||||
Message *receive(bool non_blocking=false);
|
||||
~MSGQSubSocket();
|
||||
};
|
||||
|
||||
class MSGQPubSocket : public PubSocket {
|
||||
private:
|
||||
msgq_queue_t * q = NULL;
|
||||
public:
|
||||
int connect(Context *context, std::string endpoint);
|
||||
int sendMessage(Message *message);
|
||||
int send(char *data, size_t size);
|
||||
~MSGQPubSocket();
|
||||
};
|
||||
|
||||
class MSGQPoller : public Poller {
|
||||
private:
|
||||
std::vector<SubSocket*> sockets;
|
||||
msgq_pollitem_t polls[MAX_POLLERS];
|
||||
size_t num_polls = 0;
|
||||
|
||||
public:
|
||||
void registerSocket(SubSocket *socket);
|
||||
std::vector<SubSocket*> poll(int timeout);
|
||||
~MSGQPoller(){};
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
|
||||
#include <zmq.h>
|
||||
|
||||
#include "services.h"
|
||||
#include "impl_zmq.hpp"
|
||||
|
||||
static int get_port(std::string endpoint) {
|
||||
int port = -1;
|
||||
for (const auto& it : services) {
|
||||
std::string name = it.name;
|
||||
if (name == endpoint) {
|
||||
port = it.port;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(port >= 0);
|
||||
return port;
|
||||
}
|
||||
|
||||
ZMQContext::ZMQContext() {
|
||||
context = zmq_ctx_new();
|
||||
}
|
||||
|
||||
ZMQContext::~ZMQContext() {
|
||||
zmq_ctx_term(context);
|
||||
}
|
||||
|
||||
void ZMQMessage::init(size_t sz) {
|
||||
size = sz;
|
||||
data = new char[size];
|
||||
}
|
||||
|
||||
void ZMQMessage::init(char * d, size_t sz) {
|
||||
size = sz;
|
||||
data = new char[size];
|
||||
memcpy(data, d, size);
|
||||
}
|
||||
|
||||
void ZMQMessage::close() {
|
||||
if (size > 0){
|
||||
delete[] data;
|
||||
}
|
||||
size = 0;
|
||||
}
|
||||
|
||||
ZMQMessage::~ZMQMessage() {
|
||||
this->close();
|
||||
}
|
||||
|
||||
|
||||
int ZMQSubSocket::connect(Context *context, std::string endpoint, std::string address, bool conflate){
|
||||
sock = zmq_socket(context->getRawContext(), ZMQ_SUB);
|
||||
if (sock == NULL){
|
||||
return -1;
|
||||
}
|
||||
|
||||
zmq_setsockopt(sock, ZMQ_SUBSCRIBE, "", 0);
|
||||
|
||||
if (conflate){
|
||||
int arg = 1;
|
||||
zmq_setsockopt(sock, ZMQ_CONFLATE, &arg, sizeof(int));
|
||||
}
|
||||
|
||||
int reconnect_ivl = 500;
|
||||
zmq_setsockopt(sock, ZMQ_RECONNECT_IVL_MAX, &reconnect_ivl, sizeof(reconnect_ivl));
|
||||
|
||||
full_endpoint = "tcp://" + address + ":";
|
||||
full_endpoint += std::to_string(get_port(endpoint));
|
||||
|
||||
return zmq_connect(sock, full_endpoint.c_str());
|
||||
}
|
||||
|
||||
|
||||
Message * ZMQSubSocket::receive(bool non_blocking){
|
||||
zmq_msg_t msg;
|
||||
assert(zmq_msg_init(&msg) == 0);
|
||||
|
||||
int flags = non_blocking ? ZMQ_DONTWAIT : 0;
|
||||
int rc = zmq_msg_recv(&msg, sock, flags);
|
||||
Message *r = NULL;
|
||||
|
||||
if (rc >= 0){
|
||||
// Make a copy to ensure the data is aligned
|
||||
r = new ZMQMessage;
|
||||
r->init((char*)zmq_msg_data(&msg), zmq_msg_size(&msg));
|
||||
}
|
||||
|
||||
zmq_msg_close(&msg);
|
||||
return r;
|
||||
}
|
||||
|
||||
void ZMQSubSocket::setTimeout(int timeout){
|
||||
zmq_setsockopt(sock, ZMQ_RCVTIMEO, &timeout, sizeof(int));
|
||||
}
|
||||
|
||||
ZMQSubSocket::~ZMQSubSocket(){
|
||||
zmq_close(sock);
|
||||
}
|
||||
|
||||
int ZMQPubSocket::connect(Context *context, std::string endpoint){
|
||||
sock = zmq_socket(context->getRawContext(), ZMQ_PUB);
|
||||
if (sock == NULL){
|
||||
return -1;
|
||||
}
|
||||
|
||||
full_endpoint = "tcp://*:";
|
||||
full_endpoint += std::to_string(get_port(endpoint));
|
||||
|
||||
return zmq_bind(sock, full_endpoint.c_str());
|
||||
}
|
||||
|
||||
int ZMQPubSocket::sendMessage(Message *message){
|
||||
return zmq_send(sock, message->getData(), message->getSize(), ZMQ_DONTWAIT);
|
||||
}
|
||||
|
||||
int ZMQPubSocket::send(char *data, size_t size){
|
||||
return zmq_send(sock, data, size, ZMQ_DONTWAIT);
|
||||
}
|
||||
|
||||
ZMQPubSocket::~ZMQPubSocket(){
|
||||
zmq_close(sock);
|
||||
}
|
||||
|
||||
|
||||
void ZMQPoller::registerSocket(SubSocket * socket){
|
||||
assert(num_polls + 1 < MAX_POLLERS);
|
||||
polls[num_polls].socket = socket->getRawSocket();
|
||||
polls[num_polls].events = ZMQ_POLLIN;
|
||||
|
||||
sockets.push_back(socket);
|
||||
num_polls++;
|
||||
}
|
||||
|
||||
std::vector<SubSocket*> ZMQPoller::poll(int timeout){
|
||||
std::vector<SubSocket*> r;
|
||||
|
||||
int rc = zmq_poll(polls, num_polls, timeout);
|
||||
if (rc < 0){
|
||||
return r;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_polls; i++){
|
||||
if (polls[i].revents){
|
||||
r.push_back(sockets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
#include "messaging.hpp"
|
||||
#include <zmq.h>
|
||||
#include <string>
|
||||
|
||||
#define MAX_POLLERS 128
|
||||
|
||||
class ZMQContext : public Context {
|
||||
private:
|
||||
void * context = NULL;
|
||||
public:
|
||||
ZMQContext();
|
||||
void * getRawContext() {return context;}
|
||||
~ZMQContext();
|
||||
};
|
||||
|
||||
class ZMQMessage : public Message {
|
||||
private:
|
||||
char * data;
|
||||
size_t size;
|
||||
public:
|
||||
void init(size_t size);
|
||||
void init(char *data, size_t size);
|
||||
size_t getSize(){return size;}
|
||||
char * getData(){return data;}
|
||||
void close();
|
||||
~ZMQMessage();
|
||||
};
|
||||
|
||||
class ZMQSubSocket : public SubSocket {
|
||||
private:
|
||||
void * sock;
|
||||
std::string full_endpoint;
|
||||
public:
|
||||
int connect(Context *context, std::string endpoint, std::string address, bool conflate=false);
|
||||
void setTimeout(int timeout);
|
||||
void * getRawSocket() {return sock;}
|
||||
Message *receive(bool non_blocking=false);
|
||||
~ZMQSubSocket();
|
||||
};
|
||||
|
||||
class ZMQPubSocket : public PubSocket {
|
||||
private:
|
||||
void * sock;
|
||||
std::string full_endpoint;
|
||||
public:
|
||||
int connect(Context *context, std::string endpoint);
|
||||
int sendMessage(Message *message);
|
||||
int send(char *data, size_t size);
|
||||
~ZMQPubSocket();
|
||||
};
|
||||
|
||||
class ZMQPoller : public Poller {
|
||||
private:
|
||||
std::vector<SubSocket*> sockets;
|
||||
zmq_pollitem_t polls[MAX_POLLERS];
|
||||
size_t num_polls = 0;
|
||||
|
||||
public:
|
||||
void registerSocket(SubSocket *socket);
|
||||
std::vector<SubSocket*> poll(int timeout);
|
||||
~ZMQPoller(){};
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "messaging.hpp"
|
||||
#include "impl_zmq.hpp"
|
||||
#include "impl_msgq.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
const bool MUST_USE_ZMQ = true;
|
||||
#else
|
||||
const bool MUST_USE_ZMQ = false;
|
||||
#endif
|
||||
|
||||
Context * Context::create(){
|
||||
Context * c;
|
||||
if (std::getenv("ZMQ") || MUST_USE_ZMQ){
|
||||
c = new ZMQContext();
|
||||
} else {
|
||||
c = new MSGQContext();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
SubSocket * SubSocket::create(){
|
||||
SubSocket * s;
|
||||
if (std::getenv("ZMQ") || MUST_USE_ZMQ){
|
||||
s = new ZMQSubSocket();
|
||||
} else {
|
||||
s = new MSGQSubSocket();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
SubSocket * SubSocket::create(Context * context, std::string endpoint){
|
||||
SubSocket *s = SubSocket::create();
|
||||
int r = s->connect(context, endpoint, "127.0.0.1");
|
||||
|
||||
if (r == 0) {
|
||||
return s;
|
||||
} else {
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SubSocket * SubSocket::create(Context * context, std::string endpoint, std::string address){
|
||||
SubSocket *s = SubSocket::create();
|
||||
int r = s->connect(context, endpoint, address);
|
||||
|
||||
if (r == 0) {
|
||||
return s;
|
||||
} else {
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SubSocket * SubSocket::create(Context * context, std::string endpoint, std::string address, bool conflate){
|
||||
SubSocket *s = SubSocket::create();
|
||||
int r = s->connect(context, endpoint, address, conflate);
|
||||
|
||||
if (r == 0) {
|
||||
return s;
|
||||
} else {
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
PubSocket * PubSocket::create(){
|
||||
PubSocket * s;
|
||||
if (std::getenv("ZMQ") || MUST_USE_ZMQ){
|
||||
s = new ZMQPubSocket();
|
||||
} else {
|
||||
s = new MSGQPubSocket();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
PubSocket * PubSocket::create(Context * context, std::string endpoint){
|
||||
PubSocket *s = PubSocket::create();
|
||||
int r = s->connect(context, endpoint);
|
||||
|
||||
if (r == 0) {
|
||||
return s;
|
||||
} else {
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Poller * Poller::create(){
|
||||
Poller * p;
|
||||
if (std::getenv("ZMQ") || MUST_USE_ZMQ){
|
||||
p = new ZMQPoller();
|
||||
} else {
|
||||
p = new MSGQPoller();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
Poller * Poller::create(std::vector<SubSocket*> sockets){
|
||||
Poller * p = Poller::create();
|
||||
|
||||
for (auto s : sockets){
|
||||
p->registerSocket(s);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
extern "C" Context * messaging_context_create() {
|
||||
return Context::create();
|
||||
}
|
||||
|
||||
extern "C" SubSocket * messaging_subsocket_create(Context* context, const char* endpoint) {
|
||||
return SubSocket::create(context, std::string(endpoint));
|
||||
}
|
||||
|
||||
extern "C" PubSocket * messaging_pubsocket_create(Context* context, const char* endpoint) {
|
||||
return PubSocket::create(context, std::string(endpoint));
|
||||
}
|
||||
|
||||
extern "C" Poller * messaging_poller_create(SubSocket** sockets, int size) {
|
||||
std::vector<SubSocket*> socketsVec(sockets, sockets + size);
|
||||
return Poller::create(socketsVec);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <capnp/serialize.h>
|
||||
#include "../gen/cpp/log.capnp.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define CLOCK_BOOTTIME CLOCK_MONOTONIC
|
||||
#endif
|
||||
|
||||
#define MSG_MULTIPLE_PUBLISHERS 100
|
||||
|
||||
class Context {
|
||||
public:
|
||||
virtual void * getRawContext() = 0;
|
||||
static Context * create();
|
||||
virtual ~Context(){};
|
||||
};
|
||||
|
||||
class Message {
|
||||
public:
|
||||
virtual void init(size_t size) = 0;
|
||||
virtual void init(char * data, size_t size) = 0;
|
||||
virtual void close() = 0;
|
||||
virtual size_t getSize() = 0;
|
||||
virtual char * getData() = 0;
|
||||
virtual ~Message(){};
|
||||
};
|
||||
|
||||
|
||||
class SubSocket {
|
||||
public:
|
||||
virtual int connect(Context *context, std::string endpoint, std::string address, bool conflate=false) = 0;
|
||||
virtual void setTimeout(int timeout) = 0;
|
||||
virtual Message *receive(bool non_blocking=false) = 0;
|
||||
virtual void * getRawSocket() = 0;
|
||||
static SubSocket * create();
|
||||
static SubSocket * create(Context * context, std::string endpoint);
|
||||
static SubSocket * create(Context * context, std::string endpoint, std::string address);
|
||||
static SubSocket * create(Context * context, std::string endpoint, std::string address, bool conflate);
|
||||
virtual ~SubSocket(){};
|
||||
};
|
||||
|
||||
class PubSocket {
|
||||
public:
|
||||
virtual int connect(Context *context, std::string endpoint) = 0;
|
||||
virtual int sendMessage(Message *message) = 0;
|
||||
virtual int send(char *data, size_t size) = 0;
|
||||
static PubSocket * create();
|
||||
static PubSocket * create(Context * context, std::string endpoint);
|
||||
virtual ~PubSocket(){};
|
||||
};
|
||||
|
||||
class Poller {
|
||||
public:
|
||||
virtual void registerSocket(SubSocket *socket) = 0;
|
||||
virtual std::vector<SubSocket*> poll(int timeout) = 0;
|
||||
static Poller * create();
|
||||
static Poller * create(std::vector<SubSocket*> sockets);
|
||||
virtual ~Poller(){};
|
||||
};
|
||||
|
||||
class SubMaster {
|
||||
public:
|
||||
SubMaster(const std::initializer_list<const char *> &service_list,
|
||||
const char *address = nullptr, const std::initializer_list<const char *> &ignore_alive = {});
|
||||
int update(int timeout = 1000);
|
||||
inline bool allAlive(const std::initializer_list<const char *> &service_list = {}) { return all_(service_list, false, true); }
|
||||
inline bool allValid(const std::initializer_list<const char *> &service_list = {}) { return all_(service_list, true, false); }
|
||||
inline bool allAliveAndValid(const std::initializer_list<const char *> &service_list = {}) { return all_(service_list, true, true); }
|
||||
void drain();
|
||||
~SubMaster();
|
||||
|
||||
uint64_t frame = 0;
|
||||
bool updated(const char *name) const;
|
||||
uint64_t rcv_frame(const char *name) const;
|
||||
cereal::Event::Reader &operator[](const char *name);
|
||||
|
||||
private:
|
||||
bool all_(const std::initializer_list<const char *> &service_list, bool valid, bool alive);
|
||||
Poller *poller_ = nullptr;
|
||||
struct SubMessage;
|
||||
std::map<SubSocket *, SubMessage *> messages_;
|
||||
std::map<std::string, SubMessage *> services_;
|
||||
};
|
||||
|
||||
class MessageBuilder : public capnp::MallocMessageBuilder {
|
||||
public:
|
||||
MessageBuilder() = default;
|
||||
|
||||
cereal::Event::Builder initEvent(bool valid = true) {
|
||||
cereal::Event::Builder event = initRoot<cereal::Event>();
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_BOOTTIME, &t);
|
||||
uint64_t current_time = t.tv_sec * 1000000000ULL + t.tv_nsec;
|
||||
event.setLogMonoTime(current_time);
|
||||
event.setValid(valid);
|
||||
return event;
|
||||
}
|
||||
|
||||
kj::ArrayPtr<capnp::byte> toBytes() {
|
||||
heapArray_ = capnp::messageToFlatArray(*this);
|
||||
return heapArray_.asBytes();
|
||||
}
|
||||
|
||||
private:
|
||||
kj::Array<capnp::word> heapArray_;
|
||||
};
|
||||
|
||||
class PubMaster {
|
||||
public:
|
||||
PubMaster(const std::initializer_list<const char *> &service_list);
|
||||
inline int send(const char *name, capnp::byte *data, size_t size) { return sockets_.at(name)->send((char *)data, size); }
|
||||
int send(const char *name, MessageBuilder &msg);
|
||||
~PubMaster();
|
||||
|
||||
private:
|
||||
std::map<std::string, PubSocket *> sockets_;
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
# distutils: language = c++
|
||||
#cython: language_level=3
|
||||
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
from libcpp cimport bool
|
||||
|
||||
|
||||
cdef extern from "messaging.hpp":
|
||||
cdef cppclass Context:
|
||||
@staticmethod
|
||||
Context * create()
|
||||
|
||||
cdef cppclass Message:
|
||||
void init(size_t)
|
||||
void init(char *, size_t)
|
||||
void close()
|
||||
size_t getSize()
|
||||
char *getData()
|
||||
|
||||
cdef cppclass SubSocket:
|
||||
@staticmethod
|
||||
SubSocket * create()
|
||||
int connect(Context *, string, string, bool)
|
||||
Message * receive(bool)
|
||||
void setTimeout(int)
|
||||
|
||||
cdef cppclass PubSocket:
|
||||
@staticmethod
|
||||
PubSocket * create()
|
||||
int connect(Context *, string)
|
||||
int sendMessage(Message *)
|
||||
int send(char *, size_t)
|
||||
|
||||
cdef cppclass Poller:
|
||||
@staticmethod
|
||||
Poller * create()
|
||||
void registerSocket(SubSocket *)
|
||||
vector[SubSocket*] poll(int) nogil
|
||||
@@ -0,0 +1,151 @@
|
||||
# distutils: language = c++
|
||||
# cython: c_string_encoding=ascii, language_level=3
|
||||
|
||||
import sys
|
||||
from libcpp.string cimport string
|
||||
from libcpp cimport bool
|
||||
from libc cimport errno
|
||||
|
||||
|
||||
from messaging cimport Context as cppContext
|
||||
from messaging cimport SubSocket as cppSubSocket
|
||||
from messaging cimport PubSocket as cppPubSocket
|
||||
from messaging cimport Poller as cppPoller
|
||||
from messaging cimport Message as cppMessage
|
||||
|
||||
|
||||
class MessagingError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MultiplePublishersError(MessagingError):
|
||||
pass
|
||||
|
||||
|
||||
cdef class Context:
|
||||
cdef cppContext * context
|
||||
|
||||
def __cinit__(self):
|
||||
self.context = cppContext.create()
|
||||
|
||||
def term(self):
|
||||
del self.context
|
||||
self.context = NULL
|
||||
|
||||
def __dealloc__(self):
|
||||
pass
|
||||
# Deleting the context will hang if sockets are still active
|
||||
# TODO: Figure out a way to make sure the context is closed last
|
||||
# del self.context
|
||||
|
||||
|
||||
cdef class Poller:
|
||||
cdef cppPoller * poller
|
||||
cdef list sub_sockets
|
||||
|
||||
def __cinit__(self):
|
||||
self.sub_sockets = []
|
||||
self.poller = cppPoller.create()
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.poller
|
||||
|
||||
def registerSocket(self, SubSocket socket):
|
||||
self.sub_sockets.append(socket)
|
||||
self.poller.registerSocket(socket.socket)
|
||||
|
||||
def poll(self, timeout):
|
||||
sockets = []
|
||||
cdef int t = timeout
|
||||
|
||||
with nogil:
|
||||
result = self.poller.poll(t)
|
||||
|
||||
for s in result:
|
||||
socket = SubSocket()
|
||||
socket.setPtr(s)
|
||||
sockets.append(socket)
|
||||
|
||||
return sockets
|
||||
|
||||
cdef class SubSocket:
|
||||
cdef cppSubSocket * socket
|
||||
cdef bool is_owner
|
||||
|
||||
def __cinit__(self):
|
||||
self.socket = cppSubSocket.create()
|
||||
self.is_owner = True
|
||||
|
||||
if self.socket == NULL:
|
||||
raise MessagingError
|
||||
|
||||
def __dealloc__(self):
|
||||
if self.is_owner:
|
||||
del self.socket
|
||||
|
||||
cdef setPtr(self, cppSubSocket * ptr):
|
||||
if self.is_owner:
|
||||
del self.socket
|
||||
|
||||
self.is_owner = False
|
||||
self.socket = ptr
|
||||
|
||||
def connect(self, Context context, string endpoint, string address=b"127.0.0.1", bool conflate=False):
|
||||
r = self.socket.connect(context.context, endpoint, address, conflate)
|
||||
|
||||
if r != 0:
|
||||
if errno.errno == errno.EADDRINUSE:
|
||||
raise MultiplePublishersError
|
||||
else:
|
||||
raise MessagingError
|
||||
|
||||
def setTimeout(self, int timeout):
|
||||
self.socket.setTimeout(timeout)
|
||||
|
||||
def receive(self, bool non_blocking=False):
|
||||
msg = self.socket.receive(non_blocking)
|
||||
|
||||
if msg == NULL:
|
||||
# If a blocking read returns no message check errno if SIGINT was caught in the C++ code
|
||||
if errno.errno == errno.EINTR:
|
||||
print("SIGINT received, exiting")
|
||||
sys.exit(1)
|
||||
|
||||
return None
|
||||
else:
|
||||
sz = msg.getSize()
|
||||
m = msg.getData()[:sz]
|
||||
del msg
|
||||
|
||||
return m
|
||||
|
||||
|
||||
cdef class PubSocket:
|
||||
cdef cppPubSocket * socket
|
||||
|
||||
def __cinit__(self):
|
||||
self.socket = cppPubSocket.create()
|
||||
if self.socket == NULL:
|
||||
raise MessagingError
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.socket
|
||||
|
||||
def connect(self, Context context, string endpoint):
|
||||
r = self.socket.connect(context.context, endpoint)
|
||||
|
||||
if r != 0:
|
||||
if errno.errno == errno.EADDRINUSE:
|
||||
raise MultiplePublishersError
|
||||
else:
|
||||
raise MessagingError
|
||||
|
||||
def send(self, bytes data):
|
||||
length = len(data)
|
||||
r = self.socket.send(<char*>data, length)
|
||||
|
||||
if r != length:
|
||||
if errno.errno == errno.EADDRINUSE:
|
||||
raise MultiplePublishersError
|
||||
else:
|
||||
raise MessagingError
|
||||
@@ -0,0 +1,58 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sysconfig
|
||||
from distutils.core import Extension, setup # pylint: disable=import-error,no-name-in-module
|
||||
|
||||
from Cython.Build import cythonize
|
||||
from Cython.Distutils import build_ext
|
||||
|
||||
TICI = os.path.isfile('/TICI')
|
||||
|
||||
def get_ext_filename_without_platform_suffix(filename):
|
||||
name, ext = os.path.splitext(filename)
|
||||
ext_suffix = sysconfig.get_config_var('EXT_SUFFIX')
|
||||
|
||||
if ext_suffix == ext:
|
||||
return filename
|
||||
|
||||
ext_suffix = ext_suffix.replace(ext, '')
|
||||
idx = name.find(ext_suffix)
|
||||
|
||||
if idx == -1:
|
||||
return filename
|
||||
else:
|
||||
return name[:idx] + ext
|
||||
|
||||
|
||||
class BuildExtWithoutPlatformSuffix(build_ext):
|
||||
def get_ext_filename(self, ext_name):
|
||||
filename = super().get_ext_filename(ext_name)
|
||||
return get_ext_filename_without_platform_suffix(filename)
|
||||
|
||||
|
||||
sourcefiles = ['messaging_pyx.pyx']
|
||||
extra_compile_args = ["-std=c++1z", "-Wno-nullability-completeness"]
|
||||
libraries = ['zmq']
|
||||
ARCH = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() # pylint: disable=unexpected-keyword-arg
|
||||
|
||||
if ARCH == "aarch64" and not TICI:
|
||||
# android
|
||||
extra_compile_args += ["-Wno-deprecated-register"]
|
||||
libraries += ['gnustl_shared']
|
||||
|
||||
setup(name='messaging',
|
||||
cmdclass={'build_ext': BuildExtWithoutPlatformSuffix},
|
||||
ext_modules=cythonize(
|
||||
Extension(
|
||||
"messaging_pyx",
|
||||
language="c++",
|
||||
sources=sourcefiles,
|
||||
extra_compile_args=extra_compile_args,
|
||||
libraries=libraries,
|
||||
extra_objects=[
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), '../', 'libmessaging.a'),
|
||||
]
|
||||
),
|
||||
nthreads=4,
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,467 @@
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <csignal>
|
||||
#include <random>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "services.h"
|
||||
|
||||
#include "msgq.hpp"
|
||||
|
||||
void sigusr2_handler(int signal) {
|
||||
assert(signal == SIGUSR2);
|
||||
}
|
||||
|
||||
uint64_t msgq_get_uid(void){
|
||||
std::random_device rd("/dev/urandom");
|
||||
std::uniform_int_distribution<uint64_t> distribution(0,std::numeric_limits<uint32_t>::max());
|
||||
|
||||
#ifdef __APPLE__
|
||||
// TODO: this doesn't work
|
||||
uint64_t uid = distribution(rd) << 32 | getpid();
|
||||
#else
|
||||
uint64_t uid = distribution(rd) << 32 | syscall(SYS_gettid);
|
||||
#endif
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
int msgq_msg_init_size(msgq_msg_t * msg, size_t size){
|
||||
msg->size = size;
|
||||
msg->data = new(std::nothrow) char[size];
|
||||
|
||||
return (msg->data == NULL) ? -1 : 0;
|
||||
}
|
||||
|
||||
|
||||
int msgq_msg_init_data(msgq_msg_t * msg, char * data, size_t size) {
|
||||
int r = msgq_msg_init_size(msg, size);
|
||||
|
||||
if (r == 0)
|
||||
memcpy(msg->data, data, size);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int msgq_msg_close(msgq_msg_t * msg){
|
||||
if (msg->size > 0)
|
||||
delete[] msg->data;
|
||||
|
||||
msg->size = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msgq_reset_reader(msgq_queue_t * q){
|
||||
int id = q->reader_id;
|
||||
q->read_valids[id]->store(true);
|
||||
q->read_pointers[id]->store(*q->write_pointer);
|
||||
}
|
||||
|
||||
void msgq_wait_for_subscriber(msgq_queue_t *q){
|
||||
while (*q->num_readers == 0){
|
||||
;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool service_exists(std::string path){
|
||||
for (const auto& it : services) {
|
||||
if (it.name == path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int msgq_new_queue(msgq_queue_t * q, const char * path, size_t size){
|
||||
assert(size < 0xFFFFFFFF); // Buffer must be smaller than 2^32 bytes
|
||||
if (!service_exists(std::string(path))){
|
||||
std::cout << "Warning, " << std::string(path) << " is not in service list." << std::endl;
|
||||
}
|
||||
std::signal(SIGUSR2, sigusr2_handler);
|
||||
|
||||
const char * prefix = "/dev/shm/";
|
||||
char * full_path = new char[strlen(path) + strlen(prefix) + 1];
|
||||
strcpy(full_path, prefix);
|
||||
strcat(full_path, path);
|
||||
|
||||
auto fd = open(full_path, O_RDWR | O_CREAT, 0777);
|
||||
if (fd < 0) {
|
||||
std::cout << "Warning, could not open: " << full_path << std::endl;
|
||||
delete[] full_path;
|
||||
return -1;
|
||||
}
|
||||
delete[] full_path;
|
||||
|
||||
int rc = ftruncate(fd, size + sizeof(msgq_header_t));
|
||||
if (rc < 0){
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
char * mem = (char*)mmap(NULL, size + sizeof(msgq_header_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
close(fd);
|
||||
|
||||
if (mem == NULL){
|
||||
return -1;
|
||||
}
|
||||
q->mmap_p = mem;
|
||||
|
||||
msgq_header_t *header = (msgq_header_t *)mem;
|
||||
|
||||
// Setup pointers to header segment
|
||||
q->num_readers = reinterpret_cast<std::atomic<uint64_t>*>(&header->num_readers);
|
||||
q->write_pointer = reinterpret_cast<std::atomic<uint64_t>*>(&header->write_pointer);
|
||||
q->write_uid = reinterpret_cast<std::atomic<uint64_t>*>(&header->write_uid);
|
||||
|
||||
for (size_t i = 0; i < NUM_READERS; i++){
|
||||
q->read_pointers[i] = reinterpret_cast<std::atomic<uint64_t>*>(&header->read_pointers[i]);
|
||||
q->read_valids[i] = reinterpret_cast<std::atomic<uint64_t>*>(&header->read_valids[i]);
|
||||
q->read_uids[i] = reinterpret_cast<std::atomic<uint64_t>*>(&header->read_uids[i]);
|
||||
}
|
||||
|
||||
q->data = mem + sizeof(msgq_header_t);
|
||||
q->size = size;
|
||||
q->reader_id = -1;
|
||||
|
||||
q->endpoint = path;
|
||||
q->read_conflate = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msgq_close_queue(msgq_queue_t *q){
|
||||
if (q->mmap_p != NULL){
|
||||
munmap(q->mmap_p, q->size + sizeof(msgq_header_t));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void msgq_init_publisher(msgq_queue_t * q) {
|
||||
//std::cout << "Starting publisher" << std::endl;
|
||||
uint64_t uid = msgq_get_uid();
|
||||
|
||||
*q->write_uid = uid;
|
||||
*q->num_readers = 0;
|
||||
|
||||
for (size_t i = 0; i < NUM_READERS; i++){
|
||||
*q->read_valids[i] = false;
|
||||
*q->read_uids[i] = 0;
|
||||
}
|
||||
|
||||
q->write_uid_local = uid;
|
||||
}
|
||||
|
||||
static void thread_signal(uint32_t tid) {
|
||||
#ifndef SYS_tkill
|
||||
// TODO: this won't work for multithreaded programs
|
||||
kill(tid, SIGUSR2);
|
||||
#else
|
||||
syscall(SYS_tkill, tid, SIGUSR2);
|
||||
#endif
|
||||
}
|
||||
|
||||
void msgq_init_subscriber(msgq_queue_t * q) {
|
||||
assert(q != NULL);
|
||||
assert(q->num_readers != NULL);
|
||||
|
||||
uint64_t uid = msgq_get_uid();
|
||||
|
||||
// Get reader id
|
||||
while (true){
|
||||
uint64_t cur_num_readers = *q->num_readers;
|
||||
uint64_t new_num_readers = cur_num_readers + 1;
|
||||
|
||||
// No more slots available. Reset all subscribers to kick out inactive ones
|
||||
if (new_num_readers > NUM_READERS){
|
||||
std::cout << "Warning, evicting all subscribers!" << std::endl;
|
||||
*q->num_readers = 0;
|
||||
|
||||
for (size_t i = 0; i < NUM_READERS; i++){
|
||||
*q->read_valids[i] = false;
|
||||
|
||||
uint64_t old_uid = *q->read_uids[i];
|
||||
*q->read_uids[i] = 0;
|
||||
|
||||
// Wake up reader in case they are in a poll
|
||||
thread_signal(old_uid & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use atomic compare and swap to handle race condition
|
||||
// where two subscribers start at the same time
|
||||
if (std::atomic_compare_exchange_strong(q->num_readers,
|
||||
&cur_num_readers,
|
||||
new_num_readers)){
|
||||
q->reader_id = cur_num_readers;
|
||||
q->read_uid_local = uid;
|
||||
|
||||
// We start with read_valid = false,
|
||||
// on the first read the read pointer will be synchronized with the write pointer
|
||||
*q->read_valids[cur_num_readers] = false;
|
||||
*q->read_pointers[cur_num_readers] = 0;
|
||||
*q->read_uids[cur_num_readers] = uid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//std::cout << "New subscriber id: " << q->reader_id << " uid: " << q->read_uid_local << " " << q->endpoint << std::endl;
|
||||
msgq_reset_reader(q);
|
||||
}
|
||||
|
||||
int msgq_msg_send(msgq_msg_t * msg, msgq_queue_t *q){
|
||||
// Die if we are no longer the active publisher
|
||||
if (q->write_uid_local != *q->write_uid){
|
||||
std::cout << "Killing old publisher: " << q->endpoint << std::endl;
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t total_msg_size = ALIGN(msg->size + sizeof(int64_t));
|
||||
|
||||
// We need to fit at least three messages in the queue,
|
||||
// then we can always safely access the last message
|
||||
assert(3 * total_msg_size <= q->size);
|
||||
|
||||
uint64_t num_readers = *q->num_readers;
|
||||
|
||||
uint32_t write_cycles, write_pointer;
|
||||
UNPACK64(write_cycles, write_pointer, *q->write_pointer);
|
||||
|
||||
char *p = q->data + write_pointer; // add base offset
|
||||
|
||||
// Check remaining space
|
||||
// Always leave space for a wraparound tag for the next message, including alignment
|
||||
int64_t remaining_space = q->size - write_pointer - total_msg_size - sizeof(int64_t);
|
||||
if (remaining_space <= 0){
|
||||
// Write -1 size tag indicating wraparound
|
||||
*(int64_t*)p = -1;
|
||||
|
||||
// Invalidate all readers that are beyond the write pointer
|
||||
// TODO: should we handle the case where a new reader shows up while this is running?
|
||||
for (uint64_t i = 0; i < num_readers; i++){
|
||||
uint64_t read_pointer = *q->read_pointers[i];
|
||||
uint64_t read_cycles = read_pointer >> 32;
|
||||
read_pointer &= 0xFFFFFFFF;
|
||||
|
||||
if ((read_pointer > write_pointer) && (read_cycles != write_cycles)) {
|
||||
*q->read_valids[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update global and local copies of write pointer and write_cycles
|
||||
write_pointer = 0;
|
||||
write_cycles = write_cycles + 1;
|
||||
PACK64(*q->write_pointer, write_cycles, write_pointer);
|
||||
|
||||
// Set actual pointer to the beginning of the data segment
|
||||
p = q->data;
|
||||
}
|
||||
|
||||
// Invalidate readers that are in the area that will be written
|
||||
uint64_t start = write_pointer;
|
||||
uint64_t end = ALIGN(start + sizeof(int64_t) + msg->size);
|
||||
|
||||
for (uint64_t i = 0; i < num_readers; i++){
|
||||
uint32_t read_cycles, read_pointer;
|
||||
UNPACK64(read_cycles, read_pointer, *q->read_pointers[i]);
|
||||
|
||||
if ((read_pointer >= start) && (read_pointer < end) && (read_cycles != write_cycles)) {
|
||||
*q->read_valids[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write size tag
|
||||
std::atomic<int64_t> *size_p = reinterpret_cast<std::atomic<int64_t>*>(p);
|
||||
*size_p = msg->size;
|
||||
|
||||
// Copy data
|
||||
memcpy(p + sizeof(int64_t), msg->data, msg->size);
|
||||
__sync_synchronize();
|
||||
|
||||
// Update write pointer
|
||||
uint32_t new_ptr = ALIGN(write_pointer + msg->size + sizeof(int64_t));
|
||||
PACK64(*q->write_pointer, write_cycles, new_ptr);
|
||||
|
||||
// Notify readers
|
||||
for (uint64_t i = 0; i < num_readers; i++){
|
||||
uint64_t reader_uid = *q->read_uids[i];
|
||||
thread_signal(reader_uid & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
return msg->size;
|
||||
}
|
||||
|
||||
|
||||
int msgq_msg_ready(msgq_queue_t * q){
|
||||
start:
|
||||
int id = q->reader_id;
|
||||
assert(id >= 0); // Make sure subscriber is initialized
|
||||
|
||||
if (q->read_uid_local != *q->read_uids[id]){
|
||||
std::cout << q->endpoint << ": Reader was evicted, reconnecting" << std::endl;
|
||||
msgq_init_subscriber(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
// Check valid
|
||||
if (!*q->read_valids[id]){
|
||||
msgq_reset_reader(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
uint32_t read_cycles, read_pointer;
|
||||
UNPACK64(read_cycles, read_pointer, *q->read_pointers[id]);
|
||||
|
||||
uint32_t write_cycles, write_pointer;
|
||||
UNPACK64(write_cycles, write_pointer, *q->write_pointer);
|
||||
|
||||
// Check if new message is available
|
||||
return (read_pointer != write_pointer);
|
||||
}
|
||||
|
||||
int msgq_msg_recv(msgq_msg_t * msg, msgq_queue_t * q){
|
||||
start:
|
||||
int id = q->reader_id;
|
||||
assert(id >= 0); // Make sure subscriber is initialized
|
||||
|
||||
if (q->read_uid_local != *q->read_uids[id]){
|
||||
std::cout << q->endpoint << ": Reader was evicted, reconnecting" << std::endl;
|
||||
msgq_init_subscriber(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
// Check valid
|
||||
if (!*q->read_valids[id]){
|
||||
msgq_reset_reader(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
uint32_t read_cycles, read_pointer;
|
||||
UNPACK64(read_cycles, read_pointer, *q->read_pointers[id]);
|
||||
|
||||
uint32_t write_cycles, write_pointer;
|
||||
UNPACK64(write_cycles, write_pointer, *q->write_pointer);
|
||||
|
||||
char * p = q->data + read_pointer;
|
||||
|
||||
// Check if new message is available
|
||||
if (read_pointer == write_pointer) {
|
||||
msg->size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read potential message size
|
||||
std::atomic<int64_t> *size_p = reinterpret_cast<std::atomic<int64_t>*>(p);
|
||||
std::int64_t size = *size_p;
|
||||
|
||||
// Check if the size that was read is valid
|
||||
if (!*q->read_valids[id]){
|
||||
msgq_reset_reader(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
// If size is -1 the buffer was full, and we need to wrap around
|
||||
if (size == -1){
|
||||
read_cycles++;
|
||||
PACK64(*q->read_pointers[id], read_cycles, 0);
|
||||
goto start;
|
||||
}
|
||||
|
||||
// crashing is better than passing garbage data to the consumer
|
||||
// the size will have weird value if it was overwritten by data accidentally
|
||||
assert((uint64_t)size < q->size);
|
||||
assert(size > 0);
|
||||
|
||||
uint32_t new_read_pointer = ALIGN(read_pointer + sizeof(std::int64_t) + size);
|
||||
|
||||
// If conflate is true, check if this is the latest message, else start over
|
||||
if (q->read_conflate){
|
||||
if (new_read_pointer != write_pointer){
|
||||
// Update read pointer
|
||||
PACK64(*q->read_pointers[id], read_cycles, new_read_pointer);
|
||||
goto start;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy message
|
||||
if (msgq_msg_init_size(msg, size) < 0)
|
||||
return -1;
|
||||
|
||||
__sync_synchronize();
|
||||
memcpy(msg->data, p + sizeof(int64_t), size);
|
||||
__sync_synchronize();
|
||||
|
||||
// Update read pointer
|
||||
PACK64(*q->read_pointers[id], read_cycles, new_read_pointer);
|
||||
|
||||
// Check if the actual data that was copied is valid
|
||||
if (!*q->read_valids[id]){
|
||||
msgq_msg_close(msg);
|
||||
msgq_reset_reader(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
|
||||
return msg->size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int msgq_poll(msgq_pollitem_t * items, size_t nitems, int timeout){
|
||||
int num = 0;
|
||||
|
||||
// Check if messages ready
|
||||
for (size_t i = 0; i < nitems; i++) {
|
||||
items[i].revents = msgq_msg_ready(items[i].q);
|
||||
if (items[i].revents) num++;
|
||||
}
|
||||
|
||||
int ms = (timeout == -1) ? 100 : timeout;
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000 * 1000;
|
||||
|
||||
|
||||
while (num == 0) {
|
||||
int ret;
|
||||
|
||||
ret = nanosleep(&ts, &ts);
|
||||
|
||||
// Check if messages ready
|
||||
for (size_t i = 0; i < nitems; i++) {
|
||||
if (items[i].revents == 0 && msgq_msg_ready(items[i].q)){
|
||||
num += 1;
|
||||
items[i].revents = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// exit if we had a timeout and the sleep finished
|
||||
if (timeout != -1 && ret == 0){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
|
||||
#define DEFAULT_SEGMENT_SIZE (10 * 1024 * 1024)
|
||||
#define NUM_READERS 8
|
||||
#define ALIGN(n) ((n + (8 - 1)) & -8)
|
||||
|
||||
#define UNPACK64(higher, lower, input) do {uint64_t tmp = input; higher = tmp >> 32; lower = tmp & 0xFFFFFFFF;} while (0)
|
||||
#define PACK64(output, higher, lower) output = ((uint64_t)higher << 32 ) | ((uint64_t)lower & 0xFFFFFFFF)
|
||||
|
||||
struct msgq_header_t {
|
||||
uint64_t num_readers;
|
||||
uint64_t write_pointer;
|
||||
uint64_t write_uid;
|
||||
uint64_t read_pointers[NUM_READERS];
|
||||
uint64_t read_valids[NUM_READERS];
|
||||
uint64_t read_uids[NUM_READERS];
|
||||
};
|
||||
|
||||
struct msgq_queue_t {
|
||||
std::atomic<uint64_t> *num_readers;
|
||||
std::atomic<uint64_t> *write_pointer;
|
||||
std::atomic<uint64_t> *write_uid;
|
||||
std::atomic<uint64_t> *read_pointers[NUM_READERS];
|
||||
std::atomic<uint64_t> *read_valids[NUM_READERS];
|
||||
std::atomic<uint64_t> *read_uids[NUM_READERS];
|
||||
char * mmap_p;
|
||||
char * data;
|
||||
size_t size;
|
||||
int reader_id;
|
||||
uint64_t read_uid_local;
|
||||
uint64_t write_uid_local;
|
||||
|
||||
bool read_conflate;
|
||||
std::string endpoint;
|
||||
};
|
||||
|
||||
struct msgq_msg_t {
|
||||
size_t size;
|
||||
char * data;
|
||||
};
|
||||
|
||||
struct msgq_pollitem_t {
|
||||
msgq_queue_t *q;
|
||||
int revents;
|
||||
};
|
||||
|
||||
void msgq_wait_for_subscriber(msgq_queue_t *q);
|
||||
void msgq_reset_reader(msgq_queue_t *q);
|
||||
|
||||
int msgq_msg_init_size(msgq_msg_t *msg, size_t size);
|
||||
int msgq_msg_init_data(msgq_msg_t *msg, char * data, size_t size);
|
||||
int msgq_msg_close(msgq_msg_t *msg);
|
||||
|
||||
int msgq_new_queue(msgq_queue_t * q, const char * path, size_t size);
|
||||
void msgq_close_queue(msgq_queue_t *q);
|
||||
void msgq_init_publisher(msgq_queue_t * q);
|
||||
void msgq_init_subscriber(msgq_queue_t * q);
|
||||
|
||||
int msgq_msg_send(msgq_msg_t *msg, msgq_queue_t *q);
|
||||
int msgq_msg_recv(msgq_msg_t *msg, msgq_queue_t *q);
|
||||
int msgq_msg_ready(msgq_queue_t * q);
|
||||
int msgq_poll(msgq_pollitem_t * items, size_t nitems, int timeout);
|
||||
@@ -0,0 +1,170 @@
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include "messaging.hpp"
|
||||
#include "services.h"
|
||||
|
||||
static inline uint64_t nanos_since_boot() {
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_BOOTTIME, &t);
|
||||
return t.tv_sec * 1000000000ULL + t.tv_nsec;
|
||||
}
|
||||
|
||||
static const service *get_service(const char *name) {
|
||||
for (const auto &it : services) {
|
||||
if (strcmp(it.name, name) == 0) return ⁢
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline bool inList(const std::initializer_list<const char *> &list, const char *value) {
|
||||
for (auto &v : list) {
|
||||
if (strcmp(value, v) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class MessageContext {
|
||||
public:
|
||||
MessageContext() { ctx_ = Context::create(); }
|
||||
~MessageContext() { delete ctx_; }
|
||||
Context *ctx_;
|
||||
};
|
||||
MessageContext ctx;
|
||||
|
||||
struct SubMaster::SubMessage {
|
||||
std::string name;
|
||||
SubSocket *socket = nullptr;
|
||||
int freq = 0;
|
||||
bool updated = false, alive = false, valid = false, ignore_alive;
|
||||
uint64_t rcv_time = 0, rcv_frame = 0;
|
||||
void *allocated_msg_reader = nullptr;
|
||||
capnp::FlatArrayMessageReader *msg_reader = nullptr;
|
||||
kj::Array<capnp::word> buf;
|
||||
cereal::Event::Reader event;
|
||||
};
|
||||
|
||||
SubMaster::SubMaster(const std::initializer_list<const char *> &service_list, const char *address,
|
||||
const std::initializer_list<const char *> &ignore_alive) {
|
||||
poller_ = Poller::create();
|
||||
for (auto name : service_list) {
|
||||
const service *serv = get_service(name);
|
||||
assert(serv != nullptr);
|
||||
SubSocket *socket = SubSocket::create(ctx.ctx_, name, address ? address : "127.0.0.1", true);
|
||||
assert(socket != 0);
|
||||
poller_->registerSocket(socket);
|
||||
SubMessage *m = new SubMessage{
|
||||
.socket = socket,
|
||||
.freq = serv->frequency,
|
||||
.ignore_alive = inList(ignore_alive, name),
|
||||
.allocated_msg_reader = malloc(sizeof(capnp::FlatArrayMessageReader)),
|
||||
.buf = kj::heapArray<capnp::word>(1024)};
|
||||
messages_[socket] = m;
|
||||
services_[name] = m;
|
||||
}
|
||||
}
|
||||
|
||||
int SubMaster::update(int timeout) {
|
||||
if (++frame == UINT64_MAX) frame = 1;
|
||||
for (auto &kv : messages_) kv.second->updated = false;
|
||||
|
||||
int updated = 0;
|
||||
auto sockets = poller_->poll(timeout);
|
||||
uint64_t current_time = nanos_since_boot();
|
||||
for (auto s : sockets) {
|
||||
Message *msg = s->receive(true);
|
||||
if (msg == nullptr) continue;
|
||||
|
||||
SubMessage *m = messages_.at(s);
|
||||
const size_t size = (msg->getSize() / sizeof(capnp::word)) + 1;
|
||||
if (m->buf.size() < size) {
|
||||
m->buf = kj::heapArray<capnp::word>(size);
|
||||
}
|
||||
memcpy(m->buf.begin(), msg->getData(), msg->getSize());
|
||||
delete msg;
|
||||
|
||||
if (m->msg_reader) {
|
||||
m->msg_reader->~FlatArrayMessageReader();
|
||||
}
|
||||
m->msg_reader = new (m->allocated_msg_reader) capnp::FlatArrayMessageReader(kj::ArrayPtr<capnp::word>(m->buf.begin(), size));
|
||||
m->event = m->msg_reader->getRoot<cereal::Event>();
|
||||
m->updated = true;
|
||||
m->rcv_time = current_time;
|
||||
m->rcv_frame = frame;
|
||||
m->valid = m->event.getValid();
|
||||
|
||||
++updated;
|
||||
}
|
||||
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
m->alive = (m->freq <= (1e-5) || ((current_time - m->rcv_time) * (1e-9)) < (10.0 / m->freq));
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
bool SubMaster::all_(const std::initializer_list<const char *> &service_list, bool valid, bool alive) {
|
||||
int found = 0;
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
if (service_list.size() == 0 || inList(service_list, m->name.c_str())) {
|
||||
found += (!valid || m->valid) && (!alive || (m->alive && !m->ignore_alive));
|
||||
}
|
||||
}
|
||||
return service_list.size() == 0 ? found == messages_.size() : found == service_list.size();
|
||||
}
|
||||
|
||||
void SubMaster::drain() {
|
||||
while (true) {
|
||||
auto polls = poller_->poll(0);
|
||||
if (polls.size() == 0)
|
||||
break;
|
||||
|
||||
for (auto sock : polls) {
|
||||
Message *msg = sock->receive(true);
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SubMaster::updated(const char *name) const {
|
||||
return services_.at(name)->updated;
|
||||
}
|
||||
|
||||
uint64_t SubMaster::rcv_frame(const char *name) const {
|
||||
return services_.at(name)->rcv_frame;
|
||||
}
|
||||
|
||||
cereal::Event::Reader &SubMaster::operator[](const char *name) {
|
||||
return services_.at(name)->event;
|
||||
};
|
||||
|
||||
SubMaster::~SubMaster() {
|
||||
delete poller_;
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
if (m->msg_reader) {
|
||||
m->msg_reader->~FlatArrayMessageReader();
|
||||
}
|
||||
free(m->allocated_msg_reader);
|
||||
delete m->socket;
|
||||
delete m;
|
||||
}
|
||||
}
|
||||
|
||||
PubMaster::PubMaster(const std::initializer_list<const char *> &service_list) {
|
||||
for (auto name : service_list) {
|
||||
assert(get_service(name) != nullptr);
|
||||
PubSocket *socket = PubSocket::create(ctx.ctx_, name);
|
||||
assert(socket);
|
||||
sockets_[name] = socket;
|
||||
}
|
||||
}
|
||||
|
||||
int PubMaster::send(const char *name, MessageBuilder &msg) {
|
||||
auto bytes = msg.toBytes();
|
||||
return send(name, bytes.begin(), bytes.size());
|
||||
}
|
||||
|
||||
PubMaster::~PubMaster() {
|
||||
for (auto s : sockets_) delete s.second;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
# TODO: these port numbers are hardcoded in c, fix this
|
||||
|
||||
# LogRotate: 8001 is a PUSH PULL socket between loggerd and visiond
|
||||
|
||||
# all ZMQ pub sub: port, should_log, frequency, (qlog_decimation)
|
||||
|
||||
# frame syncing packet
|
||||
frame: [8002, true, 20., 1]
|
||||
# accel, gyro, and compass
|
||||
sensorEvents: [8003, true, 100., 100]
|
||||
# GPS data, also global timestamp
|
||||
gpsNMEA: [8004, true, 9.] # 9 msgs each sec
|
||||
# CPU+MEM+GPU+BAT temps
|
||||
thermal: [8005, true, 2., 1]
|
||||
# List(CanData), list of can messages
|
||||
can: [8006, true, 100.]
|
||||
controlsState: [8007, true, 100., 100]
|
||||
#liveEvent: [8008, true, 0.]
|
||||
model: [8009, true, 20., 5]
|
||||
features: [8010, true, 0.]
|
||||
health: [8011, true, 2., 1]
|
||||
radarState: [8012, true, 20., 5]
|
||||
#liveUI: [8014, true, 0.]
|
||||
encodeIdx: [8015, true, 20.]
|
||||
liveTracks: [8016, true, 20.]
|
||||
sendcan: [8017, true, 100.]
|
||||
logMessage: [8018, true, 0.]
|
||||
liveCalibration: [8019, true, 4., 4]
|
||||
androidLog: [8020, true, 0.]
|
||||
carState: [8021, true, 100., 10]
|
||||
# 8022 is reserved for sshd
|
||||
carControl: [8023, true, 100., 10]
|
||||
plan: [8024, true, 20., 2]
|
||||
liveLocation: [8025, true, 0., 1]
|
||||
gpsLocation: [8026, true, 1., 1]
|
||||
ethernetData: [8027, true, 0.]
|
||||
navUpdate: [8028, true, 0.]
|
||||
qcomGnss: [8029, true, 0.]
|
||||
lidarPts: [8030, true, 0.]
|
||||
procLog: [8031, true, 0.5]
|
||||
gpsLocationExternal: [8032, true, 10., 1]
|
||||
ubloxGnss: [8033, true, 10.]
|
||||
clocks: [8034, true, 1., 1]
|
||||
liveMpc: [8035, false, 20.]
|
||||
liveLongitudinalMpc: [8036, false, 20.]
|
||||
navStatus: [8038, true, 0.]
|
||||
gpsLocationTrimble: [8039, true, 0.]
|
||||
trimbleGnss: [8041, true, 0.]
|
||||
ubloxRaw: [8042, true, 20.]
|
||||
gpsPlannerPoints: [8043, true, 0.]
|
||||
gpsPlannerPlan: [8044, true, 0.]
|
||||
applanixRaw: [8046, true, 0.]
|
||||
orbLocation: [8047, true, 0.]
|
||||
trafficEvents: [8048, true, 0.]
|
||||
liveLocationTiming: [8049, true, 0.]
|
||||
orbslamCorrection: [8050, true, 0.]
|
||||
liveLocationCorrected: [8051, true, 0.]
|
||||
orbObservation: [8052, true, 0.]
|
||||
applanixLocation: [8053, true, 0.]
|
||||
liveLocationKalman: [8054, true, 20., 2]
|
||||
uiNavigationEvent: [8055, true, 0.]
|
||||
orbOdometry: [8057, true, 0.]
|
||||
orbFeatures: [8058, false, 0.]
|
||||
orbKeyFrame: [8059, true, 0.]
|
||||
uiLayoutState: [8060, true, 0.]
|
||||
frontEncodeIdx: [8061, true, 5.] # should be 20fps on tici
|
||||
orbFeaturesSummary: [8062, true, 0.]
|
||||
driverState: [8063, true, 5., 1]
|
||||
liveParameters: [8064, true, 20., 2]
|
||||
liveMapData: [8065, true, 0.]
|
||||
cameraOdometry: [8066, true, 20., 5]
|
||||
pathPlan: [8067, true, 20., 2]
|
||||
kalmanOdometry: [8068, true, 0.]
|
||||
thumbnail: [8069, true, 0.2, 1]
|
||||
carEvents: [8070, true, 1., 1]
|
||||
carParams: [8071, true, 0.02, 1]
|
||||
frontFrame: [8072, true, 10.]
|
||||
dMonitoringState: [8073, true, 5., 1]
|
||||
offroadLayout: [8074, false, 0.]
|
||||
wideEncodeIdx: [8075, true, 20.]
|
||||
wideFrame: [8076, true, 20.]
|
||||
modelV2: [8077, true, 20., 20]
|
||||
|
||||
testModel: [8040, false, 0.]
|
||||
testLiveLocation: [8045, false, 0.]
|
||||
testJoystick: [8056, false, 0.]
|
||||
|
||||
# 8080 is reserved for slave testing daemon
|
||||
# 8762 is reserved for logserver
|
||||
|
||||
# manager -- base process to manage starting and stopping of all others
|
||||
# subscribes: thermal
|
||||
|
||||
# **** processes that communicate with the outside world ****
|
||||
|
||||
# thermald -- decides when to start and stop onroad
|
||||
# subscribes: health, location
|
||||
# publishes: thermal
|
||||
|
||||
# boardd -- communicates with the car
|
||||
# subscribes: sendcan
|
||||
# publishes: can, health, ubloxRaw
|
||||
|
||||
# sensord -- publishes IMU and Magnetometer
|
||||
# publishes: sensorEvents
|
||||
|
||||
# gpsd -- publishes EON's gps
|
||||
# publishes: gpsNMEA
|
||||
|
||||
# camerad -- publishes camera frames
|
||||
# publishes: frame, frontFrame, thumbnail
|
||||
# subscribes: driverState
|
||||
|
||||
# dmonitoringmodeld -- runs face detection on camera frames
|
||||
# publishes: driverState
|
||||
|
||||
# **** stateful data transformers ****
|
||||
|
||||
# modeld -- runs & publishes the model
|
||||
# publishes: model, cameraOdometry
|
||||
# subscribes: liveCalibration, pathPlan
|
||||
|
||||
# plannerd -- decides where to drive the car
|
||||
# subscribes: carState, model, radarState, controlsState, liveParameters
|
||||
# publishes: plan, pathPlan, liveMpc, liveLongitudinalMpc
|
||||
|
||||
# controlsd -- drives the car by sending CAN messages to panda
|
||||
# subscribes: can, thermal, health, plan, pathPlan, dMonitoringState, liveCalibration, model
|
||||
# publishes: carState, carControl, sendcan, controlsState, carEvents, carParams
|
||||
|
||||
# dmonitoringd -- processes driver monitoring data and publishes driver awareness
|
||||
# subscribes: driverState, liveCalibration, carState, model, gpsLocation
|
||||
# publishes: dMonitoringState
|
||||
|
||||
# radard -- processes the radar and vision data
|
||||
# subscribes: can, controlsState, model, liveParameters
|
||||
# publishes: radarState, liveTracks
|
||||
|
||||
# params_learner -- learns vehicle params by observing the vehicle dynamics
|
||||
# subscribes: controlsState, sensorEvents, cameraOdometry
|
||||
# publishes: liveParameters
|
||||
|
||||
# calibrationd -- reads posenet and applies a temporal filter on the frame region to look at
|
||||
# subscribes: cameraOdometry
|
||||
# publishes: liveCalibration
|
||||
|
||||
# ubloxd -- read raw ublox data and converts them in readable format
|
||||
# subscribes: ubloxRaw
|
||||
# publishes: ubloxGnss
|
||||
|
||||
# **** LOGGING SERVICE ****
|
||||
|
||||
# loggerd
|
||||
# subscribes: EVERYTHING
|
||||
|
||||
# **** NON VITAL SERVICES ****
|
||||
|
||||
# ui
|
||||
# subscribes: thermal, model, controlsState, uiLayout, liveCalibration, radarState, liveMpc, plusFrame, liveMapData
|
||||
|
||||
# uploader
|
||||
# communicates through file system with loggerd
|
||||
|
||||
# deleter
|
||||
# communicates through file system with loggerd and uploader
|
||||
|
||||
# logmessaged -- central logging service, can log to cloud
|
||||
# publishes: logMessage
|
||||
|
||||
# logcatd -- fetches logcat info from android
|
||||
# publishes: androidLog
|
||||
|
||||
# proclogd -- fetches process information
|
||||
# publishes: procLog
|
||||
|
||||
# tombstoned -- reports native crashes
|
||||
|
||||
# athenad -- on request, open a sub socket and return the value
|
||||
|
||||
# updated -- waits for network access and tries to update every hour
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import yaml
|
||||
|
||||
|
||||
class Service():
|
||||
def __init__(self, port, should_log, frequency, decimation=None):
|
||||
self.port = port
|
||||
self.should_log = should_log
|
||||
self.frequency = frequency
|
||||
self.decimation = decimation
|
||||
|
||||
|
||||
service_list_path = os.path.join(os.path.dirname(__file__), "service_list.yaml")
|
||||
|
||||
service_list = {}
|
||||
with open(service_list_path, "r") as f:
|
||||
for k, v in yaml.safe_load(f).items():
|
||||
decimation = None
|
||||
if len(v) == 4:
|
||||
decimation = v[3]
|
||||
|
||||
service_list[k] = Service(v[0], v[1], v[2], decimation)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("/* THIS IS AN AUTOGENERATED FILE, PLEASE EDIT service_list.yaml */")
|
||||
print("#ifndef __SERVICES_H")
|
||||
print("#define __SERVICES_H")
|
||||
print("struct service { char name[0x100]; int port; bool should_log; int frequency; int decimation; };")
|
||||
print("static struct service services[] = {")
|
||||
for k, v in service_list.items():
|
||||
print(' { .name = "%s", .port = %d, .should_log = %s, .frequency = %d, .decimation = %d },' % (k, v.port, "true" if v.should_log else "false", v.frequency, -1 if v.decimation is None else v.decimation))
|
||||
print("};")
|
||||
print("#endif")
|
||||
+3
-5
@@ -1,6 +1,4 @@
|
||||
Import('env')
|
||||
Import('envCython')
|
||||
|
||||
# parser
|
||||
env.Command(['common_pyx.so'],
|
||||
['common_pyx_setup.py', 'clock.pyx'],
|
||||
"cd common && python3 common_pyx_setup.py build_ext --inplace")
|
||||
envCython.Program('clock.so', 'clock.pyx')
|
||||
envCython.Program('params_pyx.so', 'params_pyx.pyx')
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import os
|
||||
import binascii
|
||||
import itertools
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import random
|
||||
from cereal import log
|
||||
|
||||
NetworkType = log.ThermalData.NetworkType
|
||||
|
||||
ANDROID = os.path.isfile('/EON')
|
||||
|
||||
def getprop(key):
|
||||
if not ANDROID:
|
||||
return ""
|
||||
return subprocess.check_output(["getprop", key], encoding='utf8').strip()
|
||||
|
||||
def get_imei(slot):
|
||||
slot = str(slot)
|
||||
if slot not in ("0", "1"):
|
||||
raise ValueError("SIM slot must be 0 or 1")
|
||||
|
||||
ret = parse_service_call_string(service_call(["iphonesubinfo", "3" ,"i32", str(slot)]))
|
||||
if not ret:
|
||||
# allow non android to be identified differently
|
||||
ret = "%015d" % random.randint(0, 1<<32)
|
||||
return ret
|
||||
|
||||
def get_serial():
|
||||
ret = getprop("ro.serialno")
|
||||
if ret == "":
|
||||
ret = "cccccccc"
|
||||
return ret
|
||||
|
||||
def get_subscriber_info():
|
||||
ret = parse_service_call_string(service_call(["iphonesubinfo", "7"]))
|
||||
if ret is None or len(ret) < 8:
|
||||
return ""
|
||||
return ret
|
||||
|
||||
def reboot(reason=None):
|
||||
if reason is None:
|
||||
reason_args = ["null"]
|
||||
else:
|
||||
reason_args = ["s16", reason]
|
||||
|
||||
subprocess.check_output([
|
||||
"service", "call", "power", "16", # IPowerManager.reboot
|
||||
"i32", "0", # no confirmation,
|
||||
*reason_args,
|
||||
"i32", "1" # wait
|
||||
])
|
||||
|
||||
def service_call(call):
|
||||
if not ANDROID:
|
||||
return None
|
||||
|
||||
ret = subprocess.check_output(["service", "call", *call], encoding='utf8').strip()
|
||||
if 'Parcel' not in ret:
|
||||
return None
|
||||
|
||||
return parse_service_call_bytes(ret)
|
||||
|
||||
def parse_service_call_unpack(r, fmt):
|
||||
try:
|
||||
return struct.unpack(fmt, r)[0]
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def parse_service_call_string(r):
|
||||
try:
|
||||
r = r[8:] # Cut off length field
|
||||
r = r.decode('utf_16_be')
|
||||
|
||||
# All pairs of two characters seem to be swapped. Not sure why
|
||||
result = ""
|
||||
for a, b, in itertools.zip_longest(r[::2], r[1::2], fillvalue='\x00'):
|
||||
result += b + a
|
||||
|
||||
result = result.replace('\x00', '')
|
||||
|
||||
return result
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def parse_service_call_bytes(ret):
|
||||
try:
|
||||
r = b""
|
||||
for hex_part in re.findall(r'[ (]([0-9a-f]{8})', ret):
|
||||
r += binascii.unhexlify(hex_part)
|
||||
return r
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_network_type():
|
||||
if not ANDROID:
|
||||
return NetworkType.none
|
||||
|
||||
wifi_check = parse_service_call_string(service_call(["connectivity", "2"]))
|
||||
if wifi_check is None:
|
||||
return NetworkType.none
|
||||
elif 'WIFI' in wifi_check:
|
||||
return NetworkType.wifi
|
||||
else:
|
||||
cell_check = parse_service_call_unpack(service_call(['phone', '59']), ">q")
|
||||
# from TelephonyManager.java
|
||||
cell_networks = {
|
||||
0: NetworkType.none,
|
||||
1: NetworkType.cell2G,
|
||||
2: NetworkType.cell2G,
|
||||
3: NetworkType.cell3G,
|
||||
4: NetworkType.cell2G,
|
||||
5: NetworkType.cell3G,
|
||||
6: NetworkType.cell3G,
|
||||
7: NetworkType.cell3G,
|
||||
8: NetworkType.cell3G,
|
||||
9: NetworkType.cell3G,
|
||||
10: NetworkType.cell3G,
|
||||
11: NetworkType.cell2G,
|
||||
12: NetworkType.cell3G,
|
||||
13: NetworkType.cell4G,
|
||||
14: NetworkType.cell4G,
|
||||
15: NetworkType.cell3G,
|
||||
16: NetworkType.cell2G,
|
||||
17: NetworkType.cell3G,
|
||||
18: NetworkType.cell4G,
|
||||
19: NetworkType.cell4G
|
||||
}
|
||||
return cell_networks.get(cell_check, NetworkType.none)
|
||||
@@ -38,5 +38,4 @@ def api_get(endpoint, method='GET', timeout=None, access_token=None, **params):
|
||||
|
||||
headers['User-Agent'] = "openpilot-" + version
|
||||
|
||||
return requests.request(method, backend+endpoint, timeout=timeout, headers = headers, params=params)
|
||||
|
||||
return requests.request(method, backend+endpoint, timeout=timeout, headers=headers, params=params)
|
||||
|
||||
+15
-9
@@ -6,14 +6,14 @@ import shutil
|
||||
from common.basedir import BASEDIR
|
||||
from selfdrive.swaglog import cloudlog
|
||||
|
||||
android_packages = ("ai.comma.plus.offroad", "ai.comma.plus.frame")
|
||||
android_packages = ("ai.comma.plus.offroad",)
|
||||
|
||||
def get_installed_apks():
|
||||
dat = subprocess.check_output(["pm", "list", "packages", "-f"], encoding='utf8').strip().split("\n")
|
||||
ret = {}
|
||||
for x in dat:
|
||||
if x.startswith("package:"):
|
||||
v,k = x.split("package:")[1].split("=")
|
||||
v, k = x.split("package:")[1].split("=")
|
||||
ret[k] = v
|
||||
return ret
|
||||
|
||||
@@ -26,17 +26,24 @@ def install_apk(path):
|
||||
os.remove(install_path)
|
||||
return ret == 0
|
||||
|
||||
def start_frame():
|
||||
def start_offroad():
|
||||
set_package_permissions()
|
||||
system("am start -n ai.comma.plus.frame/.MainActivity")
|
||||
system("am start -n ai.comma.plus.offroad/.MainActivity")
|
||||
|
||||
def set_package_permissions():
|
||||
pm_grant("ai.comma.plus.offroad", "android.permission.ACCESS_FINE_LOCATION")
|
||||
pm_grant("ai.comma.plus.offroad", "android.permission.READ_PHONE_STATE")
|
||||
try:
|
||||
output = subprocess.check_output(['dumpsys', 'package', 'ai.comma.plus.offroad'], encoding="utf-8")
|
||||
given_permissions = output.split("runtime permissions")[1]
|
||||
except Exception:
|
||||
given_permissions = ""
|
||||
|
||||
wanted_permissions = ["ACCESS_FINE_LOCATION", "READ_PHONE_STATE", "READ_EXTERNAL_STORAGE"]
|
||||
for permission in wanted_permissions:
|
||||
if permission not in given_permissions:
|
||||
pm_grant("ai.comma.plus.offroad", "android.permission."+permission)
|
||||
|
||||
appops_set("ai.comma.plus.offroad", "SU", "allow")
|
||||
appops_set("ai.comma.plus.offroad", "WIFI_SCAN", "allow")
|
||||
appops_set("ai.comma.plus.offroad", "READ_EXTERNAL_STORAGE", "allow")
|
||||
appops_set("ai.comma.plus.offroad", "WRITE_EXTERNAL_STORAGE", "allow")
|
||||
|
||||
def appops_set(package, op, mode):
|
||||
system(f"LD_LIBRARY_PATH= appops set {package} {op} {mode}")
|
||||
@@ -96,4 +103,3 @@ def pm_apply_packages(cmd):
|
||||
|
||||
if __name__ == "__main__":
|
||||
update_apks()
|
||||
|
||||
|
||||
+4
-7
@@ -1,11 +1,8 @@
|
||||
import os
|
||||
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../"))
|
||||
|
||||
from common.android import ANDROID
|
||||
if ANDROID:
|
||||
PERSIST = "/persist"
|
||||
PARAMS = "/data/params"
|
||||
else:
|
||||
from common.hardware import PC
|
||||
if PC:
|
||||
PERSIST = os.path.join(BASEDIR, "persist")
|
||||
PARAMS = os.path.join(BASEDIR, "persist", "params")
|
||||
|
||||
else:
|
||||
PERSIST = "/persist"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# distutils: language = c++
|
||||
# cython: language_level = 3
|
||||
from posix.time cimport clock_gettime, timespec, CLOCK_MONOTONIC_RAW, clockid_t
|
||||
|
||||
IF UNAME_SYSNAME == "Darwin":
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import collections
|
||||
from contextlib import closing
|
||||
|
||||
from common.file_helpers import mkdirs_exists_ok
|
||||
|
||||
class ColumnStoreReader():
|
||||
def __init__(self, path, mmap=False, allow_pickle=False, direct_io=False):
|
||||
if not (path and os.path.isdir(path)):
|
||||
raise ValueError("Not a column store: {}".format(path))
|
||||
|
||||
self._path = os.path.realpath(path)
|
||||
self._keys = os.listdir(self._path)
|
||||
self._mmap = mmap
|
||||
self._allow_pickle = allow_pickle
|
||||
self._direct_io = direct_io
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def get(self, key):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def keys(self):
|
||||
return list(self._keys)
|
||||
|
||||
def iteritems(self):
|
||||
for k in self:
|
||||
yield (k, self[k])
|
||||
|
||||
def itervalues(self):
|
||||
for k in self:
|
||||
yield self[k]
|
||||
|
||||
def get_npy_path(self, key):
|
||||
"""Gets a filesystem path for an npy file containing the specified array,
|
||||
or none if the column store does not contain key.
|
||||
"""
|
||||
if key in self:
|
||||
return os.path.join(self._path, key)
|
||||
else:
|
||||
return None
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
path = os.path.join(self._path, key)
|
||||
|
||||
# TODO(mgraczyk): This implementation will need to change for zip.
|
||||
if os.path.isdir(path):
|
||||
return ColumnStoreReader(path)
|
||||
else:
|
||||
if self._mmap:
|
||||
# note that direct i/o does nothing for mmap since file read/write interface is not used
|
||||
ret = np.load(path, mmap_mode='r', allow_pickle=self._allow_pickle, fix_imports=False)
|
||||
else:
|
||||
if self._direct_io:
|
||||
opener = lambda path, flags: os.open(path, os.O_RDONLY | os.O_DIRECT)
|
||||
with open(path, 'rb', buffering=0, opener=opener) as f:
|
||||
ret = np.load(f, allow_pickle=self._allow_pickle, fix_imports=False)
|
||||
else:
|
||||
ret = np.load(path, allow_pickle=self._allow_pickle, fix_imports=False)
|
||||
if type(ret) == np.lib.npyio.NpzFile:
|
||||
# if it's saved as compressed, it has arr_0 only in the file. deref this
|
||||
return ret['arr_0']
|
||||
else:
|
||||
return ret
|
||||
except IOError:
|
||||
raise KeyError(key)
|
||||
|
||||
def __contains__(self, item):
|
||||
try:
|
||||
self[item]
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def __len__(self):
|
||||
return len(self._keys)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self._keys)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._keys)
|
||||
|
||||
def __str__(self):
|
||||
return "ColumnStoreReader({})".format(str({k: "..." for k in self._keys}))
|
||||
|
||||
def __enter__(self): return self
|
||||
def __exit__(self, type, value, traceback): self.close()
|
||||
|
||||
class ColumnStoreWriter():
|
||||
def __init__(self, path, allow_pickle=False):
|
||||
self._path = path
|
||||
self._allow_pickle = allow_pickle
|
||||
mkdirs_exists_ok(self._path)
|
||||
|
||||
def map_column(self, path, dtype, shape):
|
||||
npy_path = os.path.join(self._path, path)
|
||||
mkdirs_exists_ok(os.path.dirname(npy_path))
|
||||
return np.lib.format.open_memmap(npy_path, mode='w+', dtype=dtype, shape=shape)
|
||||
|
||||
def add_column(self, path, data, dtype=None, compression=False, overwrite=False):
|
||||
npy_path = os.path.join(self._path, path)
|
||||
mkdirs_exists_ok(os.path.dirname(npy_path))
|
||||
|
||||
if overwrite:
|
||||
f = open(npy_path, "wb")
|
||||
else:
|
||||
f = os.fdopen(os.open(npy_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL), "wb")
|
||||
|
||||
with closing(f) as f:
|
||||
data2 = np.array(data, copy=False, dtype=dtype)
|
||||
if compression:
|
||||
np.savez_compressed(f, data2)
|
||||
else:
|
||||
np.save(f, data2, allow_pickle=self._allow_pickle, fix_imports=False)
|
||||
|
||||
def add_group(self, group_name):
|
||||
# TODO(mgraczyk): This implementation will need to change if we add zip or compression.
|
||||
return ColumnStoreWriter(os.path.join(self._path, group_name))
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def __enter__(self): return self
|
||||
def __exit__(self, type, value, traceback): self.close()
|
||||
|
||||
|
||||
def _save_dict_as_column_store(values, writer):
|
||||
for k, v in values.items():
|
||||
if isinstance(v, collections.Mapping):
|
||||
_save_dict_as_column_store(v, writer.add_group(k))
|
||||
else:
|
||||
writer.add_column(k, v)
|
||||
|
||||
|
||||
def save_dict_as_column_store(values, output_path):
|
||||
with ColumnStoreWriter(output_path) as writer:
|
||||
_save_dict_as_column_store(values, writer)
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
from distutils.core import Extension, setup # pylint: disable=import-error,no-name-in-module
|
||||
from Cython.Build import cythonize
|
||||
|
||||
from common.cython_hacks import BuildExtWithoutPlatformSuffix
|
||||
|
||||
sourcefiles = ['clock.pyx']
|
||||
extra_compile_args = ["-std=c++11"]
|
||||
|
||||
setup(name='Common',
|
||||
cmdclass={'build_ext': BuildExtWithoutPlatformSuffix},
|
||||
ext_modules=cythonize(
|
||||
Extension(
|
||||
"common_pyx",
|
||||
language="c++",
|
||||
sources=sourcefiles,
|
||||
extra_compile_args=extra_compile_args,
|
||||
)
|
||||
),
|
||||
nthreads=4,
|
||||
)
|
||||
@@ -1,3 +0,0 @@
|
||||
# py2,3 compatiblity helpers
|
||||
|
||||
basestring = (str, bytes)
|
||||
@@ -44,7 +44,7 @@ def compile_code(name, c_code, c_header, directory, cflags="", libraries=None):
|
||||
ffibuilder = FFI()
|
||||
ffibuilder.set_source(name, c_code, source_extension='.cpp', libraries=libraries)
|
||||
ffibuilder.cdef(c_header)
|
||||
os.environ['OPT'] = "-fwrapv -O2 -DNDEBUG -std=c++11"
|
||||
os.environ['OPT'] = "-fwrapv -O2 -DNDEBUG -std=c++1z"
|
||||
os.environ['CFLAGS'] = cflags
|
||||
ffibuilder.compile(verbose=True, debug=False, tmpdir=directory)
|
||||
|
||||
|
||||
+18
-5
@@ -3,13 +3,17 @@ import shutil
|
||||
import tempfile
|
||||
from atomicwrites import AtomicWriter
|
||||
|
||||
|
||||
def mkdirs_exists_ok(path):
|
||||
if path.startswith('http://') or path.startswith('https://'):
|
||||
raise ValueError('URL path')
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError:
|
||||
if not os.path.isdir(path):
|
||||
raise
|
||||
|
||||
|
||||
def rm_not_exists_ok(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
@@ -17,12 +21,14 @@ def rm_not_exists_ok(path):
|
||||
if os.path.exists(path):
|
||||
raise
|
||||
|
||||
|
||||
def rm_tree_or_link(path):
|
||||
if os.path.islink(path):
|
||||
os.unlink(path)
|
||||
elif os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
def get_tmpdir_on_same_filesystem(path):
|
||||
normpath = os.path.normpath(path)
|
||||
parts = normpath.split("/")
|
||||
@@ -32,6 +38,7 @@ def get_tmpdir_on_same_filesystem(path):
|
||||
return "/{}/runner/tmp".format(parts[1])
|
||||
return "/tmp"
|
||||
|
||||
|
||||
class AutoMoveTempdir():
|
||||
def __init__(self, target_path, temp_dir=None):
|
||||
self._target_path = target_path
|
||||
@@ -44,14 +51,16 @@ class AutoMoveTempdir():
|
||||
def close(self):
|
||||
os.rename(self._path, self._target_path)
|
||||
|
||||
def __enter__(self): return self
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
if type is None:
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if exc_type is None:
|
||||
self.close()
|
||||
else:
|
||||
shutil.rmtree(self._path)
|
||||
|
||||
|
||||
class NamedTemporaryDir():
|
||||
def __init__(self, temp_dir=None):
|
||||
self._path = tempfile.mkdtemp(dir=temp_dir)
|
||||
@@ -63,11 +72,13 @@ class NamedTemporaryDir():
|
||||
def close(self):
|
||||
shutil.rmtree(self._path)
|
||||
|
||||
def __enter__(self): return self
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
|
||||
def _get_fileobject_func(writer, temp_dir):
|
||||
def _get_fileobject():
|
||||
file_obj = writer.get_fileobject(dir=temp_dir)
|
||||
@@ -75,6 +86,7 @@ def _get_fileobject_func(writer, temp_dir):
|
||||
return file_obj
|
||||
return _get_fileobject
|
||||
|
||||
|
||||
def atomic_write_on_fs_tmp(path, **kwargs):
|
||||
"""Creates an atomic writer using a temporary file in a temporary directory
|
||||
on the same filesystem as path.
|
||||
@@ -92,6 +104,7 @@ def atomic_write_in_dir(path, **kwargs):
|
||||
writer = AtomicWriter(path, **kwargs)
|
||||
return writer._open(_get_fileobject_func(writer, os.path.dirname(path)))
|
||||
|
||||
|
||||
def atomic_write_in_dir_neos(path, contents, mode=None):
|
||||
"""
|
||||
Atomically writes contents to path using a temporary file in the same directory
|
||||
|
||||
@@ -6,5 +6,4 @@ class FirstOrderFilter():
|
||||
|
||||
def update(self, x):
|
||||
self.x = (1. - self.k) * self.x + self.k * x
|
||||
|
||||
|
||||
return self.x
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import numpy as np
|
||||
import os
|
||||
import csv
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
# locally cache cities from
|
||||
# https://github.com/thampiman/reverse-geocoder
|
||||
csv_file_name = path + '/rg_cities1000.csv'
|
||||
|
||||
# right hand drive is when the steering wheel is on the right of the car
|
||||
# left hand traffic is when cars driver on the left side of the road
|
||||
LHT_COUNTRIES = ['AU', 'IN', 'IE', 'JP', 'MU', 'MY', 'NZ', 'UK', 'ZA']
|
||||
|
||||
|
||||
def get_city(lat, lon):
|
||||
cities = np.array(list(csv.reader(open(csv_file_name))))[1:]
|
||||
positions = cities[:,:2].astype(np.float32)
|
||||
idx = np.argmin(np.linalg.norm((positions - np.array([lat, lon])), axis=1))
|
||||
return cities[idx]
|
||||
|
||||
|
||||
def is_lht(lat, lon):
|
||||
city = get_city(lat, lon)
|
||||
country = city[-1]
|
||||
return country in LHT_COUNTRIES
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
GPIO_HUB_RST_N = 30
|
||||
GPIO_UBLOX_RST_N = 32
|
||||
GPIO_UBLOX_SAFEBOOT_N = 33
|
||||
GPIO_UBLOX_PWR_EN = 34
|
||||
GPIO_STM_RST_N = 124
|
||||
GPIO_STM_BOOT0 = 134
|
||||
|
||||
|
||||
def gpio_init(pin, output):
|
||||
try:
|
||||
with open(f"/sys/class/gpio/gpio{pin}/direction", 'wb') as f:
|
||||
f.write(b"out" if output else b"in")
|
||||
except Exception as e:
|
||||
print(f"Failed to set gpio {pin} direction: {e}")
|
||||
|
||||
|
||||
def gpio_set(pin, high):
|
||||
try:
|
||||
with open(f"/sys/class/gpio/gpio{pin}/value", 'wb') as f:
|
||||
f.write(b"1" if high else b"0")
|
||||
except Exception as e:
|
||||
print(f"Failed to set gpio {pin} value: {e}")
|
||||
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
import random
|
||||
from typing import cast
|
||||
|
||||
from cereal import log
|
||||
from common.hardware_android import Android
|
||||
from common.hardware_tici import Tici
|
||||
from common.hardware_base import HardwareBase
|
||||
|
||||
EON = os.path.isfile('/EON')
|
||||
TICI = os.path.isfile('/TICI')
|
||||
PC = not (EON or TICI)
|
||||
ANDROID = EON
|
||||
|
||||
|
||||
NetworkType = log.ThermalData.NetworkType
|
||||
NetworkStrength = log.ThermalData.NetworkStrength
|
||||
|
||||
|
||||
class Pc(HardwareBase):
|
||||
def get_sound_card_online(self):
|
||||
return True
|
||||
|
||||
def get_imei(self, slot):
|
||||
return "%015d" % random.randint(0, 1 << 32)
|
||||
|
||||
def get_serial(self):
|
||||
return "cccccccc"
|
||||
|
||||
def get_subscriber_info(self):
|
||||
return ""
|
||||
|
||||
def reboot(self, reason=None):
|
||||
print("REBOOT!")
|
||||
|
||||
def get_network_type(self):
|
||||
return NetworkType.wifi
|
||||
|
||||
def get_sim_info(self):
|
||||
return {
|
||||
'sim_id': '',
|
||||
'mcc_mnc': None,
|
||||
'network_type': ["Unknown"],
|
||||
'sim_state': ["ABSENT"],
|
||||
'data_connected': False
|
||||
}
|
||||
|
||||
def get_network_strength(self, network_type):
|
||||
return NetworkStrength.unknown
|
||||
|
||||
|
||||
if EON:
|
||||
HARDWARE = cast(HardwareBase, Android())
|
||||
elif TICI:
|
||||
HARDWARE = cast(HardwareBase, Tici())
|
||||
else:
|
||||
HARDWARE = cast(HardwareBase, Pc())
|
||||
@@ -0,0 +1,302 @@
|
||||
import binascii
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
|
||||
from cereal import log
|
||||
from common.hardware_base import HardwareBase
|
||||
|
||||
NetworkType = log.ThermalData.NetworkType
|
||||
NetworkStrength = log.ThermalData.NetworkStrength
|
||||
|
||||
|
||||
def service_call(call):
|
||||
try:
|
||||
ret = subprocess.check_output(["service", "call", *call], encoding='utf8').strip()
|
||||
if 'Parcel' not in ret:
|
||||
return None
|
||||
return parse_service_call_bytes(ret)
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
|
||||
|
||||
def parse_service_call_unpack(r, fmt):
|
||||
try:
|
||||
return struct.unpack(fmt, r)[0]
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def parse_service_call_string(r):
|
||||
try:
|
||||
r = r[8:] # Cut off length field
|
||||
r = r.decode('utf_16_be')
|
||||
|
||||
# All pairs of two characters seem to be swapped. Not sure why
|
||||
result = ""
|
||||
for a, b, in itertools.zip_longest(r[::2], r[1::2], fillvalue='\x00'):
|
||||
result += b + a
|
||||
|
||||
result = result.replace('\x00', '')
|
||||
|
||||
return result
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def parse_service_call_bytes(ret):
|
||||
try:
|
||||
r = b""
|
||||
for hex_part in re.findall(r'[ (]([0-9a-f]{8})', ret):
|
||||
r += binascii.unhexlify(hex_part)
|
||||
return r
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def getprop(key):
|
||||
return subprocess.check_output(["getprop", key], encoding='utf8').strip()
|
||||
|
||||
|
||||
class Android(HardwareBase):
|
||||
def get_sound_card_online(self):
|
||||
return (os.path.isfile('/proc/asound/card0/state') and
|
||||
open('/proc/asound/card0/state').read().strip() == 'ONLINE')
|
||||
|
||||
def get_imei(self, slot):
|
||||
slot = str(slot)
|
||||
if slot not in ("0", "1"):
|
||||
raise ValueError("SIM slot must be 0 or 1")
|
||||
|
||||
return parse_service_call_string(service_call(["iphonesubinfo", "3", "i32", str(slot)]))
|
||||
|
||||
def get_serial(self):
|
||||
ret = getprop("ro.serialno")
|
||||
if ret == "":
|
||||
ret = "cccccccc"
|
||||
return ret
|
||||
|
||||
def get_subscriber_info(self):
|
||||
ret = parse_service_call_string(service_call(["iphonesubinfo", "7"]))
|
||||
if ret is None or len(ret) < 8:
|
||||
return ""
|
||||
return ret
|
||||
|
||||
def reboot(self, reason=None):
|
||||
# e.g. reason="recovery" to go into recover mode
|
||||
if reason is None:
|
||||
reason_args = ["null"]
|
||||
else:
|
||||
reason_args = ["s16", reason]
|
||||
|
||||
subprocess.check_output([
|
||||
"service", "call", "power", "16", # IPowerManager.reboot
|
||||
"i32", "0", # no confirmation,
|
||||
*reason_args,
|
||||
"i32", "1" # wait
|
||||
])
|
||||
|
||||
def get_sim_info(self):
|
||||
# Used for athena
|
||||
# TODO: build using methods from this class
|
||||
sim_state = getprop("gsm.sim.state").split(",")
|
||||
network_type = getprop("gsm.network.type").split(',')
|
||||
mcc_mnc = getprop("gsm.sim.operator.numeric") or None
|
||||
|
||||
sim_id = parse_service_call_string(service_call(['iphonesubinfo', '11']))
|
||||
cell_data_state = parse_service_call_unpack(service_call(['phone', '46']), ">q")
|
||||
cell_data_connected = (cell_data_state == 2)
|
||||
|
||||
return {
|
||||
'sim_id': sim_id,
|
||||
'mcc_mnc': mcc_mnc,
|
||||
'network_type': network_type,
|
||||
'sim_state': sim_state,
|
||||
'data_connected': cell_data_connected
|
||||
}
|
||||
|
||||
def get_network_type(self):
|
||||
wifi_check = parse_service_call_string(service_call(["connectivity", "2"]))
|
||||
if wifi_check is None:
|
||||
return NetworkType.none
|
||||
elif 'WIFI' in wifi_check:
|
||||
return NetworkType.wifi
|
||||
else:
|
||||
cell_check = parse_service_call_unpack(service_call(['phone', '59']), ">q")
|
||||
# from TelephonyManager.java
|
||||
cell_networks = {
|
||||
0: NetworkType.none,
|
||||
1: NetworkType.cell2G,
|
||||
2: NetworkType.cell2G,
|
||||
3: NetworkType.cell3G,
|
||||
4: NetworkType.cell2G,
|
||||
5: NetworkType.cell3G,
|
||||
6: NetworkType.cell3G,
|
||||
7: NetworkType.cell3G,
|
||||
8: NetworkType.cell3G,
|
||||
9: NetworkType.cell3G,
|
||||
10: NetworkType.cell3G,
|
||||
11: NetworkType.cell2G,
|
||||
12: NetworkType.cell3G,
|
||||
13: NetworkType.cell4G,
|
||||
14: NetworkType.cell4G,
|
||||
15: NetworkType.cell3G,
|
||||
16: NetworkType.cell2G,
|
||||
17: NetworkType.cell3G,
|
||||
18: NetworkType.cell4G,
|
||||
19: NetworkType.cell4G
|
||||
}
|
||||
return cell_networks.get(cell_check, NetworkType.none)
|
||||
|
||||
def get_network_strength(self, network_type):
|
||||
network_strength = NetworkStrength.unknown
|
||||
|
||||
# from SignalStrength.java
|
||||
def get_lte_level(rsrp, rssnr):
|
||||
INT_MAX = 2147483647
|
||||
if rsrp == INT_MAX:
|
||||
lvl_rsrp = NetworkStrength.unknown
|
||||
elif rsrp >= -95:
|
||||
lvl_rsrp = NetworkStrength.great
|
||||
elif rsrp >= -105:
|
||||
lvl_rsrp = NetworkStrength.good
|
||||
elif rsrp >= -115:
|
||||
lvl_rsrp = NetworkStrength.moderate
|
||||
else:
|
||||
lvl_rsrp = NetworkStrength.poor
|
||||
if rssnr == INT_MAX:
|
||||
lvl_rssnr = NetworkStrength.unknown
|
||||
elif rssnr >= 45:
|
||||
lvl_rssnr = NetworkStrength.great
|
||||
elif rssnr >= 10:
|
||||
lvl_rssnr = NetworkStrength.good
|
||||
elif rssnr >= -30:
|
||||
lvl_rssnr = NetworkStrength.moderate
|
||||
else:
|
||||
lvl_rssnr = NetworkStrength.poor
|
||||
return max(lvl_rsrp, lvl_rssnr)
|
||||
|
||||
def get_tdscdma_level(tdscmadbm):
|
||||
lvl = NetworkStrength.unknown
|
||||
if tdscmadbm > -25:
|
||||
lvl = NetworkStrength.unknown
|
||||
elif tdscmadbm >= -49:
|
||||
lvl = NetworkStrength.great
|
||||
elif tdscmadbm >= -73:
|
||||
lvl = NetworkStrength.good
|
||||
elif tdscmadbm >= -97:
|
||||
lvl = NetworkStrength.moderate
|
||||
elif tdscmadbm >= -110:
|
||||
lvl = NetworkStrength.poor
|
||||
return lvl
|
||||
|
||||
def get_gsm_level(asu):
|
||||
if asu <= 2 or asu == 99:
|
||||
lvl = NetworkStrength.unknown
|
||||
elif asu >= 12:
|
||||
lvl = NetworkStrength.great
|
||||
elif asu >= 8:
|
||||
lvl = NetworkStrength.good
|
||||
elif asu >= 5:
|
||||
lvl = NetworkStrength.moderate
|
||||
else:
|
||||
lvl = NetworkStrength.poor
|
||||
return lvl
|
||||
|
||||
def get_evdo_level(evdodbm, evdosnr):
|
||||
lvl_evdodbm = NetworkStrength.unknown
|
||||
lvl_evdosnr = NetworkStrength.unknown
|
||||
if evdodbm >= -65:
|
||||
lvl_evdodbm = NetworkStrength.great
|
||||
elif evdodbm >= -75:
|
||||
lvl_evdodbm = NetworkStrength.good
|
||||
elif evdodbm >= -90:
|
||||
lvl_evdodbm = NetworkStrength.moderate
|
||||
elif evdodbm >= -105:
|
||||
lvl_evdodbm = NetworkStrength.poor
|
||||
if evdosnr >= 7:
|
||||
lvl_evdosnr = NetworkStrength.great
|
||||
elif evdosnr >= 5:
|
||||
lvl_evdosnr = NetworkStrength.good
|
||||
elif evdosnr >= 3:
|
||||
lvl_evdosnr = NetworkStrength.moderate
|
||||
elif evdosnr >= 1:
|
||||
lvl_evdosnr = NetworkStrength.poor
|
||||
return max(lvl_evdodbm, lvl_evdosnr)
|
||||
|
||||
def get_cdma_level(cdmadbm, cdmaecio):
|
||||
lvl_cdmadbm = NetworkStrength.unknown
|
||||
lvl_cdmaecio = NetworkStrength.unknown
|
||||
if cdmadbm >= -75:
|
||||
lvl_cdmadbm = NetworkStrength.great
|
||||
elif cdmadbm >= -85:
|
||||
lvl_cdmadbm = NetworkStrength.good
|
||||
elif cdmadbm >= -95:
|
||||
lvl_cdmadbm = NetworkStrength.moderate
|
||||
elif cdmadbm >= -100:
|
||||
lvl_cdmadbm = NetworkStrength.poor
|
||||
if cdmaecio >= -90:
|
||||
lvl_cdmaecio = NetworkStrength.great
|
||||
elif cdmaecio >= -110:
|
||||
lvl_cdmaecio = NetworkStrength.good
|
||||
elif cdmaecio >= -130:
|
||||
lvl_cdmaecio = NetworkStrength.moderate
|
||||
elif cdmaecio >= -150:
|
||||
lvl_cdmaecio = NetworkStrength.poor
|
||||
return max(lvl_cdmadbm, lvl_cdmaecio)
|
||||
|
||||
if network_type == NetworkType.none:
|
||||
return network_strength
|
||||
if network_type == NetworkType.wifi:
|
||||
out = subprocess.check_output('dumpsys connectivity', shell=True).decode('utf-8')
|
||||
network_strength = NetworkStrength.unknown
|
||||
for line in out.split('\n'):
|
||||
signal_str = "SignalStrength: "
|
||||
if signal_str in line:
|
||||
lvl_idx_start = line.find(signal_str) + len(signal_str)
|
||||
lvl_idx_end = line.find(']', lvl_idx_start)
|
||||
lvl = int(line[lvl_idx_start : lvl_idx_end])
|
||||
if lvl >= -50:
|
||||
network_strength = NetworkStrength.great
|
||||
elif lvl >= -60:
|
||||
network_strength = NetworkStrength.good
|
||||
elif lvl >= -70:
|
||||
network_strength = NetworkStrength.moderate
|
||||
else:
|
||||
network_strength = NetworkStrength.poor
|
||||
return network_strength
|
||||
else:
|
||||
# check cell strength
|
||||
out = subprocess.check_output('dumpsys telephony.registry', shell=True).decode('utf-8')
|
||||
for line in out.split('\n'):
|
||||
if "mSignalStrength" in line:
|
||||
arr = line.split(' ')
|
||||
ns = 0
|
||||
if ("gsm" in arr[14]):
|
||||
rsrp = int(arr[9])
|
||||
rssnr = int(arr[11])
|
||||
ns = get_lte_level(rsrp, rssnr)
|
||||
if ns == NetworkStrength.unknown:
|
||||
tdscmadbm = int(arr[13])
|
||||
ns = get_tdscdma_level(tdscmadbm)
|
||||
if ns == NetworkStrength.unknown:
|
||||
asu = int(arr[1])
|
||||
ns = get_gsm_level(asu)
|
||||
else:
|
||||
cdmadbm = int(arr[3])
|
||||
cdmaecio = int(arr[4])
|
||||
evdodbm = int(arr[5])
|
||||
evdosnr = int(arr[7])
|
||||
lvl_cdma = get_cdma_level(cdmadbm, cdmaecio)
|
||||
lvl_edmo = get_evdo_level(evdodbm, evdosnr)
|
||||
if lvl_edmo == NetworkStrength.unknown:
|
||||
ns = lvl_cdma
|
||||
elif lvl_cdma == NetworkStrength.unknown:
|
||||
ns = lvl_edmo
|
||||
else:
|
||||
ns = min(lvl_cdma, lvl_edmo)
|
||||
network_strength = max(network_strength, ns)
|
||||
|
||||
return network_strength
|
||||
@@ -0,0 +1,41 @@
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
class HardwareBase:
|
||||
@staticmethod
|
||||
def get_cmdline():
|
||||
with open('/proc/cmdline') as f:
|
||||
cmdline = f.read()
|
||||
return {kv[0]: kv[1] for kv in [s.split('=') for s in cmdline.split(' ')] if len(kv) == 2}
|
||||
|
||||
@abstractmethod
|
||||
def get_sound_card_online(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_imei(self, slot):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_serial(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_subscriber_info(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def reboot(self, reason=None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_network_type(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_sim_info(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_network_strength(self, network_type):
|
||||
pass
|
||||
@@ -0,0 +1,59 @@
|
||||
import serial
|
||||
|
||||
from common.hardware_base import HardwareBase
|
||||
from cereal import log
|
||||
import subprocess
|
||||
|
||||
|
||||
NetworkType = log.ThermalData.NetworkType
|
||||
NetworkStrength = log.ThermalData.NetworkStrength
|
||||
|
||||
|
||||
def run_at_command(cmd, timeout=0.1):
|
||||
with serial.Serial("/dev/ttyUSB2", timeout=timeout) as ser:
|
||||
ser.write(cmd + b"\r\n")
|
||||
ser.readline() # Modem echos request
|
||||
return ser.readline().decode().rstrip()
|
||||
|
||||
|
||||
class Tici(HardwareBase):
|
||||
def get_sound_card_online(self):
|
||||
return True
|
||||
|
||||
def get_imei(self, slot):
|
||||
if slot != 0:
|
||||
return ""
|
||||
|
||||
for _ in range(10):
|
||||
try:
|
||||
imei = run_at_command(b"AT+CGSN")
|
||||
if len(imei) == 15:
|
||||
return imei
|
||||
except serial.SerialException:
|
||||
pass
|
||||
|
||||
raise RuntimeError("Error getting IMEI")
|
||||
|
||||
def get_serial(self):
|
||||
return self.get_cmdline()['androidboot.serialno']
|
||||
|
||||
def get_subscriber_info(self):
|
||||
return ""
|
||||
|
||||
def reboot(self, reason=None):
|
||||
subprocess.check_output(["sudo", "reboot"])
|
||||
|
||||
def get_network_type(self):
|
||||
return NetworkType.wifi
|
||||
|
||||
def get_sim_info(self):
|
||||
return {
|
||||
'sim_id': '',
|
||||
'mcc_mnc': None,
|
||||
'network_type': ["Unknown"],
|
||||
'sim_state': ["ABSENT"],
|
||||
'data_connected': False
|
||||
}
|
||||
|
||||
def get_network_strength(self, network_type):
|
||||
return NetworkStrength.unknown
|
||||
@@ -1,6 +1,3 @@
|
||||
Import('env')
|
||||
|
||||
env.Command(['simple_kalman_impl.so'],
|
||||
['simple_kalman_impl.pyx', 'simple_kalman_impl.pxd', 'simple_kalman_setup.py'],
|
||||
"cd common/kalman && python3 simple_kalman_setup.py build_ext --inplace")
|
||||
Import('envCython')
|
||||
|
||||
envCython.Program('simple_kalman_impl.so', 'simple_kalman_impl.pyx')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# cython: language_level = 3
|
||||
|
||||
cdef class KF1D:
|
||||
cdef public:
|
||||
double x0_0
|
||||
@@ -13,4 +15,4 @@ cdef class KF1D:
|
||||
double A_K_0
|
||||
double A_K_1
|
||||
double A_K_2
|
||||
double A_K_3
|
||||
double A_K_3
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# distutils: language = c++
|
||||
# cython: language_level=3
|
||||
|
||||
cdef class KF1D:
|
||||
|
||||
@@ -8,7 +8,7 @@ class KF1D:
|
||||
def __init__(self, x0, A, C, K):
|
||||
self.x = x0
|
||||
self.A = A
|
||||
self.C = C
|
||||
self.C = np.atleast_2d(C)
|
||||
self.K = K
|
||||
|
||||
self.A_K = self.A - np.dot(self.K, self.C)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
from distutils.core import Extension, setup
|
||||
|
||||
from Cython.Build import cythonize
|
||||
|
||||
from common.cython_hacks import BuildExtWithoutPlatformSuffix
|
||||
|
||||
setup(name='Simple Kalman Implementation',
|
||||
cmdclass={'build_ext': BuildExtWithoutPlatformSuffix},
|
||||
ext_modules=cythonize(Extension("simple_kalman_impl", ["simple_kalman_impl.pyx"])))
|
||||
@@ -21,10 +21,10 @@ class TestSimpleKalman(unittest.TestCase):
|
||||
K0_0 = 0.12287673
|
||||
K1_0 = 0.29666309
|
||||
|
||||
self.kf_old = KF1D_old(x0=np.matrix([[x0_0], [x1_0]]),
|
||||
A=np.matrix([[A0_0, A0_1], [A1_0, A1_1]]),
|
||||
C=np.matrix([C0_0, C0_1]),
|
||||
K=np.matrix([[K0_0], [K1_0]]))
|
||||
self.kf_old = KF1D_old(x0=np.array([[x0_0], [x1_0]]),
|
||||
A=np.array([[A0_0, A0_1], [A1_0, A1_1]]),
|
||||
C=np.array([C0_0, C0_1]),
|
||||
K=np.array([[K0_0], [K1_0]]))
|
||||
|
||||
self.kf = KF1D(x0=[[x0_0], [x1_0]],
|
||||
A=[[A0_0, A0_1], [A1_0, A1_1]],
|
||||
@@ -47,9 +47,8 @@ class TestSimpleKalman(unittest.TestCase):
|
||||
x = self.kf.update(v_wheel)
|
||||
|
||||
# Compare the output x, verify that the error is less than 1e-4
|
||||
self.assertAlmostEqual(x_old[0], x[0])
|
||||
self.assertAlmostEqual(x_old[1], x[1])
|
||||
|
||||
np.testing.assert_almost_equal(x_old[0], x[0])
|
||||
np.testing.assert_almost_equal(x_old[1], x[1])
|
||||
|
||||
def test_new_is_faster(self):
|
||||
setup = """
|
||||
@@ -70,10 +69,10 @@ C0_1 = 0.0
|
||||
K0_0 = 0.12287673
|
||||
K1_0 = 0.29666309
|
||||
|
||||
kf_old = KF1D_old(x0=np.matrix([[x0_0], [x1_0]]),
|
||||
A=np.matrix([[A0_0, A0_1], [A1_0, A1_1]]),
|
||||
C=np.matrix([C0_0, C0_1]),
|
||||
K=np.matrix([[K0_0], [K1_0]]))
|
||||
kf_old = KF1D_old(x0=np.array([[x0_0], [x1_0]]),
|
||||
A=np.array([[A0_0, A0_1], [A1_0, A1_1]]),
|
||||
C=np.array([C0_0, C0_1]),
|
||||
K=np.array([[K0_0], [K1_0]]))
|
||||
|
||||
kf = KF1D(x0=[[x0_0], [x1_0]],
|
||||
A=[[A0_0, A0_1], [A1_0, A1_1]],
|
||||
@@ -83,3 +82,6 @@ kf = KF1D(x0=[[x0_0], [x1_0]],
|
||||
kf_speed = timeit.timeit("kf.update(1234)", setup=setup, number=10000)
|
||||
kf_old_speed = timeit.timeit("kf_old.update(1234)", setup=setup, number=10000)
|
||||
self.assertTrue(kf_speed < kf_old_speed / 4)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
class lazy_property():
|
||||
"""Defines a property whose value will be computed only once and as needed.
|
||||
|
||||
This can only be used on instance methods.
|
||||
"""
|
||||
def __init__(self, func):
|
||||
self._func = func
|
||||
|
||||
def __get__(self, obj_self, cls):
|
||||
value = self._func(obj_self)
|
||||
setattr(obj_self, self._func.__name__, value)
|
||||
return value
|
||||
@@ -1,284 +0,0 @@
|
||||
import os
|
||||
import struct
|
||||
import bisect
|
||||
import numpy as np
|
||||
import _io
|
||||
|
||||
import capnp
|
||||
from cereal import log as capnp_log
|
||||
|
||||
class RawData():
|
||||
def __init__(self, f):
|
||||
self.f = _io.FileIO(f, 'rb')
|
||||
self.lenn = struct.unpack("I", self.f.read(4))[0]
|
||||
self.count = os.path.getsize(f) / (self.lenn+4)
|
||||
|
||||
def read(self, i):
|
||||
self.f.seek((self.lenn+4)*i + 4)
|
||||
return self.f.read(self.lenn)
|
||||
|
||||
def yuv420_to_rgb(raw, image_dim=None, swizzled=False):
|
||||
def expand(x):
|
||||
x = np.repeat(x, 2, axis=0)
|
||||
return np.repeat(x, 2, axis=1)
|
||||
|
||||
if image_dim is None:
|
||||
image_dim = (raw.shape[1]*2, raw.shape[2]*2)
|
||||
swizzled = True
|
||||
|
||||
if not swizzled:
|
||||
img_data = np.array(raw, copy=False, dtype=np.uint8)
|
||||
uv_len = (image_dim[0]/2)*(image_dim[1]/2)
|
||||
img_data_u = expand(img_data[image_dim[0]*image_dim[1]: \
|
||||
image_dim[0]*image_dim[1]+uv_len]. \
|
||||
reshape(image_dim[0]/2, image_dim[1]/2))
|
||||
|
||||
img_data_v = expand(img_data[image_dim[0]*image_dim[1]+uv_len: \
|
||||
image_dim[0]*image_dim[1]+2*uv_len]. \
|
||||
reshape(image_dim[0]/2, image_dim[1]/2))
|
||||
img_data_y = img_data[0:image_dim[0]*image_dim[1]].reshape(image_dim)
|
||||
else:
|
||||
img_data_y = np.zeros(image_dim, dtype=np.uint8)
|
||||
img_data_y[0::2, 0::2] = raw[0]
|
||||
img_data_y[1::2, 0::2] = raw[1]
|
||||
img_data_y[0::2, 1::2] = raw[2]
|
||||
img_data_y[1::2, 1::2] = raw[3]
|
||||
img_data_u = expand(raw[4])
|
||||
img_data_v = expand(raw[5])
|
||||
|
||||
yuv = np.stack((img_data_y, img_data_u, img_data_v)).swapaxes(0,2).swapaxes(0,1)
|
||||
yuv = yuv.astype(np.int16)
|
||||
|
||||
# http://maxsharabayko.blogspot.com/2016/01/fast-yuv-to-rgb-conversion-in-python-3.html
|
||||
# according to ITU-R BT.709
|
||||
yuv[:,:, 0] = yuv[:,:, 0].clip(16, 235).astype(yuv.dtype) - 16
|
||||
yuv[:,:,1:] = yuv[:,:,1:].clip(16, 240).astype(yuv.dtype) - 128
|
||||
|
||||
A = np.array([[1.164, 0.000, 1.793],
|
||||
[1.164, -0.213, -0.533],
|
||||
[1.164, 2.112, 0.000]])
|
||||
|
||||
# our result
|
||||
img = np.dot(yuv, A.T).clip(0, 255).astype('uint8')
|
||||
return img
|
||||
|
||||
|
||||
class YuvData():
|
||||
def __init__(self, f, dim=(160,320)):
|
||||
self.f = _io.FileIO(f, 'rb')
|
||||
self.image_dim = dim
|
||||
self.image_size = self.image_dim[0]/2 * self.image_dim[1]/2 * 6
|
||||
self.count = os.path.getsize(f) / self.image_size
|
||||
|
||||
def read_frame(self, frame):
|
||||
self.f.seek(self.image_size*frame)
|
||||
raw = self.f.read(self.image_size)
|
||||
return raw
|
||||
|
||||
def read_frames(self, range_start, range_len):
|
||||
self.f.seek(self.image_size*range_start)
|
||||
raw = self.f.read(self.image_size*range_len)
|
||||
return raw
|
||||
|
||||
def read_frames_into(self, range_start, buf):
|
||||
self.f.seek(self.image_size*range_start)
|
||||
return self.f.readinto(buf)
|
||||
|
||||
def read(self, frame):
|
||||
return yuv420_to_rgb(self.read_frame(frame), self.image_dim)
|
||||
|
||||
def close(self):
|
||||
self.f.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.close()
|
||||
|
||||
|
||||
|
||||
class OneReader():
|
||||
def __init__(self, base_path, goofy=False, segment_range=None):
|
||||
self.base_path = base_path
|
||||
|
||||
route_name = os.path.basename(base_path)
|
||||
|
||||
self.rcamera_size = (304, 560)
|
||||
|
||||
if segment_range is None:
|
||||
parent_path = os.path.dirname(base_path)
|
||||
self.segment_nums = []
|
||||
for p in os.listdir(parent_path):
|
||||
if not p.startswith(route_name+"--"):
|
||||
continue
|
||||
self.segment_nums.append(int(p.rsplit("--", 1)[-1]))
|
||||
if not self.segment_nums:
|
||||
raise Exception("no route segments found")
|
||||
self.segment_nums.sort()
|
||||
self.segment_range = (self.segment_nums[0], self.segment_nums[-1])
|
||||
else:
|
||||
self.segment_range = segment_range
|
||||
self.segment_nums = range(segment_range[0], segment_range[1]+1)
|
||||
for i in self.segment_nums:
|
||||
if not os.path.exists(base_path+"--"+str(i)):
|
||||
raise Exception("missing segment in provided range")
|
||||
|
||||
# goofy data is broken with discontinuous logs
|
||||
if goofy and (self.segment_range[0] != 0
|
||||
or self.segment_nums != range(self.segment_range[0], self.segment_range[1]+1)):
|
||||
raise Exception("goofy data needs all the segments for a route")
|
||||
|
||||
self.cur_seg = None
|
||||
self.cur_seg_f = None
|
||||
|
||||
# index the frames
|
||||
print("indexing frames {}...".format(self.segment_nums))
|
||||
|
||||
self.rcamera_encode_map = {} # frame_id -> (segment num, segment id, frame_time)
|
||||
last_frame_id = -1
|
||||
|
||||
if goofy:
|
||||
# goofy is goofy
|
||||
|
||||
frame_size = self.rcamera_size[0]*self.rcamera_size[1]*3/2
|
||||
|
||||
# find the encode id ranges for each segment by using the rcamera file size
|
||||
segment_encode_ids = []
|
||||
cur_encode_id = 0
|
||||
for n in self.segment_nums:
|
||||
camera_path = os.path.join(self.seg_path(n), "rcamera")
|
||||
if not os.path.exists(camera_path):
|
||||
# for goofy, missing camera files means a bad route
|
||||
raise Exception("Missing camera file {}".format(camera_path))
|
||||
camera_size = os.path.getsize(camera_path)
|
||||
assert (camera_size % frame_size) == 0
|
||||
|
||||
num_frames = camera_size / frame_size
|
||||
segment_encode_ids.append(cur_encode_id)
|
||||
cur_encode_id += num_frames
|
||||
|
||||
last_encode_id = -1
|
||||
# use the segment encode id map and frame events to build the frame index
|
||||
for n in self.segment_nums:
|
||||
log_path = os.path.join(self.seg_path(n), "rlog")
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, "rb") as f:
|
||||
for evt in capnp_log.Event.read_multiple(f):
|
||||
if evt.which() == 'frame':
|
||||
|
||||
if evt.frame.frameId < last_frame_id:
|
||||
# a non-increasing frame id is bad route (eg visiond was restarted)
|
||||
raise Exception("non-increasing frame id")
|
||||
last_frame_id = evt.frame.frameId
|
||||
|
||||
seg_i = bisect.bisect_right(segment_encode_ids, evt.frame.encodeId)-1
|
||||
assert seg_i >= 0
|
||||
seg_num = self.segment_nums[seg_i]
|
||||
seg_id = evt.frame.encodeId-segment_encode_ids[seg_i]
|
||||
frame_time = evt.logMonoTime / 1.0e9
|
||||
|
||||
self.rcamera_encode_map[evt.frame.frameId] = (seg_num, seg_id,
|
||||
frame_time)
|
||||
|
||||
last_encode_id = evt.frame.encodeId
|
||||
|
||||
if last_encode_id-cur_encode_id > 10:
|
||||
# too many missing frames is a bad route (eg route from before encoder rotating worked)
|
||||
raise Exception("goofy route is missing frames: {}, {}".format(
|
||||
last_encode_id, cur_encode_id))
|
||||
|
||||
else:
|
||||
# for harry data, build the index from encodeIdx events
|
||||
for n in self.segment_nums:
|
||||
log_path = os.path.join(self.seg_path(n), "rlog")
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, "rb") as f:
|
||||
for evt in capnp_log.Event.read_multiple(f):
|
||||
if evt.which() == 'encodeIdx' and evt.encodeIdx.type == 'bigBoxLossless':
|
||||
frame_time = evt.logMonoTime / 1.0e9
|
||||
self.rcamera_encode_map[evt.encodeIdx.frameId] = (
|
||||
evt.encodeIdx.segmentNum, evt.encodeIdx.segmentId,
|
||||
frame_time)
|
||||
|
||||
print("done")
|
||||
|
||||
# read the first event to find the start time
|
||||
self.reset_to_seg(self.segment_range[0])
|
||||
for evt in self.events():
|
||||
if evt.which() != 'initData':
|
||||
self.start_mono = evt.logMonoTime
|
||||
break
|
||||
self.reset_to_seg(self.segment_range[0])
|
||||
|
||||
|
||||
def seg_path(self, num):
|
||||
return self.base_path+"--"+str(num)
|
||||
|
||||
def reset_to_seg(self, seg):
|
||||
self.cur_seg = seg
|
||||
if self.cur_seg_f:
|
||||
self.cur_seg_f.close()
|
||||
self.cur_seg_f = None
|
||||
|
||||
def seek_ts(self, ts):
|
||||
seek_seg = int(ts/60)
|
||||
if seek_seg < self.segment_range[0] or seek_seg > self.segment_range[1]:
|
||||
raise ValueError
|
||||
|
||||
self.reset_to_seg(seek_seg)
|
||||
target_mono = self.start_mono + int(ts*1e9)
|
||||
for evt in self.events():
|
||||
if evt.logMonoTime >= target_mono:
|
||||
break
|
||||
|
||||
def read_event(self):
|
||||
while True:
|
||||
if self.cur_seg > self.segment_range[1]:
|
||||
return None
|
||||
if self.cur_seg_f is None:
|
||||
log_path = os.path.join(self.seg_path(self.cur_seg), "rlog")
|
||||
if not os.path.exists(log_path):
|
||||
print("missing log file!", log_path)
|
||||
self.cur_seg += 1
|
||||
continue
|
||||
self.cur_seg_f = open(log_path, "rb")
|
||||
|
||||
try:
|
||||
return capnp_log.Event.read(self.cur_seg_f)
|
||||
except capnp.lib.capnp.KjException as e:
|
||||
if 'EOF' in str(e): # dumb, but pycapnp does this too
|
||||
self.cur_seg_f.close()
|
||||
self.cur_seg_f = None
|
||||
self.cur_seg += 1
|
||||
else:
|
||||
raise
|
||||
|
||||
def events(self):
|
||||
while True:
|
||||
r = self.read_event()
|
||||
if r is None:
|
||||
break
|
||||
yield r
|
||||
|
||||
def read_frame(self, frame_id):
|
||||
encode_idx = self.rcamera_encode_map.get(frame_id)
|
||||
if encode_idx is None:
|
||||
return None
|
||||
|
||||
seg_num, seg_id, _ = encode_idx
|
||||
camera_path = os.path.join(self.seg_path(seg_num), "rcamera")
|
||||
if not os.path.exists(camera_path):
|
||||
return None
|
||||
with YuvData(camera_path, self.rcamera_size) as data:
|
||||
return data.read_frame(seg_id)
|
||||
|
||||
def close(self):
|
||||
if self.cur_seg_f is not None:
|
||||
self.cur_seg_f.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.close()
|
||||
@@ -1,92 +0,0 @@
|
||||
import sys
|
||||
import json
|
||||
# pip2 install msgpack-python
|
||||
import msgpack
|
||||
import zlib
|
||||
import os
|
||||
import logging
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
ext = ".gz"
|
||||
SWAG = '\xde\xe2\x11\x15VVC\xf2\x8ep\xd7\xe4\x87\x8d,9'
|
||||
|
||||
def compress_json(in_file, out_file):
|
||||
logging.debug("compressing %s -> %s", in_file, out_file)
|
||||
|
||||
errors = 0
|
||||
|
||||
good = []
|
||||
last_can_time = 0
|
||||
with open(in_file, 'r') as inf:
|
||||
for ln in inf:
|
||||
ln = ln.rstrip()
|
||||
if not ln: continue
|
||||
try:
|
||||
ll = json.loads(ln)
|
||||
except ValueError:
|
||||
errors += 1
|
||||
continue
|
||||
if ll is None or ll[0] is None:
|
||||
continue
|
||||
if ll[0][1] == 1:
|
||||
# no CAN in hex
|
||||
ll[1][2] = ll[1][2].decode("hex")
|
||||
# relativize the CAN timestamps
|
||||
this_can_time = ll[1][1]
|
||||
ll[1] = [ll[1][0], this_can_time - last_can_time, ll[1][2]]
|
||||
last_can_time = this_can_time
|
||||
good.append(ll)
|
||||
|
||||
logging.debug("compressing %s -> %s, read done", in_file, out_file)
|
||||
data = msgpack.packb(good)
|
||||
data_compressed = zlib.compress(data)
|
||||
# zlib doesn't care about this
|
||||
data_compressed += "\x00" * (16 - len(data_compressed)%16)
|
||||
aes = AES.new(SWAG, AES.MODE_CBC, "\x00"*16)
|
||||
data_encrypted = aes.encrypt(data_compressed)
|
||||
with open(out_file, "wb") as outf:
|
||||
outf.write(data_encrypted)
|
||||
|
||||
logging.debug("compressing %s -> %s, write done", in_file, out_file)
|
||||
|
||||
return errors
|
||||
|
||||
def decompress_json_internal(data_encrypted):
|
||||
aes = AES.new(SWAG, AES.MODE_CBC, "\x00"*16)
|
||||
data_compressed = aes.decrypt(data_encrypted)
|
||||
data = zlib.decompress(data_compressed)
|
||||
msgs = msgpack.unpackb(data)
|
||||
good = []
|
||||
last_can_time = 0
|
||||
for ll in msgs:
|
||||
if ll[0][1] == 1:
|
||||
# back into hex
|
||||
ll[1][2] = ll[1][2].encode("hex")
|
||||
# derelativize CAN timestamps
|
||||
last_can_time += ll[1][1]
|
||||
ll[1] = [ll[1][0], last_can_time, ll[1][2]]
|
||||
good.append(ll)
|
||||
return good
|
||||
|
||||
def decompress_json(in_file, out_file):
|
||||
logging.debug("decompressing %s -> %s", in_file, out_file)
|
||||
f = open(in_file)
|
||||
data_encrypted = f.read()
|
||||
f.close()
|
||||
|
||||
good = decompress_json_internal(data_encrypted)
|
||||
out = '\n'.join(map(lambda x: json.dumps(x), good)) + "\n"
|
||||
logging.debug("decompressing %s -> %s, writing", in_file, out_file)
|
||||
f = open(out_file, 'w')
|
||||
f.write(out)
|
||||
f.close()
|
||||
logging.debug("decompressing %s -> %s, write finished", in_file, out_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
for dat in sys.argv[1:]:
|
||||
print(dat)
|
||||
compress_json(dat, "/tmp/out"+ext)
|
||||
decompress_json("/tmp/out"+ext, "/tmp/test")
|
||||
os.system("diff "+dat+" /tmp/test")
|
||||
|
||||
@@ -68,8 +68,11 @@ class SwagErrorFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
return record.levelno < logging.ERROR
|
||||
|
||||
_tmpfunc = lambda: 0
|
||||
_srcfile = os.path.normcase(_tmpfunc.__code__.co_filename)
|
||||
def _tmpfunc():
|
||||
return 0
|
||||
|
||||
def _srcfile():
|
||||
return os.path.normcase(_tmpfunc.__code__.co_filename)
|
||||
|
||||
class SwagLogger(logging.Logger):
|
||||
def __init__(self):
|
||||
@@ -112,9 +115,6 @@ class SwagLogger(logging.Logger):
|
||||
if args:
|
||||
evt['args'] = args
|
||||
evt.update(kwargs)
|
||||
ctx = self.get_ctx()
|
||||
if ctx:
|
||||
evt['ctx'] = self.get_ctx()
|
||||
if 'error' in kwargs:
|
||||
self.error(evt)
|
||||
else:
|
||||
@@ -140,7 +140,9 @@ class SwagLogger(logging.Logger):
|
||||
while hasattr(f, "f_code"):
|
||||
co = f.f_code
|
||||
filename = os.path.normcase(co.co_filename)
|
||||
if filename == _srcfile:
|
||||
|
||||
# TODO: is this pylint exception correct?
|
||||
if filename == _srcfile: # pylint: disable=comparison-with-callable
|
||||
f = f.f_back
|
||||
continue
|
||||
sinfo = None
|
||||
|
||||
@@ -6,6 +6,7 @@ def clip(x, lo, hi):
|
||||
|
||||
def interp(x, xp, fp):
|
||||
N = len(xp)
|
||||
|
||||
def get_interp(xv):
|
||||
hi = 0
|
||||
while hi < N and xv > xp[hi]:
|
||||
@@ -14,8 +15,8 @@ def interp(x, xp, fp):
|
||||
return fp[-1] if hi == N and xv > xp[low] else (
|
||||
fp[0] if hi == 0 else
|
||||
(xv - xp[low]) * (fp[hi] - fp[low]) / (xp[hi] - xp[low]) + fp[low])
|
||||
return [get_interp(v) for v in x] if hasattr(
|
||||
x, '__iter__') else get_interp(x)
|
||||
|
||||
return [get_interp(v) for v in x] if hasattr(x, '__iter__') else get_interp(x)
|
||||
|
||||
def mean(x):
|
||||
return sum(x) / len(x)
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import bisect
|
||||
import numpy as np
|
||||
from scipy.interpolate import interp1d
|
||||
|
||||
|
||||
def deep_interp_0_fast(dx, x, y):
|
||||
FIX = False
|
||||
if len(y.shape) == 1:
|
||||
y = y[:, None]
|
||||
FIX = True
|
||||
ret = np.zeros((dx.shape[0], y.shape[1]))
|
||||
index = list(x)
|
||||
for i in range(dx.shape[0]):
|
||||
idx = bisect.bisect_left(index, dx[i])
|
||||
if idx == x.shape[0]:
|
||||
idx = x.shape[0] - 1
|
||||
ret[i] = y[idx]
|
||||
|
||||
if FIX:
|
||||
return ret[:, 0]
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
def running_mean(x, N):
|
||||
cumsum = np.cumsum(np.insert(x, [0]*(int(N/2)) + [-1]*(N-int(N/2)), [x[0]]*int(N/2) + [x[-1]]*(N-int(N/2))))
|
||||
return (cumsum[N:] - cumsum[:-N]) / N
|
||||
|
||||
|
||||
def deep_interp_np(x, xp, fp):
|
||||
x = np.atleast_1d(x)
|
||||
xp = np.array(xp)
|
||||
if len(xp) < 2:
|
||||
return np.repeat(fp, len(x), axis=0)
|
||||
if min(np.diff(xp)) < 0:
|
||||
raise RuntimeError('Bad x array for interpolation')
|
||||
j = np.searchsorted(xp, x) - 1
|
||||
j = np.clip(j, 0, len(xp)-2)
|
||||
d = np.divide(x - xp[j], xp[j + 1] - xp[j], out=np.ones_like(x, dtype=np.float64), where=xp[j + 1] - xp[j] != 0)
|
||||
vals_interp = (fp[j].T*(1 - d)).T + (fp[j + 1].T*d).T
|
||||
if len(vals_interp) == 1:
|
||||
return vals_interp[0]
|
||||
else:
|
||||
return vals_interp
|
||||
|
||||
|
||||
def clipping_deep_interp(x, xp, fp):
|
||||
if len(xp) < 2:
|
||||
return deep_interp_np(x, xp, fp)
|
||||
bad_idx = np.where(np.diff(xp) < 0)[0]
|
||||
if len(bad_idx) > 0:
|
||||
if bad_idx[0] ==1:
|
||||
return np.zeros([] + list(fp.shape[1:]))
|
||||
return deep_interp_np(x, xp[:bad_idx[0]], fp[:bad_idx[0]])
|
||||
else:
|
||||
return deep_interp_np(x, xp, fp)
|
||||
|
||||
|
||||
def deep_interp(dx, x, y, kind="slinear"):
|
||||
return interp1d(
|
||||
x, y,
|
||||
axis=0,
|
||||
kind=kind,
|
||||
bounds_error=False,
|
||||
fill_value="extrapolate",
|
||||
assume_sorted=True)(dx)
|
||||
Executable → Regular
+4
-415
@@ -1,415 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ROS has a parameter server, we have files.
|
||||
|
||||
The parameter store is a persistent key value store, implemented as a directory with a writer lock.
|
||||
On Android, we store params under params_dir = /data/params. The writer lock is a file
|
||||
"<params_dir>/.lock" taken using flock(), and data is stored in a directory symlinked to by
|
||||
"<params_dir>/d".
|
||||
|
||||
Each key, value pair is stored as a file with named <key> with contents <value>, located in
|
||||
<params_dir>/d/<key>
|
||||
|
||||
Readers of a single key can just open("<params_dir>/d/<key>") and read the file contents.
|
||||
Readers who want a consistent snapshot of multiple keys should take the lock.
|
||||
|
||||
Writers should take the lock before modifying anything. Writers should also leave the DB in a
|
||||
consistent state after a crash. The implementation below does this by copying all params to a temp
|
||||
directory <params_dir>/<tmp>, then atomically symlinking <params_dir>/<d> to <params_dir>/<tmp>
|
||||
before deleting the old <params_dir>/<d> directory.
|
||||
|
||||
Writers that only modify a single key can simply take the lock, then swap the corresponding value
|
||||
file in place without messing with <params_dir>/d.
|
||||
"""
|
||||
import time
|
||||
import os
|
||||
import errno
|
||||
import sys
|
||||
import shutil
|
||||
import fcntl
|
||||
import tempfile
|
||||
import threading
|
||||
from enum import Enum
|
||||
from common.basedir import PARAMS
|
||||
|
||||
def mkdirs_exists_ok(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError:
|
||||
if not os.path.isdir(path):
|
||||
raise
|
||||
|
||||
|
||||
class TxType(Enum):
|
||||
PERSISTENT = 1
|
||||
CLEAR_ON_MANAGER_START = 2
|
||||
CLEAR_ON_PANDA_DISCONNECT = 3
|
||||
|
||||
|
||||
class UnknownKeyName(Exception):
|
||||
pass
|
||||
|
||||
|
||||
keys = {
|
||||
"AccessToken": [TxType.PERSISTENT],
|
||||
"AthenadPid": [TxType.PERSISTENT],
|
||||
"CalibrationParams": [TxType.PERSISTENT],
|
||||
"CarParams": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"CarParamsCache": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"CarVin": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"CommunityFeaturesToggle": [TxType.PERSISTENT],
|
||||
"CompletedTrainingVersion": [TxType.PERSISTENT],
|
||||
"ControlsParams": [TxType.PERSISTENT],
|
||||
"DoUninstall": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"DongleId": [TxType.PERSISTENT],
|
||||
"GitBranch": [TxType.PERSISTENT],
|
||||
"GitCommit": [TxType.PERSISTENT],
|
||||
"GitRemote": [TxType.PERSISTENT],
|
||||
"GithubSshKeys": [TxType.PERSISTENT],
|
||||
"HasAcceptedTerms": [TxType.PERSISTENT],
|
||||
"HasCompletedSetup": [TxType.PERSISTENT],
|
||||
"IsLdwEnabled": [TxType.PERSISTENT],
|
||||
"IsGeofenceEnabled": [TxType.PERSISTENT],
|
||||
"IsMetric": [TxType.PERSISTENT],
|
||||
"IsOffroad": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"IsRHD": [TxType.PERSISTENT],
|
||||
"IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"IsUpdateAvailable": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"IsUploadRawEnabled": [TxType.PERSISTENT],
|
||||
"LastUpdateTime": [TxType.PERSISTENT],
|
||||
"LimitSetSpeed": [TxType.PERSISTENT],
|
||||
"LimitSetSpeedNeural": [TxType.PERSISTENT],
|
||||
"LiveParameters": [TxType.PERSISTENT],
|
||||
"LongitudinalControl": [TxType.PERSISTENT],
|
||||
"OpenpilotEnabledToggle": [TxType.PERSISTENT],
|
||||
"PandaFirmware": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"PandaFirmwareHex": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"PandaDongleId": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"Passive": [TxType.PERSISTENT],
|
||||
"RecordFront": [TxType.PERSISTENT],
|
||||
"ReleaseNotes": [TxType.PERSISTENT],
|
||||
"ShouldDoUpdate": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"SpeedLimitOffset": [TxType.PERSISTENT],
|
||||
"SubscriberInfo": [TxType.PERSISTENT],
|
||||
"TermsVersion": [TxType.PERSISTENT],
|
||||
"TrainingVersion": [TxType.PERSISTENT],
|
||||
"UpdateAvailable": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"Version": [TxType.PERSISTENT],
|
||||
"Offroad_ChargeDisabled": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"Offroad_ConnectivityNeeded": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"Offroad_ConnectivityNeededPrompt": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"Offroad_TemperatureTooHigh": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"Offroad_PandaFirmwareMismatch": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"Offroad_InvalidTime": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"Offroad_IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START],
|
||||
}
|
||||
|
||||
|
||||
def fsync_dir(path):
|
||||
fd = os.open(path, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
class FileLock():
|
||||
def __init__(self, path, create):
|
||||
self._path = path
|
||||
self._create = create
|
||||
self._fd = None
|
||||
|
||||
def acquire(self):
|
||||
self._fd = os.open(self._path, os.O_CREAT if self._create else 0)
|
||||
fcntl.flock(self._fd, fcntl.LOCK_EX)
|
||||
|
||||
def release(self):
|
||||
if self._fd is not None:
|
||||
os.close(self._fd)
|
||||
self._fd = None
|
||||
|
||||
|
||||
class DBAccessor():
|
||||
def __init__(self, path):
|
||||
self._path = path
|
||||
self._vals = None
|
||||
|
||||
def keys(self):
|
||||
self._check_entered()
|
||||
return self._vals.keys()
|
||||
|
||||
def get(self, key):
|
||||
self._check_entered()
|
||||
try:
|
||||
return self._vals[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def _get_lock(self, create):
|
||||
lock = FileLock(os.path.join(self._path, ".lock"), create)
|
||||
lock.acquire()
|
||||
return lock
|
||||
|
||||
def _read_values_locked(self):
|
||||
"""Callers should hold a lock while calling this method."""
|
||||
vals = {}
|
||||
try:
|
||||
data_path = self._data_path()
|
||||
keys = os.listdir(data_path)
|
||||
for key in keys:
|
||||
with open(os.path.join(data_path, key), "rb") as f:
|
||||
vals[key] = f.read()
|
||||
except (OSError, IOError) as e:
|
||||
# Either the DB hasn't been created yet, or somebody wrote a bug and left the DB in an
|
||||
# inconsistent state. Either way, return empty.
|
||||
if e.errno == errno.ENOENT:
|
||||
return {}
|
||||
|
||||
return vals
|
||||
|
||||
def _data_path(self):
|
||||
return os.path.join(self._path, "d")
|
||||
|
||||
def _check_entered(self):
|
||||
if self._vals is None:
|
||||
raise Exception("Must call __enter__ before using DB")
|
||||
|
||||
|
||||
class DBReader(DBAccessor):
|
||||
def __enter__(self):
|
||||
try:
|
||||
lock = self._get_lock(False)
|
||||
except OSError as e:
|
||||
# Do not create lock if it does not exist.
|
||||
if e.errno == errno.ENOENT:
|
||||
self._vals = {}
|
||||
return self
|
||||
|
||||
try:
|
||||
# Read everything.
|
||||
self._vals = self._read_values_locked()
|
||||
return self
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def __exit__(self, type, value, traceback): pass
|
||||
|
||||
|
||||
class DBWriter(DBAccessor):
|
||||
def __init__(self, path):
|
||||
super(DBWriter, self).__init__(path)
|
||||
self._lock = None
|
||||
self._prev_umask = None
|
||||
|
||||
def put(self, key, value):
|
||||
self._vals[key] = value
|
||||
|
||||
def delete(self, key):
|
||||
self._vals.pop(key, None)
|
||||
|
||||
def __enter__(self):
|
||||
mkdirs_exists_ok(self._path)
|
||||
|
||||
# Make sure we can write and that permissions are correct.
|
||||
self._prev_umask = os.umask(0)
|
||||
|
||||
try:
|
||||
os.chmod(self._path, 0o777)
|
||||
self._lock = self._get_lock(True)
|
||||
self._vals = self._read_values_locked()
|
||||
except:
|
||||
os.umask(self._prev_umask)
|
||||
self._prev_umask = None
|
||||
raise
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self._check_entered()
|
||||
|
||||
try:
|
||||
# data_path refers to the externally used path to the params. It is a symlink.
|
||||
# old_data_path is the path currently pointed to by data_path.
|
||||
# tempdir_path is a path where the new params will go, which the new data path will point to.
|
||||
# new_data_path is a temporary symlink that will atomically overwrite data_path.
|
||||
#
|
||||
# The current situation is:
|
||||
# data_path -> old_data_path
|
||||
# We're going to write params data to tempdir_path
|
||||
# tempdir_path -> params data
|
||||
# Then point new_data_path to tempdir_path
|
||||
# new_data_path -> tempdir_path
|
||||
# Then atomically overwrite data_path with new_data_path
|
||||
# data_path -> tempdir_path
|
||||
old_data_path = None
|
||||
new_data_path = None
|
||||
tempdir_path = tempfile.mkdtemp(prefix=".tmp", dir=self._path)
|
||||
|
||||
try:
|
||||
# Write back all keys.
|
||||
os.chmod(tempdir_path, 0o777)
|
||||
for k, v in self._vals.items():
|
||||
with open(os.path.join(tempdir_path, k), "wb") as f:
|
||||
f.write(v)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
fsync_dir(tempdir_path)
|
||||
|
||||
data_path = self._data_path()
|
||||
try:
|
||||
old_data_path = os.path.join(self._path, os.readlink(data_path))
|
||||
except (OSError, IOError):
|
||||
# NOTE(mgraczyk): If other DB implementations have bugs, this could cause
|
||||
# copies to be left behind, but we still want to overwrite.
|
||||
pass
|
||||
|
||||
new_data_path = "{}.link".format(tempdir_path)
|
||||
os.symlink(os.path.basename(tempdir_path), new_data_path)
|
||||
os.rename(new_data_path, data_path)
|
||||
fsync_dir(self._path)
|
||||
finally:
|
||||
# If the rename worked, we can delete the old data. Otherwise delete the new one.
|
||||
success = new_data_path is not None and os.path.exists(data_path) and (
|
||||
os.readlink(data_path) == os.path.basename(tempdir_path))
|
||||
|
||||
if success:
|
||||
if old_data_path is not None:
|
||||
shutil.rmtree(old_data_path)
|
||||
else:
|
||||
shutil.rmtree(tempdir_path)
|
||||
|
||||
# Regardless of what happened above, there should be no link at new_data_path.
|
||||
if new_data_path is not None and os.path.islink(new_data_path):
|
||||
os.remove(new_data_path)
|
||||
finally:
|
||||
os.umask(self._prev_umask)
|
||||
self._prev_umask = None
|
||||
|
||||
# Always release the lock.
|
||||
self._lock.release()
|
||||
self._lock = None
|
||||
|
||||
|
||||
def read_db(params_path, key):
|
||||
path = "%s/d/%s" % (params_path, key)
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
return f.read()
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
def write_db(params_path, key, value):
|
||||
if isinstance(value, str):
|
||||
value = value.encode('utf8')
|
||||
|
||||
prev_umask = os.umask(0)
|
||||
lock = FileLock(params_path+"/.lock", True)
|
||||
lock.acquire()
|
||||
|
||||
try:
|
||||
tmp_path = tempfile.mktemp(prefix=".tmp", dir=params_path)
|
||||
with open(tmp_path, "wb") as f:
|
||||
f.write(value)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
|
||||
path = "%s/d/%s" % (params_path, key)
|
||||
os.rename(tmp_path, path)
|
||||
fsync_dir(os.path.dirname(path))
|
||||
finally:
|
||||
os.umask(prev_umask)
|
||||
lock.release()
|
||||
|
||||
class Params():
|
||||
def __init__(self, db=PARAMS):
|
||||
self.db = db
|
||||
|
||||
# create the database if it doesn't exist...
|
||||
if not os.path.exists(self.db+"/d"):
|
||||
with self.transaction(write=True):
|
||||
pass
|
||||
|
||||
def clear_all(self):
|
||||
shutil.rmtree(self.db, ignore_errors=True)
|
||||
with self.transaction(write=True):
|
||||
pass
|
||||
|
||||
def transaction(self, write=False):
|
||||
if write:
|
||||
return DBWriter(self.db)
|
||||
else:
|
||||
return DBReader(self.db)
|
||||
|
||||
def _clear_keys_with_type(self, tx_type):
|
||||
with self.transaction(write=True) as txn:
|
||||
for key in keys:
|
||||
if tx_type in keys[key]:
|
||||
txn.delete(key)
|
||||
|
||||
def manager_start(self):
|
||||
self._clear_keys_with_type(TxType.CLEAR_ON_MANAGER_START)
|
||||
|
||||
def panda_disconnect(self):
|
||||
self._clear_keys_with_type(TxType.CLEAR_ON_PANDA_DISCONNECT)
|
||||
|
||||
def delete(self, key):
|
||||
with self.transaction(write=True) as txn:
|
||||
txn.delete(key)
|
||||
|
||||
def get(self, key, block=False, encoding=None):
|
||||
if key not in keys:
|
||||
raise UnknownKeyName(key)
|
||||
|
||||
while 1:
|
||||
ret = read_db(self.db, key)
|
||||
if not block or ret is not None:
|
||||
break
|
||||
# is polling really the best we can do?
|
||||
time.sleep(0.05)
|
||||
|
||||
if ret is not None and encoding is not None:
|
||||
ret = ret.decode(encoding)
|
||||
|
||||
return ret
|
||||
|
||||
def put(self, key, dat):
|
||||
"""
|
||||
Warning: This function blocks until the param is written to disk!
|
||||
In very rare cases this can take over a second, and your code will hang.
|
||||
|
||||
Use the put_nonblocking helper function in time sensitive code, but
|
||||
in general try to avoid writing params as much as possible.
|
||||
"""
|
||||
|
||||
if key not in keys:
|
||||
raise UnknownKeyName(key)
|
||||
|
||||
write_db(self.db, key, dat)
|
||||
|
||||
|
||||
def put_nonblocking(key, val):
|
||||
def f(key, val):
|
||||
params = Params()
|
||||
params.put(key, val)
|
||||
|
||||
t = threading.Thread(target=f, args=(key, val))
|
||||
t.start()
|
||||
return t
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
params = Params()
|
||||
if len(sys.argv) > 2:
|
||||
params.put(sys.argv[1], sys.argv[2])
|
||||
else:
|
||||
for k in keys:
|
||||
pp = params.get(k)
|
||||
if pp is None:
|
||||
print("%s is None" % k)
|
||||
elif all(ord(c) < 128 and ord(c) >= 32 for c in pp):
|
||||
print("%s = %s" % (k, pp))
|
||||
else:
|
||||
print("%s = %s" % (k, pp.encode("hex")))
|
||||
|
||||
# Test multiprocess:
|
||||
# seq 0 100000 | xargs -P20 -I{} python common/params.py DongleId {} && sleep 0.05
|
||||
# while python common/params.py DongleId; do sleep 0.05; done
|
||||
from common.params_pyx import Params, UnknownKeyName, put_nonblocking # pylint: disable=no-name-in-module, import-error
|
||||
assert Params
|
||||
assert UnknownKeyName
|
||||
assert put_nonblocking
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
from libcpp.string cimport string
|
||||
from libcpp cimport bool
|
||||
|
||||
cdef extern from "selfdrive/common/params.cc":
|
||||
pass
|
||||
|
||||
cdef extern from "selfdrive/common/util.c":
|
||||
pass
|
||||
|
||||
cdef extern from "selfdrive/common/params.h":
|
||||
cdef cppclass Params:
|
||||
Params(bool)
|
||||
Params(string)
|
||||
string get(string, bool) nogil
|
||||
int delete_db_value(string)
|
||||
int write_db_value(string, string)
|
||||
Executable
+161
@@ -0,0 +1,161 @@
|
||||
# distutils: language = c++
|
||||
# cython: language_level = 3
|
||||
from libcpp cimport bool
|
||||
from libcpp.string cimport string
|
||||
from common.params_pxd cimport Params as c_Params
|
||||
|
||||
import os
|
||||
import threading
|
||||
from common.basedir import BASEDIR
|
||||
|
||||
cdef enum TxType:
|
||||
PERSISTENT = 1
|
||||
CLEAR_ON_MANAGER_START = 2
|
||||
CLEAR_ON_PANDA_DISCONNECT = 3
|
||||
|
||||
keys = {
|
||||
b"AccessToken": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"AthenadPid": [TxType.PERSISTENT],
|
||||
b"CalibrationParams": [TxType.PERSISTENT],
|
||||
b"CarBatteryCapacity": [TxType.PERSISTENT],
|
||||
b"CarParams": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
b"CarParamsCache": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
b"CarVin": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
b"CommunityFeaturesToggle": [TxType.PERSISTENT],
|
||||
b"CompletedTrainingVersion": [TxType.PERSISTENT],
|
||||
b"DisablePowerDown": [TxType.PERSISTENT],
|
||||
b"DisableUpdates": [TxType.PERSISTENT],
|
||||
b"DoUninstall": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"DongleId": [TxType.PERSISTENT],
|
||||
b"GitBranch": [TxType.PERSISTENT],
|
||||
b"GitCommit": [TxType.PERSISTENT],
|
||||
b"GitRemote": [TxType.PERSISTENT],
|
||||
b"GithubSshKeys": [TxType.PERSISTENT],
|
||||
b"HasAcceptedTerms": [TxType.PERSISTENT],
|
||||
b"HasCompletedSetup": [TxType.PERSISTENT],
|
||||
b"IsDriverViewEnabled": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"IsLdwEnabled": [TxType.PERSISTENT],
|
||||
b"IsMetric": [TxType.PERSISTENT],
|
||||
b"IsOffroad": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"IsRHD": [TxType.PERSISTENT],
|
||||
b"IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"IsUpdateAvailable": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"IsUploadRawEnabled": [TxType.PERSISTENT],
|
||||
b"LastAthenaPingTime": [TxType.PERSISTENT],
|
||||
b"LastUpdateTime": [TxType.PERSISTENT],
|
||||
b"LastUpdateException": [TxType.PERSISTENT],
|
||||
b"LiveParameters": [TxType.PERSISTENT],
|
||||
b"OpenpilotEnabledToggle": [TxType.PERSISTENT],
|
||||
b"LaneChangeEnabled": [TxType.PERSISTENT],
|
||||
b"PandaFirmware": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
b"PandaFirmwareHex": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
b"PandaDongleId": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
b"Passive": [TxType.PERSISTENT],
|
||||
b"RecordFront": [TxType.PERSISTENT],
|
||||
b"ReleaseNotes": [TxType.PERSISTENT],
|
||||
b"ShouldDoUpdate": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"SubscriberInfo": [TxType.PERSISTENT],
|
||||
b"TermsVersion": [TxType.PERSISTENT],
|
||||
b"TrainingVersion": [TxType.PERSISTENT],
|
||||
b"UpdateAvailable": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"UpdateFailedCount": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"Version": [TxType.PERSISTENT],
|
||||
b"Offroad_ChargeDisabled": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
b"Offroad_ConnectivityNeeded": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"Offroad_ConnectivityNeededPrompt": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"Offroad_TemperatureTooHigh": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"Offroad_PandaFirmwareMismatch": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
b"Offroad_InvalidTime": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"Offroad_IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"Offroad_NeosUpdate": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"Offroad_UpdateFailed": [TxType.CLEAR_ON_MANAGER_START],
|
||||
b"Offroad_HardwareUnsupported": [TxType.CLEAR_ON_MANAGER_START],
|
||||
}
|
||||
|
||||
def ensure_bytes(v):
|
||||
if isinstance(v, str):
|
||||
return v.encode()
|
||||
else:
|
||||
return v
|
||||
|
||||
|
||||
class UnknownKeyName(Exception):
|
||||
pass
|
||||
|
||||
cdef class Params:
|
||||
cdef c_Params* p
|
||||
|
||||
def __cinit__(self, d=None, bool persistent_params=False):
|
||||
if d is None:
|
||||
self.p = new c_Params(persistent_params)
|
||||
else:
|
||||
self.p = new c_Params(<string>d.encode())
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.p
|
||||
|
||||
def clear_all(self, tx_type=None):
|
||||
for key in keys:
|
||||
if tx_type is None or tx_type in keys[key]:
|
||||
self.delete(key)
|
||||
|
||||
def manager_start(self):
|
||||
self.clear_all(TxType.CLEAR_ON_MANAGER_START)
|
||||
|
||||
def panda_disconnect(self):
|
||||
self.clear_all(TxType.CLEAR_ON_PANDA_DISCONNECT)
|
||||
|
||||
def get(self, key, block=False, encoding=None):
|
||||
key = ensure_bytes(key)
|
||||
|
||||
if key not in keys:
|
||||
raise UnknownKeyName(key)
|
||||
|
||||
cdef string k = key
|
||||
cdef bool b = block
|
||||
|
||||
cdef string val
|
||||
with nogil:
|
||||
val = self.p.get(k, b)
|
||||
|
||||
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 None
|
||||
|
||||
if encoding is not None:
|
||||
return val.decode(encoding)
|
||||
else:
|
||||
return val
|
||||
|
||||
def put(self, key, dat):
|
||||
"""
|
||||
Warning: This function blocks until the param is written to disk!
|
||||
In very rare cases this can take over a second, and your code will hang.
|
||||
Use the put_nonblocking helper function in time sensitive code, but
|
||||
in general try to avoid writing params as much as possible.
|
||||
"""
|
||||
key = ensure_bytes(key)
|
||||
dat = ensure_bytes(dat)
|
||||
|
||||
if key not in keys:
|
||||
raise UnknownKeyName(key)
|
||||
|
||||
self.p.write_db_value(key, dat)
|
||||
|
||||
def delete(self, key):
|
||||
key = ensure_bytes(key)
|
||||
self.p.delete_db_value(key)
|
||||
|
||||
|
||||
def put_nonblocking(key, val, d=None):
|
||||
def f(key, val):
|
||||
params = Params(d)
|
||||
params.put(key, val)
|
||||
|
||||
t = threading.Thread(target=f, args=(key, val))
|
||||
t.start()
|
||||
return t
|
||||
@@ -1,163 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
# Copyright (C) 2016 Sixten Bergman
|
||||
# License WTFPL
|
||||
#
|
||||
# This program is free software. It comes without any warranty, to the extent
|
||||
# permitted by applicable law.
|
||||
# You can redistribute it and/or modify it under the terms of the Do What The
|
||||
# Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See
|
||||
# http://www.wtfpl.net/ for more details.
|
||||
#
|
||||
# note that the function peakdetect is derived from code which was released to
|
||||
# public domain see: http://billauer.co.il/peakdet.html
|
||||
#
|
||||
|
||||
from math import log
|
||||
import numpy as np
|
||||
|
||||
__all__ = ["peakdetect"]
|
||||
|
||||
|
||||
|
||||
def _datacheck_peakdetect(x_axis, y_axis):
|
||||
if x_axis is None:
|
||||
x_axis = range(len(y_axis))
|
||||
|
||||
if len(y_axis) != len(x_axis):
|
||||
raise ValueError("Input vectors y_axis and x_axis must have same length")
|
||||
|
||||
#needs to be a numpy array
|
||||
y_axis = np.array(y_axis)
|
||||
x_axis = np.array(x_axis)
|
||||
return x_axis, y_axis
|
||||
|
||||
|
||||
def _pad(fft_data, pad_len):
|
||||
"""
|
||||
Pads fft data to interpolate in time domain
|
||||
|
||||
keyword arguments:
|
||||
fft_data -- the fft
|
||||
pad_len -- By how many times the time resolution should be increased by
|
||||
|
||||
return: padded list
|
||||
"""
|
||||
length = len(fft_data)
|
||||
n = _n(length * pad_len)
|
||||
fft_data = list(fft_data)
|
||||
|
||||
return fft_data[:length // 2] + [0] * (2**n-length) + fft_data[length // 2:]
|
||||
|
||||
|
||||
def _n(x):
|
||||
"""
|
||||
Find the smallest value for n, which fulfils 2**n >= x
|
||||
|
||||
keyword arguments:
|
||||
x -- the value, which 2**n must surpass
|
||||
return: the integer n
|
||||
"""
|
||||
return int(log(x)/log(2)) + 1
|
||||
|
||||
|
||||
def peakdetect(y_axis, x_axis=None, lookahead=200, delta=0):
|
||||
"""
|
||||
Converted from/based on a MATLAB script at:
|
||||
http://billauer.co.il/peakdet.html
|
||||
function for detecting local maxima and minima in a signal.
|
||||
Discovers peaks by searching for values which are surrounded by lower
|
||||
or larger values for maxima and minima respectively
|
||||
keyword arguments:
|
||||
y_axis -- A list containing the signal over which to find peaks
|
||||
x_axis -- A x-axis whose values correspond to the y_axis list and is used
|
||||
in the return to specify the position of the peaks. If omitted an
|
||||
index of the y_axis is used.
|
||||
(default: None)
|
||||
lookahead -- distance to look ahead from a peak candidate to determine if
|
||||
it is the actual peak
|
||||
(default: 200)
|
||||
'(samples / period) / f' where '4 >= f >= 1.25' might be a good value
|
||||
delta -- this specifies a minimum difference between a peak and
|
||||
the following points, before a peak may be considered a peak. Useful
|
||||
to hinder the function from picking up false peaks towards to end of
|
||||
the signal. To work well delta should be set to delta >= RMSnoise * 5.
|
||||
(default: 0)
|
||||
When omitted delta function causes a 20% decrease in speed.
|
||||
When used Correctly it can double the speed of the function
|
||||
return: two lists [max_peaks, min_peaks] containing the positive and
|
||||
negative peaks respectively. Each cell of the lists contains a tuple
|
||||
of: (position, peak_value)
|
||||
to get the average peak value do: np.mean(max_peaks, 0)[1] on the
|
||||
results to unpack one of the lists into x, y coordinates do:
|
||||
x, y = zip(*max_peaks)
|
||||
"""
|
||||
max_peaks = []
|
||||
min_peaks = []
|
||||
dump = [] # Used to pop the first hit which almost always is false
|
||||
# check input data
|
||||
x_axis, y_axis = _datacheck_peakdetect(x_axis, y_axis)
|
||||
# store data length for later use
|
||||
length = len(y_axis)
|
||||
#perform some checks
|
||||
if lookahead < 1:
|
||||
raise ValueError("Lookahead must be '1' or above in value")
|
||||
if not (np.isscalar(delta) and delta >= 0):
|
||||
raise ValueError("delta must be a positive number")
|
||||
#maxima and minima candidates are temporarily stored in
|
||||
#mx and mn respectively
|
||||
mn, mx = np.Inf, -np.Inf
|
||||
#Only detect peak if there is 'lookahead' amount of points after it
|
||||
for index, (x, y) in enumerate(zip(x_axis[:-lookahead],
|
||||
y_axis[:-lookahead])):
|
||||
if y > mx:
|
||||
mx = y
|
||||
mxpos = x
|
||||
if y < mn:
|
||||
mn = y
|
||||
mnpos = x
|
||||
####look for max####
|
||||
if y < mx-delta and mx != np.Inf:
|
||||
#Maxima peak candidate found
|
||||
#look ahead in signal to ensure that this is a peak and not jitter
|
||||
if y_axis[index:index+lookahead].max() < mx:
|
||||
max_peaks.append([mxpos, mx])
|
||||
dump.append(True)
|
||||
#set algorithm to only find minima now
|
||||
mx = np.Inf
|
||||
mn = np.Inf
|
||||
if index+lookahead >= length:
|
||||
#end is within lookahead no more peaks can be found
|
||||
break
|
||||
continue
|
||||
#else: #slows shit down this does
|
||||
# mx = ahead
|
||||
# mxpos = x_axis[np.where(y_axis[index:index+lookahead]==mx)]
|
||||
####look for min####
|
||||
if y > mn+delta and mn != -np.Inf:
|
||||
#Minima peak candidate found
|
||||
#look ahead in signal to ensure that this is a peak and not jitter
|
||||
if y_axis[index:index+lookahead].min() > mn:
|
||||
min_peaks.append([mnpos, mn])
|
||||
dump.append(False)
|
||||
#set algorithm to only find maxima now
|
||||
mn = -np.Inf
|
||||
mx = -np.Inf
|
||||
if index+lookahead >= length:
|
||||
#end is within lookahead no more peaks can be found
|
||||
break
|
||||
#else: #slows shit down this does
|
||||
# mn = ahead
|
||||
# mnpos = x_axis[np.where(y_axis[index:index+lookahead]==mn)]
|
||||
#Remove the false hit on the first value of the y_axis
|
||||
try:
|
||||
if dump[0]:
|
||||
max_peaks.pop(0)
|
||||
else:
|
||||
min_peaks.pop(0)
|
||||
del dump
|
||||
except IndexError:
|
||||
#no peaks were found, should the function return empty lists?
|
||||
pass
|
||||
return [max_peaks, min_peaks]
|
||||
+3
-4
@@ -36,11 +36,10 @@ class Profiler():
|
||||
if not self.enabled:
|
||||
return
|
||||
self.iter += 1
|
||||
print("******* Profiling *******")
|
||||
print("******* Profiling %d *******" % self.iter)
|
||||
for n, ms in sorted(self.cp.items(), key=lambda x: -x[1]):
|
||||
if n in self.cp_ignored:
|
||||
print("%30s: %7.2f percent: %3.0f IGNORED" % (n, ms*1000.0, ms/self.tot*100))
|
||||
print("%30s: %9.2f avg: %7.2f percent: %3.0f IGNORED" % (n, ms*1000.0, ms*1000.0/self.iter, ms/self.tot*100))
|
||||
else:
|
||||
print("%30s: %7.2f percent: %3.0f" % (n, ms*1000.0, ms/self.tot*100))
|
||||
print("%30s: %9.2f avg: %7.2f percent: %3.0f" % (n, ms*1000.0, ms*1000.0/self.iter, ms/self.tot*100))
|
||||
print("Iter clock: %2.6f TOTAL: %2.2f" % (self.tot/self.iter, self.tot))
|
||||
|
||||
|
||||
+19
-18
@@ -1,12 +1,11 @@
|
||||
"""Utilities for reading real time clocks and keeping soft real time constraints."""
|
||||
import gc
|
||||
import os
|
||||
import time
|
||||
import platform
|
||||
import subprocess
|
||||
import multiprocessing
|
||||
from cffi import FFI
|
||||
|
||||
from common.common_pyx import sec_since_boot # pylint: disable=no-name-in-module, import-error
|
||||
from common.hardware import PC
|
||||
from common.clock import sec_since_boot # pylint: disable=no-name-in-module, import-error
|
||||
|
||||
|
||||
# time step for each process
|
||||
@@ -16,24 +15,26 @@ DT_DMON = 0.1 # driver monitoring
|
||||
DT_TRML = 0.5 # thermald and manager
|
||||
|
||||
|
||||
ffi = FFI()
|
||||
ffi.cdef("long syscall(long number, ...);")
|
||||
libc = ffi.dlopen(None)
|
||||
class Priority:
|
||||
MIN_REALTIME = 52 # highest android process priority is 51
|
||||
CTRL_LOW = MIN_REALTIME
|
||||
CTRL_HIGH = MIN_REALTIME + 1
|
||||
|
||||
|
||||
def set_realtime_priority(level):
|
||||
if os.getuid() != 0:
|
||||
print("not setting priority, not root")
|
||||
return
|
||||
if platform.machine() == "x86_64":
|
||||
NR_gettid = 186
|
||||
elif platform.machine() == "aarch64":
|
||||
NR_gettid = 178
|
||||
else:
|
||||
raise NotImplementedError
|
||||
if not PC:
|
||||
os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level))
|
||||
|
||||
tid = libc.syscall(NR_gettid)
|
||||
return subprocess.call(['chrt', '-f', '-p', str(level), str(tid)])
|
||||
|
||||
def set_core_affinity(core):
|
||||
if not PC:
|
||||
os.sched_setaffinity(0, [core,])
|
||||
|
||||
|
||||
def config_realtime_process(core, priority):
|
||||
gc.disable()
|
||||
set_realtime_priority(priority)
|
||||
set_core_affinity(core)
|
||||
|
||||
|
||||
class Ratekeeper():
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
class SamplingBuffer():
|
||||
def __init__(self, fn, size, write=False):
|
||||
self._fn = fn
|
||||
self._write = write
|
||||
if self._write:
|
||||
self._f = open(self._fn, "ab")
|
||||
else:
|
||||
self._f = open(self._fn, "rb")
|
||||
self._size = size
|
||||
self._refresh()
|
||||
|
||||
def _refresh(self):
|
||||
self.cnt = os.path.getsize(self._fn) / self._size
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
self._refresh()
|
||||
return self.cnt
|
||||
|
||||
def _fetch_one(self, x):
|
||||
assert self._write == False
|
||||
self._f.seek(x*self._size)
|
||||
return self._f.read(self._size)
|
||||
|
||||
def sample(self, count, indices = None):
|
||||
if indices == None:
|
||||
cnt = self.count
|
||||
assert cnt != 0
|
||||
indices = map(lambda x: random.randint(0, cnt-1), range(count))
|
||||
return map(self._fetch_one, indices)
|
||||
|
||||
def write(self, dat):
|
||||
assert self._write == True
|
||||
assert (len(dat) % self._size) == 0
|
||||
self._f.write(dat)
|
||||
self._f.flush()
|
||||
|
||||
class NumpySamplingBuffer():
|
||||
def __init__(self, fn, size, dtype, write=False):
|
||||
self._size = size
|
||||
self._dtype = dtype
|
||||
self._buf = SamplingBuffer(fn, len(np.zeros(size, dtype=dtype).tobytes()), write)
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return self._buf.count
|
||||
|
||||
def write(self, dat):
|
||||
self._buf.write(dat.tobytes())
|
||||
|
||||
def sample(self, count, indices = None):
|
||||
return np.fromstring(''.join(self._buf.sample(count, indices)), dtype=self._dtype).reshape([count]+list(self._size))
|
||||
|
||||
# TODO: n IOPS needed where n is the Multi
|
||||
class MultiNumpySamplingBuffer():
|
||||
def __init__(self, fn, npa, write=False):
|
||||
self._bufs = []
|
||||
for i,n in enumerate(npa):
|
||||
self._bufs.append(NumpySamplingBuffer(fn + ("_%d" % i), n[0], n[1], write))
|
||||
|
||||
def write(self, dat):
|
||||
for b,x in zip(self._bufs, dat):
|
||||
b.write(x)
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return min(map(lambda x: x.count, self._bufs))
|
||||
|
||||
def sample(self, count):
|
||||
cnt = self.count
|
||||
assert cnt != 0
|
||||
indices = map(lambda x: random.randint(0, cnt-1), range(count))
|
||||
return map(lambda x: x.sample(count, indices), self._bufs)
|
||||
|
||||
+2
-16
@@ -8,7 +8,7 @@ class Spinner():
|
||||
try:
|
||||
self.spinner_proc = subprocess.Popen(["./spinner"],
|
||||
stdin=subprocess.PIPE,
|
||||
cwd=os.path.join(BASEDIR, "selfdrive", "ui", "spinner"),
|
||||
cwd=os.path.join(BASEDIR, "selfdrive", "ui"),
|
||||
close_fds=True)
|
||||
except OSError:
|
||||
self.spinner_proc = None
|
||||
@@ -36,24 +36,10 @@ class Spinner():
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
|
||||
class FakeSpinner():
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def update(self, _):
|
||||
pass
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
with Spinner() as s:
|
||||
|
||||
+3
-3
@@ -32,7 +32,7 @@ class RunningStat():
|
||||
self.S_last = 0.
|
||||
else:
|
||||
self.M = self.M_last + (new_data - self.M_last) / self.n
|
||||
self.S = self.S_last + (new_data - self.M_last) * (new_data - self.M);
|
||||
self.S = self.S_last + (new_data - self.M_last) * (new_data - self.M)
|
||||
self.M_last = self.M
|
||||
self.S_last = self.S
|
||||
|
||||
@@ -53,7 +53,7 @@ class RunningStat():
|
||||
|
||||
class RunningStatFilter():
|
||||
def __init__(self, raw_priors=None, filtered_priors=None, max_trackable=-1):
|
||||
self.raw_stat = RunningStat(raw_priors, max_trackable)
|
||||
self.raw_stat = RunningStat(raw_priors, -1)
|
||||
self.filtered_stat = RunningStat(filtered_priors, max_trackable)
|
||||
|
||||
def reset(self):
|
||||
@@ -64,7 +64,7 @@ class RunningStatFilter():
|
||||
_std_last = self.raw_stat.std()
|
||||
self.raw_stat.push_data(new_data)
|
||||
_delta_std = self.raw_stat.std() - _std_last
|
||||
if _delta_std<=0:
|
||||
if _delta_std <= 0:
|
||||
self.filtered_stat.push_data(new_data)
|
||||
else:
|
||||
pass
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
_DESC_FMT = """
|
||||
{} (n={}):
|
||||
MEAN={}
|
||||
VAR={}
|
||||
MIN={}
|
||||
MAX={}
|
||||
"""
|
||||
|
||||
class StatTracker():
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
self._mean = 0.
|
||||
self._var = 0.
|
||||
self._n = 0
|
||||
self._min = -float("-inf")
|
||||
self._max = -float("inf")
|
||||
|
||||
@property
|
||||
def mean(self):
|
||||
return self._mean
|
||||
|
||||
@property
|
||||
def var(self):
|
||||
return (self._n * self._var) / (self._n - 1.)
|
||||
|
||||
@property
|
||||
def min(self):
|
||||
return self._min
|
||||
|
||||
@property
|
||||
def max(self):
|
||||
return self._max
|
||||
|
||||
def update(self, samples):
|
||||
# https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Parallel_algorithm
|
||||
data = samples.reshape(-1)
|
||||
n_a = data.size
|
||||
mean_a = np.mean(data)
|
||||
var_a = np.var(data, ddof=0)
|
||||
|
||||
n_b = self._n
|
||||
mean_b = self._mean
|
||||
|
||||
delta = mean_b - mean_a
|
||||
m_a = var_a * (n_a - 1)
|
||||
m_b = self._var * (n_b - 1)
|
||||
m2 = m_a + m_b + delta**2 * n_a * n_b / (n_a + n_b)
|
||||
|
||||
self._var = m2 / (n_a + n_b)
|
||||
self._mean = (n_a * mean_a + n_b * mean_b) / (n_a + n_b)
|
||||
self._n = n_a + n_b
|
||||
|
||||
self._min = min(self._min, np.min(data))
|
||||
self._max = max(self._max, np.max(data))
|
||||
|
||||
def __str__(self):
|
||||
return _DESC_FMT.format(self._name, self._n, self._mean, self.var, self._min,
|
||||
self._max)
|
||||
|
||||
# FIXME(mgraczyk): The variance computation does not work with 1 sample batches.
|
||||
class VectorStatTracker(StatTracker):
|
||||
def __init__(self, name, dim):
|
||||
self._name = name
|
||||
self._mean = np.zeros((dim, ))
|
||||
self._var = np.zeros((dim, dim))
|
||||
self._n = 0
|
||||
self._min = np.full((dim, ), -float("-inf"))
|
||||
self._max = np.full((dim, ), -float("inf"))
|
||||
|
||||
@property
|
||||
def cov(self):
|
||||
return self.var
|
||||
|
||||
def update(self, samples):
|
||||
n_a = samples.shape[0]
|
||||
mean_a = np.mean(samples, axis=0)
|
||||
var_a = np.cov(samples, ddof=0, rowvar=False)
|
||||
|
||||
n_b = self._n
|
||||
mean_b = self._mean
|
||||
|
||||
delta = mean_b - mean_a
|
||||
m_a = var_a * (n_a - 1)
|
||||
m_b = self._var * (n_b - 1)
|
||||
m2 = m_a + m_b + delta**2 * n_a * n_b / (n_a + n_b)
|
||||
|
||||
self._var = m2 / (n_a + n_b)
|
||||
self._mean = (n_a * mean_a + n_b * mean_b) / (n_a + n_b)
|
||||
self._n = n_a + n_b
|
||||
|
||||
self._min = np.minimum(self._min, np.min(samples, axis=0))
|
||||
self._max = np.maximum(self._max, np.max(samples, axis=0))
|
||||
@@ -1,6 +0,0 @@
|
||||
def replace_right(s, old, new, occurrence):
|
||||
# replace_right('1232425', '2', ' ', 1) -> '12324 5'
|
||||
# replace_right('1232425', '2', ' ', 2) -> '123 4 5'
|
||||
|
||||
split = s.rsplit(old, occurrence)
|
||||
return new.join(split)
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import sympy as sp
|
||||
import numpy as np
|
||||
|
||||
def cross(x):
|
||||
ret = sp.Matrix(np.zeros((3,3)))
|
||||
ret[0,1], ret[0,2] = -x[2], x[1]
|
||||
ret[1,0], ret[1,2] = x[2], -x[0]
|
||||
ret[2,0], ret[2,1] = -x[1], x[0]
|
||||
return ret
|
||||
|
||||
def euler_rotate(roll, pitch, yaw):
|
||||
# make symbolic rotation matrix from eulers
|
||||
matrix_roll = sp.Matrix([[1, 0, 0],
|
||||
[0, sp.cos(roll), -sp.sin(roll)],
|
||||
[0, sp.sin(roll), sp.cos(roll)]])
|
||||
matrix_pitch = sp.Matrix([[sp.cos(pitch), 0, sp.sin(pitch)],
|
||||
[0, 1, 0],
|
||||
[-sp.sin(pitch), 0, sp.cos(pitch)]])
|
||||
matrix_yaw = sp.Matrix([[sp.cos(yaw), -sp.sin(yaw), 0],
|
||||
[sp.sin(yaw), sp.cos(yaw), 0],
|
||||
[0, 0, 1]])
|
||||
return matrix_yaw*matrix_pitch*matrix_roll
|
||||
|
||||
def quat_rotate(q0, q1, q2, q3):
|
||||
# make symbolic rotation matrix from quat
|
||||
return sp.Matrix([[q0**2 + q1**2 - q2**2 - q3**2, 2*(q1*q2 + q0*q3), 2*(q1*q3 - q0*q2)],
|
||||
[2*(q1*q2 - q0*q3), q0**2 - q1**2 + q2**2 - q3**2, 2*(q2*q3 + q0*q1)],
|
||||
[2*(q1*q3 + q0*q2), 2*(q2*q3 - q0*q1), q0**2 - q1**2 - q2**2 + q3**2]]).T
|
||||
|
||||
def quat_matrix_l(p):
|
||||
return sp.Matrix([[p[0], -p[1], -p[2], -p[3]],
|
||||
[p[1], p[0], -p[3], p[2]],
|
||||
[p[2], p[3], p[0], -p[1]],
|
||||
[p[3], -p[2], p[1], p[0]]])
|
||||
|
||||
def quat_matrix_r(p):
|
||||
return sp.Matrix([[p[0], -p[1], -p[2], -p[3]],
|
||||
[p[1], p[0], p[3], -p[2]],
|
||||
[p[2], -p[3], p[0], p[1]],
|
||||
[p[3], p[2], -p[1], p[0]]])
|
||||
|
||||
|
||||
def sympy_into_c(sympy_functions):
|
||||
from sympy.utilities import codegen
|
||||
routines = []
|
||||
for name, expr, args in sympy_functions:
|
||||
r = codegen.make_routine(name, expr, language="C99")
|
||||
|
||||
# argument ordering input to sympy is broken with function with output arguments
|
||||
nargs = []
|
||||
# reorder the input arguments
|
||||
for aa in args:
|
||||
if aa is None:
|
||||
nargs.append(codegen.InputArgument(sp.Symbol('unused'), dimensions=[1,1]))
|
||||
continue
|
||||
found = False
|
||||
for a in r.arguments:
|
||||
if str(aa.name) == str(a.name):
|
||||
nargs.append(a)
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
# [1,1] is a hack for Matrices
|
||||
nargs.append(codegen.InputArgument(aa, dimensions=[1,1]))
|
||||
# add the output arguments
|
||||
for a in r.arguments:
|
||||
if type(a) == codegen.OutputArgument:
|
||||
nargs.append(a)
|
||||
|
||||
#assert len(r.arguments) == len(args)+1
|
||||
r.arguments = nargs
|
||||
|
||||
# add routine to list
|
||||
routines.append(r)
|
||||
|
||||
[(c_name, c_code), (h_name, c_header)] = codegen.get_code_generator('C', 'ekf', 'C99').write(routines, "ekf")
|
||||
c_code = '\n'.join(x for x in c_code.split("\n") if len(x) > 0 and x[0] != '#')
|
||||
c_header = '\n'.join(x for x in c_header.split("\n") if len(x) > 0 and x[0] != '#')
|
||||
|
||||
return c_header, c_code
|
||||
@@ -1,9 +0,0 @@
|
||||
import os
|
||||
from nose.tools import nottest
|
||||
|
||||
def phone_only(x):
|
||||
if os.path.isfile("/init.qcom.rc"):
|
||||
return x
|
||||
else:
|
||||
return nottest(x)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import os
|
||||
import unittest
|
||||
from uuid import uuid4
|
||||
|
||||
from common.file_helpers import atomic_write_on_fs_tmp
|
||||
from common.file_helpers import atomic_write_in_dir
|
||||
|
||||
|
||||
class TestFileHelpers(unittest.TestCase):
|
||||
def run_atomic_write_func(self, atomic_write_func):
|
||||
path = "/tmp/tmp{}".format(uuid4())
|
||||
with atomic_write_func(path) as f:
|
||||
f.write("test")
|
||||
|
||||
with open(path) as f:
|
||||
self.assertEqual(f.read(), "test")
|
||||
self.assertEqual(os.stat(path).st_mode & 0o777, 0o644)
|
||||
os.remove(path)
|
||||
|
||||
def test_atomic_write_on_fs_tmp(self):
|
||||
self.run_atomic_write_func(atomic_write_on_fs_tmp)
|
||||
|
||||
def test_atomic_write_in_dir(self):
|
||||
self.run_atomic_write_func(atomic_write_in_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,27 +0,0 @@
|
||||
import numpy as np
|
||||
import unittest
|
||||
import timeit
|
||||
|
||||
from common.numpy_fast import interp
|
||||
|
||||
|
||||
class InterpTest(unittest.TestCase):
|
||||
def test_correctness_controls(self):
|
||||
_A_CRUISE_MIN_BP = np.asarray([0., 5., 10., 20., 40.])
|
||||
_A_CRUISE_MIN_V = np.asarray([-1.0, -.8, -.67, -.5, -.30])
|
||||
v_ego_arr = [-1, -1e-12, 0, 4, 5, 6, 7, 10, 11, 15.2, 20, 21, 39,
|
||||
39.999999, 40, 41]
|
||||
|
||||
expected = np.interp(v_ego_arr, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V)
|
||||
actual = interp(v_ego_arr, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V)
|
||||
|
||||
np.testing.assert_equal(actual, expected)
|
||||
|
||||
for v_ego in v_ego_arr:
|
||||
expected = np.interp(v_ego, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V)
|
||||
actual = interp(v_ego, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V)
|
||||
np.testing.assert_equal(actual, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,63 +0,0 @@
|
||||
from common.params import Params, UnknownKeyName
|
||||
import threading
|
||||
import time
|
||||
import tempfile
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
|
||||
class TestParams(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmpdir = tempfile.mkdtemp()
|
||||
print("using", self.tmpdir)
|
||||
self.params = Params(self.tmpdir)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmpdir)
|
||||
|
||||
def test_params_put_and_get(self):
|
||||
self.params.put("DongleId", "cb38263377b873ee")
|
||||
assert self.params.get("DongleId") == b"cb38263377b873ee"
|
||||
|
||||
def test_params_non_ascii(self):
|
||||
st = b"\xe1\x90\xff"
|
||||
self.params.put("CarParams", st)
|
||||
assert self.params.get("CarParams") == st
|
||||
|
||||
def test_params_get_cleared_panda_disconnect(self):
|
||||
self.params.put("CarParams", "test")
|
||||
self.params.put("DongleId", "cb38263377b873ee")
|
||||
assert self.params.get("CarParams") == b"test"
|
||||
self.params.panda_disconnect()
|
||||
assert self.params.get("CarParams") is None
|
||||
assert self.params.get("DongleId") is not None
|
||||
|
||||
def test_params_get_cleared_manager_start(self):
|
||||
self.params.put("CarParams", "test")
|
||||
self.params.put("DongleId", "cb38263377b873ee")
|
||||
assert self.params.get("CarParams") == b"test"
|
||||
self.params.manager_start()
|
||||
assert self.params.get("CarParams") is None
|
||||
assert self.params.get("DongleId") is not None
|
||||
|
||||
def test_params_two_things(self):
|
||||
self.params.put("DongleId", "bob")
|
||||
self.params.put("AccessToken", "knope")
|
||||
assert self.params.get("DongleId") == b"bob"
|
||||
assert self.params.get("AccessToken") == b"knope"
|
||||
|
||||
def test_params_get_block(self):
|
||||
def _delayed_writer():
|
||||
time.sleep(0.1)
|
||||
self.params.put("CarParams", "test")
|
||||
threading.Thread(target=_delayed_writer).start()
|
||||
assert self.params.get("CarParams") is None
|
||||
assert self.params.get("CarParams", True) == b"test"
|
||||
|
||||
def test_params_unknown_key_fails(self):
|
||||
with self.assertRaises(UnknownKeyName):
|
||||
self.params.get("swag")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
from common.basedir import BASEDIR
|
||||
|
||||
|
||||
class TextWindow:
|
||||
def __init__(self, text):
|
||||
try:
|
||||
self.text_proc = subprocess.Popen(["./text", text],
|
||||
stdin=subprocess.PIPE,
|
||||
cwd=os.path.join(BASEDIR, "selfdrive", "ui"),
|
||||
close_fds=True)
|
||||
except OSError:
|
||||
self.text_proc = None
|
||||
|
||||
def get_status(self):
|
||||
if self.text_proc is not None:
|
||||
self.text_proc.poll()
|
||||
return self.text_proc.returncode
|
||||
return None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
if self.text_proc is not None:
|
||||
self.text_proc.terminate()
|
||||
self.text_proc = None
|
||||
|
||||
def wait_for_exit(self):
|
||||
if self.text_proc is not None:
|
||||
while True:
|
||||
if self.get_status() == 1:
|
||||
return
|
||||
time.sleep(0.1)
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
text = """Traceback (most recent call last):
|
||||
File "./controlsd.py", line 608, in <module>
|
||||
main()
|
||||
File "./controlsd.py", line 604, in main
|
||||
controlsd_thread(sm, pm, logcan)
|
||||
File "./controlsd.py", line 455, in controlsd_thread
|
||||
1/0
|
||||
ZeroDivisionError: division by zero"""
|
||||
print(text)
|
||||
|
||||
with TextWindow(text) as s:
|
||||
for _ in range(100):
|
||||
if s.get_status() == 1:
|
||||
print("Got exit button")
|
||||
break
|
||||
time.sleep(0.1)
|
||||
print("gone")
|
||||
@@ -25,4 +25,3 @@ class Timeout:
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
signal.alarm(0)
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
|
||||
Reference Frames
|
||||
------
|
||||
Many reference frames are used throughout. This
|
||||
folder contains all helper functions needed to
|
||||
transform between them. Generally this is done
|
||||
by generating a rotation matrix and multiplying.
|
||||
|
||||
|
||||
| Name | [x, y, z] | Units | Notes |
|
||||
| :-------------: |:-------------:| :-----:| :----: |
|
||||
| Geodetic | [Latitude, Longitude, Altitude] | geodetic coordinates | Sometimes used as [lon, lat, alt], avoid this frame. |
|
||||
| ECEF | [x, y, z] | meters | We use **ITRF14 (IGS14)**, NOT NAD83. <br> This is the global Mesh3D frame. |
|
||||
| NED | [North, East, Down] | meters | Relative to earth's surface, useful for vizualizing. |
|
||||
| Device | [Forward, Right, Down] | meters | This is the Mesh3D local frame. <br> Relative to camera, **not imu.** <br> |
|
||||
| Road | [Forward, Left, Up] | meters | On the road plane aligned to the vehicle. <br>  |
|
||||
| View | [Right, Down, Forward] | meters | Like device frame, but according to camera conventions. |
|
||||
| Camera | [u, v, focal] | pixels | Like view frame, but 2d on the camera image.|
|
||||
| Normalized Camera | [u / focal, v / focal, 1] | / | |
|
||||
| Model | [u, v, focal] | pixels | The sampled rectangle of the full camera frame the model uses. |
|
||||
| Normalized Model | [u / focal, v / focal, 1] | / | |
|
||||
|
||||
|
||||
|
||||
|
||||
Orientation Conventations
|
||||
------
|
||||
Quaternions, rotation matrices and euler angles are three
|
||||
equivalent representations of orientation and all three are
|
||||
used throughout the code base.
|
||||
|
||||
For euler angles the preferred convention is [roll, pitch, yaw]
|
||||
which corresponds to rotations around the [x, y, z] axes. All
|
||||
euler angles should always be in radians or radians/s unless
|
||||
for plotting or display purposes. For quaternions the hamilton
|
||||
notations is preferred which is [q<sub>w</sub>, q<sub>x</sub>, q<sub>y</sub>, q<sub>z</sub>]. All quaternions
|
||||
should always be normalized with a strictly positive q<sub>w</sub>. **These
|
||||
quaternions are a unique representation of orientation whereas euler angles
|
||||
or rotation matrices are not.**
|
||||
|
||||
To rotate from one frame into another with euler angles the
|
||||
convention is to rotate around roll, then pitch and then yaw,
|
||||
while rotating around the rotated axes, not the original axes.
|
||||
|
||||
|
||||
Calibration
|
||||
------
|
||||
EONs are not all mounted in the exact same way. To compensate for the effects of this the vision model takes in an image that is "calibrated". This means the image is aligned so the direction of travel of the car when it is going straight and the road is flat is always in the location on the image. This calibration is defined by a pitch and yaw angle that describe the direction of travel vector in device frame.
|
||||
|
||||
Example
|
||||
------
|
||||
To transform global Mesh3D positions and orientations (positions_ecef, quats_ecef) into the local frame described by the
|
||||
first position and orientation from Mesh3D one would do:
|
||||
```
|
||||
ecef_from_local = rot_from_quat(quats_ecef[0])
|
||||
local_from_ecef = ecef_from_local.T
|
||||
positions_local = np.einsum('ij,kj->ki', local_from_ecef, postions_ecef - positions_ecef[0])
|
||||
rotations_global = rot_from_quat(quats_ecef)
|
||||
rotations_local = np.einsum('ij,kjl->kil', local_from_ecef, rotations_global)
|
||||
eulers_local = euler_from_rot(rotations_local)
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
Import('envCython')
|
||||
|
||||
envCython.Program('transformations.so', 'transformations.pyx')
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user