diff --git a/artifacts/iqpilot_alc_private/.source_sha b/artifacts/iqpilot_alc_private/.source_sha index b3535496..3a4e7bb5 100644 --- a/artifacts/iqpilot_alc_private/.source_sha +++ b/artifacts/iqpilot_alc_private/.source_sha @@ -1 +1 @@ -076c1d057f97b067222d74180fbb63ba5467254a +9953593a65d6759bb84905b47d81bee547c5bc3f diff --git a/artifacts/iqpilot_alc_private/manifest.json b/artifacts/iqpilot_alc_private/manifest.json index c1f1617d..55ac863a 100644 --- a/artifacts/iqpilot_alc_private/manifest.json +++ b/artifacts/iqpilot_alc_private/manifest.json @@ -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==" } } diff --git a/artifacts/iqpilot_alc_private/manifest.json.sig b/artifacts/iqpilot_alc_private/manifest.json.sig index d514c4fa..49c57ec5 100644 --- a/artifacts/iqpilot_alc_private/manifest.json.sig +++ b/artifacts/iqpilot_alc_private/manifest.json.sig @@ -1 +1 @@ -Xr+Nn9b/ZoT2/S94B/I16aHVQI5dZZE3hGvpS6elgasC7e4JZouZvln/ue3Lh5Hg/NTJCCEStRmpEtNfgcjbAA== +uP+OnBsv67hALUufnDP6Dux48yzqt2D/MRuzB5Kpigvi+6uTRlq4M2564Vqlf/+A8/C4H4YNRVSOK37wTM9sCA== diff --git a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so index 6ed51787..2a88c996 100755 Binary files a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/alc.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/angle_tracker.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/angle_tracker.cpython-312-aarch64-linux-gnu.so new file mode 100755 index 00000000..89f6078e Binary files /dev/null and b/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/angle_tracker.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so index bae7e976..df53569a 100755 Binary files a/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_alc_private/python/iqpilot_private/konn3kt/iqlvbs/vehicle_state.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_context_private/manifest.json b/artifacts/iqpilot_context_private/manifest.json index 8f133d5a..27118a3e 100644 --- a/artifacts/iqpilot_context_private/manifest.json +++ b/artifacts/iqpilot_context_private/manifest.json @@ -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==" } } diff --git a/artifacts/iqpilot_context_private/manifest.json.sig b/artifacts/iqpilot_context_private/manifest.json.sig index a3b4f333..b6d35d04 100644 --- a/artifacts/iqpilot_context_private/manifest.json.sig +++ b/artifacts/iqpilot_context_private/manifest.json.sig @@ -1 +1 @@ -47AltDKN9D5zvnmHB0hbFM/JvPLfDaYUk5TyXap8B1xJl2WJZvVx8KEwyclnDv9VJ4ozc1nHTGLnbK37B3ciAg== +NcjiBJcKitajD4QhhgJZWkc7JRypOIeWKlHvcc0gRMfLAHU7K3ogMd/ZYHUwWPaTZw0JS5zkUIPuZ7hN+chqDA== diff --git a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py index b9fa7fbf..2f71ed63 100644 --- a/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py +++ b/artifacts/iqpilot_context_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py @@ -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 += [ diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so index d0f02200..31026b7b 100755 Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/cot_decode.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so index 189bb6ac..b4db0672 100755 Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/drive_profile.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so index fde208ec..65f06d3a 100755 Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/feature_inputs.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so index e49defc9..ef676926 100755 Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/model_context.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so index 0ebc614d..fa8f7c2f 100755 Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/reasoning_relay.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so index 3f6ec73d..54bce54e 100755 Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/speed_limit_input.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_context_private/python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_context_private/python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so index e8854abd..9e332d5d 100755 Binary files a/artifacts/iqpilot_context_private/python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_context_private/python/iqpilot_private/context/temporal_state.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/manifest.json b/artifacts/iqpilot_emac_private/manifest.json index fa4179a5..595e6ff4 100644 --- a/artifacts/iqpilot_emac_private/manifest.json +++ b/artifacts/iqpilot_emac_private/manifest.json @@ -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==" } } diff --git a/artifacts/iqpilot_emac_private/manifest.json.sig b/artifacts/iqpilot_emac_private/manifest.json.sig index dab6944b..1da47354 100644 --- a/artifacts/iqpilot_emac_private/manifest.json.sig +++ b/artifacts/iqpilot_emac_private/manifest.json.sig @@ -1 +1 @@ -WG1x9JR+nNQKjx6iHX9H9cBQWxWpHVCMn3NHirf7IDe/MnGX/Xsdv1tHKunzhleUga6DF9uhIsxThBewBFNfAQ== +IOy/sKa0AiLLkaZKisGB1drIhqeng32bTisZn568tz5DFMDXVYRAfLhs5BTEK54fqnCJ1V0MaGv8Y5ywh1g2Cw== diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py index b54a8ce4..bfa2e4c5 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py @@ -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 diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py index dd1c0598..c2cf85e5 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py @@ -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 ( diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/daemon.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/daemon.py index 48ec9771..e8c1a9ee 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/daemon.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/daemon.py @@ -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, diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/usb_storage.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/usb_storage.py index 7dcb5221..a312eefb 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/usb_storage.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/usb_storage.py @@ -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): diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so index 020b7dfd..36c3e5b0 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so index d5cad7e2..a3a51d43 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so index 2adca81e..0065eb70 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so index 8446a097..25479060 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so index 21241d57..c13ab4c0 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so index e5550886..92ca2ba6 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so index 095e305c..cfb49abc 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so differ diff --git a/iqpilot/common/params_keys.h b/iqpilot/common/params_keys.h index 52d603dc..a59fcedc 100644 --- a/iqpilot/common/params_keys.h +++ b/iqpilot/common/params_keys.h @@ -239,6 +239,7 @@ inline static std::unordered_map 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 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"}}, diff --git a/iqpilot/sab/tests/test_sab.py b/iqpilot/sab/tests/test_sab.py index 7d5a7e10..68ddc552 100644 --- a/iqpilot/sab/tests/test_sab.py +++ b/iqpilot/sab/tests/test_sab.py @@ -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) diff --git a/iqpilot/selfdrive/car/car_specific.py b/iqpilot/selfdrive/car/car_specific.py index fa85bea9..35c5351a 100644 --- a/iqpilot/selfdrive/car/car_specific.py +++ b/iqpilot/selfdrive/car/car_specific.py @@ -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 diff --git a/iqpilot/selfdrive/car/tests/test_car_specific_events.py b/iqpilot/selfdrive/car/tests/test_car_specific_events.py index 81aa1012..67f11d79 100644 --- a/iqpilot/selfdrive/car/tests/test_car_specific_events.py +++ b/iqpilot/selfdrive/car/tests/test_car_specific_events.py @@ -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(): diff --git a/iqpilot/selfdrive/controls/lib/desire_helper.py b/iqpilot/selfdrive/controls/lib/desire_helper.py index b54a8ce4..bfa2e4c5 100644 --- a/iqpilot/selfdrive/controls/lib/desire_helper.py +++ b/iqpilot/selfdrive/controls/lib/desire_helper.py @@ -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 diff --git a/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py b/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py index dd1c0598..c2cf85e5 100644 --- a/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py +++ b/iqpilot/selfdrive/controls/lib/helpers/lane_turn.py @@ -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 ( diff --git a/iqpilot/selfdrive/controls/lib/iq_dynamic/imahelper.py b/iqpilot/selfdrive/controls/lib/iq_dynamic/imahelper.py index 610a046d..71c4558e 100644 --- a/iqpilot/selfdrive/controls/lib/iq_dynamic/imahelper.py +++ b/iqpilot/selfdrive/controls/lib/iq_dynamic/imahelper.py @@ -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 diff --git a/iqpilot/selfdrive/controls/lib/longitudinal_planner.py b/iqpilot/selfdrive/controls/lib/longitudinal_planner.py index de5baa5a..ff6d4d50 100755 --- a/iqpilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/iqpilot/selfdrive/controls/lib/longitudinal_planner.py @@ -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) diff --git a/iqpilot/selfdrive/controls/lib/tests/test_iq_dynamic_mode_transitions.py b/iqpilot/selfdrive/controls/lib/tests/test_iq_dynamic_mode_transitions.py new file mode 100644 index 00000000..cfd2aae0 --- /dev/null +++ b/iqpilot/selfdrive/controls/lib/tests/test_iq_dynamic_mode_transitions.py @@ -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 diff --git a/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py b/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py index 3c8a88cf..016c277c 100644 --- a/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py +++ b/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py @@ -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 diff --git a/iqpilot/selfdrive/iqmodeld/daemon.py b/iqpilot/selfdrive/iqmodeld/daemon.py index 48ec9771..e8c1a9ee 100755 --- a/iqpilot/selfdrive/iqmodeld/daemon.py +++ b/iqpilot/selfdrive/iqmodeld/daemon.py @@ -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, diff --git a/iqpilot/system/hardware/hardwared.py b/iqpilot/system/hardware/hardwared.py index ec2674d2..c23e9031 100755 --- a/iqpilot/system/hardware/hardwared.py +++ b/iqpilot/system/hardware/hardwared.py @@ -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: diff --git a/iqpilot/system/hardware/tests/test_hardwared.py b/iqpilot/system/hardware/tests/test_hardwared.py index 5b1a00a6..d499fc44 100644 --- a/iqpilot/system/hardware/tests/test_hardwared.py +++ b/iqpilot/system/hardware/tests/test_hardwared.py @@ -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) diff --git a/iqpilot/system/hardware/tici/set_usb_storage.sh b/iqpilot/system/hardware/tici/set_usb_storage.sh index 8c3ba1b8..85120eb0 100755 --- a/iqpilot/system/hardware/tici/set_usb_storage.sh +++ b/iqpilot/system/hardware/tici/set_usb_storage.sh @@ -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() { diff --git a/iqpilot/system/hardware/tici/tests/test_usb_storage_script_path.py b/iqpilot/system/hardware/tici/tests/test_usb_storage_script_path.py new file mode 100644 index 00000000..04ba378c --- /dev/null +++ b/iqpilot/system/hardware/tici/tests/test_usb_storage_script_path.py @@ -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" diff --git a/iqpilot/system/hardware/tici/usb_storage.py b/iqpilot/system/hardware/tici/usb_storage.py index 7dcb5221..a312eefb 100644 --- a/iqpilot/system/hardware/tici/usb_storage.py +++ b/iqpilot/system/hardware/tici/usb_storage.py @@ -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):