IQ.Pilot Release Commit @ 9b29107

This commit is contained in:
IQ.Lvbs CI [bot]
2026-09-17 11:17:38 -05:00
parent ccec601b0a
commit 4e006c0852
45 changed files with 533 additions and 100 deletions
+1 -1
View File
@@ -1 +1 @@
076c1d057f97b067222d74180fbb63ba5467254a
9953593a65d6759bb84905b47d81bee547c5bc3f
+16 -6
View File
@@ -21,13 +21,18 @@
},
"python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "268267fdfb6ae438f1b3c6f5f2c378a5b878e68357d5560f604f77d5c36a2cea",
"size": 402144
"sha256": "3e278b710bd3c5ee69b463cfb3025932ddc92e790733d95f8c218d61de769704",
"size": 468040
},
"python/iqpilot_private/konn3kt/iqlvbs/angle_tracker.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "ef70814b9b380cc0b93041b1ccca50e993336bf13a5a4c23380d99a494727652",
"size": 137168
},
"python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "b3d33e86493408e5a02f2df753e1b257f003e5028480b5b040aa5a07eeed98be",
"size": 69712
"sha256": "fc37e89b7a569fb1a50e841a20168fc36a50ad574e2e3fad1a96d3084d6e8f3e",
"size": 69608
},
"runtime": {
"entries": {
@@ -35,6 +40,10 @@
"path": "python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so",
"type": "python-module"
},
"iqpilot_private.konn3kt.iqlvbs.angle_tracker": {
"path": "python/iqpilot_private/konn3kt/iqlvbs/angle_tracker.cpython-312-aarch64-linux-gnu.so",
"type": "python-module"
},
"iqpilot_private.konn3kt.iqlvbs.vehicle_state": {
"path": "python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so",
"type": "python-module"
@@ -42,7 +51,8 @@
}
},
"signatures": {
"python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so": "v0mM7HrM8Ssbm3E/TxIUefAh5aCkzbZIKDQk5yFN6kr4EgqmVyPMNkqu7kCsn4ah0fTzr7xCrhs/gXjGf+6yBQ==",
"python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so": "5KQhJ61bt8tNmKuegPVMnMiqEHKcnjhqMtCDv2CphlSS54L/GmsOZjieHQEcgpOIDw7n/7qz2k6iFnwJ6clLCg=="
"python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so": "IJTrAoJ08yLAjBt3NBjErXVIoAuNXTBHoUVOCjsPuQhwN+PnkSNEzd5nO1sLxzSG5HfbgiljQ32jPP8VrTDSBQ==",
"python/iqpilot_private/konn3kt/iqlvbs/angle_tracker.cpython-312-aarch64-linux-gnu.so": "Z+lFLZtP3bUrcu56YEJ0xUzCt2LakywFMCqYaLFM6G4Q1jYeu7Y2/nEpPDfVOSW5hn0XVCYM4C4sT08FJlrrBg==",
"python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so": "9uqsRuEGViBybuh0SxrTdw/uBVGeJXLmS4qhajlXKAVl8HllBQeiF7UY95Di7/yJEDTiSTo4mMJbOcz3UjTSDw=="
}
}
@@ -1 +1 @@
Xr+Nn9b/ZoT2/S94B/I16aHVQI5dZZE3hGvpS6elgasC7e4JZouZvln/ue3Lh5Hg/NTJCCEStRmpEtNfgcjbAA==
uP+OnBsv67hALUufnDP6Dux48yzqt2D/MRuzB5Kpigvi+6uTRlq4M2564Vqlf/+A8/C4H4YNRVSOK37wTM9sCA==
+17 -17
View File
@@ -171,8 +171,8 @@
},
"python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": {
"mode": 420,
"sha256": "f1ddffa9bdb5071a49fd65eb4b49958b60b1873e1ec70e6f3f2e2b2501abce2f",
"size": 25584
"sha256": "e8eae4e8a75e1751525471cf54a8555b7bfd7f08ad84bf2e75642b5745f222db",
"size": 25601
},
"python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": {
"mode": 420,
@@ -206,37 +206,37 @@
},
"python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "ff7516da01c70d14589479e0d9c1f46ceb7ee771e32e7f1d0f198140689f302b",
"sha256": "506f732da9133ba5fdf8ffcb5d7557b28de44b8b789a4994fb1bd9a7c2240641",
"size": 138936
},
"python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "828c2115f11b201eab218ccfc137dcf311ada4952567effbe4e8fa271bd45d08",
"sha256": "33358560c99c961e172a1643fb4a0ac6048f90d85551c0dea595fc1793372199",
"size": 202256
},
"python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "ee5eafd557c46b22fada7811f42af9e0e0e008644a03e6f8c531c6e5f26e6197",
"sha256": "e23916b1c8ce0dd6fe28e07d381e8545c14ab9851cc3c501cc8f08e2002df6ee",
"size": 135368
},
"python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "2bf15d3d5be83086ca7a2846d9b0ab1fe08c823e6221413b3084b93c47d1f2f9",
"sha256": "cf6a559498981b3bebb4e50ef276e8c6f0816db68534c43b3372f2d88fec9f7d",
"size": 473440
},
"python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "f4bbb478faa8b8fb139235308f978efaf9c6e80ec8cb7f1b0748199811e7aa0b",
"size": 137008
"sha256": "674bb7de81a76911f536cd684b384f384b4271bdedf0591ff18c574c7378d6c7",
"size": 137560
},
"python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "f7834b1874f8b8e89d056f9fdf099505ab9e0a4face41d7ed8aa4818efbd4bb0",
"sha256": "b6b8c578e98f584ee221d35e08233f4a96afee3d3d765bc8ce5229040626f9c9",
"size": 135872
},
"python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "79cf9a03e07dff07152bfb7894ec1e56db2dca582dae0f7d01cb6c089ee85b40",
"sha256": "a08e7b38bdd666bf2aa38e7528574f2bb3650cd8439edc9689b1e36b08fcf5c2",
"size": 205456
},
"runtime": {
@@ -272,12 +272,12 @@
}
},
"signatures": {
"python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so": "fw6fxIll7mblJ8VV/eMCqYvMx3p8jsCQTXpaIQU3icE61Bu9zEiWU1SwW1tB2nU/16RTY/FuFLt8Q2T2fGsZBg==",
"python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so": "zTS46Eqyugv6h2KTmhWVVeDWPMfo6WErUKdtA4yvXjt//l4fMZRISRE2g9SRbUs2MmMuArrsp9oVm7rBBHLIBQ==",
"python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so": "bmrrXqu2A1t74MWhENmcL84zwsrgFYBdgfIyxH2V+VxglTi9OUssq48JTTDz4cqwo7yaoKXaaZ8n5h5ZkhXgBA==",
"python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so": "PR0rbQ5bf3N11dc4Og93+FjevF9+yyyTVcKthsYRSvwxR4g13s+VkdDQXznJcMW9RxwEF5VRLpctfdsLjwmSBQ==",
"python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so": "ho6NFRhUu1evnLDkaO5Kfy7l6b43+cOmSaS/Hkkyz5jq6ZhoqftK9GL/AGBMtOyN7w6aQHkKfx+VnNu+yU9eBA==",
"python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so": "gTatMeGF73ClQvTWSxXwI3xinhAd7R2chOnOkdOmVa8JCGgCBcL11r8agg3kTL52ELrZjBhv1Y1p1SToGhQXDA==",
"python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so": "LWeYZzk5kwmre0K28KHgylbIbfLa2mUnuNXpkU1fNUNf5R2ceUN6VGBHJtFaZ9Kl2m2FaAXI7O7LxrB90NMeBg=="
"python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so": "3XxFsaC/nx8fXtDtpWs+7oPABYmwlIEj1Ex30U8kTDzguB6P5RJVmkFbstAiWOnVCYfLT47sMlvHeU3VTHehDA==",
"python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so": "9SRCLPJAzPMXMJRyxmfzIR3B2uTfpRm4uzUi1UBnSufzR+ppaRxfCtLestlXWDf2nkQiiaHjF3JdPv3VhjjmBg==",
"python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so": "KdG3SA295ptxX5WcOmdjeHUkE+f+DTRkPGcJFEXgLMPPMWSgo8aVYILT9z46JTUHd+va82ijRIAJZtJHE+wqAg==",
"python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so": "yNV0YdFkYGTRyiBFwejPolZHv5ueyQ3XwBjKAoDEB5c80ps6dEDDoXzJBi+Pe1PS/hhVZq3rb5NqVzuJEBlPCA==",
"python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so": "OD6pPW3nNwopVqsY0UXVyGrAREG7KNCEO59c5j6uIPuKHaSCu2z59tNG9WZ/mgfJCyhLCAN6K9BULmX2kwRTDA==",
"python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so": "B4Pc4T9sPIuF9g35pB43YbaEzwX9h4IhPrvLzwf1HUmkPMT7knz2B1lgU0z8atz96F5OUpJSE2mG5usq5BzjAA==",
"python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so": "KYtRL67X5HZ2vVmQfqW8XhnmDVK1vxpo2geVcZ7NXGZ3FFn5vcIweK2ZFVZoIgPfGvK5QMSONnLXPUNSfc9iCw=="
}
}
@@ -1 +1 @@
47AltDKN9D5zvnmHB0hbFM/JvPLfDaYUk5TyXap8B1xJl2WJZvVx8KEwyclnDv9VJ4ozc1nHTGLnbK37B3ciAg==
NcjiBJcKitajD4QhhgJZWkc7JRypOIeWKlHvcc0gRMfLAHU7K3ogMd/ZYHUwWPaTZw0JS5zkUIPuZ7hN+chqDA==
@@ -557,7 +557,7 @@ class Tici(HardwareBase):
if self.get_device_type() in ("tici", "tizi"):
if initial_eps_apn:
subprocess.call(["mmcli", "-m", "any", f'--3gpp-set-initial-eps-bearer-settings=apn={initial_eps_apn}'])
else:
elif is_comma_profile:
subprocess.call(["mmcli", "-m", "any", '--3gpp-set-initial-eps-bearer-settings=apn='])
cmds += [
+28 -28
View File
@@ -141,8 +141,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py": {
"mode": 420,
"sha256": "0db179f0df2dd8949493bc49a5cdecb18d11383a20239c759ece59fa9ae874cc",
"size": 11398
"sha256": "5cae804fc22b0e28988147be19bd3ccf895ade0f5c5041504840695ba83d2e20",
"size": 13106
},
"python/_iqclosure/iqpilot/selfdrive/controls/lib/drive_helpers.py": {
"mode": 420,
@@ -161,8 +161,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py": {
"mode": 420,
"sha256": "faa6f87b227c805e8890a91139e8d16d7874c4b99b330dda08080392ce3479ec",
"size": 4618
"sha256": "d27bd9010967dd51dccc18ec005ff446ae965379b8e828ff7173331818723ec8",
"size": 4667
},
"python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lateral_edge_guard.py": {
"mode": 420,
@@ -211,8 +211,8 @@
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/daemon.py": {
"mode": 420,
"sha256": "57f4f87e4db2a8a86859ed0a468413d81791043df926738e518afe5ae3503795",
"size": 36288
"sha256": "62f51d922f6883bc094590a9d95c2c0849d858bbce30f35c2eba00ec9d3d9bad",
"size": 36325
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/drive_profile.py": {
"mode": 420,
@@ -541,8 +541,8 @@
},
"python/_iqclosure/iqpilot/system/hardware/tici/usb_storage.py": {
"mode": 420,
"sha256": "3352f2cf70d38e7158ef8ac20bb736dba55c73239cbdd3ab7f431dbacfb5027e",
"size": 2189
"sha256": "0026831af4dc730669e91b9c2be5aa62f870b9d8bebfaf783e2c078ef382f4be",
"size": 2515
},
"python/_iqclosure/iqpilot/system/hardware/usb.py": {
"mode": 420,
@@ -576,38 +576,38 @@
},
"python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "bc5e1c99a6efca66a1f7d69ade1bd228870f965117deae86378c32e924432d39",
"size": 333944
"sha256": "1f4bc57a92e768f4cba01d1a37b22157de2ea1baf1df7a650bc68f66dd33f816",
"size": 333976
},
"python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "5aa52680f9dd3ad393fabbed20e434168becf285ab5991df35dd8b3758d78419",
"size": 68072
"sha256": "33d7f6b1957b2bd180320f3ac05c14da2b4ea6e624cd2c526f79690502319c72",
"size": 68152
},
"python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "0a1048e38176dce88844f20f927be2dbca151e87e05e88e707bb807ff44595cc",
"size": 333544
"sha256": "8f5ede36c9ff1c05d01bfb95c25a06662ff5ff066224a904d133d7485e4b887b",
"size": 333552
},
"python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "3bbcf0e311d1fed7a8b7f8e0b995875dc1f70f89a57681fe9a507721ba61452e",
"size": 404536
"sha256": "86295ba52a1453147fa0f9050e449922bd31860ae1992f4d2bc097ad729b3532",
"size": 474592
},
"python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "23c9a32a75daab86714e127902cef5c456703838db6e178a502aff3fe6cc50f2",
"size": 531776
"sha256": "18385b31db8bb771a9d87d0442ab74ae81ace0acd4c335fc81550dae1686eab5",
"size": 532088
},
"python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "cb4888d0a6d31851e15bbd7d53666d01382252cf5b46f994b4eeff2960ac502c",
"sha256": "6e9087336511df69b8478df946fd911e60861081161206c1b834473ca5075f8f",
"size": 473440
},
"python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so": {
"mode": 493,
"sha256": "bb23a72222a72983be8cacbf13972e36c966f8c4e3d844fcf572bb54501ef8be",
"size": 205848
"sha256": "b3e6018fb5c1ca4c2deab213e53274178b7f07d0f804160960f3c6b740cb8ef7",
"size": 206032
},
"runtime": {
"entries": {
@@ -618,12 +618,12 @@
}
},
"signatures": {
"python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "nb58JcxhuvZb66ykqx6qtiYcxuMrT9RxYLVPrySBh2c/2lK4YAEPOW3NYXItamo3c77tp5X12OOcTenV+hRFDQ==",
"python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": "SlLArJdgtL4q44jc1iF2qOswWn0JaYKPLHM3WX+/CzkW6s5LLjPJLIP2/X9pT/xd8yb5jrrmNlkh756J0BlwDQ==",
"python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "XuilHMLAoLU+Uythp7DLZyUSzh5cd+Kd/ppdar60CvfI2ddWzyxUtWzRAj89MXosOg8TZlfvdbR+izFSaQaWCQ==",
"python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "3kHG7y4co2JjaMyYx+Nc2gti9VXv1EkDqgPuijpjtrUXxo2/z23HzW5OFR2tS5HOdSsoPp0QKxqNSJFU2vo1CQ==",
"python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "MqqTLheWl49buYG1d/aJk6W23WTEpBc0+BgTUAZHRHlfbrvvJzEZxutjS5EUd36QjOcqujdXns50NVrvbaAmDQ==",
"python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so": "JI2Fqs5/uhABjC1TA5nPytJQXCWQLDYlEljLTkak6Dc8chUf2WiwBhWpcwFWhZO+QKwxERJXFdsG1ZTuPlTlDQ==",
"python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so": "BkfWTqw5Ei9EMLGwDNlDabsD/3sIXFJNkk+s/VR3Tm7+EKdkzJs4Ws/dshTDvg5DZjuwKmOdeMYZFxEpHcqvAw=="
"python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "wMMKjD6k+ZfaYxp05TBlB2NFQTyYYvfMdzvDEX99MdROz6r3TEHONeLOW/N9F70eIj9nttenuc3KgZ5SOTapCw==",
"python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": "F4dXFxTj3Am+rj7muIYQMGV+uBSueo1zydF9tK3edeoG5vFtDefsjR8yiws4+WwCTzylmyz7XICOQMIKYzJYAg==",
"python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "v+t4WcMEVDBv7dKdAyYbifzTqLtETnPVQy8EV+nMZ2ZJbFv0cMzGzOEdICUsMxdSz1C3Qiiy3Rxo+dxs1j4hDg==",
"python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "Q9x3nM2BOXiHe6czjbQh3lMVdn+4BqfavTiwxWZt567TpOAUq7aD3Mm5ApDPk2mSd1A5m6hQqve+HGO9nIpCBQ==",
"python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "iY14YK0HANnh+RCyxnB0FMqog4a+WPRPLpMkxPaCuep+DSNE9kJuYgiRYk7Cvbfkuml1DzAMoXWWQKlu09sODw==",
"python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so": "OieufgDbIEweRN0FZCZSObUcv3t46RO2WobU2AmLM7orh8Y8biZxUOj1EhvZfmjAwscauU25wiVuvjOLDqn7Cg==",
"python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so": "e8iJw1fAkd+pCgLjkH3NGnys7ZNjjP6+M39Aii/xWsVZHzEXT0sENxkQlkPByOOlVyrJe/QE9sZbbUL0Btu9Aw=="
}
}
@@ -1 +1 @@
WG1x9JR+nNQKjx6iHX9H9cBQWxWpHVCMn3NHirf7IDe/MnGX/Xsdv1tHKunzhleUga6DF9uhIsxThBewBFNfAQ==
IOy/sKa0AiLLkaZKisGB1drIhqeng32bTisZn568tz5DFMDXVYRAfLhs5BTEK54fqnCJ1V0MaGv8Y5ywh1g2Cw==
@@ -4,6 +4,8 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from __future__ import annotations
import math
from iqpilot.cereal import car, custom, log
from iqpilot.common.constants import CV
@@ -25,9 +27,12 @@ NavManeuverPhase = custom.IQNavState.ManeuverPhase
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
LANE_CHANGE_TIME_MAX = 10.0
# the model desire history spans 5 s, so one rising edge per 5 s keeps exactly one turn pulse visible
TURN_DESIRE_PULSE_HOLD_FRAMES = round(5.0 / DT_MDL) - 1
TURN_DESIRE_PULSE_CYCLE_FRAMES = TURN_DESIRE_PULSE_HOLD_FRAMES + 1
# only the rising edge reaches the model and it stays visible for the 5 s the history spans, so one
# edge per 5 s keeps the turn alive through the maneuver without stacking edges that cut the corner
TURN_DESIRE_PULSE_CYCLE_FRAMES = round(5.0 / DT_MDL)
TURN_DESIRE_PULSE_HOLD_FRAMES = TURN_DESIRE_PULSE_CYCLE_FRAMES - 1
TURN_RECOIL_HEADING_RAD = math.radians(55.0)
TURN_DESIRE_MAX_SECONDS = 25.0
_LANE_CHANGE_DESIRES = {
(LaneChangeDirection.none, LaneChangeState.off): log.Desire.none,
@@ -111,6 +116,9 @@ class DesireHelper:
self.nav_turn_direction = TurnDirection.none
self.turn_desire_frames = 0
self.turn_desire_cycle_input = log.Desire.none
self.turn_heading_rad = 0.0
self.turn_desire_seconds = 0.0
self.turn_recoiled = False
@staticmethod
def get_lane_change_direction(carstate):
@@ -221,19 +229,50 @@ class DesireHelper:
def _clear_turn_desire_pulse(self) -> None:
self.turn_desire_frames = 0
self.turn_desire_cycle_input = log.Desire.none
self.turn_heading_rad = 0.0
self.turn_desire_seconds = 0.0
self.turn_recoiled = False
def _measured_yaw_rate(self) -> float:
# carState.yawRate is left unpopulated by many ports, so the model pose is the primary source
rate = getattr(getattr(getattr(self, "_last_modeldata", None), "orientationRate", None), "z", None)
try:
if rate is not None and len(rate) and math.isfinite(rate[0]):
return float(rate[0])
except (TypeError, IndexError):
pass
try:
return float(getattr(self._last_carstate, "yawRate", 0.0) or 0.0)
except (TypeError, ValueError):
return 0.0
def _turn_recoiled(self, desired_output: log.Desire) -> bool:
# blinkers keep running well past the apex, so the executed heading change has to end the
# desire: one that outlives the turn keeps steering the plan into the far side of the road
if desired_output != self.turn_desire_cycle_input:
self.turn_heading_rad = 0.0
self.turn_desire_seconds = 0.0
self.turn_recoiled = False
self.turn_heading_rad += abs(self._measured_yaw_rate()) * DT_MDL
self.turn_desire_seconds += DT_MDL
if self.turn_heading_rad >= TURN_RECOIL_HEADING_RAD or self.turn_desire_seconds >= TURN_DESIRE_MAX_SECONDS:
self.turn_recoiled = True
return self.turn_recoiled
def _pulse_turn_desire(self, desired_output: log.Desire) -> log.Desire:
if desired_output not in _PULSED_TURN_DESIRES:
self._clear_turn_desire_pulse()
return desired_output
recoiled = self._turn_recoiled(desired_output)
if desired_output != self.turn_desire_cycle_input:
self.turn_desire_frames = 0
self.turn_desire_cycle_input = desired_output
cycle_frame = self.turn_desire_frames % TURN_DESIRE_PULSE_CYCLE_FRAMES
self.turn_desire_frames += 1
if cycle_frame >= TURN_DESIRE_PULSE_HOLD_FRAMES:
if recoiled or cycle_frame >= TURN_DESIRE_PULSE_HOLD_FRAMES:
return log.Desire.none
return desired_output
@@ -250,6 +289,7 @@ class DesireHelper:
def update(self, carstate, lateral_active, lane_change_prob, nav_state=None, modeldata=None, radar_state=None):
self._last_carstate = carstate
self._last_modeldata = modeldata
self.lateral_edge_guard.update(modeldata, carstate.vEgo, DT_MDL)
self.lateral_edge_block = LateralEdgeBlock.none
one_blinker = carstate.leftBlinker != carstate.rightBlinker
@@ -16,6 +16,7 @@ TurnDirection = custom.IQTurnSignalDirection
TURN_TRIGGER_MPS = 20 * CV.MPH_TO_MS
TURN_SPEED_GATE_MPS = TURN_TRIGGER_MPS
LANE_CHANGE_SPEED_MIN = TURN_SPEED_GATE_MPS
TURN_SPEED_CEILING_MPS = 35 * CV.MPH_TO_MS
@dataclass
@@ -60,7 +61,7 @@ class TurnSignalPlanner:
def _refresh_from_params(self) -> None:
requested_gate = _mph_param_to_mps(self._params.get("IQLaneTurnValue", return_default=True))
self._state.active = self._params.get_bool("IQLaneTurnDesire")
self._state.speed_limit_mps = min(TURN_TRIGGER_MPS, requested_gate)
self._state.speed_limit_mps = min(TURN_SPEED_CEILING_MPS, requested_gate)
def _consume_legacy_kwargs(self, **legacy) -> tuple[bool, bool, bool, bool, float]:
return (
@@ -705,6 +705,7 @@ class InferenceDaemon:
populate_iq_model_reasoning(
iq_msg.iqDriveModelData, outputs, self._reasoning_wire, main_stamp.frame_id, main_stamp.timestamp_eof
)
iq_msg.valid = driving_msg.valid
populate_odometry_message(
pose_msg,
@@ -3,7 +3,18 @@ import subprocess
from iqpilot.common.params import Params
SCRIPT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "set_usb_storage.sh")
SCRIPT_NAME = "set_usb_storage.sh"
def _script_path() -> str:
# signed bundles vendor this module without the shell script beside it
sibling = os.path.join(os.path.dirname(os.path.abspath(__file__)), SCRIPT_NAME)
if os.path.isfile(sibling):
return sibling
return os.path.join(os.environ.get("BASEDIR", "/data/iqpilot"), "iqpilot", "system", "hardware", "tici", SCRIPT_NAME)
SCRIPT_PATH = _script_path()
def apply_usb_storage_state(state: bool):
+2
View File
@@ -239,6 +239,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ModelManager_ModelsCache", {PERSISTENT, JSON}},
{"IQEmacEnabled", {PERSISTENT, BOOL, "0"}},
{"IQEmacSerial", {PERSISTENT, STRING}},
{"IQEmacHost", {PERSISTENT, STRING}},
{"IQEmacModel", {PERSISTENT, STRING}},
{"IQEmacSmallModel", {PERSISTENT, BOOL, "0"}},
@@ -318,6 +319,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"iqMqbSteeringLockout", {PERSISTENT, BOOL, "0"}},
{"AllowLateralWhenLongUnavailable", {PERSISTENT, BOOL}},
{"IQEmacEnabled", {PERSISTENT, BOOL, "0"}},
{"IQEmacSerial", {PERSISTENT, STRING}},
{"IQEmacHost", {PERSISTENT, STRING}},
{"IQEmacModel", {PERSISTENT, STRING}},
{"IQEmacSmallModel", {PERSISTENT, BOOL, "0"}},
+16
View File
@@ -148,6 +148,22 @@ def test_faulted_lateral_mode_does_not_force_disable_guidance():
assert not selfdrive.events_iq.has(EventNameIQ.alcDisengaged)
def test_guidance_keeps_steering_when_long_drops_for_faulted_cruise():
guidance, selfdrive = make_engaged_guidance(False)
selfdrive.enabled_prev = True
faulted = make_vw_car_state(cruise_available=False, cruise_fault_lateral=True)
selfdrive.events.clear()
selfdrive.events_iq.clear()
selfdrive.events.add(EventName.pcmDisable)
guidance.update(faulted)
assert not selfdrive.events.has(EventName.pcmDisable)
assert not selfdrive.events_iq.has(EventNameIQ.alcDisengaged)
assert guidance.state_machine.state == GuidanceState.enabled
assert guidance.active
def test_main_switch_rising_edge_arms_guidance_during_faulted_cruise():
selfdrive = make_selfdrive(0, brand="volkswagen", main_cruise_allowed=True)
selfdrive.CS_prev = make_vw_car_state(cruise_available=False, cruise_fault_lateral=False)
+1 -1
View File
@@ -166,7 +166,7 @@ class CarSpecificEvents:
if pcm_enable:
if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled and not CS.blockPcmEnable:
events.add(EventName.pcmEnable)
elif not CS.cruiseState.enabled and not getattr(CS, 'cruiseFaultLateralMode', False):
elif not CS.cruiseState.enabled:
events.add(EventName.pcmDisable)
return events
@@ -43,7 +43,7 @@ def make_car_state(**overrides):
return SimpleNamespace(**base)
def test_pcm_disable_suppressed_during_cruise_fault_lateral_mode():
def test_pcm_disable_emitted_during_cruise_fault_lateral_mode():
cp = SimpleNamespace(
brand="volkswagen",
openpilotLongitudinalControl=False,
@@ -58,7 +58,8 @@ def test_pcm_disable_suppressed_during_cruise_fault_lateral_mode():
out = events.update(cs, cs_prev, SimpleNamespace(actuators=SimpleNamespace(accel=0.0)))
assert not out.has(EventName.pcmDisable)
assert out.has(EventName.pcmDisable)
assert out.has(EventName.cruiseFaultLateralAllowed)
def test_cruise_fault_lateral_mode_replaces_acc_faulted_alert():
@@ -4,6 +4,8 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from __future__ import annotations
import math
from iqpilot.cereal import car, custom, log
from iqpilot.common.constants import CV
@@ -25,9 +27,12 @@ NavManeuverPhase = custom.IQNavState.ManeuverPhase
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
LANE_CHANGE_TIME_MAX = 10.0
# the model desire history spans 5 s, so one rising edge per 5 s keeps exactly one turn pulse visible
TURN_DESIRE_PULSE_HOLD_FRAMES = round(5.0 / DT_MDL) - 1
TURN_DESIRE_PULSE_CYCLE_FRAMES = TURN_DESIRE_PULSE_HOLD_FRAMES + 1
# only the rising edge reaches the model and it stays visible for the 5 s the history spans, so one
# edge per 5 s keeps the turn alive through the maneuver without stacking edges that cut the corner
TURN_DESIRE_PULSE_CYCLE_FRAMES = round(5.0 / DT_MDL)
TURN_DESIRE_PULSE_HOLD_FRAMES = TURN_DESIRE_PULSE_CYCLE_FRAMES - 1
TURN_RECOIL_HEADING_RAD = math.radians(55.0)
TURN_DESIRE_MAX_SECONDS = 25.0
_LANE_CHANGE_DESIRES = {
(LaneChangeDirection.none, LaneChangeState.off): log.Desire.none,
@@ -111,6 +116,9 @@ class DesireHelper:
self.nav_turn_direction = TurnDirection.none
self.turn_desire_frames = 0
self.turn_desire_cycle_input = log.Desire.none
self.turn_heading_rad = 0.0
self.turn_desire_seconds = 0.0
self.turn_recoiled = False
@staticmethod
def get_lane_change_direction(carstate):
@@ -221,19 +229,50 @@ class DesireHelper:
def _clear_turn_desire_pulse(self) -> None:
self.turn_desire_frames = 0
self.turn_desire_cycle_input = log.Desire.none
self.turn_heading_rad = 0.0
self.turn_desire_seconds = 0.0
self.turn_recoiled = False
def _measured_yaw_rate(self) -> float:
# carState.yawRate is left unpopulated by many ports, so the model pose is the primary source
rate = getattr(getattr(getattr(self, "_last_modeldata", None), "orientationRate", None), "z", None)
try:
if rate is not None and len(rate) and math.isfinite(rate[0]):
return float(rate[0])
except (TypeError, IndexError):
pass
try:
return float(getattr(self._last_carstate, "yawRate", 0.0) or 0.0)
except (TypeError, ValueError):
return 0.0
def _turn_recoiled(self, desired_output: log.Desire) -> bool:
# blinkers keep running well past the apex, so the executed heading change has to end the
# desire: one that outlives the turn keeps steering the plan into the far side of the road
if desired_output != self.turn_desire_cycle_input:
self.turn_heading_rad = 0.0
self.turn_desire_seconds = 0.0
self.turn_recoiled = False
self.turn_heading_rad += abs(self._measured_yaw_rate()) * DT_MDL
self.turn_desire_seconds += DT_MDL
if self.turn_heading_rad >= TURN_RECOIL_HEADING_RAD or self.turn_desire_seconds >= TURN_DESIRE_MAX_SECONDS:
self.turn_recoiled = True
return self.turn_recoiled
def _pulse_turn_desire(self, desired_output: log.Desire) -> log.Desire:
if desired_output not in _PULSED_TURN_DESIRES:
self._clear_turn_desire_pulse()
return desired_output
recoiled = self._turn_recoiled(desired_output)
if desired_output != self.turn_desire_cycle_input:
self.turn_desire_frames = 0
self.turn_desire_cycle_input = desired_output
cycle_frame = self.turn_desire_frames % TURN_DESIRE_PULSE_CYCLE_FRAMES
self.turn_desire_frames += 1
if cycle_frame >= TURN_DESIRE_PULSE_HOLD_FRAMES:
if recoiled or cycle_frame >= TURN_DESIRE_PULSE_HOLD_FRAMES:
return log.Desire.none
return desired_output
@@ -250,6 +289,7 @@ class DesireHelper:
def update(self, carstate, lateral_active, lane_change_prob, nav_state=None, modeldata=None, radar_state=None):
self._last_carstate = carstate
self._last_modeldata = modeldata
self.lateral_edge_guard.update(modeldata, carstate.vEgo, DT_MDL)
self.lateral_edge_block = LateralEdgeBlock.none
one_blinker = carstate.leftBlinker != carstate.rightBlinker
@@ -16,6 +16,7 @@ TurnDirection = custom.IQTurnSignalDirection
TURN_TRIGGER_MPS = 20 * CV.MPH_TO_MS
TURN_SPEED_GATE_MPS = TURN_TRIGGER_MPS
LANE_CHANGE_SPEED_MIN = TURN_SPEED_GATE_MPS
TURN_SPEED_CEILING_MPS = 35 * CV.MPH_TO_MS
@dataclass
@@ -60,7 +61,7 @@ class TurnSignalPlanner:
def _refresh_from_params(self) -> None:
requested_gate = _mph_param_to_mps(self._params.get("IQLaneTurnValue", return_default=True))
self._state.active = self._params.get_bool("IQLaneTurnDesire")
self._state.speed_limit_mps = min(TURN_TRIGGER_MPS, requested_gate)
self._state.speed_limit_mps = min(TURN_SPEED_CEILING_MPS, requested_gate)
def _consume_legacy_kwargs(self, **legacy) -> tuple[bool, bool, bool, bool, float]:
return (
@@ -104,45 +104,49 @@ class IQFilterEngine:
class IQModeEngine:
ENTER_EVIDENCE: dict[str, float] = {'blended': 6.0, 'acc': 40.0}
MIN_DWELL_FRAMES: dict[str, int] = {'blended': 40, 'acc': 10}
EMERGENCY_HOLD_FRAMES = 40
def __init__(self):
self._state: ModeType = 'acc'
self._scores = {'acc': 1.0, 'blended': 0.0}
self._switching_timer = 0
self._pending: ModeType | None = None
self._evidence = 0.0
self._mode_age = 0
self._forced_takeover = False
self._emergency_hold = 0
def _switch(self, mode: ModeType) -> None:
self._state = mode
self._mode_age = 0
self._pending = None
self._evidence = 0.0
def request(self, mode: ModeType, urgency: float = 1.0, emergency: bool = False) -> None:
if emergency:
self._forced_takeover = True
self._state = mode
self._switching_timer = 15
self._mode_age = 0
self._emergency_hold = self.EMERGENCY_HOLD_FRAMES
if mode != self._state:
self._switch(mode)
return
self._scores[mode] = min(1.0, self._scores[mode] + 0.1 * urgency)
for key in self._scores:
if key != mode:
self._scores[key] = max(0.0, self._scores[key] - 0.05)
if self._mode_age < 10 and not self._forced_takeover:
if mode == self._state:
self._pending = None
self._evidence = 0.0
return
threshold = 0.6 if mode != self._state else 0.3
if self._scores[mode] > threshold and mode != self._state and self._switching_timer == 0:
self._switching_timer = 15
self._state = mode
self._mode_age = 0
if mode != self._pending:
self._pending = mode
self._evidence = 0.0
self._evidence += urgency
if self._emergency_hold > 0 or self._mode_age < self.MIN_DWELL_FRAMES[self._state]:
return
if self._evidence >= self.ENTER_EVIDENCE[mode]:
self._switch(mode)
def update(self) -> None:
if self._switching_timer > 0:
self._switching_timer -= 1
self._mode_age += 1
if self._forced_takeover and self._mode_age > 20:
self._forced_takeover = False
for key in self._scores:
self._scores[key] *= 0.98
if self._emergency_hold > 0:
self._emergency_hold -= 1
def get_mode(self) -> ModeType:
return self._state
@@ -42,6 +42,11 @@ E2E_MODEL_SPEED_INTENT_BP = [-0.5, 0.0]
_A_TOTAL_MAX_V = [1.7, 3.2]
_A_TOTAL_MAX_BP = [20., 40.]
MODE_BLEND_MAX_TIME = 3.0
MODE_BLEND_JERK_UP = 1.0
MODE_BLEND_JERK_DOWN = 2.5
MODE_BLEND_BRAKE_PASS = -1.0
def get_max_accel(v_ego):
return np.interp(v_ego, A_CRUISE_MAX_BP, A_CRUISE_MAX_VALS)
@@ -88,6 +93,14 @@ def get_e2e_accel(v_ego, v_cruise, model_v, a_target, should_stop):
return float(np.interp(min(accel_intent, speed_intent), [0.0, 1.0], [a_target, accel]))
def limit_mode_transition(a_target, a_prev, dt):
upper = a_prev + MODE_BLEND_JERK_UP * dt
if a_target <= MODE_BLEND_BRAKE_PASS:
return min(a_target, upper)
lower = a_prev - MODE_BLEND_JERK_DOWN * dt
return float(np.clip(a_target, lower, upper))
def get_accel_candidates(e2e, has_lead, mpc_candidate, cruise_candidate, e2e_candidate):
candidates = []
if not e2e or has_lead:
@@ -114,6 +127,8 @@ class LongitudinalPlanner(LongitudinalPlannerIQ):
self.output_a_target = 0.0
self.output_should_stop = False
self.launch_armed = False
self.prev_e2e = False
self.mode_blend_timer = 0.0
try:
self.exp_speed_conv = Params().get_bool("expSpeedConv")
except UnknownKeyName:
@@ -248,6 +263,17 @@ class LongitudinalPlanner(LongitudinalPlannerIQ):
self.output_should_stop = any(should_stop for _, _, should_stop in candidates)
self.output_should_stop = self.output_should_stop or self.forcing_stop
if e2e != self.prev_e2e:
self.mode_blend_timer = MODE_BLEND_MAX_TIME
self.prev_e2e = e2e
if reset_state or self.fcw or self.output_should_stop:
self.mode_blend_timer = 0.0
if self.mode_blend_timer > 0.0:
limited_a_target = limit_mode_transition(output_a_target, a_prev, self.dt)
self.mode_blend_timer = 0.0 if limited_a_target == output_a_target else max(self.mode_blend_timer - self.dt, 0.0)
output_a_target = limited_a_target
self.output_a_target = np.clip(output_a_target, ACCEL_MIN, ACCEL_MAX)
self.a_desired = float(self.output_a_target)
@@ -0,0 +1,92 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import pytest
from iqpilot.selfdrive.controls.lib.iq_dynamic.imahelper import IQModeEngine
from iqpilot.selfdrive.controls.lib.longitudinal_planner import (
MODE_BLEND_BRAKE_PASS, MODE_BLEND_JERK_DOWN, MODE_BLEND_JERK_UP, limit_mode_transition,
)
from iqpilot.selfdrive.controls.lib.tests.test_longitudinal_planner_accel_state import CRUISE, build_planner, build_sm
DT = 0.05
def drive(engine, requests):
modes = []
for mode, urgency, emergency in requests:
engine.request(mode, urgency, emergency)
engine.update()
modes.append(engine.get_mode())
return modes
def count_switches(modes):
return sum(1 for prev, cur in zip(modes, modes[1:], strict=False) if prev != cur)
def test_flickering_requests_do_not_chatter():
pattern = [('blended', 0.95, False)] * 15 + [('acc', 1.0, False)] * 15
modes = drive(IQModeEngine(), pattern * 20)
assert modes[-1] == 'blended'
assert count_switches(modes) == 1
def test_sustained_acc_request_leaves_blended_after_dwell():
engine = IQModeEngine()
drive(engine, [('blended', 1.0, False)] * 20)
assert engine.get_mode() == 'blended'
modes = drive(engine, [('acc', 0.7, False)] * 100)
first_acc = modes.index('acc')
assert first_acc * DT >= 2.0
assert modes[-1] == 'acc'
def test_blended_entry_is_prompt():
modes = drive(IQModeEngine(), [('acc', 0.7, False)] * 20 + [('blended', 0.9, False)] * 20)
assert modes[20:].index('blended') * DT <= 0.5
def test_emergency_switches_immediately_and_holds():
engine = IQModeEngine()
drive(engine, [('acc', 0.7, False)] * 20)
modes = drive(engine, [('blended', 1.0, True)] + [('acc', 1.0, False)] * 30)
assert modes[0] == 'blended'
assert all(mode == 'blended' for mode in modes)
@pytest.mark.parametrize("a_prev,a_target,expected", [
(0.0, 1.5, MODE_BLEND_JERK_UP * DT),
(1.5, 0.0, 1.5 - MODE_BLEND_JERK_DOWN * DT),
(0.2, MODE_BLEND_BRAKE_PASS - 0.5, MODE_BLEND_BRAKE_PASS - 0.5),
(0.3, 0.31, 0.31),
])
def test_limit_mode_transition(a_prev, a_target, expected):
assert limit_mode_transition(a_target, a_prev, DT) == pytest.approx(expected)
def test_planner_output_does_not_step_on_mode_switch():
v_ego = 12.0
planner = build_planner(init_v=v_ego)
e2e = {'active': True}
planner.is_e2e = lambda sm: e2e['active']
sm = build_sm(v_ego, 150.0, v_ego + 5.0, CRUISE)
for _ in range(100):
planner.update(sm)
blended_output = float(planner.output_a_target)
e2e['active'] = False
outputs = [blended_output]
for _ in range(60):
planner.update(sm)
outputs.append(float(planner.output_a_target))
steps = [cur - prev for prev, cur in zip(outputs, outputs[1:], strict=False)]
assert max(steps) <= MODE_BLEND_JERK_UP * planner.dt + 1e-6
assert outputs[-1] > blended_output + 0.5
@@ -6,20 +6,29 @@ import pytest
from iqpilot.cereal import custom, log
from iqpilot.selfdrive.controls.lib.desire_helper import (
DesireHelper,
TURN_DESIRE_MAX_SECONDS,
TURN_DESIRE_PULSE_CYCLE_FRAMES,
TURN_DESIRE_PULSE_HOLD_FRAMES,
TURN_RECOIL_HEADING_RAD,
)
from iqpilot.common.constants import CV
from iqpilot.common.realtime import DT_MDL
TurnDirection = custom.IQTurnSignalDirection
MODEL_HISTORY_FRAMES = 100
def helper(v_ego=0.0, yaw_rate=0.0):
def helper(v_ego=0.0, yaw_rate=0.0, model_yaw_rate=None):
result = DesireHelper.__new__(DesireHelper)
result._last_carstate = SimpleNamespace(vEgo=v_ego, yawRate=yaw_rate)
result._last_modeldata = (None if model_yaw_rate is None
else SimpleNamespace(orientationRate=SimpleNamespace(z=[model_yaw_rate])))
result.turn_desire_frames = 0
result.turn_desire_cycle_input = log.Desire.none
result.turn_heading_rad = 0.0
result.turn_desire_seconds = 0.0
result.turn_recoiled = False
result.nav_turn_direction = TurnDirection.none
result.lane_turn_direction = TurnDirection.none
result.lane_change_direction = log.LaneChangeDirection.none
@@ -39,7 +48,7 @@ def visible_pulses(outputs):
@pytest.mark.parametrize("source", ["manual", "nav"])
@pytest.mark.parametrize("v_ego,yaw_rate", [(0.0, 0.0), (6.0, 0.0), (4.0, 0.3)])
@pytest.mark.parametrize("v_ego,yaw_rate", [(0.0, 0.0), (6.0, 0.0), (4.0, 0.02)])
def test_turn_desire_keeps_exactly_one_pulse_in_model_history(source, v_ego, yaw_rate):
h = helper(v_ego, yaw_rate)
if source == "manual":
@@ -58,6 +67,64 @@ def test_turn_desire_keeps_exactly_one_pulse_in_model_history(source, v_ego, yaw
assert set(visible_pulses(outputs)[MODEL_HISTORY_FRAMES:].tolist()) == {1}
def test_turn_desire_recoils_once_the_turn_has_been_driven():
yaw_rate = 0.4
h = helper(v_ego=6.0, yaw_rate=yaw_rate)
h.lane_turn_direction = TurnDirection.turnLeft
frames_to_recoil = int(TURN_RECOIL_HEADING_RAD / (yaw_rate * DT_MDL))
outputs = []
for _ in range(frames_to_recoil + 4 * TURN_DESIRE_PULSE_CYCLE_FRAMES):
h._pick_desire_output()
outputs.append(h.desire)
assert log.Desire.turnLeft in outputs[:TURN_DESIRE_PULSE_HOLD_FRAMES]
assert h.turn_recoiled
assert all(output == log.Desire.none for output in outputs[frames_to_recoil + 1:])
def test_recoil_holds_through_a_blinker_that_stays_on():
h = helper(v_ego=6.0, yaw_rate=0.6)
h.lane_turn_direction = TurnDirection.turnRight
for _ in range(TURN_DESIRE_PULSE_CYCLE_FRAMES):
h._pick_desire_output()
assert h.turn_recoiled
h._last_carstate = SimpleNamespace(vEgo=6.0, yawRate=0.0)
outputs = []
for _ in range(3 * TURN_DESIRE_PULSE_CYCLE_FRAMES):
h._pick_desire_output()
outputs.append(h.desire)
assert all(output == log.Desire.none for output in outputs)
def test_recoil_clears_when_the_turn_request_drops():
h = helper(v_ego=6.0, yaw_rate=0.6)
h.lane_turn_direction = TurnDirection.turnRight
for _ in range(TURN_DESIRE_PULSE_CYCLE_FRAMES):
h._pick_desire_output()
assert h.turn_recoiled
h.lane_turn_direction = TurnDirection.none
h._pick_desire_output()
assert not h.turn_recoiled
assert h.turn_heading_rad == 0.0
h.lane_turn_direction = TurnDirection.turnRight
h._last_carstate = SimpleNamespace(vEgo=6.0, yawRate=0.0)
h._pick_desire_output()
assert h.desire == log.Desire.turnRight
def test_turn_desire_gives_up_when_the_blinker_outlives_the_maneuver():
h = helper(v_ego=6.0, yaw_rate=0.0)
h.lane_turn_direction = TurnDirection.turnLeft
for _ in range(int(TURN_DESIRE_MAX_SECONDS / DT_MDL) + 1):
h._pick_desire_output()
assert h.turn_recoiled
assert h.desire == log.Desire.none
def test_new_turn_direction_restarts_pulse_immediately():
h = helper()
for _ in range(TURN_DESIRE_PULSE_HOLD_FRAMES // 2):
@@ -73,3 +140,70 @@ def test_lane_change_desires_pass_through_and_reset_pulse():
assert h._pulse_turn_desire(log.Desire.laneChangeLeft) == log.Desire.laneChangeLeft
assert h.turn_desire_frames == 0
assert h.turn_desire_cycle_input == log.Desire.none
assert not h.turn_recoiled
def test_turn_speed_gate_follows_the_param_past_the_old_ceiling(monkeypatch):
from iqpilot.selfdrive.controls.lib.helpers import lane_turn
class FakeParams:
def get(self, *_a, **_k):
return "30.0"
def get_bool(self, *_a, **_k):
return True
monkeypatch.setattr(lane_turn, "Params", FakeParams)
planner = lane_turn.TurnSignalPlanner(None)
assert planner.speed_gate == pytest.approx(30.0 * CV.MPH_TO_MS)
planner.sample(blink_l=True, speed_mps=28.0 * CV.MPH_TO_MS)
assert planner.output() == TurnDirection.turnLeft
planner.sample(blink_l=True, speed_mps=31.0 * CV.MPH_TO_MS)
assert planner.output() == TurnDirection.none
def test_turn_speed_gate_is_capped_at_the_ceiling(monkeypatch):
from iqpilot.selfdrive.controls.lib.helpers import lane_turn
class FakeParams:
def get(self, *_a, **_k):
return "90.0"
def get_bool(self, *_a, **_k):
return True
monkeypatch.setattr(lane_turn, "Params", FakeParams)
planner = lane_turn.TurnSignalPlanner(None)
assert planner.speed_gate == pytest.approx(lane_turn.TURN_SPEED_CEILING_MPS)
def test_the_turn_intent_stays_alive_through_the_maneuver():
h = helper(v_ego=6.0, yaw_rate=0.15)
h.lane_turn_direction = TurnDirection.turnLeft
outputs = []
for _ in range(int(TURN_RECOIL_HEADING_RAD / (0.15 * DT_MDL)) - 1):
h._pick_desire_output()
outputs.append(h.desire)
assert not h.turn_recoiled
assert set(visible_pulses(outputs)[MODEL_HISTORY_FRAMES:].tolist()) == {1}
def test_recoil_uses_the_model_pose_when_the_car_reports_no_yaw_rate():
# every frame of 00000007--1984111bdd carries yawRate 0.0; the port never fills it in
h = helper(v_ego=6.0, yaw_rate=0.0, model_yaw_rate=0.5)
h.lane_turn_direction = TurnDirection.turnLeft
for _ in range(int(TURN_RECOIL_HEADING_RAD / (0.5 * DT_MDL)) + 2):
h._pick_desire_output()
assert h.turn_recoiled
assert h.desire == log.Desire.none
def test_recoil_falls_back_to_carstate_yaw_rate():
h = helper(v_ego=6.0, yaw_rate=0.5)
h.lane_turn_direction = TurnDirection.turnLeft
for _ in range(int(TURN_RECOIL_HEADING_RAD / (0.5 * DT_MDL)) + 2):
h._pick_desire_output()
assert h.turn_recoiled
+1
View File
@@ -705,6 +705,7 @@ class InferenceDaemon:
populate_iq_model_reasoning(
iq_msg.iqDriveModelData, outputs, self._reasoning_wire, main_stamp.frame_id, main_stamp.timestamp_eof
)
iq_msg.valid = driving_msg.valid
populate_odometry_message(
pose_msg,
+1 -1
View File
@@ -63,7 +63,7 @@ THERMAL_BANDS = OrderedDict({
OFFROAD_DANGER_TEMP = 75
prev_offroad_states: dict[str, tuple[bool, str | None]] = {}
ALLOWED_TICI_BRANCHES = {"release-new", "release-tici", "master-mici", "beta", "beta-pq", "release-prebuilt"}
ALLOWED_TICI_BRANCHES = {"release-new", "release-tici", "release-candidate", "master-mici", "beta", "beta-pq", "release-prebuilt"}
class CanStartupRecovery:
@@ -16,6 +16,12 @@ def test_beta_pq_allowed_for_tici():
assert is_supported_tici_branch(metadata)
def test_release_candidate_allowed_for_tici():
metadata = SimpleNamespace(channel="release-candidate", channel_type="release")
assert "release-candidate" in ALLOWED_TICI_BRANCHES
assert is_supported_tici_branch(metadata)
def test_tici_channel_type_allowed():
metadata = SimpleNamespace(channel="random-branch", channel_type="tici")
assert is_supported_tici_branch(metadata)
@@ -98,6 +98,18 @@ ensure_base() {
set_attr strings/0x409/product "IQ.Pilot"
set_attr configs/c.1/MaxPower 250
set_attr configs/c.1/strings/0x409/configuration "IQ.Pilot"
enable_os_desc
}
# MS OS descriptors let Windows bind WinUSB to the NutCracker bulk interface with no driver install.
# Windows caches a "no OS descriptors" answer per VID/PID/bcdDevice, so bcdDevice changes with this.
enable_os_desc() {
[ -d "$GADGET/os_desc" ] || return 0
set_attr bcdDevice 0x0501
set_attr os_desc/b_vendor_code 0xcd
set_attr os_desc/qw_sign MSFT100
set_attr os_desc/use 1
[ -L "$GADGET/os_desc/c.1" ] || sudo ln -s "$GADGET/configs/c.1" "$GADGET/os_desc/c.1" 2>/dev/null || true
}
add_adb() {
@@ -0,0 +1,24 @@
import importlib.util
import shutil
from pathlib import Path
SOURCE = Path(__file__).parents[1] / "usb_storage.py"
def load_from(directory: Path):
target = directory / "usb_storage.py"
shutil.copy(SOURCE, target)
spec = importlib.util.spec_from_file_location(f"usb_storage_{directory.name}", target)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def test_script_beside_the_module_is_used(tmp_path):
(tmp_path / "set_usb_storage.sh").write_text("")
assert load_from(tmp_path).SCRIPT_PATH == str(tmp_path / "set_usb_storage.sh")
def test_bundled_module_without_the_script_uses_the_install_tree(tmp_path, monkeypatch):
monkeypatch.setenv("BASEDIR", "/data/iqpilot")
assert load_from(tmp_path).SCRIPT_PATH == "/data/iqpilot/iqpilot/system/hardware/tici/set_usb_storage.sh"
+12 -1
View File
@@ -3,7 +3,18 @@ import subprocess
from iqpilot.common.params import Params
SCRIPT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "set_usb_storage.sh")
SCRIPT_NAME = "set_usb_storage.sh"
def _script_path() -> str:
# signed bundles vendor this module without the shell script beside it
sibling = os.path.join(os.path.dirname(os.path.abspath(__file__)), SCRIPT_NAME)
if os.path.isfile(sibling):
return sibling
return os.path.join(os.environ.get("BASEDIR", "/data/iqpilot"), "iqpilot", "system", "hardware", "tici", SCRIPT_NAME)
SCRIPT_PATH = _script_path()
def apply_usb_storage_state(state: bool):