diff --git a/artifacts/iqpilot_hephaestusd_private/manifest.json b/artifacts/iqpilot_hephaestusd_private/manifest.json index 8893405..3249777 100644 --- a/artifacts/iqpilot_hephaestusd_private/manifest.json +++ b/artifacts/iqpilot_hephaestusd_private/manifest.json @@ -16,29 +16,44 @@ }, "python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "8f9c7215f23878cf3ca3a07f013850f7e889a9e10deb4c1ec8bdabae488723e6", + "sha256": "dfc76d7b791879501396d4de4891718d0052d23b1e6cb217b0be01f3e3e6d857", "size": 135536 }, "python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "c0d031c33712c71abcad3f9e10aa8e987ee801e20407a98344295e008657aab9", + "sha256": "d1a7fd4e222b08c1e574ed1188943acddecc8d3f5ac02a1983bf90bde5e00a2c", "size": 67664 }, "python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "e049af92c953da53db2728f23484d8d691c9834d4130e3b6caa19c3ae69ce86d", + "sha256": "5393274eb99f50917fef30f994b1ad5fab66860bd535b7d270a86d144d4e25b7", "size": 204624 }, "python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "a73a6a7ba5ac0a87d114db02e895921f0c452e9a8c6bd622116d94c347216ef1", + "sha256": "9c08c3f6e3dc3b73f1a28d50d5aa76fa0894e9cd83bf02b7e9cc83bda5e84b4e", "size": 69752 }, "python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "692bde7a464bcef05c8b684224cc8fd6a6c05d226775ed3e81b6a02c748e11c5", + "sha256": "fb417b7fed56353caad6d5a96429952ff0b87ee1bab7d6a8f0856d1ca40e4b4a", "size": 203072 }, + "python/iqpilot_private/konn3kt/flockd/__init__.py": { + "mode": 420, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "size": 0 + }, + "python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": { + "mode": 493, + "sha256": "73164bb5c3eafe0a5409508140c093503ec40c1c58d0a8e79281bac3d22d811c", + "size": 202168 + }, + "python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": { + "mode": 493, + "sha256": "fb6d56fbec20965d988f936c46e7a70f31def71a2fba27a6506567b3c13574cf", + "size": 68080 + }, "python/iqpilot_private/konn3kt/hephaestus/__init__.py": { "mode": 420, "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", @@ -46,67 +61,67 @@ }, "python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip": { "mode": 420, - "sha256": "832067fea89470fc6224abc8b49c2624e0278e0027ea4e0bb93f939e03938230", + "sha256": "52026a3b96c4e7eee5cb33d70a323896ab0de02557196980653afb99498af84d", "size": 2100238 }, "python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "92b5f941d9198bdd12915996216014d4bbc9b13c1b2435c20b9a2811661b23e3", + "sha256": "aa3530a7f7a44ba1de0d8c1b5de3d26405bf0ee0b9599190bfbbea55d2ed2071", "size": 268064 }, "python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "4a71f1c7f88fdbd51707812370e733d30b22c0c46a2f22ce32d1757debc14cff", + "sha256": "5a6bf50d4f8e3ec43603c2b3d9743dd650b37c6dd2075ec19266d0f1368b8cff", "size": 340584 }, "python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "25d6899e8ebb8c0c5d813d522033d115502d9b622882c0e59f276406702d9e57", + "sha256": "ff36bfb143294790a6ab0e2fe3d14852f3b04374185ef19a614231ca56a40999", "size": 68048 }, "python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "abee9f335c9ddabaa85f341cb33aaf5eebc433b2397b71c921667329728808bc", + "sha256": "ea54bc8d8a2ba2d803ce782898a6987457d248945da81cb9ab08bc32f6763fe1", "size": 335728 }, "python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "b5900c92a037f7ac88199b515f91692c0f86c45981ca3c929b6cbeb6ad6002a0", + "sha256": "f7706a6cabf49660c48609ca4a85c840c1e22e5e4b17d250114e25b56a7b9c34", "size": 271680 }, "python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "d8ebf16a39d29f9932fd1277bd0d552b60a748b0bad6d5c6314cfe5e80859ce2", + "sha256": "5fc96f8ac2c67f3ff660b8c060d14b0e526cba7eaa5ce64fae94f3d0e784d417", "size": 134408 }, "python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "6f50c91b87b5e37033b2f7a9db22f01aba9bf5e31733b6e753a23c211ae71492", + "sha256": "85ecde61b3fcc0d18dc260855a94eb8d30ab2e02adbfea67e209b3fe320608b1", "size": 3514416 }, "python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "63eb11f7251748dab6ad9ba7483ace77b0709f0a3f59492d5a9f2b50bfd9e1ad", + "sha256": "c7b1b7a441f9c4ad395efb3de26d972379d9927f9c7ea9c121d1d06b8170139b", "size": 136448 }, "python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "88d2df85126983f2036badc3fac85ee25d79119c95bbe983030c6ea756da111b", + "sha256": "a7fddf4e77d804f255ae4821fe64eada65ee6f2cc41cfb5c995acd497a0caca4", "size": 68136 }, "python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "f0aa1d1e50f73a27fff17a40c16affafbfdd56239da484ac04315e55a7018e0e", + "sha256": "2ff9ebdb8887bb71a971a4d06fd362c51f5a4446595a073a45edc8bb630a642a", "size": 67840 }, "python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "5e4f675dbc70a3cd21156727f430fec31ecee99f28af3873fea89fb185dba39d", + "sha256": "789481c2f41a6a38492f4f6e62d90c18c59276ff7a9b60fd8a5063b1754efe82", "size": 135648 }, "python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "6f9d47e0d6c4156335fcfd6562022dbfad2dd40c860fac20288be43823195443", + "sha256": "280bee6ecfcde50d1856835a319aaa3354ecfdc4c9d552243493ae7070f9f169", "size": 269568 }, "python/iqpilot_private/konn3kt/uploaderd/__init__.py": { @@ -116,11 +131,19 @@ }, "python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "995b9bcfa8544066a4b71565e834bedc4c1c3dd6b54e14765ef2365d32ccfe05", + "sha256": "b1df9786798beaf80ae110b20b79c2e88b7bbdf38c576fa249fc896e360f9ad1", "size": 204184 }, "runtime": { "entries": { + "iqpilot_private.konn3kt.backups.backup_orchestrator": { + "path": "python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so", + "type": "python-module" + }, + "iqpilot_private.konn3kt.flockd.flockd": { + "path": "python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so", + "type": "python-module" + }, "iqpilot_private.konn3kt.hephaestus.ble_transportd": { "path": "python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so", "type": "python-module" @@ -148,23 +171,25 @@ } }, "signatures": { - "python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": "UehTRkDLBq77ERDm+ZSjxDU3+5EpJUOIzs5zk7Fz8Aru6yQX9ytRexns4dGse8BlIveGuHpWq+L+GvjG0Hf4Bw==", - "python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": "uddHEDnsMjWbMeWotUzZES/3hyLuPmZM1ENCkcTxMYXzM0g0YfbyOJlzq2FuSL4Zg2Hl+PyM/nmnEYbi88NIBg==", - "python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": "2gPFBBZeMfvlnGtQkP6JMI/8c6uanTylvVtZ+1cifaK/ixsGDyfw051gv28uorxjDvRHbgGab/q2n3QYZj8EBA==", - "python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": "KwOoTQlcNJA//GwfMeS8e1yw9agzaJDa/oUXXhiEFyrdEgZurmLbV0slgxUR+hC/LLrrzqqsw1BjQcYuqVXdCw==", - "python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": "BbNS1vTfoizdCnZE3j/2kbY84uXDcGk0WgQ+nmBtySNBBJgT/5HspuY/AOxHukgVyjh7vm7BCnBuJO6/Gm30AQ==", - "python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": "HU6uqhzgAx6mA9ScihdhYvmknQ9kZbI2p4a/ViB4BLo1OTZnfhFecQQ3rnvXNLGOyfbacqooz+s+JRuSOqEmDw==", - "python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": "Nw/QTfb9gk1QgTWKvacfS4BlKZ3DuWJKf5ll/H4Hb8YEaXmYz1xU0klwcybjHPs4twA5qsYUnjmzL110llD+Cw==", - "python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": "WE552SVLgy0vmwvrJv54kW4bDyjB9bKUhMnGa+OI/L7WZT/ReWm3H2NbT0TzWZEGbzFHKr4I01Zmi+MHJGVrAQ==", - "python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": "N9u9KO1e4Xc9su7MndJ6MfyDMY1eV89xbIy6Qo5F5eg0xqfl8QGyYcUU6btIOFdrpRdpWxXTukff9FbU0c+RBg==", - "python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": "t9HmTDU8a0a3rhm9rxzbUaYalAmjASZRbgCku8rg6AxQJohJo3eMVmH7dy7A6vtFvhYvvD9Ywi9kKeUaPTvPCQ==", - "python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": "Oq2wyp4UTAQg/Q16pgCJ9+C/4zi4ljCOHJRcnYXhUk997Tq/5LuBHBstfxrq9ZLubF2ntLLw5UhePtehjLliDg==", - "python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": "n2X6vqTFDy3dGiTiI9kWfAYySl0srw30k5aDqN/YWTzZomQabwZ1ETmUC6TIG3Wag074XNEJgrZhiguN1wgeAg==", - "python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": "ami2SR9/GUXkFRDB5hN/JrJO4rz2piQNtEYJFAsvssLmcqzVPHehRIy61gctjKr5kBLOfUGJjpNU3ceswOUWBg==", - "python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": "wCeURfEwJlXuy7FYiJ3ZWUJZUp8ehKehKqWH/nNyo+GlSlC8YhAGNvQxwZ9FH0agOsSQVHMExnL4ofLvJOytAA==", - "python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": "+G2me3BkbbjwwtVfjGz/ePsXQwNde7Y9bLELSRk8tDxLdGHWkFCU9i8ba8vue7uqfMTnRq5opCDFdIXvY7CwDg==", - "python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": "lh63nHd0YApA9e9+C8SnRd+DILlprzpzt2PR8S6x8VSMQAzAySS9xuJ8Jw76JMpIItzW030n5/wazi4i/gK+Aw==", - "python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": "3fYQP73XdXlWREMsM+P7D9EUSAwrLuih17DqmbDfQDa0S0TKbXcWJgRMbeOHRUDMZ04sUDrli82OfAkz6vlyBQ==", - "python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": "UtM9t4Db3LX6RfiMkADMI7Skre5FtPvjPB2xNuJofd4fVtrLo7r4YYcSOmq7jo4+UviYp4N3CPiGGoOc6WHzAA==" + "python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so": "/YL6W76Q7ejmJvQraTclgzsGxYGpYbSBF3qql0E8Z40MZBQcm8wy+9mrMm1en5W9xGebuaT5xN2+Rji5MUCrDQ==", + "python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so": "0/MsPs9cfZy51uqtyCEK8/U8paw+eQXgqVnglRdUM3Xx6WaPTku3DXfx7MquCTXl+ia9XlM1xdaH/ax6zxhdAw==", + "python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so": "1i1wEWih2NTsQ9jgK/cpb8V/B0y5prTk/OHG1wu4qoaMx4Qm1gVm6KAWU7rkvaQHMqC5eZG5lgL7O6tGflgOBA==", + "python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so": "Q0TBxMRrSRv99t5NIpcYgwRcj26IycOSUSX+Yh4f4S82hY3dXwME3XSqWFLaL2wNF+uG7dMZr0pZkZnHBHmaDg==", + "python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so": "wFMRv5A2/dCAjlBC/x5uN876dq/kMrgMg5F7nmOKjDlrC5lZ014MbZXU0BKzI49CSch9uU3TLbvUR1p9gWPcBA==", + "python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so": "de2W5b0SVEEAhjf9r1Ht9tg6W4RWldp7Tc/IzUQdphK4XBOzqouXIYCItN5xgEbc1tFTmGgKu2pfMXAoXkgYDw==", + "python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so": "yEkA8O096lTrh5YTvZgmuGpyuZ0vTnZDvLnLGR7dhBfypiNPsF4dxp2bcgDpz14GRKPHKa3v8O2v/G/06lk4CQ==", + "python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so": "fpzjKBczfi9su9/BzPINkMJ8+Qx2KljBmYg/lhpaCRZDQX9kIT2qgcgbu5CqahfEQAjWDH2YKDwRhAGzkx1LBw==", + "python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so": "mUUGiu3/nIkcp9McjoX+qOTzBwuJlFzxxZdbW3uU2C4ppAlQ+bYMz9p33qnmkUiJUGfjcBwFnJebL8AXo/kXBQ==", + "python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so": "Nge3RnzUjW2gPUjEyBRxh+D6frwx9dJ/N9W0e0vzFWLLvbo+cT5zjrNAT7buA7XAKGfHSy14ssOxL8Jz+uZBBw==", + "python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so": "KXjqWoG0lsVWqK8uRhOOChREx/ThGtcdKoFxaDD2WpGtEoFBTz2fDsNP8lgyLcrH+9ETT6ggnXqHMf2SnEabBw==", + "python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so": "+0ZLw/tXrJtcempSJZFTKz9vlzs7q13YrnzSkuSpHzaGUm4Z2l4H3Syy0rPj3iJ8OLbkMAE9LegxScVq9mb1Bg==", + "python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so": "Q44rxaPzw1QoSLoHyoaCMtW9N45YeYUNhtgPETz6Z1zLJ5o9NPec6yP06673sX9xdOnRXr0maeLfI2ji7iFiBg==", + "python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so": "MUg6v8qvb7Rhhk5LPoU7krD0CKb39e8wsoh5ER+VvzWYJZ2fnra7Od14xSGfhFZoHIItPwtOlASUbqlbPKReCg==", + "python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so": "ERRMJ3hrOhetsuiNyWrsXH6hwtKzw5CAjg8kB/025JkDyA4OR3yLGMnqXqryygc4r4onUUF0Q55SzTbxxHWzDA==", + "python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so": "+gyiBvn9Uz56mpHovmDtUsNxvlnPYLH8/v5J+f0o8nr/E26f1xvSk2yQbYwYrCeZZ8AOxgY6GmdGRnkly4WCCg==", + "python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so": "Y/cinSdkb/kWX44T/w4woXX/Ex9+Q1KPtUC318dtygZeTiHDjCM+0IUjfS/BXwbdKETQPEpyIoKLsJaP2Gk1Cw==", + "python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so": "N+JrGIiTUzESxzH9fdgub5y01psxzlmEw75Y1AuxQqFx7mG4Z+Tx20UeeyQtAw27UuOctiKpGPHq9trtijJwBA==", + "python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so": "1MxRuPAWgjtsNORklvPZBml0OOVqIu8vMluY0QlK/pqE3S8EoC28/Gn39Bhb6hMjk8yTAUaTnTn4HlwkENpvBA==", + "python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so": "Uxa3OMIwXvFEYz1LP+UDWB1sVhi4z8yC9bP+Xypzw26LNRAMkJZta2r+drXGPEe1TFg4zgVWrb9Q1+Y3pA9HAQ==" } } diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so index 16b05ce..fd5714b 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/archive_codec.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so index b2d2795..6ca54cd 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_keys.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so index a1bd81e..d3289e5 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/backup_orchestrator.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so index 10c4e03..e5e41c0 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/cbc_vault.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so index bcec6ab..9d49c2e 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/backups/imahelper.cpython-312-aarch64-linux-gnu.so differ diff --git a/iqdbc_repo/iqdbc/iqpilot/car/chrysler/__init__.py b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/chrysler/__init__.py rename to artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/__init__.py diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so new file mode 100755 index 0000000..fa473b5 Binary files /dev/null and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/flockd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so new file mode 100755 index 0000000..2c9d5c6 Binary files /dev/null and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/flockd/signatures.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip index 0aa10c3..1ec16bf 100644 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/_vendor/localapi_runtime.zip differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so index 56b5782..38cd773 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_auth.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so index 01ed395..36dad6a 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_gatt.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so index 562b1d4..4d3b251 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_rpc_dispatch.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so index 7887598..a835701 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/ble_transportd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so index b0033e4..93cc0c0 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/bt_gamepad.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so index 38ee1f1..1b74130 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/cloud_routes.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so index f535d73..448a7ba 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/hephaestusd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so index edde5ac..d7538b5 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/kwp2000.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so index 0ace478..9f88ebd 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/manage_hephaestusd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so index 1dcb7f7..1abab6f 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/motd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so index 1052a8f..70c0105 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/tp20.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so index 2238d6a..8736d63 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/hephaestus/vw_pq_flasher.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so index 2d81282..06a0c1d 100755 Binary files a/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_hephaestusd_private/python/iqpilot_private/konn3kt/uploaderd/iquploaderd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_iqvd_private/manifest.json b/artifacts/iqpilot_iqvd_private/manifest.json index 398fb90..0a4cacf 100644 --- a/artifacts/iqpilot_iqvd_private/manifest.json +++ b/artifacts/iqpilot_iqvd_private/manifest.json @@ -6,7 +6,7 @@ }, "python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "3731e0eaf0f13f83fa5997108de084a98812869eb16b35aeda5671225a9ae468", + "sha256": "d36af86bf1fe721522e729b6a48e3a962ce033dcfe89f9e00e3d6208b1801591", "size": 67616 }, "python/iqpilot_private/iqvd/__init__.py": { @@ -16,12 +16,12 @@ }, "python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "67f55a32787720c3adb423330fe4dba529e99be227af14ad56df2088206523ad", - "size": 201520 + "sha256": "f81036080eb7bc0c91bae10b11f52064041311a5fc67e7dc0fba32f585318e41", + "size": 201584 }, "python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "6ca0d723546fa9a670fee6bed6b96b009856628ba9727e0ae5f9b6cf90cb862f", + "sha256": "976be8806f006a175a14b0c63e9a1c2c2b845ee2dab77a41eeb6759c7feab641", "size": 204568 }, "runtime": { @@ -33,8 +33,8 @@ } }, "signatures": { - "python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so": "VwC9JwdPjTasc0t562kGkyPRBuV5Dfe+wlSmctOyBMmMZEjB6J6aBz9M/o/zmkFbylVNKWbBrnS2WMcbWiseDg==", - "python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so": "Gq4Qs2SgFk66OEq1pFVZKkhsyG+ougZTRcHd+gTqca8P5QV932gDv1zaIYz538VUzksHhZleJFi76I4y4BGVCQ==", - "python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so": "9Xh6hjmUFKQTU4LTn+WSKe/BM3lmh/gleSwdhEYXavmN3KwF0ypYdjg5DDpduGjKG/VqQauz42kqdcYKXd1hAg==" + "python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so": "bMboPEsPzZjh0Y1Zk3Brvc6fyWFF5CxuzN9KQr2RK0Hn+l8XiD6l/ym2Y10nFxvj8+uLikIZ0xSAovJlJcRUAA==", + "python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so": "seyE6m33mqlKZ4H7b1JL6pxxz+r/+dKP1PzcfXxhdhHvsl8Ka8Noi6+ln5z+QSvBgt5ulxQeBJMBP5qdu6lOCw==", + "python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so": "RvzkJLNxPA+u7yEnH5jd2jecT02PJCQkIDj/LiWvBCE4lFmFFzOf1Q+ZCGGjrFQzhLQZlm75SvW80mlqJ9BJCw==" } } diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so index 865e932..6c32f31 100755 Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/__init__.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so index f8505cb..475aca1 100755 Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/iqvd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so index 6c8995d..42efbc0 100755 Binary files a/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_iqvd_private/python/iqpilot_private/iqvd/yolov8_net.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/manifest.json b/artifacts/iqpilot_navd_private/manifest.json index 2cc44f7..e64b1c5 100644 --- a/artifacts/iqpilot_navd_private/manifest.json +++ b/artifacts/iqpilot_navd_private/manifest.json @@ -6,7 +6,7 @@ }, "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "8b6114f67eb7a83d47e90113a0e75801ffd55d6c732d1db9a6af620f3cfba1d8", + "sha256": "449495db52a85f8d0b0e2bb3128ee480598f8bdb5a44ec13153877fe8b372a91", "size": 67616 }, "python/iqpilot_private/navd/__init__.py": { @@ -16,72 +16,72 @@ }, "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "4ebabfe3b85a380441cb22730ea3779c3c477e7ec40a88d0ba4d8130a2827877", + "sha256": "ae2b25f52f7c7b02f3c977bbf46f5e9e938bfeda78bdb60f2f897eb04d698e43", "size": 136944 }, "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "7506530560907e8a39e54c05c988c5be3b86bbdaa5279059ada9d847d1e2889c", + "sha256": "f6ad0590f2a0bdbed56e7126203df2f6cdad86074f76ab9dded38b9e1600c8af", "size": 135936 }, "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "905391fcf31991b315abe6126797bc870066352596cd2e9574b289a9c0f386be", + "sha256": "b4ed48cada1dfb3383ff8ee106a7d93967bf65115d4c528b60dc3d73112a2f0e", "size": 135608 }, "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "33f91bfad8894347729d4fd072e8a81af766240e5ec8921de0f92230e3d5faf1", + "sha256": "e68aca6829cb00c5eda5330e1dfedd6269381ea5831abaca1055adf6df1f53ff", "size": 69728 }, "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "4572bb1d704de4b9919d4e81e7c28d64197dadbdd8d84f4f1a203ad924d000f6", + "sha256": "ebd30c5c34fbbe82d7d24343a2e791919e526997d192fd1441df749b15526704", "size": 68088 }, "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "5087cd98a1bf042e0f3d19021af43a0d12ae0ce3f502158a0f4d3a0ace2bbabe", + "sha256": "6e69c5fcac3c302e401b3ed3735b42f3de4af4e0d188d15a7add7aa02920f4c5", "size": 135440 }, "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "f26cb01eaad63b14c686932496a62a3ec5aef1ebb4f36fb46680c124c77e0e16", - "size": 664448 + "sha256": "55c88f8c5996dd7094c0feaf454fe0f1aec97276aed8926d7e8f6d1597de5a83", + "size": 664480 }, "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "3e4a79640333390203e50fb226b00b72aa97a78637544e0f616ed54547c40489", + "sha256": "b971e1093f5713a633636d6ee74891206a2d5abfb492406c7ae5df7a66bdc0d9", "size": 135560 }, "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "cc7040441e8ce97be6ba7bdb371761e7f14c3caf99ab15bf343635a69f4e0fb1", + "sha256": "882ccc23c3de2e4e34c685a7abb00075f6c5be634eaf7e81cab86461c624449d", "size": 136368 }, "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "2b9357dd886231d18e4a9b180b91ea2747f5fa23d91d2d349e032286ea330db2", + "sha256": "df0bf89d492dba3b6c3792dde1bcb30a60e692f62519be7c62a517ef514ec276", "size": 136072 }, "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "3a85759d30234271a83a9473d6d18af74d672c892f686b5d082e9b6df1b70159", + "sha256": "0311a1fbde1510b31055606db21a7c5f74e6872f9f1aabd6fb4a0a501395eaef", "size": 600528 }, "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "2423bca9e7637997d1b6a86365cbc9e9a7d47c89f96aa21be4de32a898ad795c", + "sha256": "1f08c7c462b4cb08a8de9140d968ef5dc74b31afffe16c9f8fad4e0271bec6cc", "size": 135752 }, "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "12b333b4c8904483cae4bae1346016b24b9bee0866d332b2b61dc9fbc30a14b7", + "sha256": "aea71be79c99884a0aeaaf8e5c0f70e37bb4f0c0123025cd07df830189774ba9", "size": 69832 }, "python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "3954330e8004abad9dd31f3a02b57306dc7e83e73ee0ba4072b1b4ecc7f88dfc", + "sha256": "5c7dd6ef7dafb9a35662ee3d22c08b17b02807df736bf146af88cb260f5bbf09", "size": 201384 }, "runtime": { @@ -105,20 +105,20 @@ } }, "signatures": { - "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": "j3fNuxY5XzC219Okj5DV4kId8oUAKcwTcZhwLpp8S/kWXpFv4gHo43Van3A+OkWPFa50ZvKqWH7WhoQc8IgPBw==", - "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": "+Pj9wj6MC4cZ8YxZvvT4XlrzS/N6gVDf5LGZ6kkWiJhVuolBW4ey5KSjbggp4NSIiZWi9aARMZfGsWabQi+nDA==", - "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": "KYCV1qnZ8+mrne7n0H7x+kTu8x/U9H71SXYZt3URy/yIgdtTTXkSS6IQs1UvusfMHzRdBGcT58ado3UcLpsNAg==", - "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": "qC8oELP8nBJSvaDsCMo7/JqoiqCZBJa/LeUE0b36DdkT7kzHSLQ+MuGP5Vc8ms8VowBh447LaLq2WWURGB/fAQ==", - "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": "6kFnnmSPQPXe1HuiBL14HrPx5cr2XmOYzTcESKHvt7DAL7M5tMKy0BWufo34F/LH4MYXQu1/k5TyzHt4wQ7jBw==", - "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": "F0JFe9uN7YtdFvtSwHXPch4l6Cz7yWTOtKPHOdBxqk9CkHh0vfO2d4EAeGTpvHINFS5Kf2KcF99qyEhWfr31Ag==", - "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": "7sQ0zXNASjEVBknO94P5wnMifC+8cVAKaBBbQjfbbaV1lsg18mm/rtTOGlFX7NJD23kJFxyLJmJ+wKjRAZXkBg==", - "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": "VTg/Ojx4I8+JJz+vf9gDwUSBY9MCUvIg9wGnNJiQwIKDJr/VkSRoaiM4nMoazauhLEgYpmlfX7C8OEAWswKhBQ==", - "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": "4i882AjYz/zCgoGlr1LGNcVGfzyybHPMOL0aKva8VNsHBEtMbnnetecRBurtAzlU6O8+p89gY1GNDaJfyi7OAQ==", - "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": "hO9yDTiAaidM3yQCfh4J8vP54R2z42ejhkqU8E6l+wWUXhRrfS4Z6qLyxeQqQGJSPSAMfsWl/yODWogqoH+MDQ==", - "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": "xPCGbuG+pQUqCjBx3NijbInbyo/rRteGLCbhVun2odrw1lGMx4JwzSyNUUsEU7xFPEZ+oZwPUaZcVOHRPnknAw==", - "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": "ZDojmbuEkZUbZ7a56pPvO8hFcYHM5zB9OvGs+r90swvPzU0GRm4SkEiDRLhG6zSyP+vHhJnrsTs+dvyaBkzvCQ==", - "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": "888SP/NpbJtdv2BKbsW7WzuD0pKh2rM50YvS4GlD8brL2xq7a7GPPeKM/b5F+pTzVvS7CbUmELqh3HmeAtOKCw==", - "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": "cmYohrfIfttybS+9x+b9DFzgxyaDGNtgr+olaMWUZ7ytgenB+EvwKAXJHmicFtvBTsKOxYzywKR2zLb71DDFDg==", - "python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": "laxlmUb7INupC/eu6k/eok3bXW4c5H7Lui5MDJXLCoSFCiutla374A6rN8Dh+g3AcSY2NfLBzrnyqQDqQH0KBw==" + "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": "E3+Gd+btNi+4qFqM+nFMfWgCLJhP21ZgIwZrE/CJjqafB1htd/qOhh9QtrulS46HW5geZ/nwW3jlxPbFeNubBw==", + "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": "hHQQCCPcxRXdO9vko9476AYbgbfNwhi9bVYpoOzQQStPC8DbIO0QN/RRzaJVGr2kgJGZlXDmnGl85m7EHgC1BA==", + "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": "Wz5yfWlMeoASj274KqrCWwEJD/wtm5WyxLenceYu7MkXaMlN1QyztvJhq8Y8K8Jfis7R5fQ/aAnlsQgWTIVEBg==", + "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": "6WFWUXmkyy21rh/9xa7s7S7CTiCRnywOahU859T6iP0447NkVxHRYmXAVCH1/vRDPXASHRNPyUk8oSDLIGK1Ag==", + "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": "ZUsjQ/AGPa5pVKAv3IkycDPIVBWh+4jBNor/xrUi7BBPHki8G6T1grv3exeHhr2WF2JiqN727ge3JaqvlU1KDQ==", + "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": "t+VHmWhxf7oKcR5e+tJe6hPKy6xz0bMY84WEhujwcwBlWgIXscfc9aNSP6e4N7GITW7cOn+RlDRTTfwQsA6PDA==", + "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": "qOrpSi2I+aFLKN4WjBljfzbk2U1rvOgDuyV+p7IvshugeUOoqTKiYYSG9xwyi2/eB/syyfiVFoqAg+ETFXXPDA==", + "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": "PcFJWRYrb52pJCbTdsAPm3IC0OR9zt/7P0uc1GlQr4CVGvzfXukbnt/7sSeL8T29w7drf9MfGkpRe4BjbWwtDw==", + "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": "gp1noPJ35MRVcQL3t4wKbsEH0L0+n9Yf8Zty+VCY7uy9EsK8211kW2YIw7Lcyw879VLLbVntHIWz/BMankSeAw==", + "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": "N6yH0R8V/Cw1J3hYPfFeZXByolRwtACwNe2wHMzPEQ69UTWnSNFRXWjOLCEvtwB8Z+iglAqFx8uztZ6wXZKUCQ==", + "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": "oQQU70Yp8wzkqB9Ktm2KDEHNVZfB3otqIoG2mkqQNh6QdtVKXraIv6cAbt2Zjob5914C06El2z+NPZQE5NjODQ==", + "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": "ayPdxCUGA/o+AavLMd9mGDY6zYnZk54gAEVaMQXFYlztjXiREC+IEmV1iETOYcsEFAxr8qtUFfcwZ3p7rXljCw==", + "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": "Rh0wUjYjRabedjFSnsE9BAjILjA1+RHMdfuCqTye1AlhFlXVUQMRo6Ier6Z71kyiOYw+CXCOrHUF9fP69cP4DQ==", + "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": "13bqig90oceup9ovo8weu9UpdQN/l/pq7REFm8dwwgqquuh8jJFkzNz7dvIhDlaxIGAQwzWJe4+QzIQfhg0jAA==", + "python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": "Vm1Inx4XY0BOmLuNizOCRNAl4tPDcvJ/4A+TwVSr1cZuMg7pg1jeFg613e6cFM6t08hYvPsysXc2+gD69zkcBA==" } } diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so index a123f65..d69fb38 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so index fa748f0..5484413 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so index 0ad349d..d89198b 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so index bd4f8fb..471549c 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so index 9d5c8f7..aa22559 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so index dc84dd8..ed72552 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so index 57c9b15..eebfd4e 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so index 9dd9504..a71abd7 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so index a43f871..e768be8 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so index f832f63..e375d95 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so index f376f1f..f50b415 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so index 42d05da..358a74a 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so index 46f26ee..ac3768f 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so index 06db4bf..c873141 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so index bd8e589..0c2597f 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so differ diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 4aed6b7..4b65a94 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -242,8 +242,8 @@ struct IQPlan @0xda401323ae805f2b { } struct E2eAlerts { - greenLightAlert @0 :Bool; - leadDepartAlert @1 :Bool; + pathOpen @0 :Bool; + leadPullaway @1 :Bool; } } @@ -734,6 +734,7 @@ struct IQVehicleTracks @0xb877ef4b20a4ae22 { frameHeight @2 :UInt16; processingMs @3 :Float32; tracks @4 :List(Track); + wide @5 :Bool; struct Track { # box corners normalized [0,1] in the road-camera frame diff --git a/cereal/log.capnp b/cereal/log.capnp index 590c92a..39e9f50 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -70,12 +70,12 @@ struct OnroadEvent @0xc4fa6047f024e718 { longitudinalManeuver @30; steerTempUnavailableSilent @31; resumeRequired @32; - preDriverDistracted @33; - promptDriverDistracted @34; - driverDistracted @35; - preDriverUnresponsive @36; - promptDriverUnresponsive @37; - driverUnresponsive @38; + driverDistracted1 @33; + driverDistracted2 @34; + driverDistracted3 @35; + driverUnresponsive1 @36; + driverUnresponsive2 @37; + driverUnresponsive3 @38; belowSteerSpeed @39; lowBattery @40; accFaulted @41; @@ -2183,6 +2183,7 @@ struct DriverStateV2 { rightBlinkProb @8 :Float32; sunglassesProb @9 :Float32; phoneProb @13 :Float32; + sleepProb @14 :Float32; notReadyProbDEPRECATED @12 :List(Float32); occludedProbDEPRECATED @10 :Float32; readyProbDEPRECATED @11 :List(Float32); @@ -2224,7 +2225,7 @@ struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 { stdDEPRECATED @2 :Float32; } -struct DriverMonitoringState @0xb83cda094a1da284 { +struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 { events @18 :List(OnroadEvent); faceDetected @1 :Bool; isDistracted @2 :Bool; @@ -2250,6 +2251,81 @@ struct DriverMonitoringState @0xb83cda094a1da284 { eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED); } +struct DriverMonitoringState { + lockout @0 :Bool; + lockoutCount @15 :Int8; + lockoutMinutesRemaining @11 :Int8; + alert3Count @12 :Int8; + noResponseCount @13 :Int8; + noResponseForceDecel @14 :Bool; + + alwaysOn @3 :Bool; + alwaysOnLockout @4 :Bool; + + alertLevel @5 :AlertLevel; + activePolicy @6 :MonitoringPolicy; + isRHD @7 :Bool; + rhdCalibration @8 :CalibrationState; + + visionPolicyState @9 :VisionPolicyState; + wheeltouchPolicyState @10 :WheeltouchPolicyState; + + enum AlertLevel { + none @0; + one @1; + two @2; + three @3; + } + + enum MonitoringPolicy { + wheeltouch @0; + vision @1; + } + + struct VisionPolicyState { + awarenessPercent @0 :Int8; + awarenessStep @1 :Float32; + isDistracted @2 :Bool; + distractedTypes @3 :DistractedTypes; + + faceDetected @4 :Bool; + pose @5 :Pose; + wheeltouchFallbackPercent @6 :Int8; + uncertainOffroadAlertPercent @7 :Int8; + + struct DistractedTypes { + pose @0: Bool; + eye @1: Bool; + phone @2: Bool; + } + + struct Pose { + pitch @0 :Float32; + yaw @1 :Float32; + pitchCalib @2 :CalibrationState; + yawCalib @3 :CalibrationState; + calibrated @4 :Bool; + uncertainty @5 :Float32; + } + } + + struct WheeltouchPolicyState { + awarenessPercent @0 :Int8; + awarenessStep @1 :Float32; + driverInteracting @2 :Bool; + } + + struct CalibrationState { + calibratedPercent @0 :Int8; + offset @1 :Float32; + } + + deprecated :group { + alertCountLockoutPercent @1 :Int8; + alertTimeLockoutPercent @2 :Int8; + } +} + struct Boot { wallTimeNanos @0 :UInt64; pstore @4 :Map(Text, Data); @@ -2570,7 +2646,7 @@ struct Event { thumbnail @66: Thumbnail; onroadEvents @134: List(OnroadEvent); carParams @69: Car.CarParams; - driverMonitoringState @71: DriverMonitoringState; + driverMonitoringState @165: DriverMonitoringState; livePose @129 :LivePose; modelV2 @75 :ModelDataV2; drivingModelData @128 :DrivingModelData; @@ -2693,6 +2769,7 @@ struct Event { wifiScanDEPRECATED @29 :List(Legacy.WifiScan); uiNavigationEventDEPRECATED @50 :Legacy.UiNavigationEvent; liveMapDataDEPRECATED @62 :LiveMapDataDEPRECATED; + driverMonitoringStateDEPRECATED @71 :DriverMonitoringStateDEPRECATED; gpsPlannerPointsDEPRECATED @40 :Legacy.GPSPlannerPoints; gpsPlannerPlanDEPRECATED @41 :Legacy.GPSPlannerPlan; applanixRawDEPRECATED @42 :Data; diff --git a/common/params_keys.h b/common/params_keys.h index dd6aed1..de2194c 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -41,6 +41,7 @@ inline static std::unordered_map keys = { {"DoShutdown", {CLEAR_ON_MANAGER_START, BOOL}}, {"DoUninstall", {CLEAR_ON_MANAGER_START, BOOL}}, {"DriverTooDistracted", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, BOOL}}, + {"DriverLockoutCount", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, INT, "0"}}, {"AlphaLongitudinalEnabled", {PERSISTENT, BOOL}}, {"ExperimentalMode", {PERSISTENT, BOOL}}, {"ExperimentalModeConfirmed", {PERSISTENT, BOOL}}, @@ -170,12 +171,12 @@ inline static std::unordered_map keys = { {"DeviceBootMode", {PERSISTENT, INT, "0"}}, {"IQDevUIInfo", {PERSISTENT, INT, "0"}}, {"EnableEsimProvisioning", {PERSISTENT, BOOL, "1"}}, - {"GreenLightAlert", {PERSISTENT, BOOL, "0"}}, + {"EndToEndAlert", {PERSISTENT, BOOL, "0"}}, {"InteractivityTimeout", {PERSISTENT, INT, "0"}}, {"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}}, {"IsReleaseIqBranch", {CLEAR_ON_MANAGER_START, BOOL}}, {"LastGPSPositionIQLoc", {PERSISTENT, STRING}}, - {"LeadDepartAlert", {PERSISTENT, BOOL, "0"}}, + {"EndToEndLeadAlert", {PERSISTENT, BOOL, "0"}}, {"LongIncrementsEnabled", {PERSISTENT, BOOL, "0"}}, {"LongIncrementTapStep", {PERSISTENT, INT, "1"}}, {"LongIncrementHoldStep", {PERSISTENT, INT, "5"}}, diff --git a/iqdbc_repo/conftest.py b/iqdbc_repo/conftest.py index da290ac..cf76ee3 100644 --- a/iqdbc_repo/conftest.py +++ b/iqdbc_repo/conftest.py @@ -42,7 +42,9 @@ if not _os.path.exists("/usr/libexec/iqpilot/runtime_integrity.json"): "update_mlb_carstate_alc_state": _noop, "update_pq_carstate_alc_state": _noop, }) - _stub_module("iqpilot_private.konn3kt.iqlvbs.iqlvbs_commander", {}) + _stub_module("iqpilot_private.konn3kt.iqlvbs.iqlvbs_commander", { + "update_turn_signals": _noop, + }) _stub_module("iqpilot_private.konn3kt.hephaestus.vw_pq_flasher", {}) try: diff --git a/iqdbc_repo/iqdbc/car/car_helpers.py b/iqdbc_repo/iqdbc/car/car_helpers.py index 3d3fe98..e5d2084 100644 --- a/iqdbc_repo/iqdbc/car/car_helpers.py +++ b/iqdbc_repo/iqdbc/car/car_helpers.py @@ -11,7 +11,7 @@ from iqdbc.car.mock.values import CAR as MOCK from iqdbc.car.values import BRANDS from iqdbc.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN -from iqdbc.iqpilot.car.interfaces import setup_interfaces as iqpilot_interfaces +from iqdbc.lvbs.car.interfaces import apply_iq_car_config as iqpilot_interfaces FRAME_FINGERPRINT = 100 # 1s diff --git a/iqdbc_repo/iqdbc/car/chrysler/carcontroller.py b/iqdbc_repo/iqdbc/car/chrysler/carcontroller.py index 1fcd239..eda8201 100644 --- a/iqdbc_repo/iqdbc/car/chrysler/carcontroller.py +++ b/iqdbc_repo/iqdbc/car/chrysler/carcontroller.py @@ -5,16 +5,16 @@ from iqdbc.car.chrysler import chryslercan from iqdbc.car.chrysler.values import RAM_CARS, CarControllerParams, ChryslerFlags, RAM_DT from iqdbc.car.interfaces import CarControllerBase -from iqdbc.iqpilot.car.chrysler.carcontroller_ext import CarControllerExt -from iqdbc.iqpilot.car.chrysler.aol import AolCarController -from iqdbc.iqpilot.car.chrysler.values_ext import ChryslerFlagsIQ +from iqdbc.lvbs.car.chrysler.iq_carcontroller import IQCarController +from iqdbc.lvbs.car.chrysler.aol import AolCarController +from iqdbc.lvbs.car.chrysler.iq_values import ChryslerFlagsIQ -class CarController(CarControllerBase, AolCarController, CarControllerExt): +class CarController(CarControllerBase, AolCarController, IQCarController): def __init__(self, dbc_names, CP, CP_IQ): CarControllerBase.__init__(self, dbc_names, CP, CP_IQ) AolCarController.__init__(self) - CarControllerExt.__init__(self, CP, CP_IQ) + IQCarController.__init__(self, CP, CP_IQ) self.apply_torque_last = 0 self.hud_count = 0 @@ -58,7 +58,7 @@ class CarController(CarControllerBase, AolCarController, CarControllerExt): # TODO: can we make this more sane? why is it different for all the cars? lkas_control_bit = self.lkas_control_bit_prev if self.CP_IQ.flags & ChryslerFlagsIQ.NO_MIN_STEERING_SPEED or self.CP.carFingerprint in RAM_DT: - lkas_control_bit = CarControllerExt.get_lkas_control_bit(self, CS, CC, lkas_control_bit) + lkas_control_bit = IQCarController.get_lkas_control_bit(self, CS, CC, lkas_control_bit) elif CS.out.vEgo > self.CP.minSteerSpeed: lkas_control_bit = True elif self.CP.flags & ChryslerFlags.HIGHER_MIN_STEERING_SPEED: diff --git a/iqdbc_repo/iqdbc/car/chrysler/carstate.py b/iqdbc_repo/iqdbc/car/chrysler/carstate.py index a0bb499..fc0cf47 100644 --- a/iqdbc_repo/iqdbc/car/chrysler/carstate.py +++ b/iqdbc_repo/iqdbc/car/chrysler/carstate.py @@ -4,17 +4,17 @@ from iqdbc.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS from iqdbc.car.common.conversions import Conversions as CV from iqdbc.car.interfaces import CarStateBase -from iqdbc.iqpilot.car.chrysler.aol import AolCarState -from iqdbc.iqpilot.car.chrysler.carstate_ext import CarStateExt +from iqdbc.lvbs.car.chrysler.aol import AolCarState +from iqdbc.lvbs.car.chrysler.iq_carstate import IQCarState ButtonType = structs.CarState.ButtonEvent.Type -class CarState(CarStateBase, AolCarState, CarStateExt): +class CarState(CarStateBase, AolCarState, IQCarState): def __init__(self, CP, CP_IQ): CarStateBase.__init__(self, CP, CP_IQ) AolCarState.__init__(self, CP, CP_IQ) - CarStateExt.__init__(self, CP, CP_IQ) + IQCarState.__init__(self, CP, CP_IQ) self.CP = CP can_define = CANDefine(DBC[CP.carFingerprint][Bus.pt]) @@ -102,7 +102,7 @@ class CarState(CarStateBase, AolCarState, CarStateExt): self.button_counter = cp.vl["CRUISE_BUTTONS"]["COUNTER"] AolCarState.update_aol(self, ret, can_parsers) - CarStateExt.update(self, ret, ret_iq, can_parsers) + IQCarState.update(self, ret, ret_iq, can_parsers) ret.buttonEvents = [ *create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}), diff --git a/iqdbc_repo/iqdbc/car/chrysler/fingerprints.py b/iqdbc_repo/iqdbc/car/chrysler/fingerprints.py index 838eeed..a9c4fa4 100644 --- a/iqdbc_repo/iqdbc/car/chrysler/fingerprints.py +++ b/iqdbc_repo/iqdbc/car/chrysler/fingerprints.py @@ -1,8 +1,8 @@ """ AUTO-FORMATTED USING iqdbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE.""" from iqdbc.car.structs import CarParams from iqdbc.car.chrysler.values import CAR -from iqdbc.iqpilot.car.fingerprints_ext import merge_fw_versions -from iqdbc.iqpilot.car.chrysler.fingerprints_ext import FW_VERSIONS_EXT +from iqdbc.lvbs.car.iq_fingerprints import extend_fw_versions +from iqdbc.lvbs.car.chrysler.iq_fingerprints import FW_VERSIONS_EXT Ecu = CarParams.Ecu @@ -791,4 +791,4 @@ FW_VERSIONS = { -FW_VERSIONS = merge_fw_versions(FW_VERSIONS, FW_VERSIONS_EXT) +FW_VERSIONS = extend_fw_versions(FW_VERSIONS, FW_VERSIONS_EXT) diff --git a/iqdbc_repo/iqdbc/car/chrysler/interface.py b/iqdbc_repo/iqdbc/car/chrysler/interface.py index 828120d..7b880ba 100755 --- a/iqdbc_repo/iqdbc/car/chrysler/interface.py +++ b/iqdbc_repo/iqdbc/car/chrysler/interface.py @@ -5,7 +5,7 @@ from iqdbc.car.chrysler.carstate import CarState from iqdbc.car.chrysler.radar_interface import RadarInterface from iqdbc.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags, ChryslerSafetyFlags from iqdbc.car.interfaces import CarInterfaceBase -from iqdbc.iqpilot.car.chrysler.values_ext import ChryslerFlagsIQ +from iqdbc.lvbs.car.chrysler.iq_values import ChryslerFlagsIQ class CarInterface(CarInterfaceBase): diff --git a/iqdbc_repo/iqdbc/car/gm/carstate.py b/iqdbc_repo/iqdbc/car/gm/carstate.py index bd190d6..b7c18c3 100644 --- a/iqdbc_repo/iqdbc/car/gm/carstate.py +++ b/iqdbc_repo/iqdbc/car/gm/carstate.py @@ -5,8 +5,8 @@ from iqdbc.car.common.conversions import Conversions as CV from iqdbc.car.interfaces import CarStateBase from iqdbc.car.gm.values import DBC, AccState, CruiseButtons, STEER_THRESHOLD, SDGM_CAR, ALT_ACCS -from iqdbc.iqpilot.car.gm.carstate_ext import CarStateExt -from iqdbc.iqpilot.car.gm.values_ext import GMFlagsIQ +from iqdbc.lvbs.car.gm.iq_carstate import IQCarState +from iqdbc.lvbs.car.gm.iq_values import GMFlagsIQ ButtonType = structs.CarState.ButtonEvent.Type TransmissionType = structs.CarParams.TransmissionType @@ -18,10 +18,10 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D CruiseButtons.MAIN: ButtonType.mainCruise, CruiseButtons.CANCEL: ButtonType.cancel} -class CarState(CarStateBase, CarStateExt): +class CarState(CarStateBase, IQCarState): def __init__(self, CP, CP_IQ): CarStateBase.__init__(self, CP, CP_IQ) - CarStateExt.__init__(self, CP, CP_IQ) + IQCarState.__init__(self, CP, CP_IQ) can_define = CANDefine(DBC[CP.carFingerprint][Bus.pt]) self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"] self.cluster_speed_hyst_gap = CV.KPH_TO_MS / 2. @@ -163,7 +163,7 @@ class CarState(CarStateBase, CarStateExt): if ret.vEgo < self.CP.minSteerSpeed: ret.lowSpeedAlert = True - CarStateExt.update(self, ret, can_parsers) + IQCarState.update(self, ret, can_parsers) return ret, ret_iq diff --git a/iqdbc_repo/iqdbc/car/gm/fingerprints.py b/iqdbc_repo/iqdbc/car/gm/fingerprints.py index 734415b..25063d7 100644 --- a/iqdbc_repo/iqdbc/car/gm/fingerprints.py +++ b/iqdbc_repo/iqdbc/car/gm/fingerprints.py @@ -1,8 +1,8 @@ # ruff: noqa: E501 """ AUTO-FORMATTED USING iqdbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE.""" from iqdbc.car.gm.values import CAR -from iqdbc.iqpilot.car.fingerprints_ext import merge_fingerprints -from iqdbc.iqpilot.car.gm.fingerprints_ext import FINGERPRINTS_EXT +from iqdbc.lvbs.car.iq_fingerprints import extend_fingerprints +from iqdbc.lvbs.car.gm.iq_fingerprints import FINGERPRINTS_EXT # Trailblazer also matches as a SILVERADO, TODO: split with fw versions # FIXME: There are Equinox users with different message lengths, specifically 304 and 320 @@ -81,4 +81,4 @@ FW_VERSIONS: dict[str, dict[tuple, list[bytes]]] = { } -FINGERPRINTS = merge_fingerprints(FINGERPRINTS, FINGERPRINTS_EXT) +FINGERPRINTS = extend_fingerprints(FINGERPRINTS, FINGERPRINTS_EXT) diff --git a/iqdbc_repo/iqdbc/car/gm/interface.py b/iqdbc_repo/iqdbc/car/gm/interface.py index c377f16..238a992 100755 --- a/iqdbc_repo/iqdbc/car/gm/interface.py +++ b/iqdbc_repo/iqdbc/car/gm/interface.py @@ -10,8 +10,8 @@ from iqdbc.car.gm.radar_interface import RadarInterface, RADAR_HEADER_MSG, CAMER from iqdbc.car.gm.values import CAR, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, SDGM_CAR, ALT_ACCS, CanBus, GMSafetyFlags from iqdbc.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, LateralAccelFromTorqueCallbackType -from iqdbc.iqpilot.car.gm.interface_ext import CarInterfaceExt -from iqdbc.iqpilot.car.gm.values_ext import GMFlagsIQ, GMSafetyFlagsIQ +from iqdbc.lvbs.car.gm.iq_interface import IQCarInterface +from iqdbc.lvbs.car.gm.iq_values import GMFlagsIQ, GMSafetyFlagsIQ TransmissionType = structs.CarParams.TransmissionType NetworkLocation = structs.CarParams.NetworkLocation @@ -30,7 +30,7 @@ NON_LINEAR_TORQUE_PARAMS = { } -class CarInterface(CarInterfaceBase, CarInterfaceExt): +class CarInterface(CarInterfaceBase, IQCarInterface): CarState = CarState CarController = CarController RadarInterface = RadarInterface @@ -40,7 +40,7 @@ class CarInterface(CarInterfaceBase, CarInterfaceExt): def __init__(self, CP, CP_IQ): CarInterfaceBase.__init__(self, CP, CP_IQ) - CarInterfaceExt.__init__(self, CP, CarInterfaceBase) + IQCarInterface.__init__(self, CP, CarInterfaceBase) @staticmethod def get_pid_accel_limits(CP, CP_IQ, current_speed, cruise_speed): diff --git a/iqdbc_repo/iqdbc/car/gm/values.py b/iqdbc_repo/iqdbc/car/gm/values.py index e1244dc..62c0611 100644 --- a/iqdbc_repo/iqdbc/car/gm/values.py +++ b/iqdbc_repo/iqdbc/car/gm/values.py @@ -4,7 +4,7 @@ from enum import Enum, IntFlag from iqdbc.car import Bus, PlatformConfig, DbcDict, Platforms, CarSpecs from iqdbc.car.structs import CarParams from iqdbc.car.docs_definitions import CarDocs, CarFootnote, CarHarness, CarParts, Column, SupportType -from iqdbc.iqpilot.car.gm.values_ext import GMFlagsIQ +from iqdbc.lvbs.car.gm.iq_values import GMFlagsIQ from iqdbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = CarParams.Ecu diff --git a/iqdbc_repo/iqdbc/car/honda/carcontroller.py b/iqdbc_repo/iqdbc/car/honda/carcontroller.py index 7325b3a..3b6ce37 100644 --- a/iqdbc_repo/iqdbc/car/honda/carcontroller.py +++ b/iqdbc_repo/iqdbc/car/honda/carcontroller.py @@ -8,8 +8,8 @@ from iqdbc.car.honda.values import CAR, CruiseButtons, HONDA_BOSCH, HONDA_BOSCH_ HONDA_BOSCH_TJA_CONTROL, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams from iqdbc.car.interfaces import CarControllerBase -from iqdbc.iqpilot.car.honda.aol import AolCarController -from iqdbc.iqpilot.car.honda.gas_interceptor import GasInterceptorCarController +from iqdbc.lvbs.car.honda.aol import AolCarController +from iqdbc.lvbs.car.honda.gas_interceptor import GasInterceptorCarController VisualAlert = structs.CarControl.HUDControl.VisualAlert LongCtrlState = structs.CarControl.Actuators.LongControlState diff --git a/iqdbc_repo/iqdbc/car/honda/carstate.py b/iqdbc_repo/iqdbc/car/honda/carstate.py index 790a170..f827771 100644 --- a/iqdbc_repo/iqdbc/car/honda/carstate.py +++ b/iqdbc_repo/iqdbc/car/honda/carstate.py @@ -10,7 +10,7 @@ from iqdbc.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA HondaFlags, CruiseButtons, CruiseSettings, GearShifter, CarControllerParams from iqdbc.car.interfaces import CarStateBase -from iqdbc.iqpilot.car.honda.carstate_ext import CarStateExt +from iqdbc.lvbs.car.honda.iq_carstate import IQCarState TransmissionType = structs.CarParams.TransmissionType ButtonType = structs.CarState.ButtonEvent.Type @@ -20,10 +20,10 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D SETTINGS_BUTTONS_DICT = {CruiseSettings.DISTANCE: ButtonType.gapAdjustCruise, CruiseSettings.LKAS: ButtonType.lkas} -class CarState(CarStateBase, CarStateExt): +class CarState(CarStateBase, IQCarState): def __init__(self, CP, CP_IQ): CarStateBase.__init__(self, CP, CP_IQ) - CarStateExt.__init__(self, CP, CP_IQ) + IQCarState.__init__(self, CP, CP_IQ) can_define = CANDefine(DBC[CP.carFingerprint][Bus.pt]) if CP.transmissionType != TransmissionType.manual: @@ -246,7 +246,7 @@ class CarState(CarStateBase, CarStateExt): *create_button_events(self.cruise_setting, prev_cruise_setting, SETTINGS_BUTTONS_DICT), ] - CarStateExt.update(self, ret, can_parsers) + IQCarState.update(self, ret, can_parsers) return ret, ret_iq diff --git a/iqdbc_repo/iqdbc/car/honda/fingerprints.py b/iqdbc_repo/iqdbc/car/honda/fingerprints.py index de74ca8..ddf4331 100644 --- a/iqdbc_repo/iqdbc/car/honda/fingerprints.py +++ b/iqdbc_repo/iqdbc/car/honda/fingerprints.py @@ -2,8 +2,8 @@ from iqdbc.car.structs import CarParams from iqdbc.car.honda.values import CAR -from iqdbc.iqpilot.car.fingerprints_ext import merge_fw_versions -from iqdbc.iqpilot.car.honda.fingerprints_ext import FW_VERSIONS_EXT +from iqdbc.lvbs.car.iq_fingerprints import extend_fw_versions +from iqdbc.lvbs.car.honda.iq_fingerprints import FW_VERSIONS_EXT Ecu = CarParams.Ecu @@ -1098,4 +1098,4 @@ FW_VERSIONS = { }, } -FW_VERSIONS = merge_fw_versions(FW_VERSIONS, FW_VERSIONS_EXT) +FW_VERSIONS = extend_fw_versions(FW_VERSIONS, FW_VERSIONS_EXT) diff --git a/iqdbc_repo/iqdbc/car/honda/hondacan.py b/iqdbc_repo/iqdbc/car/honda/hondacan.py index 8b8c4d2..818c86e 100644 --- a/iqdbc_repo/iqdbc/car/honda/hondacan.py +++ b/iqdbc_repo/iqdbc/car/honda/hondacan.py @@ -2,7 +2,7 @@ from iqdbc.car import CanBusBase from iqdbc.car.common.conversions import Conversions as CV from iqdbc.car.honda.values import (HondaFlags, HONDA_BOSCH, HONDA_BOSCH_ALT_RADAR, HONDA_BOSCH_RADARLESS, HONDA_BOSCH_CANFD, CarControllerParams) -from iqdbc.iqpilot.car.honda.values_ext import HondaFlagsIQ +from iqdbc.lvbs.car.honda.iq_values import HondaFlagsIQ # CAN bus layout with relay # 0 = ACC-CAN - radar side diff --git a/iqdbc_repo/iqdbc/car/honda/interface.py b/iqdbc_repo/iqdbc/car/honda/interface.py index bb8a835..133741f 100755 --- a/iqdbc_repo/iqdbc/car/honda/interface.py +++ b/iqdbc_repo/iqdbc/car/honda/interface.py @@ -11,7 +11,7 @@ from iqdbc.car.honda.carstate import CarState from iqdbc.car.honda.radar_interface import RadarInterface from iqdbc.car.interfaces import CarInterfaceBase -from iqdbc.iqpilot.car.honda.values_ext import HondaFlagsIQ, HondaSafetyFlagsIQ +from iqdbc.lvbs.car.honda.iq_values import HondaFlagsIQ, HondaSafetyFlagsIQ TransmissionType = structs.CarParams.TransmissionType diff --git a/iqdbc_repo/iqdbc/car/interfaces.py b/iqdbc_repo/iqdbc/car/interfaces.py index aeeef70..7f68e40 100644 --- a/iqdbc_repo/iqdbc/car/interfaces.py +++ b/iqdbc_repo/iqdbc/car/interfaces.py @@ -18,7 +18,7 @@ from iqdbc.car.values import PLATFORMS from iqdbc.can import CANParser from iqdbc.car.carlog import carlog -from iqdbc.iqpilot.car.interfaces import CarInterfaceBaseIQ +from iqdbc.lvbs.car.interfaces import CarInterfaceBaseIQ GearShifter = structs.CarState.GearShifter ButtonType = structs.CarState.ButtonEvent.Type diff --git a/iqdbc_repo/iqdbc/car/nissan/carstate.py b/iqdbc_repo/iqdbc/car/nissan/carstate.py index ce8c252..d5394b8 100644 --- a/iqdbc_repo/iqdbc/car/nissan/carstate.py +++ b/iqdbc_repo/iqdbc/car/nissan/carstate.py @@ -6,17 +6,17 @@ from iqdbc.car.common.conversions import Conversions as CV from iqdbc.car.interfaces import CarStateBase from iqdbc.car.nissan.values import CAR, DBC, CarControllerParams -from iqdbc.iqpilot.car.nissan.carstate_ext import CarStateExt +from iqdbc.lvbs.car.nissan.iq_carstate import IQCarState ButtonType = structs.CarState.ButtonEvent.Type TORQUE_SAMPLES = 12 -class CarState(CarStateBase, CarStateExt): +class CarState(CarStateBase, IQCarState): def __init__(self, CP, CP_IQ): CarStateBase.__init__(self, CP, CP_IQ) - CarStateExt.__init__(self, CP, CP_IQ) + IQCarState.__init__(self, CP, CP_IQ) can_define = CANDefine(DBC[CP.carFingerprint][Bus.pt]) self.lkas_hud_msg = {} @@ -130,7 +130,7 @@ class CarState(CarStateBase, CarStateExt): self.lkas_hud_msg = copy.copy(cp_adas.vl["PROPILOT_HUD"]) self.lkas_hud_info_msg = copy.copy(cp_adas.vl["PROPILOT_HUD_INFO_MSG"]) - CarStateExt.update(self, ret, ret_iq, can_parsers) + IQCarState.update(self, ret, ret_iq, can_parsers) ret.buttonEvents = [ *create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}), diff --git a/iqdbc_repo/iqdbc/car/nissan/interface.py b/iqdbc_repo/iqdbc/car/nissan/interface.py index d06218e..a7b680a 100644 --- a/iqdbc_repo/iqdbc/car/nissan/interface.py +++ b/iqdbc_repo/iqdbc/car/nissan/interface.py @@ -3,7 +3,7 @@ from iqdbc.car.interfaces import CarInterfaceBase from iqdbc.car.nissan.carcontroller import CarController from iqdbc.car.nissan.carstate import CarState from iqdbc.car.nissan.values import CAR, NissanSafetyFlags -from iqdbc.iqpilot.car.nissan.values import NissanSafetyFlagsIQ +from iqdbc.lvbs.car.nissan.values import NissanSafetyFlagsIQ class CarInterface(CarInterfaceBase): CarState = CarState diff --git a/iqdbc_repo/iqdbc/car/rivian/carcontroller.py b/iqdbc_repo/iqdbc/car/rivian/carcontroller.py index 81f3954..5b9638b 100644 --- a/iqdbc_repo/iqdbc/car/rivian/carcontroller.py +++ b/iqdbc_repo/iqdbc/car/rivian/carcontroller.py @@ -6,7 +6,7 @@ from iqdbc.car.interfaces import CarControllerBase from iqdbc.car.rivian.riviancan import create_lka_steering, create_longitudinal, create_wheel_touch, create_adas_status from iqdbc.car.rivian.values import CarControllerParams -from iqdbc.iqpilot.car.rivian.aol import AolCarController +from iqdbc.lvbs.car.rivian.aol import AolCarController class CarController(CarControllerBase, AolCarController): diff --git a/iqdbc_repo/iqdbc/car/rivian/carstate.py b/iqdbc_repo/iqdbc/car/rivian/carstate.py index d5c2471..1901913 100644 --- a/iqdbc_repo/iqdbc/car/rivian/carstate.py +++ b/iqdbc_repo/iqdbc/car/rivian/carstate.py @@ -4,15 +4,15 @@ from iqdbc.car import Bus, structs from iqdbc.car.interfaces import CarStateBase from iqdbc.car.rivian.values import DBC, GEAR_MAP from iqdbc.car.common.conversions import Conversions as CV -from iqdbc.iqpilot.car.rivian.carstate_ext import CarStateExt +from iqdbc.lvbs.car.rivian.iq_carstate import IQCarState GearShifter = structs.CarState.GearShifter -class CarState(CarStateBase, CarStateExt): +class CarState(CarStateBase, IQCarState): def __init__(self, CP, CP_IQ): CarStateBase.__init__(self, CP, CP_IQ) - CarStateExt.__init__(self, CP, CP_IQ) + IQCarState.__init__(self, CP, CP_IQ) self.last_speed = 30 self.acm_lka_hba_cmd = None @@ -95,7 +95,7 @@ class CarState(CarStateBase, CarStateExt): self.sccm_wheel_touch = copy.copy(cp.vl["SCCM_WheelTouch"]) self.vdm_adas_status = copy.copy(cp.vl["VDM_AdasSts"]) - CarStateExt.update(self, ret, can_parsers) + IQCarState.update(self, ret, can_parsers) return ret, ret_iq @@ -105,5 +105,5 @@ class CarState(CarStateBase, CarStateExt): Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 0), Bus.adas: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 1), Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 2), - **CarStateExt.get_parser(CP, CP_IQ), + **IQCarState.get_parser(CP, CP_IQ), } diff --git a/iqdbc_repo/iqdbc/car/rivian/interface.py b/iqdbc_repo/iqdbc/car/rivian/interface.py index dbc6ef1..a211a98 100644 --- a/iqdbc_repo/iqdbc/car/rivian/interface.py +++ b/iqdbc_repo/iqdbc/car/rivian/interface.py @@ -4,7 +4,7 @@ from iqdbc.car.rivian.carcontroller import CarController from iqdbc.car.rivian.carstate import CarState from iqdbc.car.rivian.radar_interface import RadarInterface from iqdbc.car.rivian.values import RivianSafetyFlags -from iqdbc.iqpilot.car.rivian.values import RivianFlagsIQ +from iqdbc.lvbs.car.rivian.values import RivianFlagsIQ class CarInterface(CarInterfaceBase): diff --git a/iqdbc_repo/iqdbc/car/subaru/carcontroller.py b/iqdbc_repo/iqdbc/car/subaru/carcontroller.py index 3a78a17..18ba9e5 100644 --- a/iqdbc_repo/iqdbc/car/subaru/carcontroller.py +++ b/iqdbc_repo/iqdbc/car/subaru/carcontroller.py @@ -6,7 +6,7 @@ from iqdbc.car.interfaces import CarControllerBase from iqdbc.car.subaru import subarucan from iqdbc.car.subaru.values import DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags -from iqdbc.iqpilot.car.subaru.stop_and_go import SnGCarController +from iqdbc.lvbs.car.subaru.stop_and_go import IQStopAndGoController # FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and # involves the total steering angle change rather than rate, but these limits work well for now @@ -14,10 +14,10 @@ MAX_STEER_RATE = 25 # deg/s MAX_STEER_RATE_FRAMES = 7 # tx control frames needed before torque can be cut -class CarController(CarControllerBase, SnGCarController): +class CarController(CarControllerBase, IQStopAndGoController): def __init__(self, dbc_names, CP, CP_IQ): CarControllerBase.__init__(self, dbc_names, CP, CP_IQ) - SnGCarController.__init__(self, CP, CP_IQ) + IQStopAndGoController.__init__(self, CP, CP_IQ) self.apply_torque_last = 0 self.cruise_button_prev = 0 @@ -139,7 +139,7 @@ class CarController(CarControllerBase, SnGCarController): if self.frame % 2 == 0: can_sends.append(subarucan.create_es_static_2(self.packer)) - can_sends.extend(SnGCarController.create_stop_and_go(self, self.packer, CC, CS, self.frame)) + can_sends.extend(IQStopAndGoController.create_stop_and_go(self, self.packer, CC, CS, self.frame)) new_actuators = actuators.as_builder() new_actuators.torque = self.apply_torque_last / self.p.STEER_MAX diff --git a/iqdbc_repo/iqdbc/car/subaru/carstate.py b/iqdbc_repo/iqdbc/car/subaru/carstate.py index a69c05f..5fbfc87 100644 --- a/iqdbc_repo/iqdbc/car/subaru/carstate.py +++ b/iqdbc_repo/iqdbc/car/subaru/carstate.py @@ -6,15 +6,15 @@ from iqdbc.car.interfaces import CarStateBase from iqdbc.car.subaru.values import DBC, CanBus, SubaruFlags from iqdbc.car import CanSignalRateCalculator -from iqdbc.iqpilot.car.subaru.aol import AolCarState -from iqdbc.iqpilot.car.subaru.stop_and_go import SnGCarState +from iqdbc.lvbs.car.subaru.aol import AolCarState +from iqdbc.lvbs.car.subaru.stop_and_go import IQStopAndGoState -class CarState(CarStateBase, AolCarState, SnGCarState): +class CarState(CarStateBase, AolCarState, IQStopAndGoState): def __init__(self, CP, CP_IQ): CarStateBase.__init__(self, CP, CP_IQ) AolCarState.__init__(self, CP, CP_IQ) - SnGCarState.__init__(self, CP, CP_IQ) + IQStopAndGoState.__init__(self, CP, CP_IQ) can_define = CANDefine(DBC[CP.carFingerprint][Bus.pt]) self.shifter_values = can_define.dv["Transmission"]["Gear"] @@ -144,7 +144,7 @@ class CarState(CarStateBase, AolCarState, SnGCarState): self.es_infotainment_msg = copy.copy(cp_cam.vl["ES_Infotainment"]) AolCarState.update_aol(self, ret, can_parsers) - SnGCarState.update(self, ret, can_parsers) + IQStopAndGoState.update(self, ret, can_parsers) return ret, ret_iq diff --git a/iqdbc_repo/iqdbc/car/tesla/carcontroller.py b/iqdbc_repo/iqdbc/car/tesla/carcontroller.py index a2a04fb..216a181 100644 --- a/iqdbc_repo/iqdbc/car/tesla/carcontroller.py +++ b/iqdbc_repo/iqdbc/car/tesla/carcontroller.py @@ -8,8 +8,8 @@ from iqdbc.car.tesla.teslacan import TeslaCAN from iqdbc.car.tesla.values import CarControllerParams from iqdbc.car.vehicle_model import VehicleModel from openpilot.iqpilot.selfdrive.car.enhanced_stock_longitudinal_control import get_set_speed_kph_from_params -from iqdbc.iqpilot.car.tesla.coop_steering import CoopSteeringCarController -from iqdbc.iqpilot.car.tesla.values import TeslaFlagsIQ +from iqdbc.lvbs.car.tesla.coop_steering import CoopSteeringCarController +from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ def get_safety_CP(): diff --git a/iqdbc_repo/iqdbc/car/tesla/carstate.py b/iqdbc_repo/iqdbc/car/tesla/carstate.py index 13835f0..44aa90a 100644 --- a/iqdbc_repo/iqdbc/car/tesla/carstate.py +++ b/iqdbc_repo/iqdbc/car/tesla/carstate.py @@ -6,8 +6,8 @@ from iqdbc.car.interfaces import CarStateBase from iqdbc.car.tesla import TESLA_BLINKERS from iqdbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_THRESHOLD, TeslaFlags -from iqdbc.iqpilot.car.tesla.carstate_ext import CarStateExt -from iqdbc.iqpilot.car.tesla.values import TeslaFlagsIQ +from iqdbc.lvbs.car.tesla.iq_carstate import IQCarState +from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ from openpilot.common.params import Params from openpilot.system.proprietary_runtime._verified_import import import_verified_module @@ -16,10 +16,10 @@ vehicle_state = import_verified_module("iqpilot_alc_private", "iqpilot_private.k ButtonType = structs.CarState.ButtonEvent.Type -class CarState(CarStateBase, CarStateExt): +class CarState(CarStateBase, IQCarState): def __init__(self, CP, CP_IQ): CarStateBase.__init__(self, CP, CP_IQ) - CarStateExt.__init__(self, CP, CP_IQ) + IQCarState.__init__(self, CP, CP_IQ) self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party]) self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] @@ -159,7 +159,7 @@ class CarState(CarStateBase, CarStateExt): if TESLA_BLINKERS and Bus.cam in can_parsers: self.das_body_controls_dat = bytes(can_parsers[Bus.cam].dat.get(0x3E9, b"")) - CarStateExt.update(self, ret, ret_iq, can_parsers) + IQCarState.update(self, ret, ret_iq, can_parsers) if ret.odometer > 0.0: ret.odometer = self._odometer_store.record(ret.odometer) or 0.0 @@ -170,7 +170,7 @@ class CarState(CarStateBase, CarStateExt): parsers = { Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party), Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party), - **CarStateExt.get_parser(CP, CP_IQ), + **IQCarState.get_parser(CP, CP_IQ), } # Stock DAS_bodyControls from the AP bus (bus 2) for the nav blinker. if TESLA_BLINKERS and CP_IQ.flags & TeslaFlagsIQ.HAS_VEHICLE_BUS and Bus.adas in DBC[CP.carFingerprint]: diff --git a/iqdbc_repo/iqdbc/car/tesla/interface.py b/iqdbc_repo/iqdbc/car/tesla/interface.py index 9b0eee0..1f292c5 100644 --- a/iqdbc_repo/iqdbc/car/tesla/interface.py +++ b/iqdbc_repo/iqdbc/car/tesla/interface.py @@ -5,7 +5,7 @@ from iqdbc.car.tesla.carstate import CarState from iqdbc.car.tesla.values import TeslaSafetyFlags, TeslaFlags, CANBUS, CAR, DBC, LEGACY_DAS_STEERING_FW, Ecu from iqdbc.car.tesla.radar_interface import RadarInterface, RADAR_START_ADDR -from iqdbc.iqpilot.car.tesla.values import TeslaFlagsIQ, TeslaSafetyFlagsIQ +from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ, TeslaSafetyFlagsIQ class CarInterface(CarInterfaceBase): diff --git a/iqdbc_repo/iqdbc/car/torque_data/override.toml b/iqdbc_repo/iqdbc/car/torque_data/override.toml index 5fd55a3..e7b2cfe 100644 --- a/iqdbc_repo/iqdbc/car/torque_data/override.toml +++ b/iqdbc_repo/iqdbc/car/torque_data/override.toml @@ -115,3 +115,24 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "HYUNDAI_CASPER_EV"= [2.5, 2.5, 0.1] "HYUNDAI_IONIQ_5_N" = [3.17, 2.71, 0.097] "HYUNDAI_IONIQ_5_PE" = [1.75, 1.75, 0.15] + +# Synced verbatim from comma opendbc torque_data/override.toml — these platforms +# exist upstream and were simply never carried over when iqdbc forked. +ACURA_MDX_4G_MMR = [1.25, 1.25, 0.15] +ACURA_TLX_2G = [1.2, 1.2, 0.15] +ACURA_TLX_2G_MMR = [1.7, 1.7, 0.16] +AUDI_Q5_MK1 = [1.8, 1.8, 0.18] +CUPRA_BORN_MK1 = [nan, 2.5, nan] +FORD_ESCAPE_MK4_5 = [nan, 1.5, nan] +FORD_EXPEDITION_MK4 = [nan, 1.5, nan] +HONDA_ACCORD_11G = [1.35, 1.35, 0.17] +HONDA_CITY_7G = [1.2, 1.2, 0.23] +HONDA_CRV_6G = [1.3, 1.3, 0.2] +HONDA_NBOX_2G = [1.2, 1.2, 0.2] +HONDA_ODYSSEY_5G_MMR = [0.9, 0.9, 0.2] +HONDA_PASSPORT_4G = [1.2, 1.2, 0.16] +HONDA_PILOT_4G = [1.25, 1.25, 0.21] +LEXUS_LS = [1.35, 1.7, 0.17] +PORSCHE_MACAN_MK1 = [2.0, 2.0, 0.2] +PSA_PEUGEOT_208 = [nan, 2.0, nan] +TESLA_MODEL_X = [nan, 2.5, nan] diff --git a/iqdbc_repo/iqdbc/car/torque_data/substitute.toml b/iqdbc_repo/iqdbc/car/torque_data/substitute.toml index 75cc15f..a5f5c30 100644 --- a/iqdbc_repo/iqdbc/car/torque_data/substitute.toml +++ b/iqdbc_repo/iqdbc/car/torque_data/substitute.toml @@ -110,3 +110,24 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "CHEVROLET_TRAILBLAZER_NON_ACC_2ND_GEN" = "CHEVROLET_TRAILBLAZER" "CHEVROLET_MALIBU_NON_ACC_9TH_GEN" = "CHEVROLET_VOLT" "CADILLAC_XT5_NON_ACC_1ST_GEN" = "CADILLAC_XT4" + +# Synced verbatim from comma opendbc torque_data/substitute.toml +"HONDA_ODYSSEY_TWN" = "HONDA_ODYSSEY" +"LEXUS_RC_TSS2" = "LEXUS_NX_TSS2" + +# Platform-sibling substitutes: comma has never shipped torque data for these, +# so rather than invent lateral-accel figures they borrow the tune of the closest +# platform twin. REVIEW: replace with measured values when available. +"SEAT_ALHAMBRA_MK1" = "VOLKSWAGEN_SHARAN_MK2" +"VOLKSWAGEN_PASSAT_NMS_PLUS" = "VOLKSWAGEN_PASSAT_NMS" +"VOLKSWAGEN_ID3_MK1" = "VOLKSWAGEN_ID4_MK1" +"VOLKSWAGEN_ID3_MK2" = "VOLKSWAGEN_ID4_MK2" +"SKODA_ENYAQ_MK1" = "VOLKSWAGEN_ID4_MK1" +"SKODA_ENYAQ_MK2" = "VOLKSWAGEN_ID4_MK2" +"AUDI_Q4_MK1" = "VOLKSWAGEN_ID4_MK1" +"AUDI_Q4_MK2" = "VOLKSWAGEN_ID4_MK2" +"VOLKSWAGEN_GOLF_MK8" = "VOLKSWAGEN_GOLF_MK7" +"SEAT_LEON_MK4" = "VOLKSWAGEN_GOLF_MK7" +"VOLKSWAGEN_PASSAT_MK7" = "VOLKSWAGEN_PASSAT_NMS" +"VOLKSWAGEN_PASSAT_B7" = "VOLKSWAGEN_PASSAT_NMS" +"HONDA_CLARITY" = "HONDA_ACCORD" diff --git a/iqdbc_repo/iqdbc/car/toyota/carcontroller.py b/iqdbc_repo/iqdbc/car/toyota/carcontroller.py index cf0205d..04fb4db 100644 --- a/iqdbc_repo/iqdbc/car/toyota/carcontroller.py +++ b/iqdbc_repo/iqdbc/car/toyota/carcontroller.py @@ -13,8 +13,8 @@ from iqdbc.car.toyota.values import CAR, NO_STOP_TIMER_CAR, TSS2_CAR, \ UNSUPPORTED_DSU_CAR from iqdbc.can import CANPacker -from iqdbc.iqpilot.car.toyota.gas_interceptor import GasInterceptorCarController -from iqdbc.iqpilot.car.toyota.values import ToyotaFlagsIQ +from iqdbc.lvbs.car.toyota.gas_interceptor import GasInterceptorCarController +from iqdbc.lvbs.car.toyota.values import ToyotaFlagsIQ Ecu = structs.CarParams.Ecu LongCtrlState = structs.CarControl.Actuators.LongControlState diff --git a/iqdbc_repo/iqdbc/car/toyota/carstate.py b/iqdbc_repo/iqdbc/car/toyota/carstate.py index fc95f41..8300e29 100644 --- a/iqdbc_repo/iqdbc/car/toyota/carstate.py +++ b/iqdbc_repo/iqdbc/car/toyota/carstate.py @@ -8,8 +8,8 @@ from iqdbc.car.interfaces import CarStateBase from iqdbc.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, \ TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR, \ SECOC_CAR -from iqdbc.iqpilot.car.toyota.carstate_ext import CarStateExt -from iqdbc.iqpilot.car.toyota.values import ToyotaFlagsIQ +from iqdbc.lvbs.car.toyota.iq_carstate import IQCarState +from iqdbc.lvbs.car.toyota.values import ToyotaFlagsIQ ButtonType = structs.CarState.ButtonEvent.Type SteerControlType = structs.CarParams.SteerControlType @@ -26,10 +26,10 @@ TEMP_STEER_FAULTS = (0, 9, 11, 21, 25) PERM_STEER_FAULTS = (3, 17) -class CarState(CarStateBase, CarStateExt): +class CarState(CarStateBase, IQCarState): def __init__(self, CP, CP_IQ): CarStateBase.__init__(self, CP, CP_IQ) - CarStateExt.__init__(self, CP, CP_IQ) + IQCarState.__init__(self, CP, CP_IQ) can_define = CANDefine(DBC[CP.carFingerprint][Bus.pt]) self.eps_torque_scale = EPS_SCALE[CP.carFingerprint] / 100. self.cluster_speed_hyst_gap = CV.KPH_TO_MS / 2. @@ -210,7 +210,7 @@ class CarState(CarStateBase, CarStateExt): ret.buttonEvents = buttonEvents - CarStateExt.update(self, ret, ret_iq, can_parsers) + IQCarState.update(self, ret, ret_iq, can_parsers) return ret, ret_iq diff --git a/iqdbc_repo/iqdbc/car/toyota/fingerprints.py b/iqdbc_repo/iqdbc/car/toyota/fingerprints.py index 3f1348a..582f314 100644 --- a/iqdbc_repo/iqdbc/car/toyota/fingerprints.py +++ b/iqdbc_repo/iqdbc/car/toyota/fingerprints.py @@ -2,8 +2,8 @@ from iqdbc.car.structs import CarParams from iqdbc.car.toyota.values import CAR -from iqdbc.iqpilot.car.fingerprints_ext import merge_fw_versions -from iqdbc.iqpilot.car.toyota.fingerprints_ext import FW_VERSIONS_EXT +from iqdbc.lvbs.car.iq_fingerprints import extend_fw_versions +from iqdbc.lvbs.car.toyota.iq_fingerprints import FW_VERSIONS_EXT Ecu = CarParams.Ecu @@ -1889,4 +1889,4 @@ FW_VERSIONS = { }, } -FW_VERSIONS = merge_fw_versions(FW_VERSIONS, FW_VERSIONS_EXT) +FW_VERSIONS = extend_fw_versions(FW_VERSIONS, FW_VERSIONS_EXT) diff --git a/iqdbc_repo/iqdbc/car/toyota/interface.py b/iqdbc_repo/iqdbc/car/toyota/interface.py index 4f067ad..865de2f 100644 --- a/iqdbc_repo/iqdbc/car/toyota/interface.py +++ b/iqdbc_repo/iqdbc/car/toyota/interface.py @@ -7,7 +7,7 @@ from iqdbc.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerPar ToyotaSafetyFlags, UNSUPPORTED_DSU_CAR from iqdbc.car.disable_ecu import disable_ecu from iqdbc.car.interfaces import CarInterfaceBase -from iqdbc.iqpilot.car.toyota.values import ToyotaFlagsIQ, ToyotaSafetyFlagsIQ +from iqdbc.lvbs.car.toyota.values import ToyotaFlagsIQ, ToyotaSafetyFlagsIQ SteerControlType = structs.CarParams.SteerControlType diff --git a/iqdbc_repo/iqdbc/car/toyota/tests/test_toyota_iq.py b/iqdbc_repo/iqdbc/car/toyota/tests/test_toyota_iq.py index 2c2156a..1004a9d 100644 --- a/iqdbc_repo/iqdbc/car/toyota/tests/test_toyota_iq.py +++ b/iqdbc_repo/iqdbc/car/toyota/tests/test_toyota_iq.py @@ -1,7 +1,7 @@ from iqdbc.car import gen_empty_fingerprint, structs from iqdbc.car.toyota.interface import CarInterface from iqdbc.car.toyota.values import CAR, ToyotaFlags -from iqdbc.iqpilot.car.toyota.values import ToyotaFlagsIQ +from iqdbc.lvbs.car.toyota.values import ToyotaFlagsIQ def test_secoc_toyota_not_dashcam_on_release(): diff --git a/iqdbc_repo/iqdbc/car/volkswagen/carstate.py b/iqdbc_repo/iqdbc/car/volkswagen/carstate.py index 9015eab..fff7957 100644 --- a/iqdbc_repo/iqdbc/car/volkswagen/carstate.py +++ b/iqdbc_repo/iqdbc/car/volkswagen/carstate.py @@ -504,7 +504,7 @@ class CarState(CarStateBase): ret.gasPressed = pt_cp.vl["Motor_3"]["MO3_Pedalwert"] > 0 ret.brake = pt_cp.vl["Bremse_5"]["BR5_Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects ret.brakePressed = bool(pt_cp.vl["Motor_2"]["MO2_BLS"]) - ret.parkingBrake = False # bool(pt_cp.vl["Kombi_1"]["Bremsinfo"]) + ret.parkingBrake = bool(pt_cp.vl["Kombi_1"]["Bremsinfo"]) # Update gear and/or clutch position data. if self.CP.transmissionType == TransmissionType.automatic: @@ -639,8 +639,8 @@ class CarState(CarStateBase): ret.lowSpeedAlert = self.update_low_speed_alert(ret.vEgo) - ret.fuelGauge = 0 # pt_cp.vl["Kombi_1"]["Tankinhalt"] / 55.0 - ret.fuelTankLevelL = 0 # pt_cp.vl["Kombi_1"]["Tankinhalt"] # raw liters for konn3kt + ret.fuelGauge = pt_cp.vl["Kombi_1"]["Tankinhalt"] / 55.0 + ret.fuelTankLevelL = pt_cp.vl["Kombi_1"]["Tankinhalt"] # raw liters for konn3kt if aux_cp is not None: self._update_odometer(ret, aux_cp.vl["Kombi_3"]["Kilometerstand"]) diff --git a/iqdbc_repo/iqdbc/iqpilot/__init__.py b/iqdbc_repo/iqdbc/lvbs/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/__init__.py rename to iqdbc_repo/iqdbc/lvbs/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/aol_base.py b/iqdbc_repo/iqdbc/lvbs/aol_base.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/aol_base.py rename to iqdbc_repo/iqdbc/lvbs/aol_base.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/__init__.py b/iqdbc_repo/iqdbc/lvbs/car/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/__init__.py rename to iqdbc_repo/iqdbc/lvbs/car/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/gm/__init__.py b/iqdbc_repo/iqdbc/lvbs/car/chrysler/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/gm/__init__.py rename to iqdbc_repo/iqdbc/lvbs/car/chrysler/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/chrysler/aol.py b/iqdbc_repo/iqdbc/lvbs/car/chrysler/aol.py similarity index 98% rename from iqdbc_repo/iqdbc/iqpilot/car/chrysler/aol.py rename to iqdbc_repo/iqdbc/lvbs/car/chrysler/aol.py index 6af6f4c..3486417 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/chrysler/aol.py +++ b/iqdbc_repo/iqdbc/lvbs/car/chrysler/aol.py @@ -10,7 +10,7 @@ from collections import namedtuple from iqdbc.car import Bus, structs from iqdbc.car.chrysler.values import RAM_CARS -from iqdbc.iqpilot.aol_base import AolCarStateBase +from iqdbc.lvbs.aol_base import AolCarStateBase from iqdbc.can.parser import CANParser AolDataIQ = namedtuple("AolDataIQ", ["enable_aol", "paused", "lkas_disabled"]) diff --git a/iqdbc_repo/iqdbc/iqpilot/car/chrysler/carcontroller_ext.py b/iqdbc_repo/iqdbc/lvbs/car/chrysler/iq_carcontroller.py similarity index 92% rename from iqdbc_repo/iqdbc/iqpilot/car/chrysler/carcontroller_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/chrysler/iq_carcontroller.py index 7e88389..140fff1 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/chrysler/carcontroller_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/chrysler/iq_carcontroller.py @@ -7,12 +7,12 @@ no-min-steering-speed option is set, and holds the RAM DT engagement window. from iqdbc.car import structs from iqdbc.car.interfaces import CarStateBase from iqdbc.car.chrysler.values import RAM_DT -from iqdbc.iqpilot.car.chrysler.values_ext import ChryslerFlagsIQ +from iqdbc.lvbs.car.chrysler.iq_values import ChryslerFlagsIQ GearShifter = structs.CarState.GearShifter -class CarControllerExt: +class IQCarController: def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams): self.CP = CP self.CP_IQ = CP_IQ diff --git a/iqdbc_repo/iqdbc/iqpilot/car/chrysler/carstate_ext.py b/iqdbc_repo/iqdbc/lvbs/car/chrysler/iq_carstate.py similarity index 93% rename from iqdbc_repo/iqdbc/iqpilot/car/chrysler/carstate_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/chrysler/iq_carstate.py index 1be0839..e92944a 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/chrysler/carstate_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/chrysler/iq_carstate.py @@ -8,10 +8,10 @@ from enum import StrEnum from iqdbc.car import Bus, structs from iqdbc.can.parser import CANParser -from iqdbc.iqpilot.car.chrysler.values_ext import BUTTONS +from iqdbc.lvbs.car.chrysler.iq_values import BUTTONS -class CarStateExt: +class IQCarState: def __init__(self, CP, CP_IQ): self.CP = CP self.CP_IQ = CP_IQ diff --git a/iqdbc_repo/iqdbc/iqpilot/car/chrysler/fingerprints_ext.py b/iqdbc_repo/iqdbc/lvbs/car/chrysler/iq_fingerprints.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/chrysler/fingerprints_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/chrysler/iq_fingerprints.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/chrysler/values_ext.py b/iqdbc_repo/iqdbc/lvbs/car/chrysler/iq_values.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/chrysler/values_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/chrysler/iq_values.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/nissan/__init__.py b/iqdbc_repo/iqdbc/lvbs/car/gm/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/nissan/__init__.py rename to iqdbc_repo/iqdbc/lvbs/car/gm/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/gm/carstate_ext.py b/iqdbc_repo/iqdbc/lvbs/car/gm/iq_carstate.py similarity index 92% rename from iqdbc_repo/iqdbc/iqpilot/car/gm/carstate_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/gm/iq_carstate.py index 2bfb846..96d1344 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/gm/carstate_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/gm/iq_carstate.py @@ -9,10 +9,10 @@ from enum import StrEnum from iqdbc.car import Bus, structs from iqdbc.car.common.conversions import Conversions as CV from iqdbc.can.parser import CANParser -from iqdbc.iqpilot.car.gm.values_ext import GMFlagsIQ +from iqdbc.lvbs.car.gm.iq_values import GMFlagsIQ -class CarStateExt: +class IQCarState: def __init__(self, CP, CP_IQ): self.CP = CP self.CP_IQ = CP_IQ diff --git a/iqdbc_repo/iqdbc/iqpilot/car/gm/fingerprints_ext.py b/iqdbc_repo/iqdbc/lvbs/car/gm/iq_fingerprints.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/gm/fingerprints_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/gm/iq_fingerprints.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/gm/interface_ext.py b/iqdbc_repo/iqdbc/lvbs/car/gm/iq_interface.py similarity index 93% rename from iqdbc_repo/iqdbc/iqpilot/car/gm/interface_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/gm/iq_interface.py index 2491266..1c7b64c 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/gm/interface_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/gm/iq_interface.py @@ -8,7 +8,7 @@ from math import exp from iqdbc.car import structs from iqdbc.car.gm.interface import CAR -from iqdbc.iqpilot.car.interfaces import LatControlInputs, TorqueFromLateralAccelCallbackTypeTorqueSpace +from iqdbc.lvbs.car.interfaces import LatControlInputs, TorqueFromLateralAccelCallbackTypeTorqueSpace _NON_LINEAR_TORQUE_PARAMS = { CAR.CHEVROLET_BOLT_EUV: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178], @@ -17,7 +17,7 @@ _NON_LINEAR_TORQUE_PARAMS = { } -class CarInterfaceExt: +class IQCarInterface: def __init__(self, CP: structs.CarParams, CI_Base): self.CP = CP self.CI_Base = CI_Base diff --git a/iqdbc_repo/iqdbc/iqpilot/car/gm/values_ext.py b/iqdbc_repo/iqdbc/lvbs/car/gm/iq_values.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/gm/values_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/gm/iq_values.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/honda/__init__.py b/iqdbc_repo/iqdbc/lvbs/car/honda/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/honda/__init__.py rename to iqdbc_repo/iqdbc/lvbs/car/honda/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/honda/aol.py b/iqdbc_repo/iqdbc/lvbs/car/honda/aol.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/honda/aol.py rename to iqdbc_repo/iqdbc/lvbs/car/honda/aol.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/honda/gas_interceptor.py b/iqdbc_repo/iqdbc/lvbs/car/honda/gas_interceptor.py similarity index 94% rename from iqdbc_repo/iqdbc/iqpilot/car/honda/gas_interceptor.py rename to iqdbc_repo/iqdbc/lvbs/car/honda/gas_interceptor.py index f4e9fe4..95ed00a 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/honda/gas_interceptor.py +++ b/iqdbc_repo/iqdbc/lvbs/car/honda/gas_interceptor.py @@ -6,7 +6,7 @@ import numpy as np from iqdbc.car import structs from iqdbc.car.can_definitions import CanData -from iqdbc.iqpilot.car import create_gas_interceptor_command +from iqdbc.lvbs.car import create_gas_interceptor_command class GasInterceptorCarController: diff --git a/iqdbc_repo/iqdbc/iqpilot/car/honda/carstate_ext.py b/iqdbc_repo/iqdbc/lvbs/car/honda/iq_carstate.py similarity index 93% rename from iqdbc_repo/iqdbc/iqpilot/car/honda/carstate_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/honda/iq_carstate.py index 679e85d..830b77b 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/honda/carstate_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/honda/iq_carstate.py @@ -5,10 +5,10 @@ from enum import StrEnum from iqdbc.car import Bus, structs from iqdbc.can.parser import CANParser -from iqdbc.iqpilot.car.honda.values_ext import HondaFlagsIQ +from iqdbc.lvbs.car.honda.iq_values import HondaFlagsIQ -class CarStateExt: +class IQCarState: def __init__(self, CP, CP_IQ): self.CP = CP self.CP_IQ = CP_IQ diff --git a/iqdbc_repo/iqdbc/iqpilot/car/honda/fingerprints_ext.py b/iqdbc_repo/iqdbc/lvbs/car/honda/iq_fingerprints.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/honda/fingerprints_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/honda/iq_fingerprints.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/honda/values_ext.py b/iqdbc_repo/iqdbc/lvbs/car/honda/iq_values.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/honda/values_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/honda/iq_values.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/honda/test_honda.py b/iqdbc_repo/iqdbc/lvbs/car/honda/test_honda.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/honda/test_honda.py rename to iqdbc_repo/iqdbc/lvbs/car/honda/test_honda.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/interfaces.py b/iqdbc_repo/iqdbc/lvbs/car/interfaces.py similarity index 94% rename from iqdbc_repo/iqdbc/iqpilot/car/interfaces.py rename to iqdbc_repo/iqdbc/lvbs/car/interfaces.py index c0bf1f6..f000d63 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/interfaces.py +++ b/iqdbc_repo/iqdbc/lvbs/car/interfaces.py @@ -9,9 +9,9 @@ from collections.abc import Callable from iqdbc.car import structs from iqdbc.car.can_definitions import CanRecvCallable, CanSendCallable from iqdbc.car.subaru.values import SubaruFlags -from iqdbc.iqpilot.car.subaru.values_ext import SubaruFlagsIQ, SubaruSafetyFlagsIQ -from iqdbc.iqpilot.car.tesla.values import TeslaFlagsIQ -from iqdbc.iqpilot.car.toyota.values import ToyotaFlagsIQ +from iqdbc.lvbs.car.subaru.iq_values import SubaruFlagsIQ, SubaruSafetyFlagsIQ +from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ +from iqdbc.lvbs.car.toyota.values import ToyotaFlagsIQ class LatControlInputs(NamedTuple): @@ -67,7 +67,7 @@ class NanoFFModel: return pred -def setup_interfaces(CI, CP: structs.CarParams, CP_IQ: structs.IQCarParams, +def apply_iq_car_config(CI, CP: structs.CarParams, CP_IQ: structs.IQCarParams, params_list: list[dict[str, str]] | None = None, can_recv: CanRecvCallable | None = None, can_send: CanSendCallable | None = None) -> None: if params_list is None: diff --git a/iqdbc_repo/iqdbc/iqpilot/car/fingerprints_ext.py b/iqdbc_repo/iqdbc/lvbs/car/iq_fingerprints.py similarity index 88% rename from iqdbc_repo/iqdbc/iqpilot/car/fingerprints_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/iq_fingerprints.py index 1bc43ae..1d43a62 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/fingerprints_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/iq_fingerprints.py @@ -3,7 +3,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed """ -def merge_fw_versions(fw_versions, new_fw_versions): +def extend_fw_versions(fw_versions, new_fw_versions): """ Merge firmware versions by extending lists for matching ECUs, adding all entries regardless of duplicates. @@ -22,7 +22,7 @@ def merge_fw_versions(fw_versions, new_fw_versions): return fw_versions -def merge_fingerprints(fingerprints, new_fingerprints): +def extend_fingerprints(fingerprints, new_fingerprints): """ Merge fingerprints by extending lists for matching keys, adding all entries regardless of duplicates. diff --git a/iqdbc_repo/iqdbc/iqpilot/car/lateral_ext.py b/iqdbc_repo/iqdbc/lvbs/car/iq_lateral.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/lateral_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/iq_lateral.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/rivian/__init__.py b/iqdbc_repo/iqdbc/lvbs/car/nissan/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/rivian/__init__.py rename to iqdbc_repo/iqdbc/lvbs/car/nissan/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/nissan/carstate_ext.py b/iqdbc_repo/iqdbc/lvbs/car/nissan/iq_carstate.py similarity index 93% rename from iqdbc_repo/iqdbc/iqpilot/car/nissan/carstate_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/nissan/iq_carstate.py index 73f0c76..76e8891 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/nissan/carstate_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/nissan/iq_carstate.py @@ -7,10 +7,10 @@ from enum import StrEnum from iqdbc.car import Bus, structs from iqdbc.can.parser import CANParser -from iqdbc.iqpilot.car.nissan.values import BUTTONS +from iqdbc.lvbs.car.nissan.values import BUTTONS -class CarStateExt: +class IQCarState: def __init__(self, CP, CP_IQ): self.CP = CP self.CP_IQ = CP_IQ diff --git a/iqdbc_repo/iqdbc/iqpilot/car/nissan/values.py b/iqdbc_repo/iqdbc/lvbs/car/nissan/values.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/nissan/values.py rename to iqdbc_repo/iqdbc/lvbs/car/nissan/values.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/platform_list.py b/iqdbc_repo/iqdbc/lvbs/car/platform_list.py similarity index 90% rename from iqdbc_repo/iqdbc/iqpilot/car/platform_list.py rename to iqdbc_repo/iqdbc/lvbs/car/platform_list.py index 0c659db..002bd22 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/platform_list.py +++ b/iqdbc_repo/iqdbc/lvbs/car/platform_list.py @@ -7,7 +7,7 @@ from iqdbc.car.values import PLATFORMS def get_car_list() -> dict[str, dict[str, list[str] | str]]: collected_footnote = get_all_footnotes() - sorted_list: dict[str, dict[str, list[str] | str]] = build_sorted_car_list(PLATFORMS, collected_footnote) + sorted_list: dict[str, dict[str, list[str] | str]] = collect_car_docs(PLATFORMS, collected_footnote) return sorted_list @@ -17,7 +17,7 @@ def _natural_sort_key(s): return [int(t) if t.isdigit() else t.lower() for t in re.split(r'(\d+)', normalized) if t] -def build_sorted_car_list(platforms, footnotes) -> dict[str, dict[str, list[str] | str]]: +def collect_car_docs(platforms, footnotes) -> dict[str, dict[str, list[str] | str]]: cars: dict[str, dict[str, list[str] | str]] = {} for model, platform in platforms.items(): car_docs = platform.config.get_all_docs() diff --git a/iqdbc_repo/iqdbc/iqpilot/car/subaru/__init__.py b/iqdbc_repo/iqdbc/lvbs/car/rivian/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/subaru/__init__.py rename to iqdbc_repo/iqdbc/lvbs/car/rivian/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/rivian/aol.py b/iqdbc_repo/iqdbc/lvbs/car/rivian/aol.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/rivian/aol.py rename to iqdbc_repo/iqdbc/lvbs/car/rivian/aol.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/rivian/carstate_ext.py b/iqdbc_repo/iqdbc/lvbs/car/rivian/iq_carstate.py similarity index 98% rename from iqdbc_repo/iqdbc/iqpilot/car/rivian/carstate_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/rivian/iq_carstate.py index f638afc..d7098e2 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/rivian/carstate_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/rivian/iq_carstate.py @@ -12,7 +12,7 @@ from iqdbc.car import Bus, structs from iqdbc.can.parser import CANParser from iqdbc.car.common.conversions import Conversions as CV from iqdbc.car.rivian.values import DBC -from iqdbc.iqpilot.car.rivian.values import RivianFlagsIQ +from iqdbc.lvbs.car.rivian.values import RivianFlagsIQ ButtonType = structs.CarState.ButtonEvent.Type @@ -22,7 +22,7 @@ _LONG_PRESS_FRAMES = 66 _STALK_HOLD_FRAMES = 50 -class CarStateExt: +class IQCarState: def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams): self.CP = CP self.CP_IQ = CP_IQ diff --git a/iqdbc_repo/iqdbc/iqpilot/car/rivian/values.py b/iqdbc_repo/iqdbc/lvbs/car/rivian/values.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/rivian/values.py rename to iqdbc_repo/iqdbc/lvbs/car/rivian/values.py diff --git a/iqpilot/selfdrive/controls/lib/speed_limit/tests/__init__.py b/iqdbc_repo/iqdbc/lvbs/car/subaru/__init__.py similarity index 100% rename from iqpilot/selfdrive/controls/lib/speed_limit/tests/__init__.py rename to iqdbc_repo/iqdbc/lvbs/car/subaru/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/subaru/aol.py b/iqdbc_repo/iqdbc/lvbs/car/subaru/aol.py similarity index 96% rename from iqdbc_repo/iqdbc/iqpilot/car/subaru/aol.py rename to iqdbc_repo/iqdbc/lvbs/car/subaru/aol.py index 9e0938e..6846faf 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/subaru/aol.py +++ b/iqdbc_repo/iqdbc/lvbs/car/subaru/aol.py @@ -8,7 +8,7 @@ from enum import StrEnum from iqdbc.car import Bus, structs from iqdbc.car.subaru.values import SubaruFlags -from iqdbc.iqpilot.aol_base import AolCarStateBase +from iqdbc.lvbs.aol_base import AolCarStateBase from iqdbc.can.parser import CANParser _LKAS = structs.CarState.ButtonEvent.Type.lkas diff --git a/iqdbc_repo/iqdbc/iqpilot/car/subaru/subarucan_ext.py b/iqdbc_repo/iqdbc/lvbs/car/subaru/iq_subarucan.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/subaru/subarucan_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/subaru/iq_subarucan.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/subaru/values_ext.py b/iqdbc_repo/iqdbc/lvbs/car/subaru/iq_values.py similarity index 81% rename from iqdbc_repo/iqdbc/iqpilot/car/subaru/values_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/subaru/iq_values.py index 05552bf..6536b0c 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/subaru/values_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/subaru/iq_values.py @@ -1,7 +1,7 @@ """ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos -IQ.Pilot Subaru extension flags. Selected in setup_interfaces() from the user's +IQ.Pilot Subaru extension flags. Selected in apply_iq_car_config() from the user's stop-and-go params and consumed by the stop-and-go controller and panda safety. """ from enum import IntFlag diff --git a/iqdbc_repo/iqdbc/iqpilot/car/subaru/stop_and_go.py b/iqdbc_repo/iqdbc/lvbs/car/subaru/stop_and_go.py similarity index 88% rename from iqdbc_repo/iqdbc/iqpilot/car/subaru/stop_and_go.py rename to iqdbc_repo/iqdbc/lvbs/car/subaru/stop_and_go.py index 15357c4..5256a85 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/subaru/stop_and_go.py +++ b/iqdbc_repo/iqdbc/lvbs/car/subaru/stop_and_go.py @@ -14,8 +14,8 @@ from iqdbc.car.can_definitions import CanData from iqdbc.car.interfaces import CarStateBase from iqdbc.car.subaru.values import SubaruFlags -from iqdbc.iqpilot.car.subaru import subarucan_ext -from iqdbc.iqpilot.car.subaru.values_ext import SubaruFlagsIQ +from iqdbc.lvbs.car.subaru import iq_subarucan +from iqdbc.lvbs.car.subaru.iq_values import SubaruFlagsIQ from iqdbc.can.parser import CANParser # EPB resume fires only while the lead is pulling away within this gap band (m). @@ -24,7 +24,7 @@ _RESUME_GAP_MAX = 4.5 _EPB_PULSE_FRAMES = 15 -class SnGCarController: +class IQStopAndGoController: def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams): self.CP = CP self.CP_IQ = CP_IQ @@ -73,13 +73,13 @@ class SnGCarController: return [] resume = self._want_resume(CC, CS, frame) - can_sends = [subarucan_ext.create_throttle(packer, self.CP, CS.throttle_msg, resume and not self.manual_parking_brake)] + can_sends = [iq_subarucan.create_throttle(packer, self.CP, CS.throttle_msg, resume and not self.manual_parking_brake)] if frame % 2 == 0: - can_sends.append(subarucan_ext.create_brake_pedal(packer, self.CP, CS.brake_pedal_msg, resume and self.manual_parking_brake)) + can_sends.append(iq_subarucan.create_brake_pedal(packer, self.CP, CS.brake_pedal_msg, resume and self.manual_parking_brake)) return can_sends -class SnGCarState: +class IQStopAndGoState: def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams): self.CP = CP self.CP_IQ = CP_IQ diff --git a/iqdbc_repo/iqdbc/iqpilot/car/tesla/__init__.py b/iqdbc_repo/iqdbc/lvbs/car/tesla/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/tesla/__init__.py rename to iqdbc_repo/iqdbc/lvbs/car/tesla/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/tesla/coop_steering.py b/iqdbc_repo/iqdbc/lvbs/car/tesla/coop_steering.py similarity index 99% rename from iqdbc_repo/iqdbc/iqpilot/car/tesla/coop_steering.py rename to iqdbc_repo/iqdbc/lvbs/car/tesla/coop_steering.py index 966edbc..e67d443 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/tesla/coop_steering.py +++ b/iqdbc_repo/iqdbc/lvbs/car/tesla/coop_steering.py @@ -10,7 +10,7 @@ from iqdbc.car import structs, rate_limit, DT_CTRL from iqdbc.car.vehicle_model import VehicleModel from iqdbc.car.lateral import apply_steer_angle_limits_vm from iqdbc.car.tesla.values import CarControllerParams -from iqdbc.iqpilot.car.tesla.values import TeslaFlagsIQ +from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ DT_LAT_CTRL = DT_CTRL * CarControllerParams.STEER_STEP diff --git a/iqdbc_repo/iqdbc/iqpilot/car/tesla/carstate_ext.py b/iqdbc_repo/iqdbc/lvbs/car/tesla/iq_carstate.py similarity index 97% rename from iqdbc_repo/iqdbc/iqpilot/car/tesla/carstate_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/tesla/iq_carstate.py index 5c3e68f..90be564 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/tesla/carstate_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/tesla/iq_carstate.py @@ -7,12 +7,12 @@ from iqdbc.car import Bus, create_button_events, structs from iqdbc.can.parser import CANParser from iqdbc.car.common.conversions import Conversions as CV from iqdbc.car.tesla.values import DBC, CANBUS -from iqdbc.iqpilot.car.tesla.values import TeslaFlagsIQ +from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ ButtonType = structs.CarState.ButtonEvent.Type -class CarStateExt: +class IQCarState: def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams): self.CP = CP self.CP_IQ = CP_IQ diff --git a/iqdbc_repo/iqdbc/iqpilot/car/tesla/values.py b/iqdbc_repo/iqdbc/lvbs/car/tesla/values.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/tesla/values.py rename to iqdbc_repo/iqdbc/lvbs/car/tesla/values.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/tests/__init__.py b/iqdbc_repo/iqdbc/lvbs/car/tests/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/tests/__init__.py rename to iqdbc_repo/iqdbc/lvbs/car/tests/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/tests/test_car_list.py b/iqdbc_repo/iqdbc/lvbs/car/tests/test_car_list.py similarity index 92% rename from iqdbc_repo/iqdbc/iqpilot/car/tests/test_car_list.py rename to iqdbc_repo/iqdbc/lvbs/car/tests/test_car_list.py index 6417bb6..d016349 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/tests/test_car_list.py +++ b/iqdbc_repo/iqdbc/lvbs/car/tests/test_car_list.py @@ -2,7 +2,7 @@ import json import os from iqdbc.car.common.basedir import BASEDIR -from iqdbc.iqpilot.car.platform_list import get_car_list +from iqdbc.lvbs.car.platform_list import get_car_list CATALOG_JSON = os.path.join(BASEDIR, "..", "..", "iqpilot", "selfdrive", "car", "vehicle_catalog.json") diff --git a/iqdbc_repo/iqdbc/iqpilot/car/toyota/__init__.py b/iqdbc_repo/iqdbc/lvbs/car/toyota/__init__.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/toyota/__init__.py rename to iqdbc_repo/iqdbc/lvbs/car/toyota/__init__.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/toyota/gas_interceptor.py b/iqdbc_repo/iqdbc/lvbs/car/toyota/gas_interceptor.py similarity index 97% rename from iqdbc_repo/iqdbc/iqpilot/car/toyota/gas_interceptor.py rename to iqdbc_repo/iqdbc/lvbs/car/toyota/gas_interceptor.py index 7e9e697..34ab798 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/toyota/gas_interceptor.py +++ b/iqdbc_repo/iqdbc/lvbs/car/toyota/gas_interceptor.py @@ -7,7 +7,7 @@ import numpy as np from iqdbc.car import structs from iqdbc.car.can_definitions import CanData from iqdbc.car.toyota.values import CAR, MIN_ACC_SPEED, PEDAL_TRANSITION -from iqdbc.iqpilot.car import create_gas_interceptor_command +from iqdbc.lvbs.car import create_gas_interceptor_command class GasInterceptorCarController: diff --git a/iqdbc_repo/iqdbc/iqpilot/car/toyota/carstate_ext.py b/iqdbc_repo/iqdbc/lvbs/car/toyota/iq_carstate.py similarity index 98% rename from iqdbc_repo/iqdbc/iqpilot/car/toyota/carstate_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/toyota/iq_carstate.py index 1e7aa85..df00cee 100644 --- a/iqdbc_repo/iqdbc/iqpilot/car/toyota/carstate_ext.py +++ b/iqdbc_repo/iqdbc/lvbs/car/toyota/iq_carstate.py @@ -8,7 +8,7 @@ from iqdbc.car import Bus, structs from iqdbc.car.carlog import carlog from iqdbc.can.parser import CANParser from iqdbc.car.common.conversions import Conversions as CV -from iqdbc.iqpilot.car.toyota.values import ToyotaFlagsIQ +from iqdbc.lvbs.car.toyota.values import ToyotaFlagsIQ TRAFFIC_SIGNAL_MAP = { 1: "kph", @@ -21,7 +21,7 @@ ZSS_DIFF_THRESHOLD = 4 ZSS_MAX_THRESHOLD = 10 -class CarStateExt: +class IQCarState: def __init__(self, CP, CP_IQ): self.CP = CP self.CP_IQ = CP_IQ diff --git a/iqdbc_repo/iqdbc/iqpilot/car/toyota/fingerprints_ext.py b/iqdbc_repo/iqdbc/lvbs/car/toyota/iq_fingerprints.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/toyota/fingerprints_ext.py rename to iqdbc_repo/iqdbc/lvbs/car/toyota/iq_fingerprints.py diff --git a/iqdbc_repo/iqdbc/iqpilot/car/toyota/values.py b/iqdbc_repo/iqdbc/lvbs/car/toyota/values.py similarity index 100% rename from iqdbc_repo/iqdbc/iqpilot/car/toyota/values.py rename to iqdbc_repo/iqdbc/lvbs/car/toyota/values.py diff --git a/iqdbc_repo/iqdbc/safety/iqpilot/aol.h b/iqdbc_repo/iqdbc/safety/aol/aol.h similarity index 99% rename from iqdbc_repo/iqdbc/safety/iqpilot/aol.h rename to iqdbc_repo/iqdbc/safety/aol/aol.h index e6492db..63cd62e 100644 --- a/iqdbc_repo/iqdbc/safety/iqpilot/aol.h +++ b/iqdbc_repo/iqdbc/safety/aol/aol.h @@ -6,7 +6,7 @@ #pragma once -#include "iqdbc/safety/iqpilot/aol_declarations.h" +#include "iqdbc/safety/aol/aol_declarations.h" // =============================== // Global Variables diff --git a/iqdbc_repo/iqdbc/safety/iqpilot/aol_declarations.h b/iqdbc_repo/iqdbc/safety/aol/aol_declarations.h similarity index 100% rename from iqdbc_repo/iqdbc/safety/iqpilot/aol_declarations.h rename to iqdbc_repo/iqdbc/safety/aol/aol_declarations.h diff --git a/iqdbc_repo/iqdbc/safety/lateral.h b/iqdbc_repo/iqdbc/safety/lateral.h index fcc7218..6671584 100644 --- a/iqdbc_repo/iqdbc/safety/lateral.h +++ b/iqdbc_repo/iqdbc/safety/lateral.h @@ -1,4 +1,4 @@ -#include "iqdbc/safety/iqpilot/aol.h" +#include "iqdbc/safety/aol/aol.h" #include "iqdbc/safety/declarations.h" // ISO 11270 diff --git a/iqdbc_repo/iqdbc/safety/tests/test_honda.py b/iqdbc_repo/iqdbc/safety/tests/test_honda.py index 6b73397..a12ea52 100755 --- a/iqdbc_repo/iqdbc/safety/tests/test_honda.py +++ b/iqdbc_repo/iqdbc/safety/tests/test_honda.py @@ -9,7 +9,7 @@ from iqdbc.car.structs import CarParams from iqdbc.safety.tests.common import CANPackerSafety, MAX_WRONG_COUNTERS from iqdbc.safety.tests.gas_interceptor_common import GasInterceptorSafetyTest -from iqdbc.iqpilot.car.honda.values_ext import HondaSafetyFlagsIQ +from iqdbc.lvbs.car.honda.iq_values import HondaSafetyFlagsIQ HONDA_N_COMMON_TX_MSGS = [[0xE4, 0], [0x194, 0], [0x1FA, 0], [0x30C, 0], [0x33D, 0]] diff --git a/iqdbc_repo/iqdbc/safety/tests/test_tesla.py b/iqdbc_repo/iqdbc/safety/tests/test_tesla.py index 5ee35f7..cde2451 100755 --- a/iqdbc_repo/iqdbc/safety/tests/test_tesla.py +++ b/iqdbc_repo/iqdbc/safety/tests/test_tesla.py @@ -6,7 +6,7 @@ import numpy as np import pytest try: from iqdbc.car.tesla.carcontroller import get_safety_CP - from iqdbc.iqpilot.car.tesla.values import TeslaSafetyFlagsIQ + from iqdbc.lvbs.car.tesla.values import TeslaSafetyFlagsIQ except ImportError: pytest.skip("requires openpilot dependencies", allow_module_level=True) diff --git a/iqdbc_repo/iqdbc/safety/tests/test_toyota.py b/iqdbc_repo/iqdbc/safety/tests/test_toyota.py index 10cbf1f..b5730eb 100755 --- a/iqdbc_repo/iqdbc/safety/tests/test_toyota.py +++ b/iqdbc_repo/iqdbc/safety/tests/test_toyota.py @@ -6,7 +6,7 @@ import unittest import itertools from iqdbc.car.toyota.values import ToyotaSafetyFlags -from iqdbc.iqpilot.car.toyota.values import ToyotaSafetyFlagsIQ +from iqdbc.lvbs.car.toyota.values import ToyotaSafetyFlagsIQ from iqdbc.car.structs import CarParams from iqdbc.safety.tests.libsafety import libsafety_py import iqdbc.safety.tests.common as common diff --git a/iqpilot/common/atlas_alerts.py b/iqpilot/common/atlas_alerts.py index 5339155..8060fda 100644 --- a/iqpilot/common/atlas_alerts.py +++ b/iqpilot/common/atlas_alerts.py @@ -214,9 +214,10 @@ class NoEntryCard(AlertCard): def __init__(self, alert_text_2: str, alert_text_1: str = "openpilot Unavailable", - visual_alert: car.CarControl.HUDControl.VisualAlert = VisualAlert.none): + visual_alert: car.CarControl.HUDControl.VisualAlert = VisualAlert.none, + priority: Tier = Tier.LOW): primary, secondary, size = _mici_reframe(alert_text_1, alert_text_2) - super().__init__(primary, secondary, AlertStatus.normal, size, Tier.LOW, visual_alert, AudibleAlert.refuse, 3.0) + super().__init__(primary, secondary, AlertStatus.normal, size, priority, visual_alert, AudibleAlert.refuse, 3.0) class GentleDisableCard(AlertCard): diff --git a/iqpilot/iq_maps/update_vendor_version.py b/iqpilot/iq_maps/update_vendor_version.py index 2b1675d..7573559 100755 --- a/iqpilot/iq_maps/update_vendor_version.py +++ b/iqpilot/iq_maps/update_vendor_version.py @@ -14,12 +14,14 @@ from openpilot.common.basedir import BASEDIR from openpilot.iqpilot.iq_maps import VENDOR_MAPD_PATH from openpilot.iqpilot.iq_maps.vendor_mapd_installer import ( VENDOR_RELEASE_TAG, - get_file_hash, + sha256_of_file, ) _RELEASE_SYMBOL = "VENDOR_RELEASE_TAG" _INSTALLER_SRC = os.path.join(BASEDIR, "iqpilot", "iq_maps", "vendor_mapd_installer.py") -_HASH_FILE = os.path.join(BASEDIR, "iqpilot", "iq_maps", "tests", "mapd_hash") +# public: the checked-in hash the version test compares the installed binary against +HASH_FILE = os.path.join(BASEDIR, "iqpilot", "iq_maps", "tests", "mapd_hash") +_HASH_FILE = HASH_FILE _TAG_ASSIGN = re.compile(rf'^{_RELEASE_SYMBOL}\s*=\s*["\'][^"\']*["\']', re.MULTILINE) @@ -39,7 +41,7 @@ def rewrite_pinned_tag(new_tag: str) -> bool: def refresh_hash_file() -> None: - digest = get_file_hash(VENDOR_MAPD_PATH) + digest = sha256_of_file(VENDOR_MAPD_PATH) with open(_HASH_FILE, "w") as f: f.write(digest) print(f"wrote binary hash {digest} -> {_HASH_FILE}") diff --git a/iqpilot/iq_maps/vendor_mapd_installer.py b/iqpilot/iq_maps/vendor_mapd_installer.py index 1964939..96090d4 100755 --- a/iqpilot/iq_maps/vendor_mapd_installer.py +++ b/iqpilot/iq_maps/vendor_mapd_installer.py @@ -34,7 +34,7 @@ _NET_PROBE_ATTEMPTS = 10 _NET_PROBE_INTERVAL_S = 2 -def get_file_hash(path: str) -> str: +def sha256_of_file(path: str) -> str: """Hex SHA-256 digest of a file on disk.""" digest = hashlib.sha256() with open(path, "rb") as handle: @@ -103,7 +103,7 @@ class VendorMapdInstaller: if not reference: return True try: - return get_file_hash(VENDOR_MAPD_PATH) == reference + return sha256_of_file(VENDOR_MAPD_PATH) == reference except OSError: return False diff --git a/iqpilot/selfdrive/assets/offroad/icon_map.png b/iqpilot/selfdrive/assets/offroad/icon_map.png deleted file mode 100644 index 21dd0ba..0000000 Binary files a/iqpilot/selfdrive/assets/offroad/icon_map.png and /dev/null differ diff --git a/iqpilot/selfdrive/car/interfaces.py b/iqpilot/selfdrive/car/interfaces.py index 3e0a85d..bff71f9 100644 --- a/iqpilot/selfdrive/car/interfaces.py +++ b/iqpilot/selfdrive/car/interfaces.py @@ -69,7 +69,7 @@ def _cleanup_unsupported_params(cp, cp_iq, store=None) -> None: set_speed_limit_controller_availability(cp, cp_iq, store) -def setup_interfaces(ci, store=None) -> None: +def apply_iq_car_config(ci, store=None) -> None: store = store or _Store() if _stamp_lateral_model(ci.CP, ci.CP_IQ, store): ci.configure_torque_tune(ci.CP.carFingerprint, ci.CP.lateralTuning) diff --git a/iqpilot/selfdrive/car/vehicle_catalog.py b/iqpilot/selfdrive/car/vehicle_catalog.py index 254797c..42d6e5d 100644 --- a/iqpilot/selfdrive/car/vehicle_catalog.py +++ b/iqpilot/selfdrive/car/vehicle_catalog.py @@ -81,5 +81,5 @@ def _write(vehicles: dict[str, dict], basedir: str = BASEDIR) -> str: if __name__ == "__main__": - from iqdbc.iqpilot.car.platform_list import get_car_list + from iqdbc.lvbs.car.platform_list import get_car_list print("wrote", _write(get_car_list())) diff --git a/iqpilot/selfdrive/controls/lib/helpers/e2e_alerts.py b/iqpilot/selfdrive/controls/lib/helpers/e2e_alerts.py index 025f19e..c7c6286 100644 --- a/iqpilot/selfdrive/controls/lib/helpers/e2e_alerts.py +++ b/iqpilot/selfdrive/controls/lib/helpers/e2e_alerts.py @@ -4,152 +4,140 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed from cereal import messaging, custom +from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL from openpilot.iqpilot.selfdrive.selfdrived.events import IQEvents -PATH_QUEUE_GATE = 30 -LEAD_GROWTH_GATE = 1.0 -TRIGGER_HOLD_S = 0.3 +PARAM_PATH = "EndToEndAlert" +PARAM_LEAD = "EndToEndLeadAlert" +PARAM_STRIDE_S = 2.0 + +SETTLE_S = 1.0 +CONFIRM_S = 0.4 +ROLL_MPS = 0.3 + +HORIZON_TAIL = 5 +PATH_SPEED_MPS = 3.0 + +LEAD_QUEUE_M = 12.0 +LEAD_SPEED_MPS = 1.0 +LEAD_GAP_M = 0.5 -class TriggerPhase: - IDLE = 0 - ARMED = 1 - FIRED = 2 +class _Confirm: + def __init__(self, window_s: float): + self._window = window_s + self._held = 0.0 + self._spent = False + + def clear(self) -> None: + self._held = 0.0 + self._spent = False + + def poll(self, holds: bool) -> bool: + if self._spent: + return False + self._held = self._held + DT_MDL if holds else 0.0 + if self._held < self._window: + return False + self._spent = True + return True -class StopAndLeadChimeEngine: +class _Dwell: def __init__(self): - self._frame = -1 - self._toggle = {"path_queue": False, "lead_depart": False} - self._trigger = { - "path_queue": {"phase": TriggerPhase.IDLE, "last_phase": TriggerPhase.IDLE, "fired": False, "hold_ticks": 0, "anchor": -1.0}, - "lead_depart": {"phase": TriggerPhase.IDLE, "last_phase": TriggerPhase.IDLE, "fired": False, "hold_ticks": 0, "anchor": -1.0}, - } - self._standstill_gate = {"open": False, "was_open": False, "moving_frame": -1} - self._lead_state = {"visible": False, "primed": False, "armed": False, "warmup_ticks": 0} + self.seconds = 0.0 + self.lead_floor = float('inf') - def _read_toggles(self) -> None: - self._toggle["path_queue"] = False - self._toggle["lead_depart"] = False + def clear(self) -> None: + self.seconds = 0.0 + self.lead_floor = float('inf') - def _mark_motion(self, standstill: bool, speed_mps: float) -> bool: - is_moving = (not standstill) and speed_mps > 0.1 - if is_moving: - self._standstill_gate["moving_frame"] = self._frame - return is_moving + def tick(self, lead_range: float | None) -> None: + self.seconds += DT_MDL + if lead_range is not None: + self.lead_floor = min(self.lead_floor, lead_range) - def _within_motion_grace(self) -> bool: - stamp = self._standstill_gate["moving_frame"] - if stamp == -1: - return True - return (self._frame - stamp) * DT_MDL < 2.0 + @property + def settled(self) -> bool: + return self.seconds >= SETTLE_S - def _update_gate(self, standstill: bool, speed_mps: float, gas_pressed: bool, controls_enabled: bool) -> None: - moving = self._mark_motion(standstill, speed_mps) - self._standstill_gate["open"] = (not moving) and (not gas_pressed) and (not controls_enabled) and (not self._within_motion_grace()) + @property + def queued(self) -> bool: + return self.lead_floor < LEAD_QUEUE_M - @staticmethod - def _path_end_x(path_x): - return float(path_x[-1]) if len(path_x) else 0.0 - def _path_queue_signal(self, path_x) -> bool: - node = self._trigger["path_queue"] - if node["phase"] != TriggerPhase.ARMED: - node["hold_ticks"] = 0 - return False - node["hold_ticks"] = node["hold_ticks"] + 1 if self._path_end_x(path_x) > PATH_QUEUE_GATE else 0 - return node["hold_ticks"] * DT_MDL > TRIGGER_HOLD_S +class EndToEndAlertEngine: + def __init__(self): + self._params = Params() + self._on = {"path": False, "lead": False} + self._elapsed_since_read = PARAM_STRIDE_S + self._dwell = _Dwell() + self._confirm = {"path": _Confirm(CONFIRM_S), "lead": _Confirm(CONFIRM_S)} + self._fired = {"path": False, "lead": False} - def _update_lead_prime(self, seen: bool, distance_m: float) -> None: - near_lead = seen and distance_m < 8.0 - opened_now = self._standstill_gate["open"] and not self._standstill_gate["was_open"] - - if opened_now and near_lead: - self._lead_state["primed"] = True - elif not self._standstill_gate["open"]: - self._lead_state["primed"] = False - - if self._standstill_gate["open"] and self._lead_state["primed"] and near_lead: - self._lead_state["warmup_ticks"] += 1 - self._lead_state["armed"] = self._lead_state["warmup_ticks"] * DT_MDL >= 1.0 + def _refresh_params(self) -> None: + self._elapsed_since_read += DT_MDL + if self._elapsed_since_read < PARAM_STRIDE_S: return - - self._lead_state["warmup_ticks"] = 0 - self._lead_state["armed"] = False - - def _lead_depart_signal(self, distance_m: float) -> bool: - node = self._trigger["lead_depart"] - if node["phase"] != TriggerPhase.ARMED: - node["anchor"] = -1.0 - node["hold_ticks"] = 0 - return False - - if node["anchor"] == -1.0 or distance_m < node["anchor"]: - node["anchor"] = distance_m - - grew = node["anchor"] != -1.0 and (distance_m - node["anchor"]) > LEAD_GROWTH_GATE - node["hold_ticks"] = node["hold_ticks"] + 1 if grew else 0 - return node["hold_ticks"] * DT_MDL > TRIGGER_HOLD_S - - def _sample_inputs(self, sm: messaging.SubMaster): - cs = sm['carState'] - cc = sm['carControl'] - lead_one = sm['radarState'].leadOne - - self._lead_state["visible"] = lead_one.status - lead_distance = lead_one.dRel - - self._update_gate(cs.standstill, cs.vEgo, cs.gasPressed, cc.enabled) - queue_fire = self._path_queue_signal(sm['modelV2'].position.x) - self._update_lead_prime(self._lead_state["visible"], lead_distance) - lead_fire = self._lead_depart_signal(lead_distance) - - self._standstill_gate["was_open"] = self._standstill_gate["open"] - return queue_fire, lead_fire + self._elapsed_since_read = 0.0 + self._on["path"] = self._params.get_bool(PARAM_PATH) + self._on["lead"] = self._params.get_bool(PARAM_LEAD) @staticmethod - def _step_phase(phase: int, enabled: bool, allowed: bool, fired: bool) -> tuple[int, bool]: - if phase == TriggerPhase.IDLE: - return (TriggerPhase.ARMED, fired) if (allowed and enabled) else (phase, fired) - if not allowed or not enabled: - return TriggerPhase.IDLE, fired - if phase == TriggerPhase.ARMED and fired: - return TriggerPhase.FIRED, fired - return phase, fired + def _car_holds_long(sm: messaging.SubMaster) -> bool: + # AOL steers without raising selfdriveState.enabled, so the pair reads as long authority + return bool(sm['selfdriveState'].enabled or sm['carState'].cruiseState.enabled) - def _advance_trigger(self, key: str, enabled: bool, allowed: bool, fired: bool) -> None: - node = self._trigger[key] - node["last_phase"] = node["phase"] - node["phase"], node["fired"] = self._step_phase(node["phase"], enabled, allowed, fired) + @staticmethod + def _halted(cs) -> bool: + return bool(cs.standstill) or abs(cs.vEgo) < ROLL_MPS + + @staticmethod + def _horizon_speed(model) -> float: + # capnp list readers reject slices + samples = model.velocity.x + count = len(samples) + if count < HORIZON_TAIL: + return 0.0 + return sum(samples[i] for i in range(count - HORIZON_TAIL, count)) / HORIZON_TAIL + + def _rearm(self) -> None: + self._dwell.clear() + for gate in self._confirm.values(): + gate.clear() def update(self, sm: messaging.SubMaster, iq_events: IQEvents) -> None: - self._read_toggles() - queue_fire, lead_fire = self._sample_inputs(sm) + self._refresh_params() + self._fired["path"] = self._fired["lead"] = False - self._advance_trigger("path_queue", self._toggle["path_queue"], self._standstill_gate["open"] and not self._lead_state["visible"], queue_fire) - self._advance_trigger("lead_depart", self._toggle["lead_depart"], self._standstill_gate["open"] and self._lead_state["armed"], lead_fire) + cs = sm['carState'] + lead = sm['radarState'].leadOne + lead_range = float(lead.dRel) if lead.status else None - if self._trigger["path_queue"]["fired"] or self._trigger["lead_depart"]["fired"]: + if not self._halted(cs) or cs.gasPressed or self._car_holds_long(sm): + self._rearm() + return + + self._dwell.tick(lead_range) + if not self._dwell.settled: + return + + if self._on["path"] and lead_range is None: + opened = self._horizon_speed(sm['modelV2']) > PATH_SPEED_MPS + self._fired["path"] = self._confirm["path"].poll(opened) + + if self._on["lead"] and lead_range is not None and self._dwell.queued: + pulling = lead.vLead > LEAD_SPEED_MPS and (lead_range - self._dwell.lead_floor) > LEAD_GAP_M + self._fired["lead"] = self._confirm["lead"].poll(pulling) + + if self._fired["path"] or self._fired["lead"]: iq_events.add(custom.IQOnroadEvent.EventName.e2eChime) - self._frame += 1 + @property + def path_alert(self) -> bool: + return self._fired["path"] @property - def queue_alert(self): - return self._trigger["path_queue"]["fired"] - - @queue_alert.setter - def queue_alert(self, payload): - self._trigger["path_queue"]["fired"] = bool(payload) - - @property - def lead_alert(self): - return self._trigger["lead_depart"]["fired"] - - @lead_alert.setter - def lead_alert(self, payload): - self._trigger["lead_depart"]["fired"] = bool(payload) - - -E2EAlertsHelper = StopAndLeadChimeEngine + def lead_alert(self) -> bool: + return self._fired["lead"] diff --git a/iqpilot/selfdrive/controls/lib/helpers/tests/test_e2e_alerts.py b/iqpilot/selfdrive/controls/lib/helpers/tests/test_e2e_alerts.py new file mode 100644 index 0000000..69a1f95 --- /dev/null +++ b/iqpilot/selfdrive/controls/lib/helpers/tests/test_e2e_alerts.py @@ -0,0 +1,133 @@ +""" +Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos +""" +from types import SimpleNamespace + +import pytest + +import cereal.messaging as messaging +from cereal import custom +from openpilot.common.realtime import DT_MDL +from openpilot.iqpilot.selfdrive.controls.lib.helpers.e2e_alerts import ( + EndToEndAlertEngine, CONFIRM_S, SETTLE_S, HORIZON_TAIL, PATH_SPEED_MPS, LEAD_SPEED_MPS, LEAD_GAP_M) + +E2E_CHIME = custom.IQOnroadEvent.EventName.e2eChime + + +class _Events(list): + def add(self, name): + self.append(name) + + +def _model(horizon): + msg = messaging.new_message('modelV2') + msg.modelV2.velocity.x = [0.0] * (33 - HORIZON_TAIL) + [horizon] * HORIZON_TAIL + return msg.as_reader().modelV2 + + +def _sm(*, v_ego=0.0, standstill=True, gas=False, enabled=False, cruise=False, + horizon=0.0, lead=None): + lead = lead or SimpleNamespace(status=False, dRel=0.0, vLead=0.0) + return { + 'carState': SimpleNamespace(vEgo=v_ego, standstill=standstill, gasPressed=gas, + cruiseState=SimpleNamespace(enabled=cruise)), + 'selfdriveState': SimpleNamespace(enabled=enabled), + 'radarState': SimpleNamespace(leadOne=lead), + 'modelV2': _model(horizon), + } + + +def _engine(path=True, lead=True): + engine = EndToEndAlertEngine() + engine._refresh_params = lambda: None + engine._on = {"path": path, "lead": lead} + return engine + + +def _run(engine, sm, seconds): + events = _Events() + chimes = 0 + for _ in range(int(seconds / DT_MDL)): + engine.update(sm, events) + chimes += events.count(E2E_CHIME) + events.clear() + return chimes + + +def _lead(d_rel, v_lead=0.0): + return SimpleNamespace(status=True, dRel=d_rel, vLead=v_lead) + + +def test_path_opens_chimes_once(): + engine = _engine() + assert _run(engine, _sm(horizon=0.0), SETTLE_S + 1.0) == 0 + assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), CONFIRM_S + 1.0) == 1 + assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), 5.0) == 0 + + +def test_path_needs_the_settle_dwell(): + engine = _engine() + assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), SETTLE_S - 0.2) == 0 + + +def test_path_silent_while_openpilot_long_is_engaged(): + engine = _engine() + _run(engine, _sm(horizon=0.0, enabled=True), SETTLE_S + 1.0) + assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0, enabled=True), 5.0) == 0 + + +def test_path_silent_while_stock_acc_holds_the_car(): + engine = _engine() + _run(engine, _sm(horizon=0.0, cruise=True), SETTLE_S + 1.0) + assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0, cruise=True), 5.0) == 0 + + +def test_path_chimes_under_aol(): + engine = _engine() + _run(engine, _sm(horizon=0.0), SETTLE_S + 1.0) + assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), CONFIRM_S + 1.0) == 1 + + +def test_path_ignores_a_visible_lead(): + engine = _engine(lead=False) + _run(engine, _sm(horizon=0.0, lead=_lead(6.0)), SETTLE_S + 1.0) + assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0, lead=_lead(6.0)), 5.0) == 0 + + +def test_lead_pullaway_chimes_once(): + engine = _engine() + assert _run(engine, _sm(lead=_lead(6.0)), SETTLE_S + 1.0) == 0 + moving = _sm(lead=_lead(6.0 + LEAD_GAP_M + 0.5, LEAD_SPEED_MPS + 1.0)) + assert _run(engine, moving, CONFIRM_S + 1.0) == 1 + assert _run(engine, moving, 5.0) == 0 + + +def test_lead_creep_inside_the_gap_stays_silent(): + engine = _engine() + _run(engine, _sm(lead=_lead(6.0)), SETTLE_S + 1.0) + assert _run(engine, _sm(lead=_lead(6.0 + LEAD_GAP_M / 2, LEAD_SPEED_MPS + 1.0)), 5.0) == 0 + + +def test_lead_far_ahead_is_not_a_queue(): + engine = _engine() + _run(engine, _sm(lead=_lead(40.0)), SETTLE_S + 1.0) + assert _run(engine, _sm(lead=_lead(44.0, LEAD_SPEED_MPS + 1.0)), 5.0) == 0 + + +def test_gas_and_motion_rearm_the_dwell(): + engine = _engine() + _run(engine, _sm(horizon=0.0), SETTLE_S + 1.0) + _run(engine, _sm(v_ego=5.0, standstill=False, horizon=8.0), 2.0) + assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), SETTLE_S - 0.2) == 0 + assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), CONFIRM_S + 1.0) == 1 + + +@pytest.mark.parametrize("param_off", ["path", "lead"]) +def test_each_param_gates_only_its_own_trigger(param_off): + engine = _engine(path=param_off != "path", lead=param_off != "lead") + if param_off == "path": + _run(engine, _sm(horizon=0.0), SETTLE_S + 1.0) + assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), 5.0) == 0 + else: + _run(engine, _sm(lead=_lead(6.0)), SETTLE_S + 1.0) + assert _run(engine, _sm(lead=_lead(8.0, LEAD_SPEED_MPS + 1.0)), 5.0) == 0 diff --git a/iqpilot/selfdrive/controls/lib/longitudinal_planner.py b/iqpilot/selfdrive/controls/lib/longitudinal_planner.py index 8688530..b99068b 100644 --- a/iqpilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/iqpilot/selfdrive/controls/lib/longitudinal_planner.py @@ -13,7 +13,7 @@ from openpilot.selfdrive.car.cruise import V_CRUISE_MAX from openpilot.iqpilot.selfdrive.controls.lib.custom_stop_distance import CustomStopDistance from openpilot.iqpilot.selfdrive.controls.lib.iq_dynamic.engine import IQDynamicController from openpilot.iqpilot.selfdrive.controls.lib.iq_dynamic.imahelper import IQConstants -from openpilot.iqpilot.selfdrive.controls.lib.helpers.e2e_alerts import E2EAlertsHelper +from openpilot.iqpilot.selfdrive.controls.lib.helpers.e2e_alerts import EndToEndAlertEngine from openpilot.iqpilot.selfdrive.controls.lib.slc_vcruise import SLCVCruise from openpilot.iqpilot.selfdrive.controls.lib.speed_limit_controller import LIMIT_ADAPT_ACC from openpilot.iqpilot.selfdrive.selfdrived.events import IQEvents @@ -34,7 +34,7 @@ class LongitudinalPlannerIQ: self.slimit = SLCVCruise() self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None self.source = LongitudinalPlanSource.cruise - self.iqmodeloutput = E2EAlertsHelper() + self.e2e_alerts = EndToEndAlertEngine() self.output_v_target = 0. self.output_a_target = 0. self.speed_limit_last = 0. @@ -146,7 +146,7 @@ class LongitudinalPlannerIQ: for event_name in getattr(self.slimit, 'pending_events', []): self.events_iq.add(event_name) self.custom_stop_distance.update() - self.iqmodeloutput.update(sm, self.events_iq) + self.e2e_alerts.update(sm, self.events_iq) if bool(getattr(sm["iqCarState"], "alcOverrideAlert", False)): self.events_iq.add(custom.IQOnroadEvent.EventName.steeringOverrideReengageAlc) @@ -244,10 +244,9 @@ class LongitudinalPlannerIQ: assist.vTarget = float(self.output_v_target if assist.active else 255.) assist.aTarget = float(self.slimit.slc_a_target if assist.active else 0.) - # E2E Alerts e2eAlerts = plan_msg.e2eAlerts - e2eAlerts.greenLightAlert = self.iqmodeloutput.queue_alert - e2eAlerts.leadDepartAlert = self.iqmodeloutput.lead_alert + e2eAlerts.pathOpen = self.e2e_alerts.path_alert + e2eAlerts.leadPullaway = self.e2e_alerts.lead_alert valid = sm.all_checks(service_list=['carState', 'controlsState']) diff --git a/iqpilot/selfdrive/controls/lib/speed_limit/__init__.py b/iqpilot/selfdrive/controls/lib/speed_limit/__init__.py deleted file mode 100644 index f8012f5..0000000 --- a/iqpilot/selfdrive/controls/lib/speed_limit/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos - -Shared speed-limit enums and helpers. The live resolver is IQSpeedLimitResolver -in speed_limit_controller.py. -""" diff --git a/iqpilot/selfdrive/selfdrived/events.py b/iqpilot/selfdrive/selfdrived/events.py index 04ac5d9..76d81ae 100644 --- a/iqpilot/selfdrive/selfdrived/events.py +++ b/iqpilot/selfdrive/selfdrived/events.py @@ -95,8 +95,17 @@ _CAMERA_LABELS = { def speed_camera_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: nav = sm['iqNavState'] - label = _CAMERA_LABELS.get(int(getattr(nav.cameraType, "raw", nav.cameraType)), "Speed Camera") + ctype = int(getattr(nav.cameraType, "raw", nav.cameraType)) + label = _CAMERA_LABELS.get(ctype, "Speed Camera") distance = float(nav.cameraDistance) + # RF (BLE/WiFi) Flock detection is a live proximity hit with no meaningful + # distance — flockd/navd flag it with distance 0 on the alpr camera type. + if ctype == int(custom.IQNavState.CameraType.alpr) and distance <= 0.0: + return Alert( + "Flock Camera Detected", + "", + AlertStatus.normal, AlertSize.small, + Priority.HIGH, VisualAlert.none, AudibleAlert.prompt, .2) if metric: dist_str = f"{distance:.0f} m" if distance < 1000.0 else f"{distance / 1000.0:.1f} km" else: diff --git a/iqpilot/ui/layouts/settings/iq_dynamic.py b/iqpilot/ui/layouts/settings/iq_dynamic.py index a3fb242..88d5478 100644 --- a/iqpilot/ui/layouts/settings/iq_dynamic.py +++ b/iqpilot/ui/layouts/settings/iq_dynamic.py @@ -8,8 +8,8 @@ import pyray as rl from openpilot.common.params import Params from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem, IQToggleAction, SafeIQToggleAction -from openpilot.system.ui.iqpilot.widgets.list_view import OptionControl +from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, IQToggleAction, SafeIQToggleAction +from openpilot.system.ui.iqwidgets.widgets.list_view import OptionControl from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.network import NavButton from openpilot.system.ui.widgets.scroller_tici import Scroller diff --git a/iqpilot/ui/layouts/settings/iq_panels.py b/iqpilot/ui/layouts/settings/iq_panels.py index 4db8d54..0f023af 100644 --- a/iqpilot/ui/layouts/settings/iq_panels.py +++ b/iqpilot/ui/layouts/settings/iq_panels.py @@ -18,17 +18,17 @@ from openpilot.selfdrive.ui.layouts.settings.software import time_ago from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.hardware.hw import Paths -from openpilot.system.ui.iqpilot.lib.styles import style -from openpilot.system.ui.iqpilot.lib.utils import NoElideButtonAction -from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem, toggle_item -from openpilot.system.ui.iqpilot.widgets.list_view import OptionControl -from openpilot.system.ui.iqpilot.widgets.list_view import multiple_button_item, toggle_item -from openpilot.system.ui.iqpilot.widgets.list_view import option_item, toggle_item, ToggleAction -from openpilot.system.ui.iqpilot.widgets.list_view import progress_item -from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item, multiple_button_item, IQListItem, IQLineSeparator -from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item, option_item, IQLineSeparator -from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item, simple_button_item, option_item, IQLineSeparator -from openpilot.system.ui.iqpilot.widgets.list_view import TreeFolder, TreeNode, TreeOptionDialog +from openpilot.system.ui.iqwidgets.lib.styles import style +from openpilot.system.ui.iqwidgets.lib.utils import WideButtonAction +from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, toggle_item +from openpilot.system.ui.iqwidgets.widgets.list_view import OptionControl +from openpilot.system.ui.iqwidgets.widgets.list_view import multiple_button_item, toggle_item +from openpilot.system.ui.iqwidgets.widgets.list_view import option_item, toggle_item, ToggleAction +from openpilot.system.ui.iqwidgets.widgets.list_view import progress_item +from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item, multiple_button_item, IQListItem, IQLineSeparator +from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item, option_item, IQLineSeparator +from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item, simple_button_item, option_item, IQLineSeparator +from openpilot.system.ui.iqwidgets.widgets.list_view import PickerGroup, PickerItem, PickerDialog from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.multilang import tr, tr_noop @@ -60,8 +60,8 @@ from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import CUS from openpilot.selfdrive.ui.layouts.settings import settings as OP from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout from openpilot.system.hardware import HARDWARE -from openpilot.system.ui.iqpilot.lib.styles import metrics -from openpilot.system.ui.iqpilot.widgets.list_view import ( +from openpilot.system.ui.iqwidgets.lib.styles import metrics +from openpilot.system.ui.iqwidgets.widgets.list_view import ( option_item as option_item, multiple_button_item as multiple_button_item, button_item as button_item, @@ -69,11 +69,11 @@ from openpilot.system.ui.iqpilot.widgets.list_view import ( NavSectionButton, Spacer, ) -from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem -from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem, IQMultipleButtonAction, IQToggleAction, IQLineSeparator -from openpilot.system.ui.iqpilot.widgets.list_view import button_item, toggle_item -from openpilot.system.ui.iqpilot.widgets.list_view import NoticeModal -from openpilot.system.ui.iqpilot.widgets.list_view import TreeOptionDialog, TreeNode, TreeFolder +from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem +from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, IQMultipleButtonAction, IQToggleAction, IQLineSeparator +from openpilot.system.ui.iqwidgets.widgets.list_view import button_item, toggle_item +from openpilot.system.ui.iqwidgets.widgets.list_view import NoticeModal +from openpilot.system.ui.iqwidgets.widgets.list_view import PickerDialog, PickerItem, PickerGroup from openpilot.system.ui.lib.application import gui_app, MousePos from openpilot.system.ui.lib.multilang import tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached @@ -96,9 +96,9 @@ from iqdbc.car.hyundai.values import CAR, CANFD_UNSUPPORTED_LONGITUDINAL_CAR, UN from iqdbc.car.subaru.values import CAR, SubaruFlags from iqdbc.car.volkswagen.values import CAR, VolkswagenFlags from openpilot.common.basedir import BASEDIR -from openpilot.system.ui.iqpilot.lib.styles import ink -from openpilot.system.ui.iqpilot.widgets.list_view import multiple_button_item -from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item +from openpilot.system.ui.iqwidgets.lib.styles import ink +from openpilot.system.ui.iqwidgets.widgets.list_view import multiple_button_item +from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets.list_view import ButtonAction import json @@ -116,9 +116,9 @@ def _fmt_seconds(value) -> str: def onroad_brightness_label(val) -> str: if val == OnroadBrightness.AUTO: - return tr("Auto (Default)") + return tr("Auto") if val == OnroadBrightness.AUTO_DARK: - return tr("Auto (Dark)") + return tr("Auto — Dim") return f"{(val - 1) * 5} %" @@ -140,17 +140,17 @@ class DisplayLayout(Widget): label_callback=lambda v: tr("Default") if v == 0 else f"{v} %", inline=True, ) self._onroad_brightness_row = option_item( - param="OnroadScreenOffBrightness", title=lambda: tr("Onroad Brightness"), description="", + param="OnroadScreenOffBrightness", title=lambda: tr("Driving Brightness"), description="", min_value=0, max_value=21, value_change_step=1, label_callback=onroad_brightness_label, inline=True, ) self._dim_delay_row = option_item( - param="OnroadScreenOffTimer", title=lambda: tr("Onroad Brightness Delay"), description="", + param="OnroadScreenOffTimer", title=lambda: tr("Dim Delay"), description="", min_value=0, max_value=11, value_change_step=1, value_map=ONROAD_BRIGHTNESS_TIMER_VALUES, label_callback=_fmt_seconds, inline=True, ) self._idle_close_row = option_item( - param="InteractivityTimeout", title=lambda: tr("Interactivity Timeout"), + param="InteractivityTimeout", title=lambda: tr("Auto-Close Settings"), description=lambda: tr("How long the settings screen may sit untouched before it closes itself."), min_value=0, max_value=120, value_change_step=10, label_callback=lambda v: tr("Default") if not v else _fmt_seconds(v), inline=True, @@ -195,7 +195,7 @@ CHEVRON_INFO_DESCRIPTION = { # param key -> (title fn, description fn) _HUD_TOGGLES = { "BlindSpot": ( - lambda: tr("Show Blind Spot Warnings"), + lambda: tr("Blind Spot Alerts"), lambda: tr("Flashes a side warning whenever the car reports something sitting in your blind spot (BSM-equipped cars only)."), ), "IQExpandedStatus": ( @@ -203,20 +203,20 @@ _HUD_TOGGLES = { lambda: tr("Bring back the classic UI's wide offroad status strip: temperature, vehicle, and Konn3kt state at a glance."), ), "TorqueBar": ( - lambda: tr("Steering Arc"), + lambda: tr("Steering Effort Arc"), lambda: tr("Trace an arc over the road view showing how much steering IQ.Pilot is applying while lateral control runs."), ), "RoadNameToggle": ( - lambda: tr("Display Road Name"), + lambda: tr("Road Name Overlay"), lambda: tr("Show the current road's name over the driving view." "
Requires offline map data for your region to be installed."), ), "ShowTurnSignals": ( - lambda: tr("Display Turn Signals"), + lambda: tr("Blinker Indicators"), lambda: tr("Mirror the car's blinkers as arrows on the driving screen."), ), "RocketFuel": ( - lambda: tr("Real-time Acceleration Bar"), + lambda: tr("Acceleration Meter"), lambda: tr("Draw a bar along the left edge tracking measured acceleration and braking — what the car is actually " "doing right now, not the planner's request."), ), @@ -240,7 +240,7 @@ class VisualsLayout(Widget): } self._chevron_info = multiple_button_item( - title=lambda: tr("Display Metrics Below Chevron"), + title=lambda: tr("Lead Vehicle Readouts"), description="", buttons=[lambda: tr("Off"), lambda: tr("Distance"), lambda: tr("Speed"), lambda: tr("Time"), lambda: tr("All")], param="ChevronInfo", @@ -319,7 +319,7 @@ class IQNetworkUI(NetworkUI): def _set_phase(self, phase: ScanPhase): self._phase = phase running = phase == ScanPhase.RUNNING - self.scan_button.set_text(tr("Scanning...") if running else tr("Scan")) + self.scan_button.set_text(tr("Searching...") if running else tr("Scan")) self.scan_button.set_enabled(not running) # keep the manager's spinner in step with the button, not just the quick scan call self._wifi_manager._scanning = running @@ -397,7 +397,7 @@ class IQMapsLayout(Widget): self._scroller = Scroller(self.items, line_separator=True, spacing=0) def _build_rows(self): - self._mapd_version = text_item(tr("Mapd Version"), lambda: ui_state.params.get("MapdVersion") or "Loading...") + self._mapd_version = text_item(tr("Routing Engine Version"), lambda: ui_state.params.get("MapdVersion") or "Loading...") self._online_maps_toggle = toggle_item( tr("Online On-Screen Maps"), tr("Pull live Mapbox tiles for the on-screen map whenever the device has internet."), @@ -409,15 +409,15 @@ class IQMapsLayout(Widget): "take over the moment connectivity drops. Tiles come down with your selected region."), param="OfflineOSMaps", ) - self._delete_maps_btn = IQListItem(tr("Downloaded Maps"), action_item=NoElideButtonAction(tr("DELETE"), enabled=True), + self._delete_maps_btn = IQListItem(tr("Stored Maps"), action_item=WideButtonAction(tr("DELETE"), enabled=True), callback=self._confirm_wipe) - self._progress = progress_item(tr("Downloading Map")) + self._progress = progress_item(tr("Fetching Map")) self._tile_progress = progress_item(tr("Downloading Map Tiles")) - self._update_btn = IQListItem(tr("Database Update"), action_item=NoElideButtonAction(tr("CHECK"), enabled=True), + self._update_btn = IQListItem(tr("Map Database"), action_item=WideButtonAction(tr("CHECK"), enabled=True), callback=self._confirm_db_refresh) - self._country_btn = IQListItem(tr("Country"), action_item=NoElideButtonAction(tr("SELECT"), enabled=True), + self._country_btn = IQListItem(tr("Country"), action_item=WideButtonAction(tr("SELECT"), enabled=True), callback=lambda: self._open_region_picker("Country")) - self._state_btn = IQListItem(tr("State"), action_item=NoElideButtonAction(tr("SELECT"), enabled=True), + self._state_btn = IQListItem(tr("State"), action_item=WideButtonAction(tr("SELECT"), enabled=True), callback=lambda: self._open_region_picker("State")) self.items = [self._mapd_version, self._online_maps_toggle, self._offline_maps_toggle, self._delete_maps_btn, @@ -436,7 +436,7 @@ class IQMapsLayout(Widget): def _start_wipe(self): self._delete_maps_btn.action_item.set_enabled(False) - self._delete_maps_btn.action_item.set_text(tr("DELETING...")) + self._delete_maps_btn.action_item.set_text(tr("REMOVING...")) threading.Thread(target=self._wipe_worker, daemon=True).start() def _wipe_worker(self): @@ -475,7 +475,7 @@ class IQMapsLayout(Widget): def _open_region_picker(self, region_type): btn = self._country_btn if region_type == "Country" else self._state_btn btn.action_item.set_enabled(False) - btn.action_item.set_text(tr("FETCHING...")) + btn.action_item.set_text(tr("LOADING...")) threading.Thread(target=self._region_picker_worker, args=(region_type, btn), daemon=True).start() @staticmethod @@ -483,7 +483,7 @@ class IQMapsLayout(Widget): url = _BOUNDS_BASE_URL + ("nation_bounding_boxes.json" if region_type == "Country" else "us_states_bounding_boxes.json") try: data = requests.get(url, timeout=10).json() - return sorted((TreeNode(ref=k, data={'display_name': v['full_name']}) for k, v in data.items()), + return sorted((PickerItem(ref=k, data={'display_name': v['full_name']}) for k, v in data.items()), key=lambda n: n.data['display_name']) except Exception: return [] @@ -491,14 +491,14 @@ class IQMapsLayout(Widget): def _region_picker_worker(self, region_type, btn): locations = self._fetch_regions(region_type) if region_type == "State": - locations.insert(0, TreeNode(ref="All", data={'display_name': tr("All states (~6.0 GB)")})) + locations.insert(0, PickerItem(ref="All", data={'display_name': tr("All states (~6.0 GB)")})) btn.action_item.set_enabled(True) btn.action_item.set_text(tr("SELECT")) key = "OsmLocation" if region_type == "Country" else "OsmState" current = ui_state.params.get(f"{key}Name") or "" - dialog = TreeOptionDialog(tr(f"Select {region_type}"), [TreeFolder(folder="", nodes=locations)], + dialog = PickerDialog(tr(f"Select {region_type}"), [PickerGroup(folder="", nodes=locations)], current_ref=current, search_prompt=tr("Perform a search")) done = lambda res: self._on_region_picked(region_type, locations, key, res, dialog.selection_ref) # noqa: E731 dialog.on_exit = done @@ -554,14 +554,14 @@ class IQMapsLayout(Widget): if failed: self._current_percent = 0.0 - bar_text, btn_text = "0% - Downloading Maps", tr("Error: Invalid download. Retry.") + bar_text, btn_text = "0% - Downloading Maps", tr("Download failed — try again.") elif total > 0 and downloading: self._current_percent = max(0.0, min(100.0, done / total * 100.0)) pct = int(self._current_percent) bar_text, btn_text = f"{pct}% - Downloading Maps", f"{done}/{total} ({pct}%)" else: self._current_percent = 0.0 - bar_text, btn_text = "0% - Downloading Maps", tr("Downloading Maps...") + bar_text, btn_text = "0% - Downloading Maps", tr("Fetching Maps...") self._progress.action_item.update(self._current_percent, bar_text, show_progress=total > 0 and downloading and not failed) @@ -599,7 +599,7 @@ class IQMapsLayout(Widget): dt = datetime.datetime.fromtimestamp(float(ts), tz=datetime.UTC) except (ValueError, TypeError): dt = None - self._update_btn.action_item.set_value(tr("Last checked {}").format(time_ago(dt))) + self._update_btn.action_item.set_value(tr("Checked {}").format(time_ago(dt))) def _update_state(self): now = monotonic() @@ -618,7 +618,7 @@ class IQMapsLayout(Widget): _TIMER_LABELS = { -1: lambda: tr("Off"), 0: lambda: tr("Nudge"), - 1: lambda: tr("Nudgeless"), + 1: lambda: tr("No Nudge"), 2: lambda: f"0.5 {tr('s')}", 3: lambda: f"1 {tr('s')}", 4: lambda: f"2 {tr('s')}", @@ -635,7 +635,7 @@ class LaneChangeSettingsLayout(Widget): def _build_rows(self): self._lane_change_timer = option_item( - title=lambda: tr("Auto Lane Change by Blinker"), + title=lambda: tr("Blinker-Initiated Lane Change"), param="IQLaneChangeTimer", description=lambda: tr("Delay before a blinker-triggered lane change starts on its own — no wheel nudge " "needed once a delay is set (default is Nudge).
Use the blinker for this only " @@ -645,7 +645,7 @@ class LaneChangeSettingsLayout(Widget): ) self._bsm_delay = toggle_item( param="IQLaneChangeBsmDelay", - title=lambda: tr("Auto Lane Change: Delay with Blind Spot"), + title=lambda: tr("Hold Lane Change for Blind Spot"), description=lambda: tr("Hold the automatic lane change while blind spot monitoring reports a car in the " "target lane, releasing it once the lane is clear."), ) @@ -685,7 +685,7 @@ class LaneChangeSettingsLayout(Widget): # (segment label, explanation) — order matches the AolSteeringMode param values SAB_BRAKE_RESPONSE_OPTIONS = [ - (tr("Remain Active"), tr_noop("Remain Active: braking never interrupts steering assistance.")), + (tr("Stay Engaged"), tr_noop("Remain Active: braking never interrupts steering assistance.")), (tr("Standby"), tr_noop("Standby: braking parks steering assistance; it rejoins once you're off the pedal.")), (tr("Disengage"), tr_noop("Disengage: braking shuts steering assistance off entirely.")), ] @@ -849,7 +849,7 @@ class SteeringLayout(Widget): callback=lambda: self._set_current_panel(PanelType.SAB) ) self._lane_change_settings_button = simple_button_item( - button_text=lambda: tr("Customize Lane Change"), + button_text=lambda: tr("Lane Change Options"), button_width=800, callback=lambda: self._set_current_panel(PanelType.LANE_CHANGE) ) @@ -859,7 +859,7 @@ class SteeringLayout(Widget): description="" ) self._lane_turn_desire_toggle = toggle_item( - tr("Use Lane Turn Desires"), + tr("Low-Speed Turn Planning"), tr("If you're driving at 20 mph (32 km/h) or below and have your blinker on," " the car will plan a turn in that direction at the nearest drivable path." " This prevents situations (like at red lights) where the car might plan the wrong turn direction."), @@ -1177,7 +1177,7 @@ class IQDeveloperLayout(DeveloperLayout): self._scroller.add_widget(item) def _initialize_items(self): - self.error_log_btn = button_item(tr("Error Log"), tr("VIEW"), tr("View the error log for IQ.Pilot crashes."), callback=self._on_error_log_clicked) + self.error_log_btn = button_item(tr("Crash Log"), tr("VIEW"), tr("View the error log for IQ.Pilot crashes."), callback=self._on_error_log_clicked) self.items: list = [self.error_log_btn] @@ -1188,7 +1188,7 @@ class IQDeveloperLayout(DeveloperLayout): def _on_error_log_closed(self, result, log_exists): if result == DialogResult.CONFIRM and log_exists: - dialog2 = ConfirmDialog(tr("Would you like to delete this log?"), tr("Yes"), tr("No"), rich=False) + dialog2 = ConfirmDialog(tr("Delete this log?"), tr("Yes"), tr("No"), rich=False) gui_app.set_modal_overlay(dialog2, callback=self._on_delete_confirm) def _on_error_log_clicked(self): @@ -1240,7 +1240,7 @@ class IQSoftwareLayout(SoftwareLayout): def __init__(self): super().__init__() self.disable_updates_toggle = toggle_item( - lambda: tr("Disable Updates"), + lambda: tr("Pause Updates"), description="", initial_state=ui_state.params.get_bool("DisableUpdates"), callback=self._on_disable_updates_toggled, @@ -1273,7 +1273,7 @@ class IQSoftwareLayout(SoftwareLayout): self.disable_updates_toggle.action_item.set_state(ui_state.params.get_bool("DisableUpdates")) def _on_disable_updates_toggled(self, enabled): - dialog = ConfirmDialog(tr("System reboot required for changes to take effect. Reboot now?"), tr("Reboot")) + dialog = ConfirmDialog(tr("This needs a reboot to apply. Restart now?"), tr("Reboot")) gui_app.set_modal_overlay(dialog, callback=self._handle_reboot) def _on_change_install_mode(self): @@ -1319,11 +1319,11 @@ class IQSoftwareLayout(SoftwareLayout): # single flat list: pinned branches first, then the rest alphabetically. # prebuilt ("-prebuilt") branches are internal build outputs, not user-selectable. - pinned_nodes = [TreeNode(b, {'display_name': b}) for b in top_level_branches if b in branches] - other_nodes = [TreeNode(b, {'display_name': b}) for b in sorted(branches) + pinned_nodes = [PickerItem(b, {'display_name': b}) for b in top_level_branches if b in branches] + other_nodes = [PickerItem(b, {'display_name': b}) for b in sorted(branches) if b not in top_level_branches and not b.endswith("-prebuilt")] - folders = [TreeFolder("", pinned_nodes + other_nodes)] + folders = [PickerGroup("", pinned_nodes + other_nodes)] def _on_branch_selected(result): if result == DialogResult.CONFIRM and self._branch_dialog is not None: @@ -1334,7 +1334,7 @@ class IQSoftwareLayout(SoftwareLayout): os.system("pkill -SIGUSR1 -f system.updated.updated") self._branch_dialog = None - self._branch_dialog = TreeOptionDialog(tr("Select a branch"), folders, current_target, "", + self._branch_dialog = PickerDialog(tr("Select a branch"), folders, current_target, "", on_exit=_on_branch_selected) gui_app.set_modal_overlay(self._branch_dialog, callback=_on_branch_selected) @@ -1388,11 +1388,11 @@ class IQDeviceLayout(DeviceLayout): DeviceLayout._initialize_items(self) # Using dual button with no right button for better alignment - self._always_offroad_btn = self._left_button(lambda: tr("Enable Always Offroad"), self._handle_always_offroad) + self._always_offroad_btn = self._left_button(lambda: tr("Force Offroad Mode"), self._handle_always_offroad) self._force_onroad_btn = self._left_button(lambda: tr("Force On-Road (10 min)"), self._handle_force_onroad) self._max_time_offroad = option_item( - title=lambda: tr("Max Time Offroad"), + title=lambda: tr("Offroad Shutdown Timer"), description=lambda: tr("Powers the device down once it has sat this long with the engine off.\n(30h by default)"), param="MaxTimeOffroad", min_value=0, @@ -1409,7 +1409,7 @@ class IQDeviceLayout(DeviceLayout): ) self._device_wake_mode = multiple_button_item( - title=lambda: tr("Wake Up Behavior"), + title=lambda: tr("Boot State"), description=self.wake_mode_description, param="DeviceBootMode", buttons=[lambda: tr("Default"), lambda: tr("Offroad")], @@ -1420,7 +1420,7 @@ class IQDeviceLayout(DeviceLayout): self._change_language_btn = button_item(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog) # Silent Mode moved to the settings-hub top bar (bell bubble); this is just the dcam preview now. - self._driver_camera_btn = button_item(lambda: tr("Driver Camera Preview"), lambda: tr("PREVIEW"), + self._driver_camera_btn = button_item(lambda: tr("Driver Camera Check"), lambda: tr("PREVIEW"), callback=self._show_driver_camera) self._reg_and_training = dual_button_item( @@ -1432,9 +1432,9 @@ class IQDeviceLayout(DeviceLayout): self._reg_and_training.action_item.right_button.set_button_style(ButtonStyle.NORMAL) self._onroad_uploads_and_reset_settings = dual_button_item( - left_text=lambda: tr("Onroad Uploads"), + left_text=lambda: tr("Upload While Driving"), left_callback=lambda: ui_state.params.put_bool("OnroadUploads", not ui_state.params.get_bool("OnroadUploads")), - right_text=lambda: tr("Reset Settings"), + right_text=lambda: tr("Restore Defaults"), right_callback=self._reset_settings ) @@ -1535,7 +1535,7 @@ class IQDeviceLayout(DeviceLayout): @staticmethod def _handle_always_offroad(): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Enter Always Offroad Mode"))) + gui_app.set_modal_overlay(alert_dialog(tr("Disengage before forcing offroad"))) return _offroad_mode_state = ui_state.params.get_bool("IQAlwaysOffroad") @@ -1578,7 +1578,7 @@ class IQDeviceLayout(DeviceLayout): @staticmethod def _update_max_time_offroad_label(value: int) -> str: - label = tr("Always On") if value == 0 else f"{value}" + tr("m") if value < 60 else f"{value // 60}" + tr("h") + label = tr("Never Sleep") if value == 0 else f"{value}" + tr("m") if value < 60 else f"{value // 60}" + tr("h") label += tr(" (Default)") if value == 1800 else "" return label @@ -1592,7 +1592,7 @@ class IQDeviceLayout(DeviceLayout): force_onroad_active = force_onroad_until > now # Text & Color - offroad_mode_btn_text = tr("Exit Always Offroad") if always_offroad else tr("Enable Always Offroad") + offroad_mode_btn_text = tr("Exit Offroad Mode") if always_offroad else tr("Force Offroad Mode") offroad_mode_btn_style = ButtonStyle.PRIMARY if always_offroad else ButtonStyle.DANGER self._always_offroad_btn.action_item.left_button.set_text(offroad_mode_btn_text) self._always_offroad_btn.action_item.left_button.set_button_style(offroad_mode_btn_style) @@ -1667,7 +1667,7 @@ class IQDeviceLayout(DeviceLayout): # ===== models ===== if gui_app.iqpilot_ui(): - from openpilot.system.ui.iqpilot.widgets.list_view import button_item as button_item + from openpilot.system.ui.iqwidgets.widgets.list_view import button_item as button_item _ACTIVE_BUNDLE_KEY = "ModelManager_ActiveBundle" _DOWNLOAD_INDEX_KEY = "ModelManager_DownloadIndex" @@ -1692,27 +1692,27 @@ class ModelsLayout(Widget): def _initialize_items(self): self.current_model_item = IQListItem( - title=tr("Current Model"), + title=tr("Active Model"), description="", - action_item=NoElideButtonAction(tr("SELECT")), + action_item=WideButtonAction(tr("SELECT")), callback=self._handle_current_model_clicked ) - self.supercombo_label = progress_item(tr("Driving Model")) - self.vision_label = progress_item(tr("Vision Model")) - self.policy_label = progress_item(tr("Policy Model")) + self.supercombo_label = progress_item(tr("Combined Model")) + self.vision_label = progress_item(tr("Vision Weights")) + self.policy_label = progress_item(tr("Policy Weights")) - self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "", self._on_refresh_models) + self.refresh_item = button_item(tr("Reload Model List"), tr("REFRESH"), "", self._on_refresh_models) self.clear_cache_item = IQListItem( - title=tr("Clear Model Cache"), + title=tr("Purge Model Cache"), description="", - action_item=NoElideButtonAction(tr("CLEAR")), + action_item=WideButtonAction(tr("CLEAR")), callback=self._clear_cache ) self.redownload_item = button_item(tr("Redownload Current Model"), tr("REDOWNLOAD"), "", self._redownload_model) - self.cancel_download_item = button_item(tr("Cancel Download"), tr("Cancel"), "", self._cancel_model_request) + self.cancel_download_item = button_item(tr("Stop Download"), tr("Cancel"), "", self._cancel_model_request) self.items = [self.current_model_item, self.cancel_download_item, self.supercombo_label, self.vision_label, self.policy_label, self.redownload_item, self.refresh_item, self.clear_cache_item] @@ -1938,7 +1938,7 @@ class ModelsLayout(Widget): @staticmethod def _bundle_to_node(bundle): - return TreeNode(bundle.ref, {'display_name': bundle.displayName, 'short_name': bundle.internalName}) + return PickerItem(bundle.ref, {'display_name': bundle.displayName, 'short_name': bundle.internalName}) def _get_folders(self, favorites): bundles = self.model_manager.availableBundles @@ -1946,14 +1946,14 @@ class ModelsLayout(Widget): for bundle in bundles: folders.setdefault(next((ov_ride.value for ov_ride in bundle.overrides if ov_ride.key == "folder"), ""), []).append(bundle) - folders_list = [TreeFolder("", [TreeNode("Default", {'display_name': tr("Default (CD210)"), 'short_name': "Default"})])] + folders_list = [PickerGroup("", [PickerItem("Default", {'display_name': tr("Default (CD210)"), 'short_name': "Default"})])] for folder, folder_bundles in sorted(folders.items(), key=lambda x: max((bundle.index for bundle in x[1]), default=-1), reverse=True): folder_bundles.sort(key=lambda bundle: bundle.index, reverse=True) name = folder + (f" - (Updated: {m.group(1)})" if folder_bundles and (m := re.search(r'\(([^)]*)\)[^(]*$', folder_bundles[0].displayName)) else "") - folders_list.append(TreeFolder(name, [self._bundle_to_node(bundle) for bundle in folder_bundles])) + folders_list.append(PickerGroup(name, [self._bundle_to_node(bundle) for bundle in folder_bundles])) if favorites and (fav_bundles := [bundle for bundle in bundles if bundle.ref in favorites]): - folders_list.insert(1, TreeFolder("Favorites", [self._bundle_to_node(bundle) for bundle in fav_bundles])) + folders_list.insert(1, PickerGroup("Favorites", [self._bundle_to_node(bundle) for bundle in fav_bundles])) return folders_list def _handle_current_model_clicked(self): @@ -1962,7 +1962,7 @@ class ModelsLayout(Widget): folders_list = self._get_folders(favorites) active_ref = self.model_manager.activeBundle.ref if self._has_active_bundle_param() and self.model_manager.activeBundle else "Default" - self.model_dialog = TreeOptionDialog(tr("Select a Model"), folders_list, active_ref, "ModelManager_Favs", + self.model_dialog = PickerDialog(tr("Choose a Model"), folders_list, active_ref, "ModelManager_Favs", get_folders_fn=self._get_folders, on_exit=self._on_model_selected) gui_app.set_modal_overlay(self.model_dialog, callback=self._on_model_selected) @@ -2165,7 +2165,7 @@ class IQSettingsLayout(OP.SettingsLayout): # ===== vehicle_brands_base ===== -class BrandSettings: +class BrandPanel: """Per-brand extra settings rows; brands without any simply don't register.""" def __init__(self): @@ -2184,12 +2184,12 @@ _TUNING_BLURBS = ( ) -class HyundaiSettings(BrandSettings): +class HyundaiSettings(BrandPanel): def __init__(self): super().__init__() self.alpha_long_available = False self.longitudinal_tuning_item = multiple_button_item( - tr("Custom Longitudinal Tuning"), "", + tr("Longitudinal Tune Profile"), "", [tr("Off"), tr("Dynamic"), tr("Predictive")], button_width=300, param="HyundaiLongitudinalTuning", inline=False, callback=lambda index: ui_state.params.put("HyundaiLongitudinalTuning", index)) @@ -2227,14 +2227,14 @@ class HyundaiSettings(BrandSettings): _UNSUPPORTED_FLAGS = SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID -class SubaruSettings(BrandSettings): +class SubaruSettings(BrandPanel): def __init__(self): super().__init__() self._supported = False - self.stop_and_go_toggle = toggle_item(tr("Stop and Go (Beta)"), "", param="SubaruStopAndGo", + self.stop_and_go_toggle = toggle_item(tr("Creep from Standstill (Beta)"), "", param="SubaruStopAndGo", callback=lambda _: self.update_settings()) self.stop_and_go_manual_parking_brake_toggle = toggle_item( - tr("Stop and Go for Manual Parking Brake (Beta)"), "", + tr("Creep from Standstill — Manual Handbrake (Beta)"), "", param="SubaruStopAndGoManualParkingBrake", callback=lambda _: self.update_settings()) self.items = [self.stop_and_go_toggle, self.stop_and_go_manual_parking_brake_toggle] @@ -2283,7 +2283,7 @@ def _speed_text(kmh: int) -> str: return f"{round(kmh * KM_TO_MILE)} mph" -class TeslaSettings(BrandSettings): +class TeslaSettings(BrandPanel): def __init__(self): super().__init__() self.coop_steering_toggle = toggle_item(tr("VTB (Virtual Torque Blending)"), "", param="TeslaCoopSteering") @@ -2306,11 +2306,11 @@ class TeslaSettings(BrandSettings): # ===== vehicle_brands_toyota ===== -class ToyotaSettings(BrandSettings): +class ToyotaSettings(BrandPanel): def __init__(self): super().__init__() self.enforce_stock_longitudinal = toggle_item( - lambda: tr("Enforce Factory Longitudinal Control"), + lambda: tr("Keep Factory Gas and Brake"), description=lambda: tr("Keeps gas and brakes with the factory Toyota system; IQ.Pilot steers only."), initial_state=ui_state.params.get_bool("ToyotaEnforceStockLongitudinal"), callback=self._on_toggled, @@ -2361,7 +2361,7 @@ DESCRIPTIONS = { } -class VolkswagenSettings(BrandSettings): +class VolkswagenSettings(BrandPanel): def __init__(self): super().__init__() @@ -2445,7 +2445,7 @@ class VolkswagenSettings(BrandSettings): # ===== vehicle_brands_factory ===== # Only brands that actually ship extra rows appear here; every other brand gets None. -_REGISTRY: dict[str, type[BrandSettings]] = { +_REGISTRY: dict[str, type[BrandPanel]] = { "hyundai": HyundaiSettings, "subaru": SubaruSettings, "tesla": TeslaSettings, @@ -2454,12 +2454,12 @@ _REGISTRY: dict[str, type[BrandSettings]] = { } -def brand_settings_for(brand: str) -> BrandSettings | None: +def brand_settings_for(brand: str) -> BrandPanel | None: cls = _REGISTRY.get(brand) return cls() if cls else None -class BrandSettingsFactory: +class BrandPanelFactory: """Legacy shim for callers using the old factory name.""" create_brand_settings = staticmethod(brand_settings_for) @@ -2503,7 +2503,7 @@ class VehicleSelection: return bundle.get("name", "") if ui_state.CP and ui_state.CP.carFingerprint != "MOCK": return ui_state.CP.carFingerprint - return tr("No vehicle selected") + return tr("No vehicle set") def force(self, platform_name: str) -> bool: data = self.platforms.get(platform_name) @@ -2516,22 +2516,22 @@ class VehicleSelection: def clear(): ui_state.params.remove("CarPlatformBundle") - def picker_folders(self) -> list[TreeFolder]: - def node_for(name: str) -> TreeNode: + def picker_folders(self) -> list[PickerGroup]: + def node_for(name: str) -> PickerItem: info = self.platforms[name] years = ' '.join(map(str, info.get('year', []))) - return TreeNode(name, { + return PickerItem(name, { 'display_name': name, 'search_tags': f"{name} {info.get('make')} {years} {info.get('model', name)}", }) names = sorted(self.platforms) makes = sorted({self.platforms[n].get('make') for n in names}) - return [TreeFolder(make, [node_for(n) for n in names if self.platforms[n].get('make') == make]) + return [PickerGroup(make, [node_for(n) for n in names if self.platforms[n].get('make') == make]) for make in makes] -class PlatformSelector(Button): +class VehiclePicker(Button): """Row button that either clears a forced platform or opens the vehicle picker.""" def __init__(self, on_platform_change: Callable[[], None] | None = None): @@ -2574,7 +2574,7 @@ class PlatformSelector(Button): self._open_picker() def _open_picker(self): - dialog = TreeOptionDialog( + dialog = PickerDialog( tr("Pick your vehicle"), self.selection.picker_folders(), search_prompt=tr("Search make or model"), @@ -2600,16 +2600,16 @@ class PlatformSelector(Button): _LEGEND = ( - (FingerprintStatus.AUTO, lambda: tr("Fingerprinted automatically")), - (FingerprintStatus.FORCED, lambda: tr("Manually selected fingerprint")), - (FingerprintStatus.NONE, lambda: tr("Not fingerprinted or manually selected")), + (FingerprintStatus.AUTO, lambda: tr("Detected automatically")), + (FingerprintStatus.FORCED, lambda: tr("Chosen manually")), + (FingerprintStatus.NONE, lambda: tr("Undetected and unset")), ) class LegendWidget(Widget): """Explains the fingerprint status colours; the active row is highlighted.""" - def __init__(self, platform_selector: PlatformSelector): + def __init__(self, platform_selector: VehiclePicker): super().__init__() self.set_rect(rl.Rectangle(0, 0, 0, 350)) self._selector = platform_selector @@ -2619,9 +2619,9 @@ class LegendWidget(Widget): def _render(self, rect): x = rect.x + 20 y = rect.y + 20 - rl.draw_text_ex(self._font, tr("Select vehicle to force fingerprint manually."), rl.Vector2(x, y), 40, 0, ink.CAPTION) + rl.draw_text_ex(self._font, tr("Pin a vehicle to skip auto-detection."), rl.Vector2(x, y), 40, 0, ink.CAPTION) y += 80 - rl.draw_text_ex(self._font, tr("Colors represent vehicle fingerprint status:"), rl.Vector2(x, y), 40, 0, ink.CAPTION) + rl.draw_text_ex(self._font, tr("Colour key for fingerprint state:"), rl.Vector2(x, y), 40, 0, ink.CAPTION) y += 80 active = self._selector.status @@ -2650,7 +2650,7 @@ class VehicleLayout(Widget): super().__init__() self._brand_settings = None self._current_brand = None - self._platform_selector = PlatformSelector(self._on_vehicle_changed) + self._platform_selector = VehiclePicker(self._on_vehicle_changed) self._vehicle_item = IQListItem(title=self._platform_selector.text, action_item=ButtonAction(text=tr("SELECT")), callback=self._platform_selector._on_clicked) self._legend_widget = LegendWidget(self._platform_selector) diff --git a/iqpilot/ui/mici/onroad/hud_renderer.py b/iqpilot/ui/mici/onroad/hud_renderer.py index 5895ffa..e080640 100644 --- a/iqpilot/ui/mici/onroad/hud_renderer.py +++ b/iqpilot/ui/mici/onroad/hud_renderer.py @@ -4,7 +4,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed import pyray as rl from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer -from openpilot.iqpilot.ui.onroad.hud_overlays import BlindSpotIndicators +from openpilot.iqpilot.ui.onroad.hud_overlays import IQBlindSpotOverlay class IQMiciHudRenderer(HudRenderer): @@ -16,7 +16,7 @@ class IQMiciHudRenderer(HudRenderer): def __init__(self): super().__init__() - self._overlays = [BlindSpotIndicators()] + self._overlays = [IQBlindSpotOverlay()] def _update_state(self) -> None: super()._update_state() diff --git a/iqpilot/ui/onroad/driver_state.py b/iqpilot/ui/onroad/driver_state.py index 216d4b5..1f3a93d 100644 --- a/iqpilot/ui/onroad/driver_state.py +++ b/iqpilot/ui/onroad/driver_state.py @@ -8,7 +8,7 @@ import pyray as rl from openpilot.common.params import Params from openpilot.selfdrive.ui import UI_BORDER_SIZE from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer, BTN_SIZE, ARC_LENGTH -from openpilot.iqpilot.ui.onroad.hud_overlays import DeveloperUiRenderer +from openpilot.iqpilot.ui.onroad.hud_overlays import IQDevMetricsOverlay from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached @@ -50,7 +50,7 @@ class DriverStateRendererIQ(DriverStateRenderer): self._toast_color: rl.Color = rl.WHITE self._font = gui_app.font(FontWeight.SEMI_BOLD) - self.dev_ui_offset = DeveloperUiRenderer.get_bottom_dev_ui_offset() + self.dev_ui_offset = IQDevMetricsOverlay.get_bottom_dev_ui_offset() self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", BTN_SIZE, BTN_SIZE) self._dm_person = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_person.png", 118, 118) self._dm_cone = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_cone.png", 118, 118) diff --git a/iqpilot/ui/onroad/hud_overlays.py b/iqpilot/ui/onroad/hud_overlays.py index 489e6a4..33598f6 100644 --- a/iqpilot/ui/onroad/hud_overlays.py +++ b/iqpilot/ui/onroad/hud_overlays.py @@ -22,7 +22,7 @@ from openpilot.selfdrive.ui.mici.onroad.alert_renderer import IconSide, TURN_SIG from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.iqpilot.lib import canvas +from openpilot.system.ui.iqwidgets.lib import canvas from iqdbc.car.volkswagen.values import VolkswagenFlags @@ -40,13 +40,29 @@ def _speed_scale() -> float: # Accel strip # ============================================================================ _STRIP_WIDTH = 28 +_STRIP_INSET = 14 _STRIP_CEILING = 0.85 _STRIP_EMA = 5.0 -_STRIP_GO = canvas.shade(0, 245, 0, 200) -_STRIP_STOP = canvas.shade(245, 0, 0, 200) +_STRIP_ARC_SEGMENTS = 24 +_STRIP_ACCEL = (33, 112, 115) +_STRIP_ACCEL_NEON = (94, 232, 236) +_STRIP_DECEL = (255, 0, 247) +_STRIP_DECEL_NEON = (255, 145, 251) +_STRIP_TIP_ALPHA = 235 +_STRIP_ROOT_ALPHA = 55 +_STRIP_CORE_DEPTH = 7.0 +_STRIP_CORE_LAYERS = 8 +_STRIP_CORE_ALPHA = 0.15 +_STRIP_CORE_FLOOR = 0.3 +_STRIP_CORE_TAIL = 22.0 +_STRIP_HALO_SPREAD = 11.0 +_STRIP_HALO_LAYERS = 11 +_STRIP_HALO_ALPHA = 0.095 +_STRIP_HALO_TAIL = 34.0 +_STRIP_NEON_FULL = 2.0 # m/s^2 at which the edges reach full brightness -class RocketFuel: +class IQAccelBar: def __init__(self): self._eased = 0.0 @@ -54,6 +70,52 @@ class RocketFuel: mag = abs(self._eased) return 0.0 if mag == 0.0 else max(0.0, _STRIP_CEILING - 0.1 / mag) + @staticmethod + def _tint(fill, frac: float): + return canvas.shade(*fill, int(_STRIP_TIP_ALPHA + (_STRIP_ROOT_ALPHA - _STRIP_TIP_ALPHA) * frac)) + + @staticmethod + def _halo(center, cap: float, up: bool, neon, heat: float, tail: float) -> None: + step = int(255 * _STRIP_HALO_ALPHA * heat) + if step < 1: + return + start, end = (180.0, 360.0) if up else (0.0, 180.0) + tint = canvas.shade(*neon, step) + faded = canvas.shade(*neon, 0) + top, bottom = (tint, faded) if up else (faded, tint) + for i in range(_STRIP_HALO_LAYERS): + spread = _STRIP_HALO_SPREAD * (1.0 - i / _STRIP_HALO_LAYERS) + if spread <= 0.0: + continue + canvas.annulus(center, cap, cap + spread, start, end, _STRIP_ARC_SEGMENTS, tint) + if tail <= 0.0: + continue + run = min(tail, _STRIP_HALO_TAIL) + y = center.y if up else center.y - run + canvas.v_sweep(center.x - cap - spread, y, spread, run, top, bottom) + canvas.v_sweep(center.x + cap, y, spread, run, top, bottom) + + @staticmethod + def _cap(center, cap: float, up: bool, fill, neon, heat: float, tail: float) -> None: + start, end = (180.0, 360.0) if up else (0.0, 180.0) + canvas.annulus(center, 0.0, cap, start, end, _STRIP_ARC_SEGMENTS, fill) + IQAccelBar._halo(center, cap, up, neon, heat, tail) + lit = _STRIP_CORE_FLOOR + (1.0 - _STRIP_CORE_FLOOR) * heat + core = canvas.shade(*neon, max(1, int(255 * _STRIP_CORE_ALPHA * lit))) + faded = canvas.shade(*neon, 0) + top, bottom = (core, faded) if up else (faded, core) + run = min(tail, _STRIP_CORE_TAIL) + for i in range(_STRIP_CORE_LAYERS): + depth = _STRIP_CORE_DEPTH * (1.0 - i / _STRIP_CORE_LAYERS) + if depth <= 0.0: + continue + canvas.annulus(center, max(0.0, cap - depth), cap, start, end, _STRIP_ARC_SEGMENTS, core) + if run <= 0.0: + continue + y = center.y if up else center.y - run + canvas.v_sweep(center.x - cap, y, depth, run, top, bottom) + canvas.v_sweep(center.x + cap - depth, y, depth, run, top, bottom) + def render(self, rect, sm) -> None: if not ui_state.rocket_fuel: return @@ -61,9 +123,21 @@ class RocketFuel: reach = self._reach() * rect.height / 2.0 if reach <= 0.0: return + accelerating = self._eased > 0.0 mid = rect.y + rect.height / 2.0 - top, tint = (mid - reach, _STRIP_GO) if self._eased > 0.0 else (mid, _STRIP_STOP) - canvas.slab(rect.x, top, _STRIP_WIDTH, reach, tint) + top = mid - reach if accelerating else mid + x = rect.x + _STRIP_INSET + cap = min(_STRIP_WIDTH / 2.0, reach / 2.0) + fill, neon = (_STRIP_ACCEL, _STRIP_ACCEL_NEON) if accelerating else (_STRIP_DECEL, _STRIP_DECEL_NEON) + near = cap / reach + frac_top, frac_bottom = (near, 1.0 - near) if accelerating else (1.0 - near, near) + heat = min(abs(self._eased) / _STRIP_NEON_FULL, 1.0) + + canvas.v_sweep(x, top + cap, cap * 2.0, reach - cap * 2.0, + self._tint(fill, frac_top), self._tint(fill, frac_bottom)) + tail = max(0.0, reach / 2.0 - cap) + self._cap(canvas.Pt(x + cap, top + cap), cap, True, self._tint(fill, frac_top), neon, heat, tail) + self._cap(canvas.Pt(x + cap, top + reach - cap), cap, False, self._tint(fill, frac_bottom), neon, heat, tail) # ============================================================================ @@ -92,7 +166,7 @@ class _BlindSide: canvas.stamp(tex, x, rect.y + _BS_DROP, canvas.shade(255, 255, 255, int(255 * self.glow.x))) -class BlindSpotIndicators: +class IQBlindSpotOverlay: def __init__(self): self._left = _BlindSide("left") self._right = _BlindSide("right") @@ -121,7 +195,7 @@ _MQB_CLUSTER_EXEMPT = (VolkswagenFlags.PQ | VolkswagenFlags.MLB | VolkswagenFlag VolkswagenFlags.MEB_GEN2 | VolkswagenFlags.MQB_EVO) -class SpeedRenderer: +class IQSpeedOverlay: def __init__(self): self.speed: float = 0.0 self._cluster_ever_live: bool = False @@ -285,7 +359,7 @@ def _lamp_modes(event_name: str, cs, remembered): return left, right, None -class TurnSignalController: +class IQTurnSignalOverlay: def __init__(self, config: TurnSignalConfig | None = None): self._config = config or TurnSignalConfig() self._lamps = {IconSide.left: _IndicatorLamp(IconSide.left), @@ -702,7 +776,7 @@ def _desired_probe(sm): return desired_lat_accel -class DeveloperUiRenderer(Widget): +class IQDevMetricsOverlay(Widget): DEV_UI_OFF = 0 DEV_UI_RIGHT = 1 DEV_UI_BOTTOM = 2 @@ -716,7 +790,7 @@ class DeveloperUiRenderer(Widget): @staticmethod def get_bottom_dev_ui_offset(): - return DeveloperUiRenderer.BOTTOM_BAR_HEIGHT if ui_state.developer_ui != DeveloperUiRenderer.DEV_UI_OFF else 0 + return IQDevMetricsOverlay.BOTTOM_BAR_HEIGHT if ui_state.developer_ui != IQDevMetricsOverlay.DEV_UI_OFF else 0 def _update_state(self) -> None: self.dev_ui_mode = ui_state.developer_ui @@ -775,7 +849,7 @@ def _dim(color, alpha: float): return canvas.with_opacity(color, 255 * alpha) -class SpeedLimitRenderer(Widget): +class IQSpeedLimitOverlay(Widget): """Regulatory sign, upcoming-limit preview and pre-active nudge arrows.""" def __init__(self): diff --git a/iqpilot/ui/onroad/hud_renderer.py b/iqpilot/ui/onroad/hud_renderer.py index 59930d4..d0505db 100644 --- a/iqpilot/ui/onroad/hud_renderer.py +++ b/iqpilot/ui/onroad/hud_renderer.py @@ -11,12 +11,12 @@ from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer from openpilot.iqpilot.ui.onroad.hud_overlays import ( - DeveloperUiRenderer, + IQDevMetricsOverlay, RoadNameRenderer, - RocketFuel, - SpeedLimitRenderer, - TurnSignalController, - SpeedRenderer, + IQAccelBar, + IQSpeedLimitOverlay, + IQTurnSignalOverlay, + IQSpeedOverlay, ) from openpilot.iqpilot.ui.onroad.nav_map_panel import NavMapPanel from openpilot.iqpilot.ui.onroad.soft_warning import SoftWarningRenderer @@ -28,13 +28,13 @@ ENABLE_SPLIT_NAV_MAP_PANEL = True class IQHudRenderer(HudRenderer): def __init__(self): super().__init__() - self.developer_ui = DeveloperUiRenderer() + self.developer_ui = IQDevMetricsOverlay() self.nav_map_panel = NavMapPanel() self.road_name_renderer = RoadNameRenderer() - self.rocket_fuel = RocketFuel() - self.speed_limit_renderer = SpeedLimitRenderer() - self.turn_signal_controller = TurnSignalController() - self.speed_renderer = SpeedRenderer() + self.rocket_fuel = IQAccelBar() + self.speed_limit_renderer = IQSpeedLimitOverlay() + self.turn_signal_controller = IQTurnSignalOverlay() + self.speed_renderer = IQSpeedOverlay() self.soft_warning_renderer = SoftWarningRenderer() self._torque_bar = TorqueBar(scale=3.0, always=True) @@ -59,8 +59,8 @@ class IQHudRenderer(HudRenderer): if ui_state.torque_bar: torque_rect = rect - if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_BOTTOM, DeveloperUiRenderer.DEV_UI_BOTH): - torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - DeveloperUiRenderer.BOTTOM_BAR_HEIGHT) + if ui_state.developer_ui in (IQDevMetricsOverlay.DEV_UI_BOTTOM, IQDevMetricsOverlay.DEV_UI_BOTH): + torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - IQDevMetricsOverlay.BOTTOM_BAR_HEIGHT) self._torque_bar.render(torque_rect) if not self.split_nav_enabled(): diff --git a/launch_env.sh b/launch_env.sh index 1ca9150..2869567 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -23,7 +23,7 @@ if [ -z "$AGNOS_VERSION" ]; then case "$DEVICE_MODEL" in *) - export AGNOS_VERSION="IQ.OS 4.8" + export AGNOS_VERSION="IQ.OS 4.9" ;; esac fi diff --git a/msgq_repo/msgq/msgq.cc b/msgq_repo/msgq/msgq.cc index d9e4ed4..2b2493a 100644 --- a/msgq_repo/msgq/msgq.cc +++ b/msgq_repo/msgq/msgq.cc @@ -161,6 +161,7 @@ void msgq_init_publisher(msgq_queue_t * q) { //std::cout << "Starting publisher" << std::endl; uint64_t uid = msgq_get_uid(); + q->last_reap_us = 0; *q->write_uid = uid; *q->num_readers = 0; @@ -173,6 +174,7 @@ void msgq_init_publisher(msgq_queue_t * q) { } static void thread_signal(uint32_t tid) { + if (tid == 0) return; #ifdef __APPLE__ // macOS doesn't have tkill, rely on polling instead (void)tid; @@ -184,6 +186,39 @@ static void thread_signal(uint32_t tid) { #endif } +#ifdef __linux__ +// Zero reader slots whose owning thread died without closing, so the notify loop +// stops signaling their TIDs: the kernel recycles TIDs, and a stale slot means +// every send fires SIGUSR2 at an unrelated process — fatal for one with no +// handler installed (e.g. a subscriber's short-lived compiler child). Amortized +// to ~1Hz per publisher; reclaim-at-overflow alone leaves stale slots signaled +// indefinitely while the table has free space. +static void msgq_reap_dead_readers(msgq_queue_t *q) { + int64_t now_us = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); + if (now_us - q->last_reap_us < 1000000) + return; + q->last_reap_us = now_us; + + uint64_t num_readers = std::min(NUM_READERS, *q->num_readers); + for (uint64_t i = 0; i < num_readers; i++){ + uint64_t slot_uid = *q->read_uids[i]; + uint32_t slot_tid = slot_uid & 0xFFFFFFFF; + if (slot_uid == 0 || slot_tid == 0) + continue; + char proc_path[32]; + snprintf(proc_path, sizeof(proc_path), "/proc/%u", slot_tid); + struct stat st; + if (stat(proc_path, &st) == 0) + continue; + uint64_t expected = slot_uid; + if (std::atomic_compare_exchange_strong(q->read_uids[i], &expected, (uint64_t)0)){ + *q->read_valids[i] = false; + } + } +} +#endif + // claim slot i (its uid CAS already succeeded) for the calling thread static void msgq_claim_slot(msgq_queue_t * q, size_t i, uint64_t uid) { q->reader_id = i; @@ -378,6 +413,10 @@ int msgq_msg_send(msgq_msg_t * msg, msgq_queue_t *q){ uint32_t new_ptr = ALIGN(write_pointer + msg->size + sizeof(int64_t)); PACK64(*q->write_pointer, write_cycles, new_ptr); +#ifdef __linux__ + msgq_reap_dead_readers(q); +#endif + // Notify readers for (uint64_t i = 0; i < num_readers; i++){ uint64_t reader_uid = *q->read_uids[i]; diff --git a/msgq_repo/msgq/msgq.h b/msgq_repo/msgq/msgq.h index 195ae06..539e4dc 100644 --- a/msgq_repo/msgq/msgq.h +++ b/msgq_repo/msgq/msgq.h @@ -41,6 +41,7 @@ struct msgq_queue_t { int reader_id; uint64_t read_uid_local; uint64_t write_uid_local; + int64_t last_reap_us; bool read_conflate; std::string endpoint; diff --git a/panda/board/drivers/harness.h b/panda/board/drivers/harness.h index 3ebf109..883cb32 100644 --- a/panda/board/drivers/harness.h +++ b/panda/board/drivers/harness.h @@ -14,8 +14,10 @@ void set_intercept_relay(bool intercept, bool ignition_relay) { harness.relay_driven = true; } - // wait until we're not reading the analog voltages anymore - while (harness.sbu_adc_lock) {} + // The relay pins are separate from the SBU sense pins, so no need to wait for + // orientation sampling: it runs in thread context, sees relay_driven set, and + // discards its result. Spinning on sbu_adc_lock here would deadlock when called + // from interrupt context while thread-context sampling holds the lock. if (harness.status == HARNESS_STATUS_NORMAL) { set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !ignition_relay); @@ -31,20 +33,25 @@ void set_intercept_relay(bool intercept, bool ignition_relay) { } bool harness_check_ignition(void) { - bool ret = false; + // The SBU pins are in analog mode while orientation sampling is in flight + // (thread context), so a digital read would return 0. Return the last good + // value instead of spinning: this is called from interrupt context, where + // waiting for preempted thread-context sampling to finish would deadlock. + bool ret = harness.ignition_line; - // wait until we're not reading the analog voltages anymore - while (harness.sbu_adc_lock) {} - - switch(harness.status){ - case HARNESS_STATUS_NORMAL: - ret = !get_gpio_input(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1); - break; - case HARNESS_STATUS_FLIPPED: - ret = !get_gpio_input(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2); - break; - default: - break; + if (!harness.sbu_adc_lock) { + switch(harness.status){ + case HARNESS_STATUS_NORMAL: + ret = !get_gpio_input(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1); + break; + case HARNESS_STATUS_FLIPPED: + ret = !get_gpio_input(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2); + break; + default: + ret = false; + break; + } + harness.ignition_line = ret; } return ret; } @@ -81,6 +88,12 @@ static uint8_t harness_detect_orientation(void) { set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_INPUT); set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_INPUT); harness.sbu_adc_lock = false; + + // This runs in thread context and can be preempted by an interrupt driving + // the relay mid-sample, which changes the SBU line voltages; discard + if (harness.relay_driven) { + ret = harness.status; + } } #endif diff --git a/panda/board/drivers/harness_declarations.h b/panda/board/drivers/harness_declarations.h index 6f4327a..6f7af28 100644 --- a/panda/board/drivers/harness_declarations.h +++ b/panda/board/drivers/harness_declarations.h @@ -4,12 +4,15 @@ #define HARNESS_STATUS_NORMAL 1U #define HARNESS_STATUS_FLIPPED 2U +// orientation sampling runs in thread context and shares this state with +// interrupt handlers, so the fields are volatile struct harness_t { - uint8_t status; - uint16_t sbu1_voltage_mV; - uint16_t sbu2_voltage_mV; - bool relay_driven; - bool sbu_adc_lock; + volatile uint8_t status; + volatile uint16_t sbu1_voltage_mV; + volatile uint16_t sbu2_voltage_mV; + volatile bool ignition_line; + volatile bool relay_driven; + volatile bool sbu_adc_lock; }; extern struct harness_t harness; diff --git a/panda/board/main.c b/panda/board/main.c index 0cfd708..8033ad6 100644 --- a/panda/board/main.c +++ b/panda/board/main.c @@ -117,6 +117,22 @@ static void __attribute__ ((noinline)) enable_fpu(void) { #define HEARTBEAT_IGNITION_CNT_OFF 2U // called at 8Hz +static volatile bool tick_sample_pending = false; + +// All runtime ADC sampling runs here, in thread context. ADC conversions +// busy-wait for tens to hundreds of microseconds, and all interrupts share one +// NVIC priority: sampling in the 8Hz tick interrupt delayed the SPI slave's +// RX DMA re-arm long enough for the host to clock into an unarmed peripheral +// (checksum failures -> NACK retry storms -> multi-second CAN blackouts). +static void tick_sample_poll(void) { + if (tick_sample_pending) { + tick_sample_pending = false; + harness_tick(); + voltage_mV = current_board->read_voltage_mV(); + current_mA = current_board->read_current_mA(); + } +} + static void tick_handler(void) { static uint32_t siren_countdown = 0; // siren plays while countdown > 0 static uint32_t controls_allowed_countdown = 0; @@ -131,7 +147,7 @@ static void tick_handler(void) { // tick drivers at 8Hz fan_tick(); - harness_tick(); + tick_sample_pending = true; // ADC sampling deferred to thread context (see tick_sample_poll) simple_watchdog_kick(); sound_tick(); @@ -304,6 +320,10 @@ int main(void) { current_board->set_can_mode(CAN_MODE_NORMAL); harness_init(); + // seed the ADC caches before interrupts are live + voltage_mV = current_board->read_voltage_mV(); + current_mA = current_board->read_current_mA(); + // panda has an FPU, let's use it! enable_fpu(); @@ -347,6 +367,7 @@ int main(void) { // LED should keep on blinking all the time while (true) { + tick_sample_poll(); if (power_save_status == POWER_SAVE_STATUS_DISABLED) { #ifdef DEBUG_FAULTS if (fault_status == FAULT_STATUS_NONE) { @@ -357,6 +378,7 @@ int main(void) { delay(fade >> 4); led_set(LED_RED, false); delay((MAX_LED_FADE - fade) >> 4); + tick_sample_poll(); } for (uint32_t fade = MAX_LED_FADE; fade > 0U; fade -= 1U) { @@ -364,6 +386,7 @@ int main(void) { delay(fade >> 4); led_set(LED_RED, false); delay((MAX_LED_FADE - fade) >> 4); + tick_sample_poll(); } #ifdef DEBUG_FAULTS diff --git a/panda/board/main_comms.h b/panda/board/main_comms.h index 6450c0f..c43b208 100644 --- a/panda/board/main_comms.h +++ b/panda/board/main_comms.h @@ -9,8 +9,10 @@ static int get_health_pkt(void *dat) { struct health_t * health = (struct health_t*)dat; health->uptime_pkt = uptime_cnt; - health->voltage_pkt = current_board->read_voltage_mV(); - health->current_pkt = current_board->read_current_mA(); + // cached values sampled in thread context (see tick_sample_poll in main.c); + // reading the ADC here would busy-wait in interrupt context + health->voltage_pkt = voltage_mV; + health->current_pkt = current_mA; health->ignition_line_pkt = (uint8_t)(harness_check_ignition()); health->ignition_can_pkt = ignition_can; diff --git a/panda/board/main_declarations.h b/panda/board/main_declarations.h index 52aaa6c..e6737bb 100644 --- a/panda/board/main_declarations.h +++ b/panda/board/main_declarations.h @@ -13,6 +13,10 @@ extern uint8_t hw_type; extern board *current_board; extern uint32_t uptime_cnt; +// ADC results, sampled in thread context (see tick_sample_poll in main.c) +extern uint32_t voltage_mV; +extern uint32_t current_mA; + // heartbeat state extern uint32_t heartbeat_counter; extern bool heartbeat_lost; diff --git a/panda/board/main_definitions.h b/panda/board/main_definitions.h index 83c9109..27d5eb3 100644 --- a/panda/board/main_definitions.h +++ b/panda/board/main_definitions.h @@ -5,6 +5,10 @@ uint8_t hw_type = 0; board *current_board; uint32_t uptime_cnt = 0; +// ADC results, sampled in thread context (see tick_sample_poll in main.c) +uint32_t voltage_mV = 0; +uint32_t current_mA = 0; + // heartbeat state uint32_t heartbeat_counter = 0; bool heartbeat_lost = false; diff --git a/selfdrive/assets/sounds/pre_alert.wav b/selfdrive/assets/sounds/pre_alert.wav new file mode 100644 index 0000000..f83b710 Binary files /dev/null and b/selfdrive/assets/sounds/pre_alert.wav differ diff --git a/selfdrive/assets/sounds/prompt_distracted.wav b/selfdrive/assets/sounds/prompt_distracted.wav index c3d4475..6cad556 100644 Binary files a/selfdrive/assets/sounds/prompt_distracted.wav and b/selfdrive/assets/sounds/prompt_distracted.wav differ diff --git a/selfdrive/assets/sounds/warning_immediate.wav b/selfdrive/assets/sounds/warning_immediate.wav index b1815a9..99b4b03 100644 Binary files a/selfdrive/assets/sounds/warning_immediate.wav and b/selfdrive/assets/sounds/warning_immediate.wav differ diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index a0b6d47..0b2bdc1 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -129,7 +129,7 @@ class Car: self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params, fixed_fingerprint, init_params_list_iq, is_release_iq) - iqpilot_interfaces.setup_interfaces(self.CI, self.params) + iqpilot_interfaces.apply_iq_car_config(self.CI, self.params) self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_IQ) self.CP = self.CI.CP self.CP_IQ = self.CI.CP_IQ diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 286cdbb..2991300 100644 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -39,7 +39,7 @@ class TestCarInterfaces: car_interface.CP.openpilotLongitudinalControl = False car_params = car_interface.CP.as_reader() car_params_iq = car_interface.CP_IQ - iqpilot_interfaces.setup_interfaces(car_interface) + iqpilot_interfaces.apply_iq_car_config(car_interface) cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True) cc_sp_msg = FuzzyGenerator.get_random_msg(data.draw, custom.IQCarControl, real_floats=True) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 1bd7b31..3def978 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -260,7 +260,7 @@ class Controls(IQControlsLayer): hudControl.leadFollowTime = 1.45 hudControl.visualAlert = self.sm['selfdriveState'].alertHudVisual hudControl.audibleAlert = self.sm['selfdriveState'].alertSound - hudControl.driverUnresponsive = self.sm['selfdriveState'].alertType.split('/', 1)[0] == 'driverUnresponsive' + hudControl.driverUnresponsive = self.sm['driverMonitoringState'].noResponseForceDecel hudControl.rightLaneVisible = True hudControl.leftLaneVisible = True @@ -292,7 +292,7 @@ class Controls(IQControlsLayer): cs.upAccelCmd = float(self.LoC.pid.p) cs.uiAccelCmd = float(self.LoC.pid.i) cs.ufAccelCmd = float(self.LoC.pid.f) - cs.forceDecel = bool((self.sm['driverMonitoringState'].awarenessStatus < 0.) or + cs.forceDecel = bool(self.sm['driverMonitoringState'].noResponseForceDecel or (self.sm['selfdriveState'].state == State.softDisabling)) lat_tuning = self.CP.lateralTuning.which() diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index e220569..a407ffa 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -10,8 +10,8 @@ import numpy as np from cereal import log, custom # noqa: F401 (custom kept available for downstream imports) from iqdbc.car import structs from iqdbc.car.lateral import FRICTION_THRESHOLD, get_friction -from iqdbc.iqpilot.car.interfaces import LatControlInputs -from iqdbc.iqpilot.car.lateral_ext import get_friction as get_friction_in_torque_space +from iqdbc.lvbs.car.interfaces import LatControlInputs +from iqdbc.lvbs.car.iq_lateral import get_friction as get_friction_in_torque_space from openpilot.common.basedir import BASEDIR from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY from openpilot.common.filter_simple import FirstOrderFilter diff --git a/selfdrive/controls/tests/test_latcontrol.py b/selfdrive/controls/tests/test_latcontrol.py index 5d54397..93e8f30 100644 --- a/selfdrive/controls/tests/test_latcontrol.py +++ b/selfdrive/controls/tests/test_latcontrol.py @@ -26,7 +26,7 @@ class TestLatControl: CP = CarInterface.get_non_essential_params(car_name) CP_IQ = CarInterface.get_non_essential_params_iq(CP, car_name) CI = CarInterface(CP, CP_IQ) - iqpilot_interfaces.setup_interfaces(CI) + iqpilot_interfaces.apply_iq_car_config(CI) CP_IQ = convert_to_capnp(CP_IQ) VM = VehicleModel(CP) diff --git a/selfdrive/controls/tests/test_latcontrol_torque_buffer.py b/selfdrive/controls/tests/test_latcontrol_torque_buffer.py index 798d49b..06aac5e 100644 --- a/selfdrive/controls/tests/test_latcontrol_torque_buffer.py +++ b/selfdrive/controls/tests/test_latcontrol_torque_buffer.py @@ -17,7 +17,7 @@ def get_controller(car_name): CP = CarInterface.get_non_essential_params(car_name) CP_IQ = CarInterface.get_non_essential_params_iq(CP, car_name) CI = CarInterface(CP, CP_IQ) - iqpilot_interfaces.setup_interfaces(CI) + iqpilot_interfaces.apply_iq_car_config(CI) CP_IQ = convert_to_capnp(CP_IQ) VM = VehicleModel(CP) controller = LatControlTorque(CP.as_reader(), CP_IQ.as_reader(), CI, DT_CTRL) diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index 5b25d8c..c1f30bc 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -30,9 +30,9 @@ def cycle_alerts(duration=200, is_metric=False): (EventName.accFaulted, ET.IMMEDIATE_DISABLE), # DM sequence - (EventName.preDriverDistracted, ET.WARNING), - (EventName.promptDriverDistracted, ET.WARNING), - (EventName.driverDistracted, ET.WARNING), + (EventName.driverDistracted1, ET.WARNING), + (EventName.driverDistracted2, ET.WARNING), + (EventName.driverDistracted3, ET.WARNING), ] # debug alerts diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 40e7401..71989c8 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -60,7 +60,7 @@ def host_tinygrad_flags(*, float16=False): return f"{base} FLOAT16=1" if float16 else base # Compile small models -for model_name in ['dmonitoring_model']: +for model_name in ['dmonitoring_model', 'dmonitoring_model_mici']: # The optimization flags are mandatory on QCOM: without FLOAT16/NOLOCALS/JIT_BATCH_SIZE/OPENPILOT_HACKS these # models compile to unoptimized QCOM kernels and run ~20x slower (dmonitoring_model: ~300ms -> ~14ms), # which starves the driving model on the shared Adreno. IMAGE=2 (not upstream's IMAGE=1) because the diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 0e50932..422a4a9 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -20,19 +20,25 @@ from openpilot.selfdrive.locationd.calibration_helpers import get_calibrated_rpy from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address +from openpilot.system.hardware import HARDWARE PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') -MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' -METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' +MODELS_PATH = Path(__file__).parent / 'models' + + +def get_model_paths(device_type: str) -> tuple[Path, Path]: + model_name = 'dmonitoring_model_mici' if device_type == 'mici' else 'dmonitoring_model' + return MODELS_PATH / f'{model_name}_tinygrad.pkl', MODELS_PATH / f'{model_name}_metadata.pkl' class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self, cl_ctx): - with open(METADATA_PATH, 'rb') as f: + def __init__(self, cl_ctx, device_type=None): + model_path, metadata_path = get_model_paths(device_type or HARDWARE.get_device_type()) + with open(metadata_path, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] @@ -43,7 +49,7 @@ class ModelState: } self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} - with open(MODEL_PKL_PATH, "rb") as f: + with open(model_path, "rb") as f: self.model_run = pickle.load(f) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: @@ -75,8 +81,10 @@ def parse_model_output(model_output): face_descs = model_output[f'face_descs_{ds_suffix}'] parsed[f'face_descs_{ds_suffix}'] = face_descs[:, :-6] parsed[f'face_descs_{ds_suffix}_std'] = safe_exp(face_descs[:, -6:]) - for key in ['face_prob', 'left_eye_prob', 'right_eye_prob','left_blink_prob', 'right_blink_prob', 'sunglasses_prob', 'using_phone_prob']: - parsed[f'{key}_{ds_suffix}'] = sigmoid(model_output[f'{key}_{ds_suffix}']) + for key in ['face_prob', 'left_eye_prob', 'right_eye_prob','left_blink_prob', 'right_blink_prob', 'sunglasses_prob', 'using_phone_prob', 'sleep_prob']: + output_key = f'{key}_{ds_suffix}' + if output_key in model_output: + parsed[output_key] = sigmoid(model_output[output_key]) return parsed def fill_driver_data(msg, model_output, ds_suffix): @@ -91,6 +99,8 @@ def fill_driver_data(msg, model_output, ds_suffix): msg.rightBlinkProb = model_output[f'right_blink_prob_{ds_suffix}'][0, 0].item() msg.sunglassesProb = model_output[f'sunglasses_prob_{ds_suffix}'][0, 0].item() msg.phoneProb = model_output[f'using_phone_prob_{ds_suffix}'][0, 0].item() + sleep_prob = model_output.get(f'sleep_prob_{ds_suffix}') + msg.sleepProb = sleep_prob[0, 0].item() if sleep_prob is not None else 0. def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_time: float, gpu_exec_time: float): msg = messaging.new_message('driverStateV2', valid=True) diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 51d1849..873c74b 100644 Binary files a/selfdrive/modeld/models/dmonitoring_model.onnx and b/selfdrive/modeld/models/dmonitoring_model.onnx differ diff --git a/selfdrive/modeld/models/dmonitoring_model_metadata.pkl b/selfdrive/modeld/models/dmonitoring_model_metadata.pkl index 7869f9d..9aad8d4 100644 Binary files a/selfdrive/modeld/models/dmonitoring_model_metadata.pkl and b/selfdrive/modeld/models/dmonitoring_model_metadata.pkl differ diff --git a/selfdrive/modeld/models/dmonitoring_model_mici.onnx b/selfdrive/modeld/models/dmonitoring_model_mici.onnx new file mode 100644 index 0000000..51d1849 Binary files /dev/null and b/selfdrive/modeld/models/dmonitoring_model_mici.onnx differ diff --git a/selfdrive/modeld/models/dmonitoring_model_mici_metadata.pkl b/selfdrive/modeld/models/dmonitoring_model_mici_metadata.pkl new file mode 100644 index 0000000..7869f9d Binary files /dev/null and b/selfdrive/modeld/models/dmonitoring_model_mici_metadata.pkl differ diff --git a/selfdrive/modeld/models/dmonitoring_model_mici_tinygrad.pkl b/selfdrive/modeld/models/dmonitoring_model_mici_tinygrad.pkl new file mode 100644 index 0000000..a89f4f1 Binary files /dev/null and b/selfdrive/modeld/models/dmonitoring_model_mici_tinygrad.pkl differ diff --git a/selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl b/selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl index a89f4f1..8f33bbb 100644 Binary files a/selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl and b/selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl differ diff --git a/selfdrive/modeld/models/prebuilt_check.json b/selfdrive/modeld/models/prebuilt_check.json index 6d0839d..1014aa4 100644 --- a/selfdrive/modeld/models/prebuilt_check.json +++ b/selfdrive/modeld/models/prebuilt_check.json @@ -1,8 +1,15 @@ { "dmonitoring_model": { "outputs": { - "dmonitoring_model_metadata.pkl": "31a86ab7a92dc0af088b15787a440dd3b210aa662e445a15145900e559a1b5c3", - "dmonitoring_model_tinygrad.pkl": "806c0ea75df6bf6dfeb81b832314c68e31df5865a52d0359e6eeb76d93ad2b52" + "dmonitoring_model_metadata.pkl": "5999c262b1c25c62e485fb4ced5806d20a8ca59e3ae94e0ff499c0fe3497fedc", + "dmonitoring_model_tinygrad.pkl": "5aca89a35b42376d56f67ccd28a1080806706d546dd8c63f6e1b4c681f8e2c01" + }, + "signature": "2364ebd4bb95c4b4e539c9b1ba68324b713cbb56617396b73262accf9cdcfbfc" + }, + "dmonitoring_model_mici": { + "outputs": { + "dmonitoring_model_mici_metadata.pkl": "31a86ab7a92dc0af088b15787a440dd3b210aa662e445a15145900e559a1b5c3", + "dmonitoring_model_mici_tinygrad.pkl": "806c0ea75df6bf6dfeb81b832314c68e31df5865a52d0359e6eeb76d93ad2b52" }, "signature": "e1eeb5ce45774a816c8da2394e6ee35ebf700b71dff345e0141dbab8ff592349" } diff --git a/selfdrive/modeld/prebuilt_models.py b/selfdrive/modeld/prebuilt_models.py index 73a16de..384939f 100644 --- a/selfdrive/modeld/prebuilt_models.py +++ b/selfdrive/modeld/prebuilt_models.py @@ -11,7 +11,7 @@ BASEDIR = MODELD_DIR.parents[1] TINYGRAD_DIR = BASEDIR / 'tinygrad_repo' METADATA_SCRIPT = MODELD_DIR / 'get_model_metadata.py' -MODEL_NAMES = ['dmonitoring_model'] +MODEL_NAMES = ['dmonitoring_model', 'dmonitoring_model_mici'] def _hash_file(h, path: Path) -> None: diff --git a/selfdrive/modeld/test_dmonitoringmodeld.py b/selfdrive/modeld/test_dmonitoringmodeld.py new file mode 100644 index 0000000..daf7be6 --- /dev/null +++ b/selfdrive/modeld/test_dmonitoringmodeld.py @@ -0,0 +1,37 @@ +import pickle + +import numpy as np +import pytest + +from openpilot.selfdrive.modeld.dmonitoringmodeld import get_driverstate_packet, get_model_paths, parse_model_output, slice_outputs + + +@pytest.mark.parametrize("device_type, model_name", [ + ("mici", "dmonitoring_model_mici"), + ("tici", "dmonitoring_model"), + ("tizi", "dmonitoring_model"), +]) +def test_model_paths(device_type, model_name): + model_path, metadata_path = get_model_paths(device_type) + + assert model_path.name == f"{model_name}_tinygrad.pkl" + assert metadata_path.name == f"{model_name}_metadata.pkl" + + +@pytest.mark.parametrize("device_type, expected_sleep_prob", [ + ("mici", 0.), + ("tici", 0.5), + ("tizi", 0.5), +]) +def test_sleep_probability_output(device_type, expected_sleep_prob): + _, metadata_path = get_model_paths(device_type) + with open(metadata_path, 'rb') as f: + metadata = pickle.load(f) + + output = np.zeros(metadata['output_shapes']['outputs'][1], dtype=np.float32) + parsed = parse_model_output(slice_outputs(output, metadata['output_slices'])) + parsed['raw_pred'] = b'' + msg = get_driverstate_packet(parsed, 1, 0, 0., 0.) + + assert msg.driverStateV2.leftDriverData.sleepProb == expected_sleep_prob + assert msg.driverStateV2.rightDriverData.sleepProb == expected_sleep_prob diff --git a/selfdrive/modeld/test_prebuilt_models.py b/selfdrive/modeld/test_prebuilt_models.py index a994982..1b05e51 100644 --- a/selfdrive/modeld/test_prebuilt_models.py +++ b/selfdrive/modeld/test_prebuilt_models.py @@ -1,51 +1,52 @@ import hashlib import json +import pytest + from openpilot.selfdrive.modeld import prebuilt_models -def write_outputs(models_dir, check_path): - outputs = {} - for name, contents in { - 'dmonitoring_model_tinygrad.pkl': b'tinygrad', - 'dmonitoring_model_metadata.pkl': b'metadata', - }.items(): - (models_dir / name).write_bytes(contents) - outputs[name] = hashlib.sha256(contents).hexdigest() - check_path.write_text(json.dumps({'dmonitoring_model': {'outputs': outputs}})) +def write_outputs(models_dir, check_path, model_names): + checks = {} + for model_name in model_names: + outputs = {} + for suffix, contents in { + 'tinygrad.pkl': b'tinygrad', + 'metadata.pkl': b'metadata', + }.items(): + name = f'{model_name}_{suffix}' + (models_dir / name).write_bytes(contents) + outputs[name] = hashlib.sha256(contents).hexdigest() + checks[model_name] = {'outputs': outputs} + check_path.write_text(json.dumps(checks)) -def test_packaged_prebuilt_without_onnx(tmp_path, monkeypatch): +@pytest.fixture +def packaged_models(tmp_path, monkeypatch): models_dir = tmp_path / 'models' models_dir.mkdir() check_path = models_dir / 'prebuilt_check.json' - write_outputs(models_dir, check_path) + write_outputs(models_dir, check_path, prebuilt_models.MODEL_NAMES) monkeypatch.setattr(prebuilt_models, 'MODELS_DIR', models_dir) monkeypatch.setattr(prebuilt_models, 'CHECK_PATH', check_path) - - assert prebuilt_models.packaged_prebuilt_matches('dmonitoring_model') - assert not prebuilt_models.verify_prebuilt('dmonitoring_model', 'flags') + return models_dir -def test_packaged_prebuilt_rejects_corrupt_output(tmp_path, monkeypatch): - models_dir = tmp_path / 'models' - models_dir.mkdir() - check_path = models_dir / 'prebuilt_check.json' - write_outputs(models_dir, check_path) - (models_dir / 'dmonitoring_model_tinygrad.pkl').write_bytes(b'corrupt') - monkeypatch.setattr(prebuilt_models, 'MODELS_DIR', models_dir) - monkeypatch.setattr(prebuilt_models, 'CHECK_PATH', check_path) - - assert not prebuilt_models.packaged_prebuilt_matches('dmonitoring_model') +@pytest.mark.parametrize("model_name", prebuilt_models.MODEL_NAMES) +def test_packaged_prebuilt_without_onnx(packaged_models, model_name): + assert prebuilt_models.packaged_prebuilt_matches(model_name) + assert not prebuilt_models.verify_prebuilt(model_name, 'flags') -def test_source_checkout_is_not_packaged_prebuilt(tmp_path, monkeypatch): - models_dir = tmp_path / 'models' - models_dir.mkdir() - check_path = models_dir / 'prebuilt_check.json' - write_outputs(models_dir, check_path) - (models_dir / 'dmonitoring_model.onnx').write_bytes(b'onnx') - monkeypatch.setattr(prebuilt_models, 'MODELS_DIR', models_dir) - monkeypatch.setattr(prebuilt_models, 'CHECK_PATH', check_path) +@pytest.mark.parametrize("model_name", prebuilt_models.MODEL_NAMES) +def test_packaged_prebuilt_rejects_corrupt_output(packaged_models, model_name): + (packaged_models / f'{model_name}_tinygrad.pkl').write_bytes(b'corrupt') - assert not prebuilt_models.packaged_prebuilt_matches('dmonitoring_model') + assert not prebuilt_models.packaged_prebuilt_matches(model_name) + + +@pytest.mark.parametrize("model_name", prebuilt_models.MODEL_NAMES) +def test_source_checkout_is_not_packaged_prebuilt(packaged_models, model_name): + (packaged_models / f'{model_name}.onnx').write_bytes(b'onnx') + + assert not prebuilt_models.packaged_prebuilt_matches(model_name) diff --git a/selfdrive/monitoring/README.md b/selfdrive/monitoring/README.md deleted file mode 100644 index 2a29ea0..0000000 --- a/selfdrive/monitoring/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# driver monitoring (DM) - -Uploading driver-facing camera footage is opt-in, but it is encouraged to opt-in to improve the DM model. You can always change your preference using the "Record and Upload Driver Camera" toggle. - -## Troubleshooting - -Before creating a bug report, go through these troubleshooting steps. - -* Ensure the driver-facing camera has a good view of the driver in normal driving positions. - * This can be checked in Settings -> Device -> Preview Driver Camera (when car is off). -* If the camera can't see the driver, the device should be re-mounted. - -## Bug report - -In order for us to look into DM bug reports, we'll need the driver-facing camera footage. If you don't normally have this enabled, simply enable the toggle for a single drive. Also ensure the "Upload Raw Logs" toggle is enabled before going for a drive. diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 022415a..552b8fb 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -1,8 +1,33 @@ #!/usr/bin/env python3 +from types import SimpleNamespace + import cereal.messaging as messaging from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process -from openpilot.selfdrive.monitoring.helpers import DriverMonitoring +from openpilot.selfdrive.monitoring.legacy_policy import DRIVER_MONITOR_SETTINGS as LegacySettings +from openpilot.selfdrive.monitoring.legacy_policy import DriverMonitoring as LegacyDriverMonitoring +from openpilot.selfdrive.monitoring.policy import DriverMonitoring as UpstreamDriverMonitoring +from openpilot.system.hardware import HARDWARE + + +def use_legacy_dm(device_type: str) -> bool: + return device_type == 'mici' + + +def create_driver_monitoring(device_type: str, rhd_saved: bool, always_on: bool): + if use_legacy_dm(device_type): + return LegacyDriverMonitoring(rhd_saved=rhd_saved, settings=LegacySettings(device_type), always_on=always_on) + return UpstreamDriverMonitoring(rhd_saved=rhd_saved, always_on=always_on) + + +def get_dm_inputs(sm): + return { + 'driverStateV2': sm['driverStateV2'], + 'liveCalibration': sm['liveCalibration'], + 'carState': sm['carState'], + 'selfdriveState': SimpleNamespace(enabled=sm['selfdriveState'].enabled or sm['carControl'].latActive), + 'modelV2': sm['modelV2'], + } def dmonitoringd_thread(): @@ -13,7 +38,9 @@ def dmonitoringd_thread(): sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'selfdriveState', 'modelV2', 'carControl'], poll='driverStateV2') - DM = DriverMonitoring(rhd_saved=params.get_bool("IsRhdDetected"), always_on=params.get_bool("AlwaysOnDM")) + device_type = HARDWARE.get_device_type() + legacy_dm = use_legacy_dm(device_type) + DM = create_driver_monitoring(device_type, params.get_bool("IsRhdDetected"), params.get_bool("AlwaysOnDM")) demo_mode=False # 20Hz <- dmonitoringmodeld @@ -25,9 +52,9 @@ def dmonitoringd_thread(): valid = sm.all_checks() if demo_mode and sm.valid['driverStateV2']: - DM.run_step(sm, demo=demo_mode) + DM.run_step(sm if legacy_dm else get_dm_inputs(sm), demo=True) elif valid: - DM.run_step(sm, demo=demo_mode) + DM.run_step(sm if legacy_dm else get_dm_inputs(sm), demo=demo_mode) # publish dat = DM.get_state_packet(valid=valid) @@ -39,10 +66,11 @@ def dmonitoringd_thread(): demo_mode = params.get_bool("IsDriverViewEnabled") # save rhd virtual toggle every 5 mins + wheelpos_offsetter = DM.wheelpos.prob_offseter if legacy_dm else DM.wheelpos_offsetter if (sm['driverStateV2'].frameId % 6000 == 0 and not demo_mode and - DM.wheelpos.prob_offseter.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and - DM.wheel_on_right == (DM.wheelpos.prob_offseter.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)): - params.put_bool_nonblocking("IsRhdDetected", DM.wheel_on_right) + wheelpos_offsetter.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and + DM.wheel_on_right == (wheelpos_offsetter.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)): + params.put_bool("IsRhdDetected", DM.wheel_on_right) def main(): dmonitoringd_thread() diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/legacy_policy.py similarity index 86% rename from selfdrive/monitoring/helpers.py rename to selfdrive/monitoring/legacy_policy.py index 6f8752d..c450fd7 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/legacy_policy.py @@ -13,6 +13,12 @@ from openpilot.selfdrive.locationd.calibration_helpers import get_calibrated_rpy from openpilot.system.hardware import HARDWARE EventName = log.OnroadEvent.EventName +AlertLevel = log.DriverMonitoringState.AlertLevel +MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy + + +def to_percent(v): + return int(min(max(v * 100., 0.), 100.)) # ****************************************************************************************** # NOTE: To fork maintainers. @@ -391,44 +397,61 @@ class DriverMonitoring: alert = None if self.awareness <= 0.: # terminal red alert: disengagement required - alert = EventName.driverDistracted if self.active_monitoring_mode else EventName.driverUnresponsive + alert = EventName.driverDistracted3 if self.active_monitoring_mode else EventName.driverUnresponsive3 self.terminal_time += 1 if awareness_prev > 0.: self.terminal_alert_cnt += 1 elif self.awareness <= self.threshold_prompt: # prompt orange alert - alert = EventName.promptDriverDistracted if self.active_monitoring_mode else EventName.promptDriverUnresponsive + alert = EventName.driverDistracted2 if self.active_monitoring_mode else EventName.driverUnresponsive2 elif self.awareness <= self.threshold_pre and not always_on_lowspeed_exemption: # pre green alert - alert = EventName.preDriverDistracted if self.active_monitoring_mode else EventName.preDriverUnresponsive + alert = EventName.driverDistracted1 if self.active_monitoring_mode else EventName.driverUnresponsive1 if alert is not None: self.current_events.add(alert) def get_state_packet(self, valid=True): - # build driverMonitoringState packet dat = messaging.new_message('driverMonitoringState', valid=valid) - dat.driverMonitoringState = { - "events": self.current_events.to_msg(), - "faceDetected": self.face_detected, - "isDistracted": self.driver_distracted, - "distractedType": sum(self.distracted_types), - "awarenessStatus": self.awareness, - "posePitchOffset": self.pose.pitch_offseter.filtered_stat.mean(), - "posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n, - "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), - "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, - "phoneProbOffset": self.phone.prob_offseter.filtered_stat.mean(), - "phoneProbValidCount": self.phone.prob_offseter.filtered_stat.n, - "stepChange": self.step_change, - "awarenessActive": self.awareness_active, - "awarenessPassive": self.awareness_passive, - "isLowStd": self.pose.low_std, - "hiStdCount": self.hi_stds, - "isActiveMode": self.active_monitoring_mode, - "isRHD": self.wheel_on_right, - "uncertainCount": self.dcam_uncertain_cnt, - } + dm = dat.driverMonitoringState + + dm.lockout = self.too_distracted + dm.alert3Count = self.terminal_alert_cnt + dm.noResponseCount = int(self.terminal_time >= self.settings._MAX_TERMINAL_DURATION) + dm.noResponseForceDecel = self.awareness <= 0. + dm.alwaysOn = self.always_on + dm.alwaysOnLockout = self.always_on and self.awareness <= self.threshold_prompt + if self.awareness <= 0.: + dm.alertLevel = AlertLevel.three + elif self.awareness <= self.threshold_prompt: + dm.alertLevel = AlertLevel.two + elif self.awareness <= self.threshold_pre: + dm.alertLevel = AlertLevel.one + dm.activePolicy = MonitoringPolicy.vision if self.active_monitoring_mode else MonitoringPolicy.wheeltouch + dm.isRHD = self.wheel_on_right + dm.rhdCalibration.calibratedPercent = to_percent(self.wheelpos.prob_offseter.filtered_stat.n / self.settings._WHEELPOS_FILTER_MIN_COUNT) + dm.rhdCalibration.offset = self.wheelpos.prob_offseter.filtered_stat.M + + dm.visionPolicyState.awarenessPercent = to_percent(self.awareness if self.active_monitoring_mode else self.awareness_active) + dm.visionPolicyState.awarenessStep = self.step_change if self.active_monitoring_mode else 0. + dm.visionPolicyState.isDistracted = self.driver_distracted + dm.visionPolicyState.distractedTypes.pose = DistractedType.DISTRACTED_POSE in self.distracted_types + dm.visionPolicyState.distractedTypes.eye = DistractedType.DISTRACTED_BLINK in self.distracted_types + dm.visionPolicyState.distractedTypes.phone = DistractedType.DISTRACTED_PHONE in self.distracted_types + dm.visionPolicyState.faceDetected = self.face_detected + dm.visionPolicyState.pose.pitch = self.pose.pitch + dm.visionPolicyState.pose.yaw = self.pose.yaw + dm.visionPolicyState.pose.calibrated = self.pose.calibrated + dm.visionPolicyState.pose.pitchCalib.calibratedPercent = to_percent(self.pose.pitch_offseter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT) + dm.visionPolicyState.pose.pitchCalib.offset = self.pose.pitch_offseter.filtered_stat.M + dm.visionPolicyState.pose.yawCalib.calibratedPercent = to_percent(self.pose.yaw_offseter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT) + dm.visionPolicyState.pose.yawCalib.offset = self.pose.yaw_offseter.filtered_stat.M + dm.visionPolicyState.pose.uncertainty = max(self.pose.pitch_std, self.pose.yaw_std) + dm.visionPolicyState.wheeltouchFallbackPercent = to_percent(self.hi_stds / self.settings._HI_STD_FALLBACK_TIME) + dm.visionPolicyState.uncertainOffroadAlertPercent = to_percent(self.dcam_uncertain_cnt / int(60 / self.settings._DT_DMON)) + + dm.wheeltouchPolicyState.awarenessPercent = to_percent(self.awareness if not self.active_monitoring_mode else self.awareness_passive) + dm.wheeltouchPolicyState.awarenessStep = self.step_change if not self.active_monitoring_mode else 0. return dat def run_step(self, sm, demo=False): diff --git a/selfdrive/monitoring/policy.py b/selfdrive/monitoring/policy.py new file mode 100644 index 0000000..1c55b4d --- /dev/null +++ b/selfdrive/monitoring/policy.py @@ -0,0 +1,467 @@ +from collections import defaultdict +from math import atan2, radians +import numpy as np + +from cereal import car, log +import cereal.messaging as messaging +from openpilot.common.realtime import DT_DMON +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.params import Params +from openpilot.common.stat_live import RunningStatFilter +from openpilot.common.transformations.camera import DEVICE_CAMERAS + +AlertLevel = log.DriverMonitoringState.AlertLevel +MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy + +def to_percent(v): + return int(min(max(v * 100., 0.), 100.)) + +# ****************************************************************************************** +# NOTE: To fork maintainers. +# Disabling or nerfing safety features will get you and your users banned from our servers. +# We recommend that you do not change these numbers from the defaults. +# ****************************************************************************************** + +class DRIVER_MONITOR_SETTINGS: + def __init__(self): + # https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=OJ:L_202501899 + self._ALERT_MIN_SPEED = 2.8 # 10 km/h + + self._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT = 5. + self._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT = 15. + self._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT = 25. + self._VISION_POLICY_ALERT_1_TIMEOUT = 5. + self._VISION_POLICY_ALERT_2_TIMEOUT = 8. + self._VISION_POLICY_ALERT_3_TIMEOUT = 13. + + # no response = alert_3 sustained for certain amount of time + self._NO_RESPONSE_TIMEOUT = 5. + + # lockout specs + self._MAX_ALERT_3 = 2 + self._MAX_NO_RESPONSE = 1 + self._LOCKOUT_TIMES = [int(60 * n_min / DT_DMON) for n_min in [1, 5, 15, 30]] + + self._TIMEOUT_RECOVERY_FACTOR_MAX = 5. + self._TIMEOUT_RECOVERY_FACTOR_MIN = 1.25 + + self._FACE_THRESHOLD = 0.7 + self._EYE_THRESHOLD = 0.65 + self._SG_THRESHOLD = 0.9 + self._BLINK_THRESHOLD = 0.865 + self._PHONE_THRESH = 0.5 + self._POSE_PITCH_THRESHOLD = 0.3133 + self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 + self._POSE_PITCH_THRESHOLD_STRICT = self._POSE_PITCH_THRESHOLD + self._POSE_YAW_THRESHOLD = 0.4020 + self._POSE_YAW_THRESHOLD_SLACK = 0.5042 + self._POSE_YAW_THRESHOLD_STRICT = self._POSE_YAW_THRESHOLD + self._POSE_YAW_MIN_STEER_DEG = 30 + self._POSE_YAW_STEER_FACTOR = 0.15 + self._POSE_YAW_STEER_MAX_OFFSET = 0.3927 + self._PITCH_NATURAL_OFFSET = 0.011 # initial value before offset is learned + self._PITCH_NATURAL_THRESHOLD = 0.449 + self._YAW_NATURAL_OFFSET = 0.075 # initial value before offset is learned + self._PITCH_NATURAL_VAR = 3*0.01 + self._YAW_NATURAL_VAR = 3*0.05 + self._PITCH_MAX_OFFSET = 0.124 + self._PITCH_MIN_OFFSET = -0.0881 + self._YAW_MAX_OFFSET = 0.289 + self._YAW_MIN_OFFSET = -0.0246 + + self._DCAM_UNCERTAIN_ALERT_THRESHOLD = 0.1 + self._DCAM_UNCERTAIN_ALERT_COUNT = int(60 / DT_DMON) + self._DCAM_UNCERTAIN_RESET_COUNT = int(2 / DT_DMON) + self._HI_STD_THRESHOLD = 0.3 + self._HI_STD_FALLBACK_TIME = int(10 / DT_DMON) # fall back to wheel touch if model is uncertain for 10s + self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz + + self._POSE_CALIB_MIN_SPEED = 13 # 30 mph + self._POSE_OFFSET_MIN_COUNT = int(60 / DT_DMON) # valid data counts before calibration completes, 1min cumulative + self._POSE_OFFSET_MAX_COUNT = int(360 / DT_DMON) # stop deweighting new data after 6 min, aka "short term memory" + self._WHEELPOS_CALIB_MIN_SPEED = 11 + self._WHEELPOS_THRESHOLD = 0.5 + self._WHEELPOS_FILTER_MIN_COUNT = int(15 / DT_DMON) # allow 15 seconds to converge wheel side + self._WHEELPOS_DATA_AVG = 0.03 + self._WHEELPOS_DATA_VAR = 3*5.5e-5 + self._WHEELPOS_MAX_COUNT = -1 + +class DriverPose: + def __init__(self, settings): + pitch_filter_raw_priors = (settings._PITCH_NATURAL_OFFSET, settings._PITCH_NATURAL_VAR, 2) + yaw_filter_raw_priors = (settings._YAW_NATURAL_OFFSET, settings._YAW_NATURAL_VAR, 2) + self.yaw = 0. + self.pitch = 0. + self.pitch_offsetter = RunningStatFilter(raw_priors=pitch_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT) + self.yaw_offsetter = RunningStatFilter(raw_priors=yaw_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT) + self.calibrated = False + self.low_std = True + self.cfactor_pitch = 1. + self.cfactor_yaw = 1. + self.steer_yaw_offset = 0. + +class DriverBlink: + def __init__(self): + self.left = 0. + self.right = 0. + +# model output refers to center of undistorted+leveled image +ref_undistorted_cam = DEVICE_CAMERAS[("tici", "ar0231")].dcam +dcam_undistorted_FL = 598.0 +dcam_undistorted_W, dcam_undistorted_H = (ref_undistorted_cam.width, ref_undistorted_cam.height) + +def face_orientation_from_model(orient_model, pos_model, rpy_calib): + pitch_model = orient_model[0] + yaw_model = orient_model[1] + + face_pixel_position = ((pos_model[0]+0.5)*dcam_undistorted_W, (pos_model[1]+0.5)*dcam_undistorted_H) + yaw_focal_angle = atan2(face_pixel_position[0] - dcam_undistorted_W//2, dcam_undistorted_FL) + pitch_focal_angle = atan2(face_pixel_position[1] - dcam_undistorted_H//2, dcam_undistorted_FL) + + pitch = pitch_model + pitch_focal_angle + yaw = -yaw_model + yaw_focal_angle + + pitch -= rpy_calib[1] + yaw -= rpy_calib[2] + return pitch, yaw + + +class DriverMonitoring: + def __init__(self, rhd_saved=False, settings=None, always_on=False): + # init policy settings + self.settings = settings if settings is not None else DRIVER_MONITOR_SETTINGS() + + # init driver status + wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) + self.wheelpos_offsetter = RunningStatFilter(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) + self.pose = DriverPose(settings=self.settings) + self.blink = DriverBlink() + self.phone_prob = 0. + + self.alert_level = AlertLevel.none + self.always_on = always_on + self.distracted_types = defaultdict(bool) + self.driver_distracted = False + self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, DT_DMON) + self.wheel_on_right = False + self.wheel_on_right_last = None + self.wheel_on_right_default = rhd_saved + self.face_detected = False + self.alert_3_cnt = 0 + self.cnt_since_alert_3 = 0 + self.no_response_timeout = int(self.settings._NO_RESPONSE_TIMEOUT / DT_DMON) + self.no_response_cnt = 0 + self.lockout_active = Params().get_bool("DriverTooDistracted") + self.lockout_count = Params().get("DriverLockoutCount") or 0 + self.lockout_duration = self.settings._LOCKOUT_TIMES[min(max(self.lockout_count - 1, 0), len(self.settings._LOCKOUT_TIMES) - 1)] + self.lockout_time_elapsed = 0 + self.step_change = 0. + self.active_policy = MonitoringPolicy.vision + self.driver_interacting = False + self.is_model_uncertain = False + self.hi_stds = 0 + self.model_std_max = 0. + self.threshold_alert_1 = 0. + self.threshold_alert_2 = 0. + self.dcam_uncertain_cnt = 0 + self.dcam_reset_cnt = 0 + + self._reset_awareness() + self._set_policy(MonitoringPolicy.vision) + + def _reset_awareness(self): + self.awareness = 1. + self.last_vision_awareness = 1. + self.last_wheeltouch_awareness = 1. + + def _set_policy(self, target_policy): + if self.active_policy == MonitoringPolicy.vision and self.awareness <= self.threshold_alert_2: + if target_policy == MonitoringPolicy.vision: + self.step_change = DT_DMON / self.settings._VISION_POLICY_ALERT_3_TIMEOUT + else: + self.step_change = 0. + return # no exploit after orange alert + elif self.awareness <= 0.: + return + + if target_policy == MonitoringPolicy.vision: + # when falling back from passive mode to active mode, reset awareness to avoid false alert + if self.active_policy != MonitoringPolicy.vision: + self.last_wheeltouch_awareness = self.awareness + self.awareness = self.last_vision_awareness + + self.threshold_alert_1 = 1. - self.settings._VISION_POLICY_ALERT_1_TIMEOUT / self.settings._VISION_POLICY_ALERT_3_TIMEOUT + self.threshold_alert_2 = 1. - self.settings._VISION_POLICY_ALERT_2_TIMEOUT / self.settings._VISION_POLICY_ALERT_3_TIMEOUT + self.step_change = DT_DMON / self.settings._VISION_POLICY_ALERT_3_TIMEOUT + self.active_policy = MonitoringPolicy.vision + else: + if self.active_policy == MonitoringPolicy.vision: + self.last_vision_awareness = self.awareness + self.awareness = self.last_wheeltouch_awareness + + self.threshold_alert_1 = 1. - self.settings._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + self.threshold_alert_2 = 1. - self.settings._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + self.step_change = DT_DMON / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + self.active_policy = MonitoringPolicy.wheeltouch + + def _set_pose_strictness(self, brake_disengage_prob, car_speed): + bp = brake_disengage_prob + k1 = max(-0.00156*((car_speed-16)**2)+0.6, 0.2) + bp_normal = max(min(bp / k1, 0.5),0) + self.pose.cfactor_pitch = np.interp(bp_normal, [0, 0.5], + [self.settings._POSE_PITCH_THRESHOLD_SLACK, + self.settings._POSE_PITCH_THRESHOLD_STRICT]) / self.settings._POSE_PITCH_THRESHOLD + self.pose.cfactor_yaw = np.interp(bp_normal, [0, 0.5], + [self.settings._POSE_YAW_THRESHOLD_SLACK, + self.settings._POSE_YAW_THRESHOLD_STRICT]) / self.settings._POSE_YAW_THRESHOLD + + def _get_distracted_types(self): + self.distracted_types = defaultdict(bool) + + if not self.pose.calibrated: + pitch_error = self.pose.pitch - self.settings._PITCH_NATURAL_OFFSET + yaw_error = self.pose.yaw - self.settings._YAW_NATURAL_OFFSET + else: + pitch_error = self.pose.pitch - min(max(self.pose.pitch_offsetter.filtered_stat.mean(), + self.settings._PITCH_MIN_OFFSET), self.settings._PITCH_MAX_OFFSET) + yaw_error = self.pose.yaw - min(max(self.pose.yaw_offsetter.filtered_stat.mean(), + self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET) + pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit + + if yaw_error * self.pose.steer_yaw_offset > 0: # unidirectional + yaw_error = max(abs(yaw_error) - min(abs(self.pose.steer_yaw_offset), self.settings._POSE_YAW_STEER_MAX_OFFSET), 0.) + else: + yaw_error = abs(yaw_error) + + pitch_threshold = self.settings._POSE_PITCH_THRESHOLD * self.pose.cfactor_pitch if self.pose.calibrated else self.settings._PITCH_NATURAL_THRESHOLD + yaw_threshold = self.settings._POSE_YAW_THRESHOLD * self.pose.cfactor_yaw + + self.distracted_types['pose'] = bool((pitch_error > pitch_threshold) or (yaw_error > yaw_threshold)) + self.distracted_types['eye'] = bool((self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD) + self.distracted_types['phone'] = bool(self.phone_prob > self.settings._PHONE_THRESH) + + def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, lowspeed, demo_mode=False, steering_angle_deg=0.): + rhd_pred = driver_state.wheelOnRightProb + # calibrates only when there's movement and either face detected + if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or + driver_state.rightDriverData.faceProb > self.settings._FACE_THRESHOLD): + self.wheelpos_offsetter.push_and_update(rhd_pred) + + wheelpos_calibrated = self.wheelpos_offsetter.filtered_stat.n >= self.settings._WHEELPOS_FILTER_MIN_COUNT + + if wheelpos_calibrated or demo_mode: + self.wheel_on_right = self.wheelpos_offsetter.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD + else: + self.wheel_on_right = self.wheel_on_right_default # use default/saved if calibration is unfinished + # make sure no switching when engaged + if op_engaged and self.wheel_on_right_last is not None and self.wheel_on_right_last != self.wheel_on_right and not demo_mode: + self.wheel_on_right = self.wheel_on_right_last + driver_data = driver_state.rightDriverData if self.wheel_on_right else driver_state.leftDriverData + if not all(len(x) > 0 for x in (driver_data.faceOrientation, driver_data.facePosition, + driver_data.faceOrientationStd, driver_data.facePositionStd)): + return + + self.face_detected = driver_data.faceProb > self.settings._FACE_THRESHOLD + self.pose.pitch, self.pose.yaw = face_orientation_from_model(driver_data.faceOrientation, driver_data.facePosition, cal_rpy) + steer_d = max(abs(steering_angle_deg) - self.settings._POSE_YAW_MIN_STEER_DEG, 0.) + self.pose.steer_yaw_offset = radians(steer_d) * -np.sign(steering_angle_deg) * self.settings._POSE_YAW_STEER_FACTOR + if self.wheel_on_right: + self.pose.yaw *= -1 + self.pose.steer_yaw_offset *= -1 + self.wheel_on_right_last = self.wheel_on_right + self.model_std_max = max(driver_data.faceOrientationStd[0], driver_data.faceOrientationStd[1]) + self.pose.low_std = self.model_std_max < self.settings._HI_STD_THRESHOLD + self.blink.left = driver_data.leftBlinkProb * (driver_data.leftEyeProb > self.settings._EYE_THRESHOLD) \ + * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) + self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ + * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) + self.phone_prob = driver_data.phoneProb + + self._get_distracted_types() + self.driver_distracted = any(self.distracted_types.values()) and driver_data.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std + self.driver_distraction_filter.update(self.driver_distracted) + + # only update offsetter when driver is actively driving the car above a certain speed + if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): + self.pose.pitch_offsetter.push_and_update(self.pose.pitch) + self.pose.yaw_offsetter.push_and_update(self.pose.yaw) + + self.pose.calibrated = self.pose.pitch_offsetter.filtered_stat.n >= self.settings._POSE_OFFSET_MIN_COUNT and \ + self.pose.yaw_offsetter.filtered_stat.n >= self.settings._POSE_OFFSET_MIN_COUNT + + if self.face_detected and not self.driver_distracted: + dcam_uncertain = self.model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD + if dcam_uncertain and not lowspeed: + self.dcam_uncertain_cnt += 1 + self.dcam_reset_cnt = 0 + else: + self.dcam_reset_cnt += 1 + if self.dcam_reset_cnt > self.settings._DCAM_UNCERTAIN_RESET_COUNT: + self.dcam_uncertain_cnt = 0 + + self.is_model_uncertain = self.hi_stds >= self.settings._HI_STD_FALLBACK_TIME + self._set_policy(MonitoringPolicy.vision if self.face_detected and not self.is_model_uncertain else MonitoringPolicy.wheeltouch) + if self.face_detected and not self.pose.low_std and not self.driver_distracted: + self.hi_stds += 1 + elif self.face_detected and self.pose.low_std: + self.hi_stds = 0 + + def _update_events(self, driver_engaged, op_engaged, lowspeed, wrong_gear): + self.alert_level = AlertLevel.none + self.driver_interacting = driver_engaged + + if self.alert_3_cnt >= self.settings._MAX_ALERT_3 or self.no_response_cnt >= self.settings._MAX_NO_RESPONSE: + if not self.lockout_active: + self.lockout_count += 1 + self.lockout_duration = self.settings._LOCKOUT_TIMES[min(self.lockout_count - 1, len(self.settings._LOCKOUT_TIMES) - 1)] + Params().put("DriverLockoutCount", self.lockout_count) + self.lockout_active = True + + if self.lockout_active: + self.lockout_time_elapsed += 1 + if self.lockout_time_elapsed > self.lockout_duration: + self.lockout_active = False + self.alert_3_cnt = 0 + self.cnt_since_alert_3 = 0 + self.no_response_cnt = 0 + self.lockout_time_elapsed = 0 + + always_on_valid = self.always_on and not wrong_gear + if (self.driver_interacting and self.awareness > 0 and self.active_policy == MonitoringPolicy.wheeltouch) or \ + (not always_on_valid and not op_engaged) or \ + (always_on_valid and not op_engaged and self.awareness <= 0): + # always reset on disengage with normal mode; disengage resets only on red if always on + self._reset_awareness() + return + + awareness_prev = self.awareness + _reaching_alert_1 = self.awareness - self.step_change <= self.threshold_alert_1 + _reaching_alert_3 = self.awareness - self.step_change <= 0 + lowspeed_exemption = lowspeed and _reaching_alert_1 + always_on_exemption = always_on_valid and not op_engaged and _reaching_alert_3 + + if self.awareness > 0 and \ + ((self.driver_distraction_filter.x < 0.37 and self.face_detected and self.pose.low_std) or lowspeed_exemption): + if self.driver_interacting: + self._reset_awareness() + return + # only restore awareness when paying attention and alert is not red + self.awareness = min(self.awareness + ((self.settings._TIMEOUT_RECOVERY_FACTOR_MAX-self.settings._TIMEOUT_RECOVERY_FACTOR_MIN)* + (1.-self.awareness)+self.settings._TIMEOUT_RECOVERY_FACTOR_MIN)*self.step_change, 1.) + if self.awareness == 1.: + self.last_wheeltouch_awareness = min(self.last_wheeltouch_awareness + self.step_change, 1.) + # don't display alert banner when awareness is recovering and has cleared orange + if self.awareness > self.threshold_alert_2: + return + + certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected + maybe_distracted = self.is_model_uncertain or not self.face_detected + + if certainly_distracted or maybe_distracted: + # should always be counting if distracted unless at low speed and reaching green + # also will not be reaching 0 if DM is active when not engaged + if not (lowspeed_exemption or always_on_exemption): + self.awareness = max(self.awareness - self.step_change, -0.1) + + if self.awareness <= 0.: + # terminal alert: disengagement required + self.alert_level = AlertLevel.three + if awareness_prev > 0.: + self.alert_3_cnt += 1 + self.cnt_since_alert_3 = 0 + else: + self.cnt_since_alert_3 += 1 + if self.cnt_since_alert_3 == self.no_response_timeout: + self.no_response_cnt += 1 + else: + if self.awareness <= self.threshold_alert_2: + self.alert_level = AlertLevel.two + elif self.awareness <= self.threshold_alert_1: + self.alert_level = AlertLevel.one + + def get_state_packet(self, valid=True): + # build driverMonitoringState packet + dat = messaging.new_message('driverMonitoringState', valid=valid) + dm = dat.driverMonitoringState + + dm.lockout = self.lockout_active + dm.lockoutCount = self.lockout_count + if self.lockout_active: + dm.lockoutMinutesRemaining = max(1, round((self.lockout_duration - self.lockout_time_elapsed) * DT_DMON / 60.)) + dm.alert3Count = self.alert_3_cnt + dm.noResponseCount = self.no_response_cnt + dm.noResponseForceDecel = self.alert_level == AlertLevel.three and self.cnt_since_alert_3 >= self.no_response_timeout + dm.alwaysOn = self.always_on + dm.alwaysOnLockout = self.always_on and self.awareness <= self.threshold_alert_2 + dm.alertLevel = self.alert_level + dm.activePolicy = self.active_policy + dm.isRHD = self.wheel_on_right + dm.rhdCalibration.calibratedPercent = to_percent(self.wheelpos_offsetter.filtered_stat.n / self.settings._WHEELPOS_FILTER_MIN_COUNT) + dm.rhdCalibration.offset = self.wheelpos_offsetter.filtered_stat.M + + dm.visionPolicyState.awarenessPercent = to_percent(self.last_vision_awareness if self.active_policy != MonitoringPolicy.vision else self.awareness) + dm.visionPolicyState.awarenessStep = self.step_change if self.active_policy == MonitoringPolicy.vision else 0. + dm.visionPolicyState.isDistracted = self.driver_distracted + dm.visionPolicyState.distractedTypes.pose = self.distracted_types['pose'] + dm.visionPolicyState.distractedTypes.eye = self.distracted_types['eye'] + dm.visionPolicyState.distractedTypes.phone = self.distracted_types['phone'] + dm.visionPolicyState.faceDetected = self.face_detected + dm.visionPolicyState.pose.pitch = self.pose.pitch + dm.visionPolicyState.pose.yaw = self.pose.yaw + dm.visionPolicyState.pose.calibrated = self.pose.calibrated + dm.visionPolicyState.pose.pitchCalib.calibratedPercent = to_percent(self.pose.pitch_offsetter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT) + dm.visionPolicyState.pose.pitchCalib.offset = self.pose.pitch_offsetter.filtered_stat.M + dm.visionPolicyState.pose.yawCalib.calibratedPercent = to_percent(self.pose.yaw_offsetter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT) + dm.visionPolicyState.pose.yawCalib.offset = self.pose.yaw_offsetter.filtered_stat.M + dm.visionPolicyState.pose.uncertainty = self.model_std_max + dm.visionPolicyState.wheeltouchFallbackPercent = to_percent(self.hi_stds / self.settings._HI_STD_FALLBACK_TIME) + dm.visionPolicyState.uncertainOffroadAlertPercent = to_percent(self.dcam_uncertain_cnt / self.settings._DCAM_UNCERTAIN_ALERT_COUNT) + + dm.wheeltouchPolicyState.awarenessPercent = to_percent(self.last_wheeltouch_awareness if self.active_policy == MonitoringPolicy.vision else self.awareness) + dm.wheeltouchPolicyState.awarenessStep = 0. if self.active_policy == MonitoringPolicy.vision else self.step_change + dm.wheeltouchPolicyState.driverInteracting = self.driver_interacting + return dat + + def run_step(self, sm, demo=False): + if demo: + car_speed = 30 + enabled = True + wrong_gear = False + lowspeed = False + driver_engaged = False + brake_disengage_prob = 1.0 + steering_angle_deg = 0.0 + rpyCalib = [0., 0., 0.] + else: + car_speed = sm['carState'].vEgo + enabled = sm['selfdriveState'].enabled + wrong_gear = sm['carState'].gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low) + lowspeed = car_speed < self.settings._ALERT_MIN_SPEED + driver_engaged = sm['carState'].steeringPressed or sm['carState'].gasPressed + brake_disengage_prob = sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s + steering_angle_deg = sm['carState'].steeringAngleDeg + rpyCalib = sm['liveCalibration'].rpyCalib + + self._set_pose_strictness( + brake_disengage_prob=brake_disengage_prob, + car_speed=car_speed, + ) + + # Parse data from dmonitoringmodeld + self._update_states( + driver_state=sm['driverStateV2'], + cal_rpy=rpyCalib, + car_speed=car_speed, + op_engaged=enabled, + lowspeed=lowspeed, + demo_mode=demo, + steering_angle_deg=steering_angle_deg, + ) + + # Update distraction events + self._update_events( + driver_engaged=driver_engaged, + op_engaged=enabled, + lowspeed=lowspeed, + wrong_gear=wrong_gear, + ) diff --git a/selfdrive/monitoring/test_dmonitoringd.py b/selfdrive/monitoring/test_dmonitoringd.py new file mode 100644 index 0000000..750489d --- /dev/null +++ b/selfdrive/monitoring/test_dmonitoringd.py @@ -0,0 +1,61 @@ +from types import SimpleNamespace + +import pytest + +from cereal import log +from openpilot.selfdrive.monitoring.dmonitoringd import create_driver_monitoring, get_dm_inputs, use_legacy_dm +from openpilot.selfdrive.monitoring.legacy_policy import DriverMonitoring as LegacyDriverMonitoring +from openpilot.selfdrive.monitoring.policy import DriverMonitoring as UpstreamDriverMonitoring + + +@pytest.mark.parametrize("device_type, expected_type", [ + ("mici", LegacyDriverMonitoring), + ("tici", UpstreamDriverMonitoring), + ("tizi", UpstreamDriverMonitoring), +]) +def test_policy_selection(device_type, expected_type): + assert use_legacy_dm(device_type) == (device_type == "mici") + assert isinstance(create_driver_monitoring(device_type, False, False), expected_type) + + +def test_mici_uses_legacy_thresholds(): + dm = create_driver_monitoring("mici", False, False) + + assert dm.settings._DISTRACTED_TIME == 11. + assert dm.settings._AWARENESS_TIME == 30. + assert dm.settings._PHONE_THRESH == 0.75 + + +@pytest.mark.parametrize("enabled, lat_active, expected", [ + (False, False, False), + (True, False, True), + (False, True, True), + (True, True, True), +]) +def test_iq_enabled_adapter(enabled, lat_active, expected): + sm = { + 'driverStateV2': object(), + 'liveCalibration': object(), + 'carState': object(), + 'selfdriveState': SimpleNamespace(enabled=enabled), + 'modelV2': object(), + 'carControl': SimpleNamespace(latActive=lat_active), + } + + assert get_dm_inputs(sm)['selfdriveState'].enabled == expected + + +def test_legacy_policy_packet_uses_shared_schema(): + dm = create_driver_monitoring("mici", False, False) + dm.awareness = 0. + dm.terminal_alert_cnt = 2 + dm.terminal_time = dm.settings._MAX_TERMINAL_DURATION + dm.too_distracted = True + + state = dm.get_state_packet().driverMonitoringState + + assert state.lockout + assert state.alert3Count == 2 + assert state.noResponseCount == 1 + assert state.noResponseForceDecel + assert state.alertLevel == log.DriverMonitoringState.AlertLevel.three diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index fef6a7e..37606fe 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -1,19 +1,15 @@ -import numpy as np -import pytest - -from cereal import log, car +from cereal import log from openpilot.common.realtime import DT_DMON -from openpilot.selfdrive.monitoring.helpers import DriverMonitoring, DRIVER_MONITOR_SETTINGS -from openpilot.system.hardware import HARDWARE +from openpilot.selfdrive.monitoring.policy import DriverMonitoring, DRIVER_MONITOR_SETTINGS EventName = log.OnroadEvent.EventName -dm_settings = DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type()) +dm_settings = DRIVER_MONITOR_SETTINGS() TEST_TIMESPAN = 120 # seconds -DISTRACTED_SECONDS_TO_ORANGE = dm_settings._DISTRACTED_TIME - dm_settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + 1 -DISTRACTED_SECONDS_TO_RED = dm_settings._DISTRACTED_TIME + 1 -INVISIBLE_SECONDS_TO_ORANGE = dm_settings._AWARENESS_TIME - dm_settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL + 1 -INVISIBLE_SECONDS_TO_RED = dm_settings._AWARENESS_TIME + 1 +DISTRACTED_SECONDS_TO_ORANGE = dm_settings._VISION_POLICY_ALERT_2_TIMEOUT + 1 +DISTRACTED_SECONDS_TO_RED = dm_settings._VISION_POLICY_ALERT_3_TIMEOUT + 1 +INVISIBLE_SECONDS_TO_ORANGE = dm_settings._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT + 1 +INVISIBLE_SECONDS_TO_RED = dm_settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + 1 def make_msg(face_detected, distracted=False, model_uncertain=False): ds = log.DriverStateV2.new_message() @@ -37,7 +33,7 @@ msg_ATTENTIVE = make_msg(True) msg_DISTRACTED = make_msg(True, distracted=True) msg_ATTENTIVE_UNCERTAIN = make_msg(True, model_uncertain=True) msg_DISTRACTED_UNCERTAIN = make_msg(True, distracted=True, model_uncertain=True) -msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN = make_msg(True, distracted=True, model_uncertain=dm_settings._POSESTD_THRESHOLD*1.5) +msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN = make_msg(True, distracted=True, model_uncertain=dm_settings._HI_STD_THRESHOLD*1.5) # driver interaction with car car_interaction_DETECTED = True @@ -51,51 +47,66 @@ always_true = [True] * int(TEST_TIMESPAN / DT_DMON) always_false = [False] * int(TEST_TIMESPAN / DT_DMON) class TestMonitoring: - def _run_seq(self, msgs, interaction, engaged, standstill): + def _run_seq(self, msgs, interaction, engaged, lowspeed): DM = DriverMonitoring() - events = [] + alert_lvls = [] for idx in range(len(msgs)): - DM._update_states(msgs[idx], [0, 0, 0], 0, engaged[idx], standstill[idx]) + DM._update_states(msgs[idx], [0, 0, 0], 0, engaged[idx], lowspeed[idx]) # cal_rpy and car_speed don't matter here # evaluate events at 10Hz for tests - DM._update_events(interaction[idx], engaged[idx], standstill[idx], 0, 0) - events.append(DM.current_events) - assert len(events) == len(msgs), f"got {len(events)} for {len(msgs)} driverState input msgs" - return events, DM + DM._update_events(interaction[idx], engaged[idx], lowspeed[idx], 0) + alert_lvls.append(DM.alert_level) + assert len(alert_lvls) == len(msgs), f"got {len(alert_lvls)} for {len(msgs)} driverState input msgs" + return alert_lvls, DM - def _assert_no_events(self, events): - assert all(not len(e) for e in events) # engaged, driver is attentive all the time def test_fully_aware_driver(self): - events, _ = self._run_seq(always_attentive, always_false, always_true, always_false) - self._assert_no_events(events) + alert_lvls, d_status = self._run_seq(always_attentive, always_false, always_true, always_false) + assert all(a == 0 for a in alert_lvls) + assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.vision # engaged, driver is distracted and does nothing def test_fully_distracted_driver(self): - events, d_status = self._run_seq(always_distracted, always_false, always_true, always_false) - assert len(events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0 - assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL + \ - ((d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == \ - EventName.preDriverDistracted - assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + \ - ((d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.promptDriverDistracted - assert events[int((d_status.settings._DISTRACTED_TIME + \ - ((TEST_TIMESPAN-10-d_status.settings._DISTRACTED_TIME)/2))/DT_DMON)].names[0] == EventName.driverDistracted + alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, always_false) + s = d_status.settings + assert alert_lvls[int(s._VISION_POLICY_ALERT_1_TIMEOUT / 2 / DT_DMON)] == 0 + assert alert_lvls[int((s._VISION_POLICY_ALERT_1_TIMEOUT + \ + (s._VISION_POLICY_ALERT_2_TIMEOUT - s._VISION_POLICY_ALERT_1_TIMEOUT) / 2) / DT_DMON)] == 1 + assert alert_lvls[int((s._VISION_POLICY_ALERT_2_TIMEOUT + \ + (s._VISION_POLICY_ALERT_3_TIMEOUT - s._VISION_POLICY_ALERT_2_TIMEOUT) / 2) / DT_DMON)] == 2 + assert alert_lvls[int((s._VISION_POLICY_ALERT_3_TIMEOUT + \ + (TEST_TIMESPAN - 10 - s._VISION_POLICY_ALERT_3_TIMEOUT) / 2) / DT_DMON)] == 3 assert isinstance(d_status.awareness, float) + # engaged, distracted past red and beyond the no-response window -> unavailability response + lockout + def test_distracted_lockout(self): + alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, always_false) + assert alert_lvls[int(DISTRACTED_SECONDS_TO_RED / DT_DMON)] == 3 + assert d_status.lockout_active + assert d_status.lockout_time_elapsed > 0 + assert d_status.lockout_count >= 1 + + # no face -> wheeltouch red, sustained past the no-response timeout -> unavailability response + lockout + def test_invisible_lockout(self): + _, d_status = self._run_seq(always_no_face, always_false, always_true, always_false) + assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.wheeltouch + assert d_status.lockout_active + assert d_status.lockout_count >= 1 + # engaged, no face detected the whole time, no action def test_fully_invisible_driver(self): - events, d_status = self._run_seq(always_no_face, always_false, always_true, always_false) - assert len(events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0 - assert events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL + \ - ((d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == \ - EventName.preDriverUnresponsive - assert events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL + \ - ((d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.promptDriverUnresponsive - assert events[int((d_status.settings._AWARENESS_TIME + \ - ((TEST_TIMESPAN-10-d_status.settings._AWARENESS_TIME)/2))/DT_DMON)].names[0] == EventName.driverUnresponsive + alert_lvls, d_status = self._run_seq(always_no_face, always_false, always_true, always_false) + s = d_status.settings + assert alert_lvls[int(s._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT / 2 / DT_DMON)] == 0 + assert alert_lvls[int((s._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT + \ + (s._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT - s._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT) / 2) / DT_DMON)] == 1 + assert alert_lvls[int((s._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT + \ + (s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT - s._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT) / 2) / DT_DMON)] == 2 + assert alert_lvls[int((s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + \ + (TEST_TIMESPAN - 10 - s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT) / 2) / DT_DMON)] == 3 + assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.wheeltouch # engaged, down to orange, driver pays attention, back to normal; then down to orange, driver touches wheel # - should have short orange recovery time and no green afterwards; wheel touch only recovers when paying attention @@ -106,13 +117,13 @@ class TestMonitoring: [msg_ATTENTIVE] * (int(TEST_TIMESPAN/DT_DMON)-int((DISTRACTED_SECONDS_TO_ORANGE*3+2)/DT_DMON)) interaction_vector = [car_interaction_NOT_DETECTED] * int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON) + \ [car_interaction_DETECTED] * (int(TEST_TIMESPAN/DT_DMON)-int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON)) - events, _ = self._run_seq(ds_vector, interaction_vector, always_true, always_false) - assert len(events[int(DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 - assert events[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted - assert len(events[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)]) == 0 - assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted - assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted - assert len(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)]) == 0 + alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, always_true, always_false) + assert alert_lvls[int(DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)] == 0 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)] == 2 + assert alert_lvls[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)] == 0 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)] == 2 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)] == 2 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)] == 0 # engaged, down to orange, driver dodges camera, then comes back still distracted, down to red, \ # driver dodges, and then touches wheel to no avail, disengages and reengages @@ -130,31 +141,31 @@ class TestMonitoring: = [True] * int(1/DT_DMON) op_vector[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+2.5)/DT_DMON):int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3)/DT_DMON)] \ = [False] * int(0.5/DT_DMON) - events, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) - assert events[int((DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)].names[0] == EventName.promptDriverDistracted - assert events[int((DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)].names[0] == EventName.driverDistracted - assert events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)].names[0] == EventName.driverDistracted - assert len(events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)]) == 0 + alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) + assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)] == 2 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)] == 3 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)] == 3 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)] == 0 # engaged, invisible driver, down to orange, driver touches wheel; then down to orange again, driver appears # - both actions should clear the alert, but momentary appearance should not def test_sometimes_transparent_commuter(self): - _visible_time = np.random.choice([0.5, 10]) - ds_vector = always_no_face[:]*2 - interaction_vector = always_false[:]*2 - ds_vector[int((2*INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON):int((2*INVISIBLE_SECONDS_TO_ORANGE+1+_visible_time)/DT_DMON)] = \ - [msg_ATTENTIVE] * int(_visible_time/DT_DMON) - interaction_vector[int((INVISIBLE_SECONDS_TO_ORANGE)/DT_DMON):int((INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON)] = [True] * int(1/DT_DMON) - events, _ = self._run_seq(ds_vector, interaction_vector, 2*always_true, 2*always_false) - assert len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 - assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive - assert len(events[int((INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)]) == 0 - if _visible_time == 0.5: - assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive - assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)].names[0] == EventName.preDriverUnresponsive - elif _visible_time == 10: - assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive - assert len(events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)]) == 0 + for _visible_time in (0.5, 10): + ds_vector = always_no_face[:]*2 + interaction_vector = always_false[:]*2 + ds_vector[int((2*INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON):int((2*INVISIBLE_SECONDS_TO_ORANGE+1+_visible_time)/DT_DMON)] = \ + [msg_ATTENTIVE] * int(_visible_time/DT_DMON) + interaction_vector[int((INVISIBLE_SECONDS_TO_ORANGE)/DT_DMON):int((INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON)] = [True] * int(1/DT_DMON) + alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, 2*always_true, 2*always_false) + assert alert_lvls[int(dm_settings._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT/2/DT_DMON)] == 0 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)] == 2 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)] == 0 + if _visible_time == 0.5: + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)] == 2 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)] == 2 + elif _visible_time == 10: + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)] == 2 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)] == 0 # engaged, invisible driver, down to red, driver appears and then touches wheel, then disengages/reengages # - only disengage will clear the alert @@ -166,105 +177,51 @@ class TestMonitoring: ds_vector[int(INVISIBLE_SECONDS_TO_RED/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time)/DT_DMON)] = [msg_ATTENTIVE] * int(_visible_time/DT_DMON) interaction_vector[int((INVISIBLE_SECONDS_TO_RED+_visible_time)/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON)] = [True] * int(1/DT_DMON) op_vector[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)] = [False] * int(0.5/DT_DMON) - events, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) - assert len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 - assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive - assert events[int((INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive - assert events[int((INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)].names[0] == EventName.driverUnresponsive - assert events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)].names[0] == EventName.driverUnresponsive - assert len(events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)]) == 0 + alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) + assert alert_lvls[int(dm_settings._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT/2/DT_DMON)] == 0 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)] == 2 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)] == 3 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)] == 3 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)] == 3 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)] == 0 # disengaged, always distracted driver # - dm should stay quiet when not engaged def test_pure_dashcam_user(self): - events, _ = self._run_seq(always_distracted, always_false, always_false, always_false) - assert sum(len(event) for event in events) == 0 + alert_lvls, _ = self._run_seq(always_distracted, always_false, always_false, always_false) + assert all(a == 0 for a in alert_lvls) # engaged, car stops at traffic light, down to orange, no action, then car starts moving # - should only reach green when stopped, but continues counting down on launch def test_long_traffic_light_victim(self): _redlight_time = 60 # seconds - standstill_vector = always_true[:] - standstill_vector[int(_redlight_time/DT_DMON):] = [False] * int((TEST_TIMESPAN-_redlight_time)/DT_DMON) - events, d_status = self._run_seq(always_distracted, always_false, always_true, standstill_vector) - assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL+1)/DT_DMON)].names[0] == \ - EventName.preDriverDistracted - assert events[int((_redlight_time-0.1)/DT_DMON)].names[0] == EventName.preDriverDistracted - assert events[int((_redlight_time+0.5)/DT_DMON)].names[0] == EventName.promptDriverDistracted + lowspeed_vector = always_true[:] + lowspeed_vector[int(_redlight_time/DT_DMON):] = [False] * int((TEST_TIMESPAN-_redlight_time)/DT_DMON) + alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, lowspeed_vector) + s = d_status.settings + assert alert_lvls[int((_redlight_time-0.1)/DT_DMON)] == 0 + _alert_1_to_2 = s._VISION_POLICY_ALERT_2_TIMEOUT - s._VISION_POLICY_ALERT_1_TIMEOUT + assert alert_lvls[int((_redlight_time+0.5)/DT_DMON)] == 1 + assert alert_lvls[int((_redlight_time+_alert_1_to_2+0.5)/DT_DMON)] == 2 + + # engaged, distracted while moving, then car stops after reaching orange + # - should reset timer to pre green at low speed + def test_distracted_then_stops(self): + _stop_time = DISTRACTED_SECONDS_TO_ORANGE + 1 # stop 1 second after reaching orange + lowspeed_vector = always_false[:] + lowspeed_vector[int(_stop_time/DT_DMON):] = [True] * int((TEST_TIMESPAN-_stop_time)/DT_DMON) + alert_lvls, _ = self._run_seq(always_distracted, always_false, always_true, lowspeed_vector) + # just before and briefly after stopping: orange alert; goes away quickly after stopped + assert alert_lvls[int((_stop_time+0.1)/DT_DMON)] == 2 + assert alert_lvls[int((_stop_time+0.5)/DT_DMON)] == 0 # engaged, model is somehow uncertain and driver is distracted # - should fall back to wheel touch after uncertain alert def test_somehow_indecisive_model(self): ds_vector = [msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN] * int(TEST_TIMESPAN/DT_DMON) interaction_vector = always_false[:] - events, d_status = self._run_seq(ds_vector, interaction_vector, always_true, always_false) - assert EventName.preDriverUnresponsive in \ - events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME-0.1)/DT_DMON)].names - assert EventName.promptDriverUnresponsive in \ - events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names - assert EventName.driverUnresponsive in \ - events[int((INVISIBLE_SECONDS_TO_RED-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names - - -@pytest.mark.parametrize("enabled_state, lat_active_state, expected", [ - (False, False, False), # Both Disabled - (True, False, True), # OP Enabled, Lat Inactive - (False, True, True), # OP Disabled, Lat Active (e.g. AOL) - (True, True, True) # Both Active -]) -def test_enabled_states(enabled_state, lat_active_state, expected): - """ - Test DriverMonitoring.run_step with all 4 combinations of: - - selfdriveState.enabled (True/False) - - carControl.latActive (True/False) - """ - cs = car.CarState.new_message() - cs.vEgo = 30.0 - cs.gearShifter = car.CarState.GearShifter.drive - cs.standstill = False - cs.steeringPressed = False - cs.gasPressed = False - - ss = log.SelfdriveState.new_message() - ss.enabled = enabled_state - - cc = car.CarControl.new_message() - cc.latActive = lat_active_state - - mv2 = log.ModelDataV2.new_message() - mv2.meta.disengagePredictions.brakeDisengageProbs = [0.0] - - lc = log.LiveCalibrationData.new_message() - lc.rpyCalib = [0.0, 0.0, 0.0] - - ds = make_msg(False) - - sm = { - 'carState': cs, - 'selfdriveState': ss, - 'carControl': cc, - 'modelV2': mv2, - 'liveCalibration': lc, - 'driverStateV2': ds - } - - driver_monitoring = DriverMonitoring() - - # run_test doesn't assign enabled to a variable, so we need to spy on _update_events to see its value - captured_args = [] - original_update_events = driver_monitoring._update_events - - def spy_update_events(driver_engaged, op_engaged, standstill, wrong_gear, car_speed): - captured_args.append(op_engaged) - return original_update_events(driver_engaged, op_engaged, standstill, wrong_gear, car_speed) - - driver_monitoring._update_events = spy_update_events - - driver_monitoring.run_step(sm, demo=False) - - # Assertion - assert len(captured_args) == 1, "Expected _update_events to be called exactly once" - actual_enabled = captured_args[0] - - assert actual_enabled == expected, f"Expected op_engaged={expected}, but got {actual_enabled}" - + alert_lvls, d_status = self._run_seq(ds_vector, interaction_vector, always_true, always_false) + s = d_status.settings + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*s._HI_STD_FALLBACK_TIME-0.1)/DT_DMON)] == 1 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*s._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)] == 2 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED-1+DT_DMON*s._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)] == 3 diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 1085e11..5f47508 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -79,6 +79,15 @@ def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.S Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4) +def too_distracted_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: + if sm['driverMonitoringState'].lockout: + mins_left = sm['driverMonitoringState'].lockoutMinutesRemaining + if mins_left <= 0: + return NoEntryAlert("Distraction Level Too High", priority=Priority.HIGH) + return NoEntryAlert("Too Distracted", f"{mins_left} minute{'s' if mins_left != 1 else ''} Left", priority=Priority.HIGH) + return NoEntryAlert("Pay Attention to Engage", priority=Priority.HIGH) + + def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: first_word = 'Recalibrating' if sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.recalibrating else 'Calibrating' return Alert( @@ -226,7 +235,8 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin return NormalPermanentAlert(title, text) -def invalid_lkas_setting_no_entry_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: +def invalid_lkas_setting_no_entry_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, + metric: bool, soft_disable_time: int, personality) -> Alert: if CP.brand == "tesla": return NoEntryAlert("FSD / Autosteer is active", alert_text_1="Dashcam Mode") return NoEntryAlert("Invalid LKAS setting") @@ -360,15 +370,15 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.8), }, - EventName.preDriverDistracted: { + EventName.driverDistracted1: { ET.PERMANENT: Alert( "Pay Attention", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), + Priority.LOW, VisualAlert.none, AudibleAlert.preAlert, .1), }, - EventName.promptDriverDistracted: { + EventName.driverDistracted2: { ET.PERMANENT: Alert( "Pay Attention", "Driver Distracted", @@ -376,7 +386,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1), }, - EventName.driverDistracted: { + EventName.driverDistracted3: { ET.PERMANENT: Alert( "DISENGAGE IMMEDIATELY", "Driver Distracted", @@ -384,7 +394,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1), }, - EventName.preDriverUnresponsive: { + EventName.driverUnresponsive1: { ET.PERMANENT: Alert( "Touch Steering Wheel: No Face Detected", "", @@ -392,7 +402,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .1), }, - EventName.promptDriverUnresponsive: { + EventName.driverUnresponsive2: { ET.PERMANENT: Alert( "Touch Steering Wheel", "Driver Unresponsive", @@ -400,7 +410,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1), }, - EventName.driverUnresponsive: { + EventName.driverUnresponsive3: { ET.PERMANENT: Alert( "DISENGAGE IMMEDIATELY", "Driver Unresponsive", @@ -632,7 +642,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.tooDistracted: { - ET.NO_ENTRY: NoEntryAlert("Distraction Level Too High"), + ET.NO_ENTRY: too_distracted_alert, }, EventName.excessiveActuation: { @@ -878,14 +888,14 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { if HARDWARE.get_device_type() == 'mici': EVENTS.update({ - EventName.preDriverDistracted: { + EventName.driverDistracted1: { ET.PERMANENT: Alert( "Pay Attention", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 2), + Priority.LOW, VisualAlert.none, AudibleAlert.preAlert, 2), }, - EventName.promptDriverDistracted: { + EventName.driverDistracted2: { ET.PERMANENT: Alert( "Pay Attention", "Driver Distracted", diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 285d46f..15da7cc 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -54,6 +54,8 @@ EventName = log.OnroadEvent.EventName ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel TurnDirection = custom.IQTurnSignalDirection +AlertLevel = log.DriverMonitoringState.AlertLevel +MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) @@ -147,6 +149,7 @@ class SelfdriveD(GapButtonActions): self.events_prev = [] self.logged_comm_issue = None self.not_running_prev = None + self.dm_lockout_set = False self.experimental_mode = False self.personality = get_sanitize_int_param( "LongitudinalPersonality", @@ -183,7 +186,6 @@ class SelfdriveD(GapButtonActions): self.events_iq = IQEvents() self.events_iq_prev = [] - self._cached_dm_event_names: tuple[int, ...] = () self._cached_plan_event_names: tuple[int, ...] = () self._cached_model_event_names: tuple[int, ...] = () self._cached_nav_event_names: tuple[int, ...] = () @@ -205,10 +207,6 @@ class SelfdriveD(GapButtonActions): if self.sm.updated['iqPlan']: self._cached_plan_event_names = tuple(event.name.raw for event in self._get_longitudinal_plan_ext().events) - def _refresh_cached_dm_events(self) -> None: - if self.sm.updated['driverMonitoringState']: - self._cached_dm_event_names = tuple(event.name.raw for event in self.sm['driverMonitoringState'].events) - def _refresh_cached_model_events(self) -> None: if not self.sm.updated['iqDriveModelData']: return @@ -297,8 +295,24 @@ class SelfdriveD(GapButtonActions): self.events.add(EventName.resumeBlocked) if not self.CP.notCar: - self._refresh_cached_dm_events() - self._add_event_names(self._cached_dm_event_names) + if self.sm['driverMonitoringState'].lockout and not self.dm_lockout_set: + self.params.put_bool("DriverTooDistracted", True) + self.dm_lockout_set = True + elif not self.sm['driverMonitoringState'].lockout and self.dm_lockout_set: + self.params.remove("DriverTooDistracted") + self.dm_lockout_set = False + + if self.sm['driverMonitoringState'].lockout or self.sm['driverMonitoringState'].alwaysOnLockout: + self.events.add(EventName.tooDistracted) + + vision_dm = self.sm['driverMonitoringState'].activePolicy == MonitoringPolicy.vision + if self.sm['driverMonitoringState'].alertLevel == AlertLevel.one: + self.events.add(EventName.driverDistracted1 if vision_dm else EventName.driverUnresponsive1) + elif self.sm['driverMonitoringState'].alertLevel == AlertLevel.two: + self.events.add(EventName.driverDistracted2 if vision_dm else EventName.driverUnresponsive2) + elif self.sm['driverMonitoringState'].alertLevel == AlertLevel.three: + self.events.add(EventName.driverDistracted3 if vision_dm else EventName.driverUnresponsive3) + self._refresh_cached_plan_events() self._add_iq_event_names(self._cached_plan_event_names) diff --git a/selfdrive/selfdrived/tests/test_driver_monitoring.py b/selfdrive/selfdrived/tests/test_driver_monitoring.py new file mode 100644 index 0000000..71bf8dd --- /dev/null +++ b/selfdrive/selfdrived/tests/test_driver_monitoring.py @@ -0,0 +1,40 @@ +from types import SimpleNamespace + +from cereal import car, log + +from openpilot.selfdrive.selfdrived.events import EVENTS, ET + + +EventName = log.OnroadEvent.EventName +AudibleAlert = car.CarControl.HUDControl.AudibleAlert + + +def test_driver_monitoring_alert_stages(): + expected_sounds = { + EventName.driverDistracted1: AudibleAlert.preAlert, + EventName.driverDistracted2: AudibleAlert.promptDistracted, + EventName.driverDistracted3: AudibleAlert.warningImmediate, + EventName.driverUnresponsive1: AudibleAlert.none, + EventName.driverUnresponsive2: AudibleAlert.promptDistracted, + EventName.driverUnresponsive3: AudibleAlert.warningImmediate, + } + + for event_name, audible_alert in expected_sounds.items(): + assert EVENTS[event_name][ET.PERMANENT].audible_alert == audible_alert + + +def test_driver_monitoring_lockout_alert(): + callback = EVENTS[EventName.tooDistracted][ET.NO_ENTRY] + sm = {'driverMonitoringState': SimpleNamespace(lockout=True, lockoutMinutesRemaining=5)} + alert = callback(None, None, sm, False, 0, None) + + assert alert.alert_text_1 == "5 minutes Left" + assert alert.alert_text_2 == "Too Distracted" + + +def test_legacy_driver_monitoring_lockout_alert(): + callback = EVENTS[EventName.tooDistracted][ET.NO_ENTRY] + sm = {'driverMonitoringState': SimpleNamespace(lockout=True, lockoutMinutesRemaining=0)} + alert = callback(None, None, sm, False, 0, None) + + assert alert.alert_text_2 == "Distraction Level Too High" diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 010ce3f..b135a68 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -455,21 +455,46 @@ def migrate_onroadEvents(msgs): return ops, [], [] -@migration(inputs=["driverMonitoringState"]) +@migration(inputs=["driverMonitoringStateDEPRECATED"]) def migrate_driverMonitoringState(msgs): ops = [] for index, msg in msgs: - msg = msg.as_builder() - events = [] - for event in msg.driverMonitoringState.eventsDEPRECATED: - try: - if not str(event.name).endswith('DEPRECATED'): - # dict converts name enum into string representation - events.append(log.OnroadEvent(**event.to_dict())) - except RuntimeError: # Member was null - traceback.print_exc() + old = msg.driverMonitoringStateDEPRECATED + new_msg = messaging.new_message('driverMonitoringState', valid=msg.valid, logMonoTime=msg.logMonoTime) + dm = new_msg.driverMonitoringState + dm.isRHD = old.isRHD + dm.activePolicy = log.DriverMonitoringState.MonitoringPolicy.vision if old.isActiveMode else \ + log.DriverMonitoringState.MonitoringPolicy.wheeltouch - msg.driverMonitoringState.events = events - ops.append((index, msg.as_reader())) + AlertLevel = log.DriverMonitoringState.AlertLevel + event_to_alert_level = { + 'driverDistracted1': AlertLevel.one, 'driverUnresponsive1': AlertLevel.one, + 'driverDistracted2': AlertLevel.two, 'driverUnresponsive2': AlertLevel.two, + 'driverDistracted3': AlertLevel.three, 'driverUnresponsive3': AlertLevel.three, + } + for event in old.events: + level = event_to_alert_level.get(str(event.name)) + if level is not None: + dm.alertLevel = level + break + dm.lockout = any(str(event.name) == 'tooDistracted' for event in old.events) + + dm.visionPolicyState.awarenessPercent = int(max(0, min(100, (old.awarenessStatus if old.isActiveMode else old.awarenessActive) * 100))) + dm.visionPolicyState.awarenessStep = old.stepChange if old.isActiveMode else 0. + dm.visionPolicyState.isDistracted = old.isDistracted + dm.visionPolicyState.distractedTypes.pose = bool(old.distractedType & 1) + dm.visionPolicyState.distractedTypes.eye = bool(old.distractedType & 2) + dm.visionPolicyState.distractedTypes.phone = bool(old.distractedType & 4) + dm.visionPolicyState.faceDetected = old.faceDetected + dm.visionPolicyState.pose.pitchCalib.offset = old.posePitchOffset + dm.visionPolicyState.pose.pitchCalib.calibratedPercent = int(min(100, old.posePitchValidCount / 1200 * 100)) + dm.visionPolicyState.pose.yawCalib.offset = old.poseYawOffset + dm.visionPolicyState.pose.yawCalib.calibratedPercent = int(min(100, old.poseYawValidCount / 1200 * 100)) + dm.visionPolicyState.pose.calibrated = old.posePitchValidCount >= 1200 and old.poseYawValidCount >= 1200 + dm.visionPolicyState.wheeltouchFallbackPercent = int(min(100, old.hiStdCount / 200 * 100)) + dm.visionPolicyState.uncertainOffroadAlertPercent = int(min(100, old.uncertainCount / 1200 * 100)) + dm.wheeltouchPolicyState.awarenessPercent = int(max(0, min(100, (old.awarenessPassive if old.isActiveMode else old.awarenessStatus) * 100))) + dm.wheeltouchPolicyState.awarenessStep = 0. if old.isActiveMode else old.stepChange + ops.append((index, new_msg.as_reader())) return ops, [], [] diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index c2004e9..7a45da0 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -9,7 +9,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop if gui_app.iqpilot_ui(): - from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item + from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item # Description constants DESCRIPTIONS = { diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 30aebc7..f65eb7a 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -21,7 +21,7 @@ from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.widgets.scroller_tici import Scroller if gui_app.iqpilot_ui(): - from openpilot.system.ui.iqpilot.widgets.list_view import button_item + from openpilot.system.ui.iqwidgets.widgets.list_view import button_item # Description constants DESCRIPTIONS = { diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 9dc8e3e..712eb9f 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -12,7 +12,7 @@ from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.widgets.scroller_tici import Scroller if gui_app.iqpilot_ui(): - from openpilot.system.ui.iqpilot.widgets.list_view import button_item + from openpilot.system.ui.iqwidgets.widgets.list_view import button_item # TODO: remove this. updater fails to respond on startup if time is not correct UPDATED_TIMEOUT = 10 # seconds to wait for updated to respond @@ -237,7 +237,7 @@ class SoftwareLayout(Widget): def _on_auth_branch(self): # Collect username then token; store encrypted and signal the updater to # re-check (refreshing the available-branch list for private repos). - from openpilot.system.ui.iqpilot.widgets.list_view import open_text_prompt + from openpilot.system.ui.iqwidgets.widgets.list_view import open_text_prompt from openpilot.common import git_creds creds = git_creds.get_credentials() diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 9974ebf..eda7e3b 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -10,8 +10,8 @@ from openpilot.system.ui.widgets import DialogResult from openpilot.selfdrive.ui.ui_state import ui_state if gui_app.iqpilot_ui(): - from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item - from openpilot.system.ui.iqpilot.widgets.list_view import multiple_button_item + from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item + from openpilot.system.ui.iqwidgets.widgets.list_view import multiple_button_item from openpilot.iqpilot.ui.layouts.settings.iq_dynamic import IQDynamicLayout PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants diff --git a/selfdrive/ui/layouts/settings_hub.py b/selfdrive/ui/layouts/settings_hub.py index bb9c52a..7142f49 100644 --- a/selfdrive/ui/layouts/settings_hub.py +++ b/selfdrive/ui/layouts/settings_hub.py @@ -544,10 +544,10 @@ class SettingsHubLayout(Widget): def _toggle_offroad_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Enter Always Offroad Mode"))) + gui_app.set_modal_overlay(alert_dialog(tr("Disengage before forcing offroad"))) return active = ui_state.params.get_bool("IQAlwaysOffroad") - msg = tr("Are you sure you want to exit Always Offroad mode?") if active else tr("Are you sure you want to enter Always Offroad mode?") + msg = tr("Leave forced-offroad mode now?") if active else tr("Switch the device into forced-offroad mode?") def _confirm(result: int): if result == DialogResult.CONFIRM and not ui_state.engaged: diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 11a8154..8efcdd9 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -200,7 +200,7 @@ class TrainingGuideDMTutorial(Widget): looking_center = False # stay at 100% once reached - if (dm_state.faceDetected and looking_center) or self._progress.x > 0.99: + if (dm_state.visionPolicyState.faceDetected and looking_center) or self._progress.x > 0.99: slow = self._progress.x < 0.25 duration = self.PROGRESS_DURATION * 2 if slow else self.PROGRESS_DURATION self._progress.x += 1.0 / (duration * gui_app.target_fps) diff --git a/selfdrive/ui/mici/layouts/settings/display.py b/selfdrive/ui/mici/layouts/settings/display.py index e2a16a6..8943605 100644 --- a/selfdrive/ui/mici/layouts/settings/display.py +++ b/selfdrive/ui/mici/layouts/settings/display.py @@ -27,7 +27,7 @@ class DisplayLayoutMici(NavScroller): self._force_mici = BigParamControl("force mici UI", "ForceSmallUI") self._display_bright = MappedParamToggle("display brightness", "Brightness", _DISPLAY_BRIGHT_OPTIONS, _DISPLAY_BRIGHT_VALUES) - self._onroad_bright = MappedParamToggle("onroad brightness", "OnroadScreenOffBrightness", + self._onroad_bright = MappedParamToggle("driving brightness", "OnroadScreenOffBrightness", _ONROAD_BRIGHT_OPTIONS, _ONROAD_BRIGHT_VALUES) self._delay = MappedParamToggle("brightness delay", "OnroadScreenOffTimer", _DELAY_OPTIONS, _DELAY_VALUES) diff --git a/selfdrive/ui/mici/layouts/settings/models.py b/selfdrive/ui/mici/layouts/settings/models.py index d6d1aba..23fd338 100644 --- a/selfdrive/ui/mici/layouts/settings/models.py +++ b/selfdrive/ui/mici/layouts/settings/models.py @@ -10,7 +10,7 @@ import pyray as rl from cereal import custom -from openpilot.system.ui.iqpilot.widgets.helpers.glyphs import draw_star +from openpilot.system.ui.iqwidgets.widgets.helpers.glyphs import draw_star from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app @@ -96,10 +96,10 @@ class ModelsLayoutMici(NavScroller): self._redownload_icon = gui_app.texture("icons_mici/settings/device/update.png", 56, 56, keep_aspect_ratio=True) self._reset_icon = gui_app.texture("icons_mici/wheel.png", 56, 56) - self._current = BigButton("current model") + self._current = BigButton("active model") self._current.set_click_callback(self._show_folders) - self._cancel = BigButton("cancel download") + self._cancel = BigButton("stop download") self._cancel.set_click_callback(self._cancel_model_request) self._cancel.set_visible(self._is_downloading) @@ -107,17 +107,17 @@ class ModelsLayoutMici(NavScroller): self._redownload.set_click_callback(self._confirm_redownload_model) self._redownload.set_enabled(self._can_redownload) - self._refresh = BigButton("refresh model list") + self._refresh = BigButton("reload model list") self._refresh.set_click_callback(lambda: ui_state.params.put("ModelManager_LastSyncTime", 0)) - self._supercombo = GreyBigButton("driving model") + self._supercombo = GreyBigButton("combined model") self._supercombo.set_visible(False) - self._vision = GreyBigButton("vision model") + self._vision = GreyBigButton("vision weights") self._vision.set_visible(False) - self._policy = GreyBigButton("policy model") + self._policy = GreyBigButton("policy weights") self._policy.set_visible(False) - self._clear = BigButton("clear model cache") + self._clear = BigButton("purge model cache") self._clear.set_click_callback(self._confirm_clear_cache) self._clear.set_enabled(lambda: ui_state.is_offroad()) @@ -125,7 +125,7 @@ class ModelsLayoutMici(NavScroller): self._sw_delay = MappedParamToggle("manual delay offset", "IQSoftwareSteerDelay", _DELAY_OPTIONS, _DELAY_VALUES) self._sw_delay.set_visible(lambda: not self._steer_delay._checked) - self._lane_turn = BigParamControl("use lane turn desires", "IQLaneTurnDesire") + self._lane_turn = BigParamControl("low-speed turn planning", "IQLaneTurnDesire") self._lane_speed = MappedParamToggle("lane turn speed", "IQLaneTurnValue", _LANE_TURN_OPTIONS, _LANE_TURN_VALUES) self._lane_speed.set_visible(lambda: self._lane_turn._checked) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index ea48633..cfca724 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -99,6 +99,32 @@ class ForgetButton(Widget): rl.draw_texture_ex(self._trash_txt, (trash_x, trash_y), 0, 1.0, rl.WHITE) +class DisconnectButton(Widget): + MARGIN = 12 + RADIUS = 42 + # the only round button art is the destructive red one, and dropping a connection is not destructive + BG = rl.Color(56, 56, 61, 255) + BG_PRESSED = rl.Color(84, 84, 90, 255) + + def __init__(self, disconnect_network: Callable): + super().__init__() + self._disconnect_network = disconnect_network + self._slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 38, 38) + self.set_rect(rl.Rectangle(0, 0, 84 + self.MARGIN * 2, 84 + self.MARGIN * 2)) + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + dlg = BigConfirmationDialog("slide to\ndisconnect", gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 54, 54), + self._disconnect_network) + gui_app.push_widget(dlg) + + def _render(self, _): + center = rl.Vector2(self._rect.x + self._rect.width / 2, self._rect.y + self._rect.height / 2) + rl.draw_circle_v(center, self.RADIUS, self.BG_PRESSED if self.is_pressed else self.BG) + rl.draw_texture_ex(self._slash_txt, (center.x - self._slash_txt.width / 2, center.y - self._slash_txt.height / 2), + 0, 1.0, rl.WHITE) + + class WifiButton(BigButton): LABEL_PADDING = 98 LABEL_WIDTH = 402 - 98 - 28 @@ -111,9 +137,11 @@ class WifiButton(BigButton): self._connecting_ssid = connecting_ssid self._wifi_icon = WifiIcon(network) self._forget_btn = ForgetButton(self._forget_network) + self._disconnect_btn = DisconnectButton(self._disconnect_network) self._check_txt = gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 32, 32) self._network_missing = False self._network_forgetting = False + self._network_disconnecting = False self._wrong_password = False @property @@ -142,9 +170,18 @@ class WifiButton(BigButton): self._network_forgetting = True self._wifi_manager.forget_connection(self._network.ssid) + def _disconnect_network(self): + if self._network_disconnecting: + return + self._network_disconnecting = True + self._wifi_manager.disconnect_connection(self._network.ssid) + def on_forgotten(self): self._network_forgetting = False + def on_disconnected(self): + self._network_disconnecting = False + def set_network_missing(self, missing: bool): self._network_missing = missing self._wifi_icon.set_network_missing(missing) @@ -171,31 +208,44 @@ class WifiButton(BigButton): @property def _show_forget_btn(self) -> bool: - if self._is_tethering or self._network_forgetting: + if self._is_tethering or self._network_forgetting or self._show_disconnect_btn: return False return (self._is_saved and not self._wrong_password) or self._is_connecting + @property + def _show_disconnect_btn(self) -> bool: + # 402 units of row cannot hold both buttons plus the status word, so the slot is contextual: + # disconnect while connected, forget once it is only saved + if self._is_tethering or self._network_forgetting or self._network_disconnecting: + return False + return self._is_connected + def _handle_mouse_release(self, mouse_pos: MousePos): if self._show_forget_btn and rl.check_collision_point_rec(mouse_pos, self._forget_btn.rect): return + if self._show_disconnect_btn and rl.check_collision_point_rec(mouse_pos, self._disconnect_btn.rect): + return super()._handle_mouse_release(mouse_pos) def _get_label_font_size(self): return 48 def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: - super().set_touch_valid_callback(lambda: touch_callback() and not self._forget_btn.is_pressed) + super().set_touch_valid_callback(lambda: touch_callback() and not self._forget_btn.is_pressed and not self._disconnect_btn.is_pressed) self._forget_btn.set_touch_valid_callback(touch_callback) + self._disconnect_btn.set_touch_valid_callback(touch_callback) def _update_state(self): super()._update_state() if any((self._network_missing, self._is_connecting, self._is_connected, self._network_forgetting, - self._network.security_type == SecurityType.UNSUPPORTED)): + self._network_disconnecting, self._network.security_type == SecurityType.UNSUPPORTED)): self.set_enabled(False) self._sub_label.set_color(rl.Color(255, 255, 255, int(255 * 0.585))) self._sub_label.set_font_weight(FontWeight.ROMAN) if self._network_forgetting: self.set_value("forgetting...") + elif self._network_disconnecting: + self.set_value("disconnecting...") elif self._is_connecting: self.set_value("starting..." if self._is_tethering else "connecting...") elif self._is_connected: @@ -219,9 +269,10 @@ class WifiButton(BigButton): if self.value: sub_label_x = self._rect.x + self.LABEL_HORIZONTAL_PADDING label_y = btn_y + self._rect.height - self.LABEL_VERTICAL_PADDING - sub_label_w = self.SUB_LABEL_WIDTH - (self._forget_btn.rect.width if self._show_forget_btn else 0) + sub_label_w = self.SUB_LABEL_WIDTH - (self._forget_btn.rect.width if self._show_forget_btn else 0) \ + - (self._disconnect_btn.rect.width if self._show_disconnect_btn else 0) sub_label_height = self._sub_label.get_content_height(sub_label_w) - if self._is_connected and not self._network_forgetting: + if self._is_connected and not self._network_forgetting and not self._network_disconnecting: check_y = int(label_y - sub_label_height + (sub_label_height - self._check_txt.height) / 2) rl.draw_texture_ex(self._check_txt, rl.Vector2(sub_label_x, check_y), 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65))) sub_label_x += self._check_txt.width + 14 @@ -230,11 +281,19 @@ class WifiButton(BigButton): self._wifi_icon.render(rl.Rectangle(self._rect.x + 30, btn_y + 30, self._wifi_icon.rect.width, self._wifi_icon.rect.height)) + btn_right = self._rect.x + self._rect.width if self._show_forget_btn: self._forget_btn.render(rl.Rectangle( - self._rect.x + self._rect.width - self._forget_btn.rect.width, + btn_right - self._forget_btn.rect.width, btn_y + self._rect.height - self._forget_btn.rect.height, self._forget_btn.rect.width, self._forget_btn.rect.height)) + btn_right -= self._forget_btn.rect.width + + if self._show_disconnect_btn: + self._disconnect_btn.render(rl.Rectangle( + btn_right - self._disconnect_btn.rect.width, + btn_y + self._rect.height - self._disconnect_btn.rect.height, + self._disconnect_btn.rect.width, self._disconnect_btn.rect.height)) class ScanningButton(BigButton): @@ -298,6 +357,9 @@ class WifiUIMici(NavScroller): def _on_disconnected(self): self._connecting = None + for btn in self._scroller.items: + if isinstance(btn, WifiButton): + btn.on_disconnected() def _on_forgotten(self, ssid=None): self._connecting = None diff --git a/selfdrive/ui/mici/layouts/settings/steering.py b/selfdrive/ui/mici/layouts/settings/steering.py index 2fa864f..70711d2 100644 --- a/selfdrive/ui/mici/layouts/settings/steering.py +++ b/selfdrive/ui/mici/layouts/settings/steering.py @@ -47,7 +47,7 @@ class SabSettingsPanel(NavScroller): self._main_cruise = BigParamControl("Availability While Cruise Changes", "AolMainCruiseAllowed") self._brake = SabBrakeToggle() self._mode = MappedParamToggle("Brake Response Mode", "AolSteeringMode", - ["remain active", "standby", "disengage"], [0, 1, 2]) + ["stay engaged", "standby", "disengage"], [0, 1, 2]) self._scroller.add_widgets([self._main_cruise, self._brake, self._mode]) def show_event(self): @@ -67,7 +67,7 @@ class LaneChangePanel(NavScroller): def __init__(self): super().__init__() self._timer = MappedParamToggle("Auto Lane Change", "IQLaneChangeTimer", - ["off", "nudge", "nudgeless", "0.5 s", "1 s", "2 s", "3 s"], + ["off", "nudge", "no nudge", "0.5 s", "1 s", "2 s", "3 s"], [-1, 0, 1, 2, 3, 4, 5]) self._bsm_delay = BigParamControl("Delay with Blind Spot", "IQLaneChangeBsmDelay") self._continuous = BigParamControl("Continuous Changes", "LaneChangeContinuous") @@ -87,7 +87,7 @@ class LaneChangePanel(NavScroller): class SteeringLayoutMici(NavScroller): - _AOL_MODES = ["remain active", "standby", "disengage"] + _AOL_MODES = ["stay engaged", "standby", "disengage"] def __init__(self): super().__init__() diff --git a/selfdrive/ui/mici/layouts/settings/vehicle.py b/selfdrive/ui/mici/layouts/settings/vehicle.py index 571c575..be0f29d 100644 --- a/selfdrive/ui/mici/layouts/settings/vehicle.py +++ b/selfdrive/ui/mici/layouts/settings/vehicle.py @@ -73,7 +73,7 @@ class VehicleLayoutMici(NavScroller): toggle_callback=self._on_toyota_long) self._hyundai_tuning = MappedParamToggle("hyundai long. tuning", "HyundaiLongitudinalTuning", ["off", "dynamic", "predictive"], [0, 1, 2]) - self._subaru_snag = BigParamControl("stop and go (beta)", "SubaruStopAndGo") + self._subaru_snag = BigParamControl("creep from standstill (beta)", "SubaruStopAndGo") self._subaru_manual = BigParamControl("stop and go manual brake", "SubaruStopAndGoManualParkingBrake") self._vw_pq_hca = BigParamControl("PQ HCA status 7 mode", "pqhca5or7Toggle") self._vw_lateral = BigParamControl("lateral when cruise faulted", "AllowLateralWhenLongUnavailable") diff --git a/selfdrive/ui/mici/layouts/settings/visuals.py b/selfdrive/ui/mici/layouts/settings/visuals.py index 1abbba9..67f7cce 100644 --- a/selfdrive/ui/mici/layouts/settings/visuals.py +++ b/selfdrive/ui/mici/layouts/settings/visuals.py @@ -10,7 +10,7 @@ class VisualsLayoutMici(NavScroller): def __init__(self): super().__init__() self._blind_spot = BigParamControl("Blind Spot Warnings", "BlindSpot") - self._steering_arc = BigParamControl("Steering Arc", "TorqueBar") + self._steering_arc = BigParamControl("Steering Effort Arc", "TorqueBar") self._road_name = BigParamControl("Road Name", "RoadNameToggle") self._turn_signals = BigParamControl("Turn Signals", "ShowTurnSignals") self._accel_bar = BigParamControl("Acceleration Bar", "RocketFuel") diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index 68ff785..1ad90a2 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -1,20 +1,14 @@ import pyray as rl -from cereal import log, messaging +from cereal import car, log, messaging from msgq.visionipc import VisionStreamType from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.ui_state import ui_state, device -from openpilot.selfdrive.selfdrived.events import EVENTS, ET from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import gui_label -EventName = log.OnroadEvent.EventName - -EVENT_TO_INT = EventName.schema.enumerants - - class DriverCameraView(CameraView): def _calc_frame_matrix(self, rect: rl.Rectangle): base = super()._calc_frame_matrix(rect) @@ -116,10 +110,14 @@ class DriverCameraDialog(NavWidget): return msg = messaging.new_message('selfdriveState') - if dm_state is not None and len(dm_state.events): - event_name = EVENT_TO_INT[dm_state.events[0].name] - if event_name is not None and event_name in EVENTS and ET.PERMANENT in EVENTS[event_name]: - msg.selfdriveState.alertSound = EVENTS[event_name][ET.PERMANENT].audible_alert + if dm_state is not None: + AudibleAlert = car.CarControl.HUDControl.AudibleAlert + alert_sounds = { + 'one': AudibleAlert.preAlert, + 'two': AudibleAlert.promptDistracted, + 'three': AudibleAlert.warningImmediate, + } + msg.selfdriveState.alertSound = alert_sounds.get(str(dm_state.alertLevel), AudibleAlert.none) self._pm.send('selfdriveState', msg) def _render_dm_alerts(self, rect: rl.Rectangle): @@ -127,29 +125,30 @@ class DriverCameraDialog(NavWidget): dm_state = ui_state.sm["driverMonitoringState"] self._publish_alert_sound(dm_state) + is_vision = dm_state.activePolicy == log.DriverMonitoringState.MonitoringPolicy.vision + awareness_pct = dm_state.visionPolicyState.awarenessPercent if is_vision else dm_state.wheeltouchPolicyState.awarenessPercent gui_label(rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height), - f"Awareness: {dm_state.awarenessStatus * 100:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM, + f"Awareness: {awareness_pct:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, color=rl.Color(0, 0, 0, 180)) - gui_label(rect, f"Awareness: {dm_state.awarenessStatus * 100:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM, + gui_label(rect, f"Awareness: {awareness_pct:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, color=rl.Color(255, 255, 255, int(255 * 0.9))) - if not dm_state.events: + if dm_state.alertLevel == log.DriverMonitoringState.AlertLevel.none: return - # Show first event (only one should be active at a time) - event_name_str = str(dm_state.events[0].name).split('.')[-1] + alert_level_str = f"{'Pay Attention' if is_vision else 'Touch Wheel'} - level {dm_state.alertLevel}" alignment = rl.GuiTextAlignment.TEXT_ALIGN_RIGHT if self.driver_state_renderer.is_rhd else rl.GuiTextAlignment.TEXT_ALIGN_LEFT shadow_rect = rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height) - gui_label(shadow_rect, event_name_str, font_size=40, font_weight=FontWeight.BOLD, + gui_label(shadow_rect, alert_level_str, font_size=40, font_weight=FontWeight.BOLD, alignment=alignment, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, color=rl.Color(0, 0, 0, 180)) - gui_label(rect, event_name_str, font_size=40, font_weight=FontWeight.BOLD, + gui_label(rect, alert_level_str, font_size=40, font_weight=FontWeight.BOLD, alignment=alignment, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, color=rl.Color(255, 255, 255, int(255 * 0.9))) @@ -166,7 +165,7 @@ class DriverCameraDialog(NavWidget): def _draw_face_detection(self, rect: rl.Rectangle): dm_state = ui_state.sm["driverMonitoringState"] driver_data = self.driver_state_renderer.get_driver_data() - if not dm_state.faceDetected: + if not dm_state.visionPolicyState.faceDetected: return # Get face position and orientation diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index 8a20137..bd2e694 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -6,12 +6,12 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.monitoring.helpers import face_orientation_from_net AlertSize = log.SelfdriveState.AlertSize DEBUG = False ACTIVE_ACCENT = rl.Color(0x0C, 0x94, 0x96, 0xFF) +CONE_COLOR_ORANGE = (255, 115, 0) LOOKING_CENTER_THRESHOLD_UPPER = math.radians(6) LOOKING_CENTER_THRESHOLD_LOWER = math.radians(3) @@ -21,6 +21,7 @@ class DriverStateRenderer(Widget): BASE_SIZE = 60 LINES_ANGLE_INCREMENT = 5 LINES_STALE_ANGLES = 3.0 # seconds + AWARENESS_UNFULL_PERCENT = 95 def __init__(self, lines: bool = False, inset: bool = False): super().__init__() @@ -35,11 +36,15 @@ class DriverStateRenderer(Widget): self._is_active = False self._is_rhd = False self._face_detected = False + self._face_pitch = 0. + self._face_yaw = 0. self._should_draw = False self._force_active = False self._looking_center = False + self._awareness_unfull = False self._fade_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps) + self._color_fade_filter = FirstOrderFilter(1.0, 0.05, 1 / gui_app.target_fps) self._pitch_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) self._yaw_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) self._rotation_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps, initialized=False) @@ -95,6 +100,7 @@ class DriverStateRenderer(Widget): rl.Color(255, 255, 255, int(255 * 0.9 * self._fade_filter.x))) if self.effective_active: + active_amount = self._color_fade_filter.update(0.0 if self._awareness_unfull else 1.0) source_rect = rl.Rectangle(0, 0, self._dm_cone.width, self._dm_cone.height) dest_rect = rl.Rectangle( self._rect.x + self._rect.width / 2, @@ -104,13 +110,16 @@ class DriverStateRenderer(Widget): ) if not self._lines: + r = int(round(ACTIVE_ACCENT.r * active_amount + CONE_COLOR_ORANGE[0] * (1 - active_amount))) + g = int(round(ACTIVE_ACCENT.g * active_amount + CONE_COLOR_ORANGE[1] * (1 - active_amount))) + b = int(round(ACTIVE_ACCENT.b * active_amount + CONE_COLOR_ORANGE[2] * (1 - active_amount))) rl.draw_texture_pro( self._dm_cone, source_rect, dest_rect, rl.Vector2(dest_rect.width / 2, dest_rect.height / 2), self._rotation_filter.x - 90, - rl.Color(ACTIVE_ACCENT.r, ACTIVE_ACCENT.g, ACTIVE_ACCENT.b, int(255 * self._fade_filter.x)), + rl.Color(r, g, b, int(255 * self._fade_filter.x)), ) else: @@ -150,9 +159,12 @@ class DriverStateRenderer(Widget): sm = ui_state.sm dm_state = sm["driverMonitoringState"] - self._is_active = dm_state.isActiveMode + self._is_active = dm_state.activePolicy == log.DriverMonitoringState.MonitoringPolicy.vision self._is_rhd = dm_state.isRHD - self._face_detected = dm_state.faceDetected + self._face_detected = dm_state.visionPolicyState.faceDetected + self._awareness_unfull = self.effective_active and dm_state.visionPolicyState.awarenessPercent < self.AWARENESS_UNFULL_PERCENT + self._face_pitch = dm_state.visionPolicyState.pose.pitch + math.radians(6) + self._face_yaw = -dm_state.visionPolicyState.pose.yaw driverstate = sm["driverStateV2"] driver_data = driverstate.rightDriverData if self._is_rhd else driverstate.leftDriverData @@ -160,24 +172,9 @@ class DriverStateRenderer(Widget): def _update_state(self): # Get monitoring state - driver_data = self.get_driver_data() - driver_orient = driver_data.faceOrientation - - if len(driver_orient) != 3: - return - - # Calibrate orientation so looking straight ahead at the road (instead of at the device) reads - # (0, 0), using live calibration. Makes the cone point in the correct direction. (stock PR #37149) - sm = ui_state.sm - if sm.valid['liveCalibration'] and len(sm['liveCalibration'].rpyCalib) == 3: - cal_rpy = sm['liveCalibration'].rpyCalib - else: - cal_rpy = [0.0, 0.0, 0.0] - _, pitch, yaw = face_orientation_from_net(driver_orient, driver_data.facePosition, cal_rpy) - yaw = -yaw # undo sign flip in face_orientation_from_net to match UI convention - - pitch = self._pitch_filter.update(pitch) - yaw = self._yaw_filter.update(yaw) + _ = self.get_driver_data() + pitch = self._pitch_filter.update(self._face_pitch) + yaw = self._yaw_filter.update(self._face_yaw) # hysteresis on looking center if abs(pitch) < LOOKING_CENTER_THRESHOLD_LOWER and abs(yaw) < LOOKING_CENTER_THRESHOLD_LOWER: @@ -198,9 +195,8 @@ class DriverStateRenderer(Widget): rl.draw_circle(int(pitch_x), 100, 5, rl.GREEN) rl.draw_circle(int(yaw_x), 120, 5, rl.GREEN) - # filter head rotation, handling wrap-around (bias pitch up since calib/DM pose isn't exact, - # and halve yaw sensitivity) - rotation = math.degrees(math.atan2((pitch + math.radians(6)) * 2, yaw)) + # filter head rotation, handling wrap-around + rotation = math.degrees(math.atan2(pitch * 2, yaw)) angle_diff = rotation - self._rotation_filter.x angle_diff = ((angle_diff + 180) % 360) - 180 self._rotation_filter.update(self._rotation_filter.x + angle_diff) diff --git a/selfdrive/ui/onroad/driver_state.py b/selfdrive/ui/onroad/driver_state.py index 39aa6d1..e34b2c1 100644 --- a/selfdrive/ui/onroad/driver_state.py +++ b/selfdrive/ui/onroad/driver_state.py @@ -114,7 +114,7 @@ class DriverStateRenderer(Widget): # Get monitoring state dm_state = sm["driverMonitoringState"] - self.is_active = dm_state.isActiveMode + self.is_active = dm_state.activePolicy == log.DriverMonitoringState.MonitoringPolicy.vision self.is_rhd = dm_state.isRHD # Update fade state (smoother transition between active/inactive) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index 4e12fab..ec10c64 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -52,6 +52,22 @@ class LeadVehicle: fill_alpha: int = 0 +@dataclass +class VisionDot: + x: float + y: float + tx: float + ty: float + radius: float + tradius: float + alpha: float = 0.0 + talpha: float = 1.0 + rgb: tuple[int, int, int] | None = None + + +_VD_EASE = 0.4 + + class ModelRenderer(Widget, IQModelRenderer): def __init__(self): Widget.__init__(self) @@ -66,14 +82,14 @@ class ModelRenderer(Widget, IQModelRenderer): self._road_edge_stds = np.zeros(2, dtype=np.float32) self._lead_vehicles = [LeadVehicle(), LeadVehicle()] self._track_dots: list[LeadVehicle] = [] - self._vision_dots: list[LeadVehicle] = [] - self._sign_dots: list[tuple[float, float, float, rl.Color]] = [] + self._vision_dots: list[VisionDot] = [] + self._vt_frame = -1 self._frame_transform: np.ndarray | None = None self._frame_transform_wide = False self._path_offset_z = HEIGHT_INIT[0] self._counter = -1 self._camera_offset = ui_state.params.get("CameraOffset", return_default=True) if ui_state.active_bundle else 0.0 - self._ambient_dots = ui_state.params.get_bool("AmbientTrackDots") + self._ambient_dots = bool(ui_state.params.get("AmbientTrackDots", return_default=True)) # Initialize ModelPoints objects self._path = ModelPoints() self._lane_lines = [ModelPoints() for _ in range(4)] @@ -127,7 +143,7 @@ class ModelRenderer(Widget, IQModelRenderer): if self._counter % 60 == 0: self._camera_offset = ui_state.params.get("CameraOffset", return_default=True) if ui_state.active_bundle else 0.0 - self._ambient_dots = ui_state.params.get_bool("AmbientTrackDots") + self._ambient_dots = bool(ui_state.params.get("AmbientTrackDots", return_default=True)) self._counter += 1 if sm.updated['carParams']: @@ -164,7 +180,6 @@ class ModelRenderer(Widget, IQModelRenderer): if self._ambient_dots: self._update_vision_dots(sm) self._draw_vision_dots() - self._draw_sign_dots() if render_lead_indicator and radar_state: if self._ambient_dots: @@ -230,20 +245,34 @@ class ModelRenderer(Widget, IQModelRenderer): self._track_dots.append(LeadVehicle(center=(float(x), float(y)), radius=float(radius), sz=float(sz))) def _update_vision_dots(self, sm): - self._vision_dots = [] - if (self._frame_transform is None or self._frame_transform_wide or - not sm.alive['iqVehicleTracks'] or not sm.valid['iqVehicleTracks']): - return + # iqVehicleTracks arrives at a few Hz; the dots are eased toward the latest + # detection every render frame so they glide instead of teleporting. + hidden = (self._frame_transform is None or + not sm.alive['iqVehicleTracks'] or not sm.valid['iqVehicleTracks']) + if not hidden: + vt = sm['iqVehicleTracks'] + hidden = bool(vt.wide) != self._frame_transform_wide or vt.frameWidth == 0 or vt.frameHeight == 0 - self._sign_dots = [] - vt = sm['iqVehicleTracks'] + if hidden: + for d in self._vision_dots: + d.talpha = 0.0 + elif vt.frameId != self._vt_frame: + self._vt_frame = vt.frameId + self._retarget_vision_dots(vt) + + for d in self._vision_dots: + d.x += (d.tx - d.x) * _VD_EASE + d.y += (d.ty - d.y) * _VD_EASE + d.radius += (d.tradius - d.radius) * _VD_EASE + d.alpha += (d.talpha - d.alpha) * _VD_EASE + self._vision_dots = [d for d in self._vision_dots if d.alpha > 0.02 or d.talpha > 0.0] + + def _retarget_vision_dots(self, vt): fw, fh = vt.frameWidth, vt.frameHeight - if fw == 0 or fh == 0: - return - + m = self._frame_transform occupied = [d.center for d in self._lead_vehicles + self._track_dots if d.center is not None] - m = self._frame_transform + targets = [] for t in vt.tracks: is_vehicle = t.label in VEHICLE_TRACK_LABELS sign_color = SIGN_TRACK_COLORS.get(t.label) @@ -256,18 +285,34 @@ class ModelRenderer(Widget, IQModelRenderer): if not (self._rect.x <= x <= self._rect.x + self._rect.width and self._rect.y <= y <= self._rect.y + self._rect.height): continue - box_h = (t.y2 - t.y1) * fh * m[1, 1] radius = float(np.clip(box_h * 0.35, 14.0, 40.0)) - if sign_color is not None: - self._sign_dots.append((float(x), float(y), min(radius, 22.0), sign_color)) - continue + targets.append((x, y, min(radius, 22.0), (sign_color.r, sign_color.g, sign_color.b))) + elif not any((x - ox) ** 2 + (y - oy) ** 2 < (radius * 2.2) ** 2 for ox, oy in occupied): + targets.append((x, y, radius, None)) - if any((x - ox) ** 2 + (y - oy) ** 2 < (radius * 2.2) ** 2 for ox, oy in occupied): - continue + dots = self._vision_dots + used = [False] * len(dots) + for tx, ty, tr, rgb in targets: + best, best_d2 = -1, 1e18 + for i, d in enumerate(dots): + if used[i] or (d.rgb is None) != (rgb is None): + continue + d2 = (d.x - tx) ** 2 + (d.y - ty) ** 2 + if d2 < best_d2: + best, best_d2 = i, d2 + if best >= 0 and best_d2 <= (max(tr, dots[best].radius) * 3.0) ** 2: + d = dots[best] + used[best] = True + d.tx, d.ty, d.tradius, d.talpha, d.rgb = tx, ty, tr, 1.0, rgb + else: + dots.append(VisionDot(x=tx, y=ty, tx=tx, ty=ty, radius=tr, tradius=tr, alpha=0.0, talpha=1.0, rgb=rgb)) + used.append(True) - self._vision_dots.append(LeadVehicle(center=(float(x), float(y)), radius=radius, sz=radius)) + for i, d in enumerate(dots): + if not used[i]: + d.talpha = 0.0 def _update_model(self, lead, path_x_array): """Update model visualization data based on model message""" @@ -411,17 +456,17 @@ class ModelRenderer(Widget, IQModelRenderer): draw_polygon(self._rect, self._path.projected_points, gradient=gradient) def _draw_vision_dots(self): - src = rl.Rectangle(0, 0, self._lead_orb.width, self._lead_orb.height) for dot in self._vision_dots: - cx, cy = dot.center - r = dot.radius - dest = rl.Rectangle(cx, cy, r * 2.0, r * 2.0) - rl.draw_texture_pro(self._lead_orb, src, dest, rl.Vector2(r, r), 0.0, rl.Color(255, 255, 255, 90)) - - def _draw_sign_dots(self): - for x, y, r, color in self._sign_dots: - rl.draw_circle(int(x), int(y), r, color) - rl.draw_circle_lines(int(x), int(y), r, rl.Color(255, 255, 255, 160)) + a = dot.alpha + if a <= 0.02: + continue + x, y, r = int(dot.x), int(dot.y), dot.radius + if dot.rgb is None: + # soft teal glow, brighter in the center fading to transparent at the edge + rl.draw_circle_gradient(x, y, r, rl.Color(120, 235, 225, int(165 * a)), rl.Color(0, 150, 150, 0)) + else: + rl.draw_circle_gradient(x, y, r, rl.Color(dot.rgb[0], dot.rgb[1], dot.rgb[2], int(210 * a)), + rl.Color(dot.rgb[0], dot.rgb[1], dot.rgb[2], 0)) def _draw_track_dots(self): src = rl.Rectangle(0, 0, self._lead_orb.width, self._lead_orb.height) diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index e956e73..9d92e80 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -45,26 +45,28 @@ sound_list_iq: dict[int, tuple[str, int | None, float]] = { AudibleAlertIQ.promptSingleHigh: ("prompt_single_high.wav", 1, MAX_VOLUME), } -sound_list: dict[int, tuple[str, int | None, float]] = { - # AudibleAlert, file name, play count (none for infinite) - AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME), - AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME), - AudibleAlert.refuse: ("refuse.wav", 1, MAX_VOLUME), - - AudibleAlert.prompt: ("prompt.wav", 1, MAX_VOLUME), - AudibleAlert.promptRepeat: ("prompt.wav", None, MAX_VOLUME), - AudibleAlert.promptDistracted: ("prompt_distracted.wav", None, MAX_VOLUME), - - AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME), - AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME), - - **sound_list_iq, -} -if HARDWARE.get_device_type() in ("tizi", "tici"): - sound_list.update({ +def get_sound_list(device_type: str) -> dict[int, tuple[str, int | None, float]]: + sounds = { AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME), AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME), - }) + AudibleAlert.refuse: ("refuse.wav", 1, MAX_VOLUME), + AudibleAlert.prompt: ("prompt.wav", 1, MAX_VOLUME), + AudibleAlert.promptRepeat: ("prompt.wav", None, MAX_VOLUME), + AudibleAlert.promptDistracted: ("prompt_distracted.wav", None, MAX_VOLUME), + AudibleAlert.preAlert: ("pre_alert.wav", 1, MAX_VOLUME), + AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME), + AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME), + **sound_list_iq, + } + if device_type in ("tizi", "tici"): + sounds.update({ + AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME), + AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME), + }) + return sounds + + +sound_list = get_sound_list(HARDWARE.get_device_type()) def check_selfdrive_timeout_alert(sm): ss_missing = time.monotonic() - sm.recv_time['selfdriveState'] diff --git a/selfdrive/ui/tests/test_soundd.py b/selfdrive/ui/tests/test_soundd.py index a9da845..a1e18e8 100644 --- a/selfdrive/ui/tests/test_soundd.py +++ b/selfdrive/ui/tests/test_soundd.py @@ -2,13 +2,19 @@ from cereal import car from cereal import messaging from cereal.messaging import SubMaster, PubMaster from openpilot.selfdrive.ui.soundd import SELFDRIVE_STATE_TIMEOUT, check_selfdrive_timeout_alert +from openpilot.selfdrive.ui.soundd import get_sound_list +import pytest import time AudibleAlert = car.CarControl.HUDControl.AudibleAlert class TestSoundd: + @pytest.mark.parametrize("device_type", ["mici", "tici", "tizi"]) + def test_prompt_distracted_sound(self, device_type): + assert get_sound_list(device_type)[AudibleAlert.promptDistracted][0] == "prompt_distracted.wav" + def test_check_selfdrive_timeout_alert(self): sm = SubMaster(['selfdriveState']) pm = PubMaster(['selfdriveState']) @@ -32,4 +38,3 @@ class TestSoundd: assert check_selfdrive_timeout_alert(sm) # TODO: add test with micd for checking that soundd actually outputs sounds - diff --git a/system/flockd.service b/system/flockd.service new file mode 100644 index 0000000..78e6bcf --- /dev/null +++ b/system/flockd.service @@ -0,0 +1,34 @@ +# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos + +[Unit] +Description=Konn3kt Flock/ALPR RF Detector +Documentation=https://gitlvb.teallvbs.xyz/teal/iqpilot +After=bluetooth.service dbus.service NetworkManager.service +Wants=bluetooth.service dbus.service +StartLimitIntervalSec=300 +StartLimitBurst=10 + +[Service] +Type=simple +User=comma +AmbientCapabilities=CAP_SYS_NICE +Environment="IQPILOT_SOURCE_ROOT=/data/openpilot/openpilot" +Environment="PYTHONPATH=/usr/libexec/iqpilot/python:/data/openpilot" +Environment="PYTHONSAFEPATH=1" +Environment="PATH=/usr/local/venv/bin:/usr/sbin:/usr/bin:/sbin:/bin" +WorkingDirectory=/data/openpilot +ExecStartPre=/bin/bash -c 'for i in $(seq 1 120); do if [ -x /usr/libexec/iqpilot/iqpilot_bundle_runner ]; then exit 0; fi; echo "Waiting for iqpilot_bundle_runner..."; sleep 5; done; exit 1' +ExecStartPre=/bin/bash -c 'if [ -f /data/openpilot/artifacts/runtime/ensure_private_installed.sh ]; then bash /data/openpilot/artifacts/runtime/ensure_private_installed.sh || true; fi' +ExecStart=/bin/bash -lc 'exec /usr/libexec/iqpilot/iqpilot_bundle_runner --bundle iqpilot_hephaestusd_private --mode python-module --entry iqpilot_private.konn3kt.flockd.flockd --daemon-name flockd' +TimeoutStartSec=600 +Restart=always +RestartSec=15 +StandardOutput=journal +StandardError=journal +PrivateTmp=yes +NoNewPrivileges=false +ProtectSystem=full +ProtectHome=no + +[Install] +WantedBy=multi-user.target diff --git a/system/hardware/base.py b/system/hardware/base.py index 455dcfa..6dac5f6 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -200,6 +200,9 @@ class HardwareBase(ABC): def reboot_modem(self): pass + def recover_sim_detection(self) -> bool: + return False + def get_networks(self): return None diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index f55d5d3..8a9a7e0 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -204,6 +204,7 @@ def hw_state_thread(end_event, hw_queue): modem_configured = False modem_missing_count = 0 modem_restart_count = 0 + sim_detection_recovered = False while not end_event.is_set(): # these are expensive calls. update every 10s @@ -254,6 +255,10 @@ def hw_state_thread(end_event, hw_queue): HARDWARE.configure_modem() modem_configured = True + if modem_configured and not sim_detection_recovered and HARDWARE.recover_sim_detection(): + cloudlog.event("sim missing with hot-swap detect armed, rebooting modem with detect disabled", error=True) + sim_detection_recovered = True + prev_hw_state = hw_state except Exception: cloudlog.exception("Error getting hardware state") @@ -549,15 +554,17 @@ def hardware_thread(end_event, hw_queue) -> None: som_power_draw = HARDWARE.get_som_power_draw() msg.deviceState.somPowerDrawW = som_power_draw - # FastSleep deep standby: shed heavy processes at low battery instead of shutting down, - # recover on ignition or once the alternator is charging + # FastSleep deep standby: shed heavy processes once parked with the screen idled off + # (or at low battery) instead of shutting down, recover on ignition or once the + # alternator is charging fast_sleep = params.get_bool("FastSleep") if fast_sleep and not tesla_no_sleep: if low_power: if onroad_conditions["ignition"] or power_monitor.car_voltage_mV >= (VBATT_LOW_POWER_EXIT * 1e3): low_power = False else: - low_power = power_monitor.should_enter_low_power(onroad_conditions["ignition"], in_car, off_ts) + screen_off = msg.deviceState.screenBrightnessPercent < 1e-3 + low_power = power_monitor.should_enter_low_power(onroad_conditions["ignition"], in_car, off_ts, screen_off) else: low_power = False diff --git a/system/hardware/power_monitoring.py b/system/hardware/power_monitoring.py index ff7adfa..a72ee58 100644 --- a/system/hardware/power_monitoring.py +++ b/system/hardware/power_monitoring.py @@ -13,11 +13,13 @@ CAR_CHARGING_RATE_W = 45 VBATT_PAUSE_CHARGING = 11.8 # Lower limit on the LPF car battery voltage -# FastSleep (deep standby): enter low power at the normal shutdown voltage, shut down -# at a lower floor, exit once the alternator is charging +# FastSleep (deep standby): enter low power once parked with the screen idled off, or +# immediately at the normal shutdown voltage; shut down at a lower floor, exit once the +# alternator is charging VBATT_LOW_POWER_ENTRY = 11.8 VBATT_LOW_POWER_EXIT = 12.8 VBATT_HARD_SHUTDOWN = 11.5 +LOW_POWER_ENTRY_TIME_S = 300 MAX_TIME_OFFROAD_S = 30*3600 MIN_ON_TIME_S = 3600 DELAY_SHUTDOWN_TIME_S = 300 # Wait at least DELAY_SHUTDOWN_TIME_S seconds after offroad_time to shutdown. @@ -124,14 +126,18 @@ class PowerMonitoring: return 0 < iq_max_time_val_s <= offroad_time # FastSleep: see if we should enter low power mode instead of shutting down - def should_enter_low_power(self, ignition: bool, in_car: bool, offroad_timestamp: float | None) -> bool: + def should_enter_low_power(self, ignition: bool, in_car: bool, offroad_timestamp: float | None, screen_off: bool) -> bool: if offroad_timestamp is None or ignition or not in_car: return False if not self.params.get_bool("FastSleep"): return False offroad_time = time.monotonic() - offroad_timestamp - return (self.car_voltage_mV < (VBATT_LOW_POWER_ENTRY * 1e3) and - offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S) + # a healthy battery rests above VBATT_LOW_POWER_ENTRY, so parked entry must be + # time-based; the voltage trigger stays as the sagging-battery fast path + low_voltage = (self.car_voltage_mV < (VBATT_LOW_POWER_ENTRY * 1e3) and + offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S) + parked_idle = screen_off and offroad_time > LOW_POWER_ENTRY_TIME_S + return low_voltage or parked_idle # See if we need to shutdown def should_shutdown(self, ignition: bool, in_car: bool, offroad_timestamp: float | None, started_seen: bool): @@ -141,12 +147,15 @@ class PowerMonitoring: now = time.monotonic() should_shutdown = False offroad_time = (now - offroad_timestamp) - vbatt_min = VBATT_HARD_SHUTDOWN if self.params.get_bool("FastSleep") else VBATT_PAUSE_CHARGING + fast_sleep = self.params.get_bool("FastSleep") + vbatt_min = VBATT_HARD_SHUTDOWN if fast_sleep else VBATT_PAUSE_CHARGING low_voltage_shutdown = (self.car_voltage_mV < (vbatt_min * 1e3) and offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S) should_shutdown |= self.max_time_offroad_exceeded(offroad_time) should_shutdown |= low_voltage_shutdown - should_shutdown |= (self.car_battery_capacity_uWh <= 0) + # the 30 Wh bookkeeping model empties within hours at offroad draw regardless of the + # real battery state; under FastSleep the measured voltage floors govern instead + should_shutdown |= (self.car_battery_capacity_uWh <= 0) and not fast_sleep should_shutdown &= not ignition should_shutdown &= (not self.params.get_bool("DisablePowerDown")) should_shutdown &= in_car diff --git a/system/hardware/tests/test_power_monitoring.py b/system/hardware/tests/test_power_monitoring.py index 451efcd..c0b0146 100644 --- a/system/hardware/tests/test_power_monitoring.py +++ b/system/hardware/tests/test_power_monitoring.py @@ -2,7 +2,8 @@ import pytest from openpilot.common.params import Params from openpilot.system.hardware.power_monitoring import PowerMonitoring, CAR_BATTERY_CAPACITY_uWh, \ - CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S, MAX_TIME_OFFROAD_S + CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S, MAX_TIME_OFFROAD_S, \ + VBATT_HARD_SHUTDOWN, LOW_POWER_ENTRY_TIME_S # Create fake time ssb = 0. @@ -232,3 +233,59 @@ class TestPowerMonitoring: result = pm.max_time_offroad_exceeded(offroad_time_s) assert result == expected_result + + # FastSleep must not shut down on the empty bookkeeping model while voltage is healthy + def test_fast_sleep_ignores_battery_capacity_model(self, mocker): + self.params.put_bool("FastSleep", True) + self.params.put("MaxTimeOffroad", 0) + try: + pm_patch(mocker, "HARDWARE.get_current_power_draw", 0) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = 0 + start_time = ssb + for _ in range(DELAY_SHUTDOWN_TIME_S + 100): + pm.calculate(GOOD_VOLTAGE, False) + assert not pm.should_shutdown(False, True, start_time, True) + finally: + self.params.put_bool("FastSleep", False) + + # FastSleep still shuts down below the hard voltage floor + def test_fast_sleep_hard_voltage_floor(self, mocker): + self.params.put_bool("FastSleep", True) + try: + pm_patch(mocker, "HARDWARE.get_current_power_draw", 0) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh + start_time = ssb + for _ in range(DELAY_SHUTDOWN_TIME_S + 100): + pm.calculate((VBATT_HARD_SHUTDOWN - 0.5) * 1e3, False) + assert pm.should_shutdown(False, True, start_time, True) + finally: + self.params.put_bool("FastSleep", False) + + def test_fast_sleep_low_power_entry(self, mocker): + self.params.put_bool("FastSleep", True) + try: + pm_patch(mocker, "HARDWARE.get_current_power_draw", 0) + + # parked with the screen idled off: time-based entry at healthy voltage + pm = PowerMonitoring() + start_time = ssb + for _ in range(LOW_POWER_ENTRY_TIME_S + 10): + pm.calculate(GOOD_VOLTAGE, False) + assert pm.should_enter_low_power(False, True, start_time, screen_off=True) + assert not pm.should_enter_low_power(False, True, start_time, screen_off=False) + assert not pm.should_enter_low_power(True, True, start_time, screen_off=True) + assert not pm.should_enter_low_power(False, False, start_time, screen_off=True) + + # sagging battery: voltage entry regardless of screen state + pm = PowerMonitoring() + start_time = ssb + for _ in range(100): + pm.calculate((VBATT_HARD_SHUTDOWN + 0.1) * 1e3, False) + assert pm.should_enter_low_power(False, True, start_time, screen_off=False) + + self.params.put_bool("FastSleep", False) + assert not pm.should_enter_low_power(False, True, start_time, screen_off=True) + finally: + self.params.put_bool("FastSleep", False) diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 582f604..2c4b332 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -78,17 +78,17 @@ }, { "name": "system", - "url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img.xz", - "hash": "654de83dd64190701c5a9dc7297fb569ba106cc63217725893920df6b8c9eaa0", - "hash_raw": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b", + "url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img.xz", + "hash": "0c9e7dee6c7365600c77b33c4996456459d04aad005e3e41bf6ee2e8af74ceb9", + "hash_raw": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a", "size": 6291456000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "8269bd017b18a1a4fd5e093b96efa181fd803e200fa92364fe9307f25445f596", + "ondevice_hash": "1ee589d3d728a03561718383bf640171f1aef048573e46fe17239617c7c19fff", "alt": { - "hash": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b", - "url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img", + "hash": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a", + "url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img", "size": 6291456000 } } diff --git a/system/hardware/tici/agnos.json.sig b/system/hardware/tici/agnos.json.sig index 2a4b9e7..362267f 100644 --- a/system/hardware/tici/agnos.json.sig +++ b/system/hardware/tici/agnos.json.sig @@ -1 +1 @@ -6ZRxEduDaLm1coUVvI/0QAZtmbf+tY0TWMZtaEaxV7I2fKFfGPG/6oOizuFsmd/SSQls84kap9MwKDEc8XfIDA== ++LyP/p4uBBxbTwRwPrwR/2qPaHkAXmlFkg0GdxOBuujPY89GxP4t5SNKxirqiw9LmMcfA0JfVwMeR3QD9rQ6Dg== diff --git a/system/hardware/tici/agnos_tici_15_1.json b/system/hardware/tici/agnos_tici_15_1.json index 2acf2e7..da5a9ab 100644 --- a/system/hardware/tici/agnos_tici_15_1.json +++ b/system/hardware/tici/agnos_tici_15_1.json @@ -67,17 +67,17 @@ }, { "name": "system", - "url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img.xz", - "hash": "654de83dd64190701c5a9dc7297fb569ba106cc63217725893920df6b8c9eaa0", - "hash_raw": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b", + "url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img.xz", + "hash": "0c9e7dee6c7365600c77b33c4996456459d04aad005e3e41bf6ee2e8af74ceb9", + "hash_raw": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a", "size": 6291456000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "8269bd017b18a1a4fd5e093b96efa181fd803e200fa92364fe9307f25445f596", + "ondevice_hash": "1ee589d3d728a03561718383bf640171f1aef048573e46fe17239617c7c19fff", "alt": { - "hash": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b", - "url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img", + "hash": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a", + "url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img", "size": 6291456000 } } diff --git a/system/hardware/tici/agnos_tici_15_1.json.sig b/system/hardware/tici/agnos_tici_15_1.json.sig index db7a65e..fc701fd 100644 --- a/system/hardware/tici/agnos_tici_15_1.json.sig +++ b/system/hardware/tici/agnos_tici_15_1.json.sig @@ -1 +1 @@ -+gqhJbKMr9i9lW7WrtKebqKXlM0Asr/i7ZodOzIAHzAgzGEvPCWbfco2jo0/jO7O2UzZwKJUl/WzHlsOtJvnCw== +RCYk34Ifjx7S9PIhPm6p49vFMhamPwvw8Lfbb6a6HO8sUOi3J2PcY4VDUcqQZWU6+Am++HP3nJSea8OmAsckAg== diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 7091275..e1b17c8 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -69,6 +69,9 @@ NetworkStrength = log.DeviceState.NetworkStrength MM_MODEM_ACCESS_TECHNOLOGY_UMTS = 1 << 5 MM_MODEM_ACCESS_TECHNOLOGY_LTE = 1 << 14 +# MMModemStateFailedReason +MM_MODEM_STATE_FAILED_REASON_SIM_MISSING = 2 + def affine_irq(val, action): irqs = get_irqs_for_action(action) @@ -605,6 +608,32 @@ class Tici(HardwareBase): os.system(f"sudo cp {tf.name} {dest}") os.system(f"sudo nmcli con load {dest}") + def recover_sim_detection(self) -> bool: + # A worn SIM-tray presence switch can read "removed" while the SIM pads still make + # contact; with hot-swap detect armed (AT+QSIMDET=1) the modem never powers the SIM + # and lands in failed/sim-missing. Disabling detect and rebooting the modem makes it + # probe the SIM electrically. Safe to retry on failure: firing disarms the QSIMDET + # gate, so a genuinely SIM-less device gets at most one extra modem reboot per boot. + if self.get_device_type() not in ("tici", "tizi"): + return False + + try: + modem = self.get_modem() + state = modem.Get(MM_MODEM, 'State', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) + if state != MM_MODEM_STATE.FAILED: + return False + reason = modem.Get(MM_MODEM, 'StateFailedReason', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) + if reason != MM_MODEM_STATE_FAILED_REASON_SIM_MISSING: + return False + detect = str(modem.Command('AT+QSIMDET?', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)).strip() + if not detect.startswith('+QSIMDET: 1'): + return False + modem.Command('AT+QSIMDET=0,0', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT) + modem.Command('AT+CFUN=1,1', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT) + return True + except Exception: + return False + def reboot_modem(self): modem = None try: diff --git a/system/install_flockd_service.sh b/system/install_flockd_service.sh new file mode 100644 index 0000000..21b4a8a --- /dev/null +++ b/system/install_flockd_service.sh @@ -0,0 +1,42 @@ +#!/usr/bin/bash +set -e + +SERVICE_FILE="/data/openpilot/system/flockd.service" +SERVICE_NAME="flockd.service" +SERVICE_OVERRIDE="/etc/systemd/system/${SERVICE_NAME}" +SERVICE_BAKED="/lib/systemd/system/${SERVICE_NAME}" + +echo "Installing Flock RF detector systemd service..." + +if [ -f "$SERVICE_BAKED" ] && grep -q "/usr/libexec/iqpilot/iqpilot_bundle_runner" "$SERVICE_BAKED"; then + echo "Using IQ.OS baked ${SERVICE_NAME}; removing stale override if present..." + sudo mount -o remount,rw / + sudo rm -f "$SERVICE_OVERRIDE" + sudo systemctl daemon-reload + sudo mount -o remount,ro / +else + if [ ! -f "$SERVICE_FILE" ]; then + echo "ERROR: Service file not found at $SERVICE_FILE" + exit 1 + fi + + echo "IQ.OS baked unit unavailable; installing fallback override into /etc/systemd/system..." + sudo cp "$SERVICE_FILE" "$SERVICE_OVERRIDE" + sudo systemctl daemon-reload +fi + +echo "Enabling $SERVICE_NAME to start at boot..." +sudo systemctl enable "$SERVICE_NAME" + +echo "Starting $SERVICE_NAME..." +sudo systemctl restart "$SERVICE_NAME" + +echo "" +echo "Service status:" +sudo systemctl status "$SERVICE_NAME" --no-pager + +echo "" +echo "Useful commands:" +echo " sudo systemctl status flockd - Check service status" +echo " sudo systemctl restart flockd - Restart service" +echo " sudo journalctl -u flockd -f - View live logs" diff --git a/system/manager/process.py b/system/manager/process.py index 0415d08..1314b11 100644 --- a/system/manager/process.py +++ b/system/manager/process.py @@ -181,6 +181,32 @@ class NativeProcess(ManagerProcess): self.shutting_down = False +def _normalize_bundle_modes(bundle: str) -> None: + import json + candidates = [] + if env_root := os.environ.get("IQPILOT_PROPRIETARY_ROOT"): + candidates += [os.path.join(env_root, bundle), env_root] + candidates += [ + os.path.join(BASEDIR, ".iqpilot", "bundles", bundle), + os.path.join(os.path.dirname(BASEDIR), ".iqpilot", "bundles", bundle), + os.path.join(BASEDIR, "artifacts", bundle), + ] + root = next((c for c in candidates if os.path.isfile(os.path.join(c, "manifest.json"))), None) + if root is None: + return + try: + with open(os.path.join(root, "manifest.json")) as f: + manifest = json.load(f) + for rel, meta in manifest.items(): + if not (isinstance(meta, dict) and "mode" in meta and "sha256" in meta): + continue + path = os.path.join(root, rel) + if os.path.isfile(path) and (os.stat(path).st_mode & 0o777) != meta["mode"]: + os.chmod(path, meta["mode"]) + except Exception: + cloudlog.exception(f"failed to normalize bundle modes for {bundle}") + + class BundleProcess(NativeProcess): def __init__(self, name, bundle, entry, should_run, enabled=True, sigkill=False, restart_if_crash=False): self.bundle = bundle @@ -204,6 +230,11 @@ class BundleProcess(NativeProcess): sigkill=sigkill, ) + def start(self) -> None: + if self.proc is None: + _normalize_bundle_modes(self.bundle) + super().start() + class PythonProcess(ManagerProcess): def __init__(self, name, module, should_run, enabled=True, sigkill=False, restart_if_crash=False): diff --git a/system/ui/iqpilot/__init__.py b/system/ui/iqwidgets/__init__.py similarity index 100% rename from system/ui/iqpilot/__init__.py rename to system/ui/iqwidgets/__init__.py diff --git a/system/ui/iqpilot/lib/__init__.py b/system/ui/iqwidgets/lib/__init__.py similarity index 100% rename from system/ui/iqpilot/lib/__init__.py rename to system/ui/iqwidgets/lib/__init__.py diff --git a/system/ui/iqpilot/lib/application.py b/system/ui/iqwidgets/lib/application.py similarity index 100% rename from system/ui/iqpilot/lib/application.py rename to system/ui/iqwidgets/lib/application.py diff --git a/system/ui/iqpilot/lib/canvas.py b/system/ui/iqwidgets/lib/canvas.py similarity index 100% rename from system/ui/iqpilot/lib/canvas.py rename to system/ui/iqwidgets/lib/canvas.py diff --git a/system/ui/iqpilot/lib/styles.py b/system/ui/iqwidgets/lib/styles.py similarity index 100% rename from system/ui/iqpilot/lib/styles.py rename to system/ui/iqwidgets/lib/styles.py diff --git a/system/ui/iqpilot/lib/utils.py b/system/ui/iqwidgets/lib/utils.py similarity index 61% rename from system/ui/iqpilot/lib/utils.py rename to system/ui/iqwidgets/lib/utils.py index 3bd63df..213b2ea 100644 --- a/system/ui/iqpilot/lib/utils.py +++ b/system/ui/iqwidgets/lib/utils.py @@ -1,9 +1,9 @@ """ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos """ -from openpilot.system.ui.iqpilot.widgets.list_view import IQButtonAction +from openpilot.system.ui.iqwidgets.widgets.list_view import IQButtonAction -class NoElideButtonAction(IQButtonAction): +class WideButtonAction(IQButtonAction): def get_width_hint(self): return super().get_width_hint() + 1 diff --git a/system/ui/iqpilot/widgets/__init__.py b/system/ui/iqwidgets/widgets/__init__.py similarity index 100% rename from system/ui/iqpilot/widgets/__init__.py rename to system/ui/iqwidgets/widgets/__init__.py diff --git a/system/ui/iqpilot/widgets/helpers/__init__.py b/system/ui/iqwidgets/widgets/helpers/__init__.py similarity index 100% rename from system/ui/iqpilot/widgets/helpers/__init__.py rename to system/ui/iqwidgets/widgets/helpers/__init__.py diff --git a/system/ui/iqpilot/widgets/helpers/glyphs.py b/system/ui/iqwidgets/widgets/helpers/glyphs.py similarity index 100% rename from system/ui/iqpilot/widgets/helpers/glyphs.py rename to system/ui/iqwidgets/widgets/helpers/glyphs.py diff --git a/system/ui/iqpilot/widgets/list_view.py b/system/ui/iqwidgets/widgets/list_view.py similarity index 99% rename from system/ui/iqpilot/widgets/list_view.py rename to system/ui/iqwidgets/widgets/list_view.py index 11d3c16..3736934 100644 --- a/system/ui/iqpilot/widgets/list_view.py +++ b/system/ui/iqwidgets/widgets/list_view.py @@ -7,9 +7,9 @@ import time from collections.abc import Callable import pyray as rl -from openpilot.system.ui.iqpilot.lib.styles import ink, metrics +from openpilot.system.ui.iqwidgets.lib.styles import ink, metrics from openpilot.system.ui.widgets.button import Button, ButtonStyle -from openpilot.system.ui.iqpilot.lib import canvas +from openpilot.system.ui.iqwidgets.lib import canvas from openpilot.system.ui.widgets.scroller_tici import LineSeparator, LINE_COLOR, LINE_PADDING from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets.toggle import Toggle @@ -899,8 +899,8 @@ def progress_item(title): from dataclasses import dataclass, field from openpilot.common.params import Params -from openpilot.system.ui.iqpilot.lib.styles import ink -from openpilot.system.ui.iqpilot.widgets.helpers.glyphs import draw_star +from openpilot.system.ui.iqwidgets.lib.styles import ink +from openpilot.system.ui.iqwidgets.widgets.helpers.glyphs import draw_star from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr @@ -925,13 +925,13 @@ _FRAME_PAD = 50 @dataclass -class TreeNode: +class PickerItem: ref: str data: dict = field(default_factory=dict) @dataclass -class TreeFolder: +class PickerGroup: folder: str nodes: list @@ -999,7 +999,7 @@ class _TreeRow(Button): return super()._handle_mouse_release(mouse_pos) -class TreeOptionDialog(MultiOptionDialog): +class PickerDialog(MultiOptionDialog): """Folder/leaf picker with search, favourites, and a pinned current selection.""" def __init__(self, title, folders, current_ref="", fav_param="", option_font_weight=FontWeight.MEDIUM, search_prompt=None, @@ -1018,7 +1018,7 @@ class TreeOptionDialog(MultiOptionDialog): self.on_exit = on_exit self.display_func = display_func or (lambda node: node.data.get('display_name', node.ref)) self.search_funcs = search_funcs or [lambda node: node.data.get('display_name', ''), lambda node: node.data.get('short_name', '')] - self.search_title = search_title or tr("Enter search query") + self.search_title = search_title or tr("Type to search") self.search_subtitle = search_subtitle self._search_rect: rl.Rectangle | None = None self._search_pressed = False diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index b145bad..404854a 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -26,7 +26,7 @@ from openpilot.system.hardware import HARDWARE, PC from openpilot.system.ui.lib.multilang import multilang from openpilot.common.realtime import Ratekeeper -from openpilot.system.ui.iqpilot.lib.application import IQAppHooks +from openpilot.system.ui.iqwidgets.lib.application import IQAppHooks from openpilot.system.ui.lib.screen_recorder import ScreenRecorder _DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20, 'tici': 20}.get(HARDWARE.get_device_type(), 60))) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 4fe310a..e5659df 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -636,6 +636,7 @@ class WifiManager: cloudlog.warning("No WiFi device found") return + self._set_device_autoconnect(True) self._connecting_to_ssid = ssid self._router_main.send(new_method_call(self._nm, 'ActivateConnection', 'ooo', (conn_path, self._wifi_device, "/"))) @@ -645,6 +646,37 @@ class WifiManager: else: threading.Thread(target=worker, daemon=True).start() + def disconnect_connection(self, ssid: str, block: bool = False): + def worker(): + if self._router_main is None: + cloudlog.warning(f"WiFi not ready while disconnecting {ssid}") + return + + if ssid not in self._get_connections(): + return + + # the profile stays saved and untouched; without clearing autoconnect on the device + # NetworkManager re-associates within seconds + self._set_device_autoconnect(False) + self._connecting_to_ssid = "" + self._deactivate_connection(ssid) + self._update_networks() + self._enqueue_callbacks(self._disconnected) + + if block: + worker() + else: + threading.Thread(target=worker, daemon=True).start() + + def _set_device_autoconnect(self, enabled: bool) -> None: + if self._router_main is None or self._wifi_device is None: + return + + dev_addr = DBusAddress(self._wifi_device, bus_name=NM, interface=NM_DEVICE_IFACE) + reply = self._router_main.send_and_get_reply(Properties(dev_addr).set('Autoconnect', 'b', enabled)) + if reply.header.message_type == MessageType.error: + cloudlog.warning(f'Failed to set device autoconnect={enabled}: {reply}') + def _deactivate_connection(self, ssid: str): target_conn_path = self._get_connections().get(ssid, None) if target_conn_path is None: diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index cc3167d..c67d26c 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -17,10 +17,10 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item if gui_app.iqpilot_ui(): - from openpilot.system.ui.iqpilot.widgets.list_view import button_item - from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem as ListItem - from openpilot.system.ui.iqpilot.widgets.list_view import IQToggleAction as ToggleAction - from openpilot.system.ui.iqpilot.widgets.list_view import IQMultipleButtonAction as MultipleButtonAction + from openpilot.system.ui.iqwidgets.widgets.list_view import button_item + from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem as ListItem + from openpilot.system.ui.iqwidgets.widgets.list_view import IQToggleAction as ToggleAction + from openpilot.system.ui.iqwidgets.widgets.list_view import IQMultipleButtonAction as MultipleButtonAction # These are only used for AdvancedNetworkSettings, standalone apps just need WifiManagerUI try: @@ -66,6 +66,7 @@ class UIState(IntEnum): NEEDS_AUTH = 2 SHOW_FORGET_CONFIRM = 3 FORGETTING = 4 + DISCONNECTING = 5 class NavButton(Widget): @@ -328,6 +329,7 @@ class WifiManagerUI(Widget): self._state_network: Network | None = None # for CONNECTING / NEEDS_AUTH / SHOW_FORGET_CONFIRM / FORGETTING self._password_retry: bool = False # for NEEDS_AUTH self.btn_width: int = 200 + self.disconnect_btn_width: int = 300 self.scroll_panel = GuiScrollPanel() self.keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True) self._load_icons() @@ -335,6 +337,7 @@ class WifiManagerUI(Widget): self._networks: list[Network] = [] self._networks_buttons: dict[str, Button] = {} self._forget_networks_buttons: dict[str, Button] = {} + self._disconnect_networks_buttons: dict[str, Button] = {} self._wifi_manager.add_callbacks(need_auth=self._on_need_auth, activated=self._on_activated, @@ -417,7 +420,9 @@ class WifiManagerUI(Widget): def _draw_network_item(self, rect, network: Network): spacing = 50 - ssid_rect = rl.Rectangle(rect.x, rect.y, rect.width - self.btn_width * 2, ITEM_HEIGHT) + show_disconnect = network.is_connected and self.state not in (UIState.CONNECTING, UIState.DISCONNECTING) + reserved = self.btn_width * 2 + (self.disconnect_btn_width + spacing if show_disconnect else 0) + ssid_rect = rl.Rectangle(rect.x, rect.y, rect.width - reserved, ITEM_HEIGHT) signal_icon_rect = rl.Rectangle(rect.x + rect.width - ICON_SIZE, rect.y + (ITEM_HEIGHT - ICON_SIZE) / 2, ICON_SIZE, ICON_SIZE) security_icon_rect = rl.Rectangle(signal_icon_rect.x - spacing - ICON_SIZE, rect.y + (ITEM_HEIGHT - ICON_SIZE) / 2, ICON_SIZE, ICON_SIZE) @@ -434,6 +439,10 @@ class WifiManagerUI(Widget): if self._state_network.ssid == network.ssid: self._networks_buttons[network.ssid].set_enabled(False) status_text = tr("FORGETTING...") + elif self.state == UIState.DISCONNECTING and self._state_network: + if self._state_network.ssid == network.ssid: + self._networks_buttons[network.ssid].set_enabled(False) + status_text = tr("DISCONNECTING...") elif network.security_type == SecurityType.UNSUPPORTED: self._networks_buttons[network.ssid].set_enabled(False) else: @@ -455,6 +464,15 @@ class WifiManagerUI(Widget): ) self._forget_networks_buttons[network.ssid].render(forget_btn_rect) + if show_disconnect: + disconnect_btn_rect = rl.Rectangle( + forget_btn_rect.x - self.btn_width - spacing, + forget_btn_rect.y, + self.btn_width, + 80, + ) + self._disconnect_networks_buttons[network.ssid].render(disconnect_btn_rect) + self._draw_status_icon(security_icon_rect, network) self._draw_signal_strength_icon(signal_icon_rect, network) @@ -470,6 +488,9 @@ class WifiManagerUI(Widget): self.state = UIState.SHOW_FORGET_CONFIRM self._state_network = network + def _disconnect_networks_buttons_callback(self, network): + self.disconnect_network(network) + def _draw_status_icon(self, rect, network: Network): """Draw the status icon based on network's connection state""" icon_file = None @@ -506,6 +527,11 @@ class WifiManagerUI(Widget): self._state_network = network self._wifi_manager.forget_connection(network.ssid) + def disconnect_network(self, network: Network): + self.state = UIState.DISCONNECTING + self._state_network = network + self._wifi_manager.disconnect_connection(network.ssid) + def _on_network_updated(self, networks: list[Network]): self._networks = networks for n in self._networks: @@ -515,6 +541,9 @@ class WifiManagerUI(Widget): self._forget_networks_buttons[n.ssid] = Button(tr("Forget"), partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, font_size=45) self._forget_networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) + self._disconnect_networks_buttons[n.ssid] = Button(tr("Disconnect"), partial(self._disconnect_networks_buttons_callback, n), + button_style=ButtonStyle.FORGET_WIFI, font_size=45) + self._disconnect_networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) def _on_need_auth(self, ssid): network = next((n for n in self._networks if n.ssid == ssid), None) @@ -532,7 +561,7 @@ class WifiManagerUI(Widget): self.state = UIState.IDLE def _on_disconnected(self): - if self.state == UIState.CONNECTING: + if self.state in (UIState.CONNECTING, UIState.DISCONNECTING): self.state = UIState.IDLE diff --git a/tools/sim/lib/simulated_sensors.py b/tools/sim/lib/simulated_sensors.py index a8374a0..6ac7c4f 100644 --- a/tools/sim/lib/simulated_sensors.py +++ b/tools/sim/lib/simulated_sensors.py @@ -92,11 +92,12 @@ class SimulatedSensors: # dmonitoringd output dat = messaging.new_message('driverMonitoringState', valid=True) - dat.driverMonitoringState = { - "faceDetected": True, - "isDistracted": False, - "awarenessStatus": 1., - } + dm = dat.driverMonitoringState + dm.alertLevel = log.DriverMonitoringState.AlertLevel.none + dm.activePolicy = log.DriverMonitoringState.MonitoringPolicy.vision + dm.visionPolicyState.faceDetected = True + dm.visionPolicyState.isDistracted = False + dm.visionPolicyState.awarenessPercent = 100 self.pm.send('driverMonitoringState', dat) def send_camera_images(self, world: 'World'):