diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index aa217735bf..3b0cc29963 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -247,6 +247,7 @@ jobs: $UNIT_TEST selfdrive/athena && \ $UNIT_TEST selfdrive/thermald && \ $UNIT_TEST tools/lib/tests && \ + ./selfdrive/boardd/tests/test_boardd_usbprotocol && \ ./selfdrive/common/tests/test_util && \ ./selfdrive/loggerd/tests/test_logger &&\ ./selfdrive/proclogd/tests/test_proclog && \ diff --git a/Jenkinsfile b/Jenkinsfile index bcdb7c99f4..a776132025 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ def phone(String ip, String step_label, String cmd) { withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) { def ssh_cmd = """ -ssh -tt -o StrictHostKeyChecking=no -i ${key_file} -p 8022 'comma@${ip}' /usr/bin/bash <<'EOF' +ssh -tt -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'EOF' set -e @@ -57,38 +57,29 @@ pipeline { } stages { - - stage('Build release2') { - agent { - docker { - image 'python:3.7.3' - args '--user=root' - } - } + stage('build releases') { when { branch 'devel-staging' } - steps { - phone_steps("eon-build", [ - ["build release2-staging & dashcam-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], - ]) - } - } - stage('Build release3') { - agent { - docker { - image 'python:3.7.3' - args '--user=root' + parallel { + stage('release2') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + steps { + phone_steps("eon-build", [ + ["build release2-staging & dashcam-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], + ]) + } + } + + stage('release3') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + steps { + phone_steps("tici", [ + ["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], + ]) + } } - } - when { - branch 'devel-staging' - } - steps { - phone_steps("tici", [ - ["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], - ]) } } @@ -105,43 +96,8 @@ pipeline { } stages { - - /* - stage('PC tests') { - agent { - dockerfile { - filename 'Dockerfile.openpilotci' - args '--privileged --shm-size=1G --user=root' - } - } - stages { - stage('Build') { - steps { - sh 'scons -j$(nproc)' - } - } - } - post { - always { - // fix permissions since docker runs as another user - sh "chmod -R 777 ." - } - } - } - */ - stage('On-device Tests') { - agent { - docker { - /* - filename 'Dockerfile.ondevice_ci' - args "--privileged -v /dev:/dev --shm-size=1G --user=root" - */ - image 'python:3.7.3' - args '--user=root' - } - } - + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } stages { stage('parallel tests') { parallel { @@ -247,6 +203,15 @@ pipeline { } } + stage('C3: replay') { + steps { + phone_steps("tici-party", [ + ["build", "cd selfdrive/manager && ./build.py"], + ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], + ]) + } + } + } } diff --git a/Pipfile b/Pipfile index 3fc26b363b..fdba040198 100644 --- a/Pipfile +++ b/Pipfile @@ -57,6 +57,7 @@ pycapnp = "==1.1.0" pycryptodome = "*" PyJWT = "*" pylint = "*" +pyopencl = "*" pyserial = "*" python-dateutil = "*" PyYAML = "*" diff --git a/Pipfile.lock b/Pipfile.lock index a1aaeb8766..81dc794b53 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c0f1a46ca2ccdc208a392c5ce78d9d928f82d40afd46a33da7b99b895798fce2" + "sha256": "6b23dd7136502655f1c18c37d55db33d77f1b85e0572aed69869401df936003a" }, "pipfile-spec": 6, "requires": { @@ -16,13 +16,20 @@ ] }, "default": { + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, "astroid": { "hashes": [ - "sha256:11f7356737b624c42e21e71fe85eea6875cb94c03c82ac76bd535a0ff10b0f25", - "sha256:abc423a1e85bc1553954a14f2053473d2b7f8baf32eae62a328be24f436b5107" + "sha256:5939cf55de24b92bda00345d4d0659d01b3c7dafb5055165c330bc7c568ba273", + "sha256:776ca0b748b4ad69c00bfe0fff38fa2d21c338e12c84aa9715ee0d473c422778" ], "markers": "python_version ~= '3.6'", - "version": "==2.8.5" + "version": "==2.9.0" }, "atomicwrites": { "hashes": [ @@ -141,11 +148,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0", - "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" + "sha256:735e240d9a8506778cd7a453d97e817e536bb1fc29f4f6961ce297b9c7a917b0", + "sha256:83fcdeb225499d6344c8f7f34684c2981270beacc32ede2e669e94f7fa544405" ], "markers": "python_version >= '3'", - "version": "==2.0.7" + "version": "==2.0.8" }, "click": { "hashes": [ @@ -167,29 +174,30 @@ }, "cryptography": { "hashes": [ - "sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6", - "sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6", - "sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c", - "sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999", - "sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e", - "sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992", - "sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d", - "sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588", - "sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa", - "sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d", - "sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd", - "sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d", - "sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953", - "sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2", - "sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8", - "sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6", - "sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9", - "sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6", - "sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad", - "sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76" - ], - "index": "pypi", - "version": "==35.0.0" + "sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681", + "sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed", + "sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4", + "sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568", + "sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e", + "sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f", + "sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f", + "sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712", + "sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e", + "sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58", + "sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44", + "sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6", + "sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d", + "sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636", + "sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba", + "sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120", + "sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3", + "sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d", + "sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b", + "sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81", + "sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8" + ], + "index": "pypi", + "version": "==36.0.0" }, "cython": { "hashes": [ @@ -759,11 +767,23 @@ }, "pylint": { "hashes": [ - "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126", - "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436" + "sha256:4f4a52b132c05b49094b28e109febcec6bfb7bc6961c7485a5ad0a0f961df289", + "sha256:b4b5a7b6d04e914a11c198c816042af1fb2d3cda29bb0c98a9c637010da2a5c5" + ], + "index": "pypi", + "version": "==2.12.1" + }, + "pyopencl": { + "hashes": [ + "sha256:05ccbdc341f64f448bfdff173d1b1e79887129cb6c147605628bbd2e56bc3929", + "sha256:0e705b47733d1055c4d8f7478907222e5881519a0dbadd1bf288baebfc024999", + "sha256:51425e65ec49c738eefe21b1eeb1f39245b01cc0ddfd495fbe1f8df33dbc6c9e", + "sha256:59f9824426d823b544717cc25dee221e1a5c5143143efb8d94034cf4ef562913", + "sha256:662899ca2fb74f2d14c2d7ac0560d3cac07e6b0847a245694bb86a27a4d350ca", + "sha256:99e26fde9e5c418878a5a4a685b8ebca118f423bb09f33e7e24cfd8ef94aee30" ], "index": "pypi", - "version": "==2.11.1" + "version": "==2021.2.9" }, "pyserial": { "hashes": [ @@ -781,6 +801,13 @@ "index": "pypi", "version": "==2.8.2" }, + "pytools": { + "hashes": [ + "sha256:db6cf83c9ba0a165d545029e2301621486d1e9ef295684072e5cd75316a13755" + ], + "markers": "python_version ~= '3.6'", + "version": "==2021.2.9" + }, "pyyaml": { "hashes": [ "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", @@ -883,19 +910,19 @@ }, "scons": { "hashes": [ - "sha256:663f819e744ddadcdf4f46b03289a7210313b86041efe1b9c8dde81dba437b72", - "sha256:691893b63f38ad14295f5104661d55cb738ec6514421c6261323351c25432b0a" + "sha256:8c13911a2aa40552553488f7d625af4c0768fc8cdedab4a858d8ce42c8c3664d", + "sha256:d47081587e3675cc168f1f54f0d74a69b328a2fc90ec4feb85f728677419b879" ], "index": "pypi", - "version": "==4.2.0" + "version": "==4.3.0" }, "sentry-sdk": { "hashes": [ - "sha256:b9844751e40710e84a457c5bc29b21c383ccb2b63d76eeaad72f7f1c808c8828", - "sha256:c091cc7115ff25fe3a0e410dbecd7a996f81a3f6137d2272daef32d6c3cfa6dc" + "sha256:0db297ab32e095705c20f742c3a5dac62fe15c4318681884053d0898e5abb2f6", + "sha256:789a11a87ca02491896e121efdd64e8fd93327b69e8f2f7d42f03e2569648e88" ], "index": "pypi", - "version": "==1.4.3" + "version": "==1.5.0" }, "setproctitle": { "hashes": [ @@ -1187,11 +1214,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0", - "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" + "sha256:735e240d9a8506778cd7a453d97e817e536bb1fc29f4f6961ce297b9c7a917b0", + "sha256:83fcdeb225499d6344c8f7f34684c2981270beacc32ede2e669e94f7fa544405" ], "markers": "python_version >= '3'", - "version": "==2.0.7" + "version": "==2.0.8" }, "control": { "hashes": [ @@ -1255,29 +1282,30 @@ }, "cryptography": { "hashes": [ - "sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6", - "sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6", - "sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c", - "sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999", - "sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e", - "sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992", - "sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d", - "sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588", - "sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa", - "sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d", - "sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd", - "sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d", - "sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953", - "sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2", - "sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8", - "sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6", - "sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9", - "sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6", - "sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad", - "sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76" - ], - "index": "pypi", - "version": "==35.0.0" + "sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681", + "sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed", + "sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4", + "sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568", + "sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e", + "sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f", + "sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f", + "sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712", + "sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e", + "sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58", + "sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44", + "sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6", + "sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d", + "sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636", + "sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba", + "sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120", + "sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3", + "sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d", + "sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b", + "sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81", + "sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8" + ], + "index": "pypi", + "version": "==36.0.0" }, "cycler": { "hashes": [ @@ -1343,11 +1371,11 @@ }, "fonttools": { "hashes": [ - "sha256:68071406009e7ef6a5fdcd85d95975cd6963867bb226f2b786bfffe15d1959ef", - "sha256:8c8f84131bf04f3b1dcf99b9763cec35c347164ab6ad006e18d2f99fcab05529" + "sha256:dca694331af74c8ad47acc5171e57f6b78fac5692bf050f2ab572964577ac0dd", + "sha256:eff1da7ea274c54cb8842853005a139f711646cbf6f1bcfb6c9b86a627f35ff0" ], "markers": "python_version >= '3.7'", - "version": "==4.28.1" + "version": "==4.28.2" }, "hexdump": { "hashes": [ @@ -1358,19 +1386,19 @@ }, "hypothesis": { "hashes": [ - "sha256:8f7318839b64bae1410704933fe01f3db5f80f20f2ba8d4ea3520443ae3a001d", - "sha256:f1627022f75306c653115fbd169ba6a4322804f60533a7b46625fd0eecba43d5" + "sha256:92005826894782cc57c47b98debe1bc889b716f0774e60f83536f14360a9199e", + "sha256:c4943d159f299a96b1c23277c4bcf0759a757888b67a6d73e9c70d853024aa15" ], "index": "pypi", - "version": "==6.24.5" + "version": "==6.27.2" }, "identify": { "hashes": [ - "sha256:4f85f9bd8e6e5e2d61b2f8de5ff5313d8a1cfac4c88822d74406de45ad10bd82", - "sha256:8cb609a671d2f861ae7fe583711a43fd2faab0c892f39cbc4568b0c51b354238" + "sha256:a33ae873287e81651c7800ca309dc1f84679b763c9c8b30680e16fbfa82f0107", + "sha256:eba31ca80258de6bb51453084bff4a923187cd2193b9c13710f2516ab30732cc" ], "markers": "python_full_version >= '3.6.1'", - "version": "==2.3.6" + "version": "==2.4.0" }, "idna": { "hashes": [ @@ -1682,47 +1710,47 @@ }, "opencv-python-headless": { "hashes": [ - "sha256:014e4a892f67043610521e673883da5ad98a6e61356a609c6502b9d22144f2f1", - "sha256:01a6411eb9eecf58db3a50fa761f0527bdd2b23b884b3bea44eb57e6d06e4767", - "sha256:067e4736cdc70712f43c064bfe53ba80492c20479ad3290849337f948572c392", - "sha256:09b21c48144a9dd82e435d66ccd834f3a391a2cd2b69f787aabbc7cd3576f2f1", - "sha256:113b8ce88f337ab3a1ee670b8c5afc3fbf0d8aafec7b2ee897ad5efd9926ce90", - "sha256:1162de92301d0d13642afe8e163593982d3159ef21eba95a56ba3f5ca8024861", - "sha256:1799ae397677bd3a68aff4eb4f5c122baf3ee73a26fe67391e0d618bd2b7d046", - "sha256:1e0498bc4a39d887e84801b82df5cc943a5ae4b596511da7e69df64b263f293b", - "sha256:27b9118b342dc464590ed233c7f468c8cb68ee0e5d9734d9b67fb0bc1b10abd6", - "sha256:32e1f478457adeea4e1a3d6baf33310331740cceffd83a2f34e00a7e5bae6407", - "sha256:37e20b1a0c623c7e01381668b83342387798d0f32ff8a7d7f3dd0ef127eb10fa", - "sha256:3e32018e65eabc9aab10c7a109be5f735cf2c3934a47025b6903dcec034d460b", - "sha256:4b9fa45f1b364e7585fd4a2781565667efed49124748fb3ec2a66e40b7398338", - "sha256:526417214a1cad27e3d2d064e535868ae05f5a07a0e344d54fe81078bbacfa57", - "sha256:540fa340b29dd86670e1f91bbcd2675df032b774f77d08909faf818696524617", - "sha256:64bfc55fe60ed9ad778ec73161f0cea58e425a3f2f06293756f82880319bffa9", - "sha256:685c41fce11c83a238ab40bd57d5b39c3fc4f87a898565cd27e4a521b5902a62", - "sha256:69c55e055e81a2eead40e6f2521ad27bdd11523dc0c1de5e5aea23041a0a49f0", - "sha256:6e0757762096158b803b366c386f25f24715662465c448ed70ce65ecd93e41a2", - "sha256:79ca8b8feab911a80692fa3871c02526844f112e0f8bf4749f78383696cfaa4c", - "sha256:807ac9d48c11b3f734d9de5eda69e1c4197204bac1e6db3d3d51859832df6fc1", - "sha256:84f037ba74482548c066ad5e341f77ebcd3d08d06a540a4a1182300045b8bde8", - "sha256:989d4817a2f22a601707ea36a3664a6c26cee155ed441363d5f29d73d9aec610", - "sha256:a148f8bdb54e03b1d899acb95aa6d870d6f10eed037d9b4700912068f3952c32", - "sha256:bb29a911c4a67f9710b53b94766be1bb22926b98460790091d4620a534329736", - "sha256:c8b595731b5c519daf2bf3c074ec2080eb51e8efef198a4493f930b5e4439bb0", - "sha256:d1735296d88e16379d001c4946c1a196b87e3c3ff4da0c10762918dded02f670", - "sha256:d73972a62d3e48ee01783d4eee01152c93348c70305c91f85fb668ddcc8907ad", - "sha256:dabf703efe12ec4341efd71c1dd384542e28fc49b7d2ea02f185ef22bb75a1c8", - "sha256:fdd80034749b8b2f211c2549293b34af691e4f90bf17b320a84b09b784275cc2" - ], - "index": "pypi", - "version": "==4.5.4.58" + "sha256:01f76ca55fdb7e94c3e7eab5035376d06518155e3d88a08096e4670e57a0cee4", + "sha256:03349d9fb28703b2eaa8b1f333a6139b9849596ae4445cb1d76e2a7f5e4a2cf8", + "sha256:29f5372dabdcd571074f0539bd294a2f5a245a00b871827af6d75a971b3f657e", + "sha256:30261b87477a718993fa7cd8a44b7de986b81f8005e23110978c58fd53eb5e43", + "sha256:33e534fbc7a417a05ef6b14812fe8ff6b6b7152c22d502b61536c50ad63f80cb", + "sha256:3a8457918ecbca57669f141e7dba92e56af370876d022d75d58b94174d11e26b", + "sha256:4ef93f338b16e95418b69293924745a36f23e3d05da5ee10dde76af72b0889e3", + "sha256:5009a183be7a6817ff216dcb63ef95022c74e360011fa52aa33bc833256693b5", + "sha256:5331ce17a094bea4f8132ee23b2eaade85904199c0d04501102c9bb889302c67", + "sha256:659107ea6059b5cc953e1a32136a54998540cefea47b01dd62f1e806d10cbe39", + "sha256:6d949ec3e10cffa915ab1853e490674b8c420ba29eb0aeea72785f3e254dc7a1", + "sha256:6e7710aff3a0717f39c9ade77fdd9111203b09589539655044e73cc5d9960666", + "sha256:7b4bd3c6a0d2601b2619ae406eb85a41dff538a7c9cb2a54fb1e90631bf33887", + "sha256:7da49405e163b7a2cf891bf54a877ff3e198bc0bfe55009c1d19eb5a0153921d", + "sha256:7f8dd594ea0b0049d1614d7bfba984ebd926b2f12670edf6ae3d9d5d6ff8f8f0", + "sha256:8f8a06f75dc69631404e0846038d30ff43c9a9d60fcffe07c7a88f8b8c8c776c", + "sha256:99e678db353102119cbfe9d17aef520bacf585a3a287c4278dd1ce6fcd3be8f7", + "sha256:a1f9d41c6afe86fdbe85ac31ff9a6ce893af2d0fce68fbd1581dbbc0e4dfcb25", + "sha256:a1fd5bbf5db00432fb368c73e7d70ead13f69619b33e01dabf2906426a1a9277", + "sha256:a5461ad9789c784e75713d6c213c0e34b709073c71ec8ed94129419ea0ce7c01", + "sha256:a6ba305364df31b8ac8471a719371d0c05e1e5f7cc5b8a2295e7e958f9bc39bb", + "sha256:bbf37d5de98b09e7513e61fca6ebf6466fd82c3c2f0475e51d2a3c80e0bc1a92", + "sha256:bc9502064e8c3ff6f40b74c8a68fb31d0c9eae18c1d3f52d4e3f0ccda986f7cb", + "sha256:cdea7ab1698b69274eb69b16efdd7b16944c5019c06f0ace9530f91862496cf4", + "sha256:cdfec5dedd44617d94725170446cbe77c0b45044188bdc97cd251e698aeee822", + "sha256:db112fe9ffde7af96df09befcefdd33c4338f3a34fbfe894e04e66e14f584d9e", + "sha256:db461f2f0bfac155d56be7688ab6b43c140ce8b944aa5e6cfcb754bfeeeca750", + "sha256:dc303a5e09089001fd4fd51bd18a6d519e81ad5cbc36bb4b5fc3388d22a64be1", + "sha256:eb9e571427b7f44b8d8f9a3b6b7b25e45bc8e8895ed3cf3ecd917c0125cf3477", + "sha256:f4fbd431b2b0014b7d99e870f428eebf50a0149e4be1a72b905569aaadf4b540" + ], + "index": "pypi", + "version": "==4.5.4.60" }, "packaging": { "hashes": [ - "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966", - "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0" + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" ], "markers": "python_version >= '3.6'", - "version": "==21.2" + "version": "==21.3" }, "parameterized": { "hashes": [ @@ -1923,11 +1951,11 @@ }, "pyparsing": { "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4", + "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.7" + "markers": "python_version >= '3.6'", + "version": "==3.0.6" }, "pyprof2calltree": { "hashes": [ @@ -2007,33 +2035,35 @@ }, "scipy": { "hashes": [ - "sha256:1437073f1d4664990879aa8f9547524764372e0fef84a077be4b19e82bba7a8d", - "sha256:17fd991a275e4283453f89d404209aa92059ac68d76d804b4bc1716a3742e1b5", - "sha256:1ea6233f5a365cb7945b4304bd06323ece3ece85d6a3fa8598d2f53e513467c9", - "sha256:2d25272c03ee3c0fe5e0dff1bb7889280bb6c9e1766fa9c7bde81ad8a5f78694", - "sha256:30bdda199667e74b50208a793eb1ba47a04e5e3fa16f5ff06c6f7969ae78e4da", - "sha256:359b60a0cccd17723b9d5e329a5212a710e771a3ddde800e472fb93732756c46", - "sha256:39f838ea5ce8da868785193d88d05cf5a6d5c390804ec99de29a28e1dcdd53e6", - "sha256:4d175ba93e00d8eef8f7cd70d4d88a9106a86800c82ea03cf2268c36d6545483", - "sha256:5273d832fb9cd5724ee0d335c16a903b923441107dd973d27fc4293075a9f4e3", - "sha256:54951f51d731c832b1b8885e0a92e89f33d087de7e40d02078bf0d49c7cbdbb5", - "sha256:74f518ce542533054695f743e4271cb8986b63f95bb51d70fcee4f3929cbff7d", - "sha256:7b1d0f5f524518f1a86f288443528e4ff4a739c0966db663af4129b7ac7849f8", - "sha256:82c5befebf54d799d77e5f0205c03030f57f69ba2541baa44d2e6ad138c28cd3", - "sha256:8482c8e45857ab0a5446eb7460d2307a27cbbe659d6d2257820c6d6eb950fd0f", - "sha256:87cf3964db0f1cce17aeed5bfc1b89a6b4b07dbfc48e50d21fa3549e00456803", - "sha256:8b5726a0fedeaa6beb1095e4466998bdd1d1e960b28db9b5a16c89cbd7b2ebf1", - "sha256:97eb573e361a73a553b915dc195c6f72a08249964b1a33f157f9659f3b6210d1", - "sha256:a80eb01c43fd98257ec7a49ff5cec0edba32031b5f86503f55399a48cb2c5379", - "sha256:cac71d5476a6f56b50459da21f6221707e0051ebd428b2137db32ef4a43bb15e", - "sha256:d86abd1ddf421dea5e9cebfeb4de0d205b3dc04e78249afedba9c6c3b2227ff2", - "sha256:dc2d1bf41294e63c7302bf499973ac0c7f73c93c01763db43055f6525234bf11", - "sha256:e08b81fcd9bf98740b58dc6fdd7879e33a64dcb682201c1135f7d4a75216bb05", - "sha256:e3efe7ef75dfe627b354ab0af0dbc918eadee97cc80ff1aabea6d3e01114ebdd", - "sha256:fa2dbabaaecdb502641b0b3c00dec05fb475ae48655c66da16c9ed24eda1e711" - ], - "index": "pypi", - "version": "==1.7.2" + "sha256:033ce76ed4e9f62923e1f8124f7e2b0800db533828c853b402c7eec6e9465d80", + "sha256:173308efba2270dcd61cd45a30dfded6ec0085b4b6eb33b5eb11ab443005e088", + "sha256:21b66200cf44b1c3e86495e3a436fc7a26608f92b8d43d344457c54f1c024cbc", + "sha256:2c56b820d304dffcadbbb6cbfbc2e2c79ee46ea291db17e288e73cd3c64fefa9", + "sha256:304dfaa7146cffdb75fbf6bb7c190fd7688795389ad060b970269c8576d038e9", + "sha256:3f78181a153fa21c018d346f595edd648344751d7f03ab94b398be2ad083ed3e", + "sha256:4d242d13206ca4302d83d8a6388c9dfce49fc48fdd3c20efad89ba12f785bf9e", + "sha256:5d1cc2c19afe3b5a546ede7e6a44ce1ff52e443d12b231823268019f608b9b12", + "sha256:5f2cfc359379c56b3a41b17ebd024109b2049f878badc1e454f31418c3a18436", + "sha256:65bd52bf55f9a1071398557394203d881384d27b9c2cad7df9a027170aeaef93", + "sha256:7edd9a311299a61e9919ea4192dd477395b50c014cdc1a1ac572d7c27e2207fa", + "sha256:8499d9dd1459dc0d0fe68db0832c3d5fc1361ae8e13d05e6849b358dc3f2c279", + "sha256:866ada14a95b083dd727a845a764cf95dd13ba3dc69a16b99038001b05439709", + "sha256:87069cf875f0262a6e3187ab0f419f5b4280d3dcf4811ef9613c605f6e4dca95", + "sha256:93378f3d14fff07572392ce6a6a2ceb3a1f237733bd6dcb9eb6a2b29b0d19085", + "sha256:95c2d250074cfa76715d58830579c64dff7354484b284c2b8b87e5a38321672c", + "sha256:ab5875facfdef77e0a47d5fd39ea178b58e60e454a4c85aa1e52fcb80db7babf", + "sha256:ca36e7d9430f7481fc7d11e015ae16fbd5575615a8e9060538104778be84addf", + "sha256:ceebc3c4f6a109777c0053dfa0282fddb8893eddfb0d598574acfb734a926168", + "sha256:e2c036492e673aad1b7b0d0ccdc0cb30a968353d2c4bf92ac8e73509e1bf212c", + "sha256:eb326658f9b73c07081300daba90a8746543b5ea177184daed26528273157294", + "sha256:eb7ae2c4dbdb3c9247e07acc532f91077ae6dbc40ad5bd5dca0bb5a176ee9bda", + "sha256:edad1cf5b2ce1912c4d8ddad20e11d333165552aba262c882e28c78bbc09dbf6", + "sha256:eef93a446114ac0193a7b714ce67659db80caf940f3232bad63f4c7a81bc18df", + "sha256:f7eaea089345a35130bc9a39b89ec1ff69c208efa97b3f8b25ea5d4c41d88094", + "sha256:f99d206db1f1ae735a8192ab93bd6028f3a42f6fa08467d37a14eb96c9dd34a3" + ], + "index": "pypi", + "version": "==1.7.3" }, "setuptools-scm": { "hashes": [ diff --git a/README.md b/README.md index b9a58f6a10..b012883d78 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![](https://user-images.githubusercontent.com/37757984/127420744-89ca219c-8f8e-46d3-bccf-c1cb53b81bb1.png) +![](https://i.imgur.com/b0ZyIx5.jpg) Table of Contents ======================= @@ -21,16 +21,16 @@ What is openpilot? - - - - + + + + - - - - + + + +
@@ -40,7 +40,7 @@ Running in a car To use openpilot in a car, you need four things * This software. It's free and available right here. -* One of [the 140+ supported cars](docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, and more. If your car is not supported, but has adaptive cruise control and lane keeping assist, it's likely able to run openpilot. +* One of [the 150+ supported cars](docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, and more. If your car is not supported, but has adaptive cruise control and lane keeping assist, it's likely able to run openpilot. * A supported device to run this software. This can be a [comma two](https://comma.ai/shop/products/two), [comma three](https://comma.ai/shop/products/three), or if you like to experiment, a [Ubuntu computer with webcams](https://github.com/commaai/openpilot/tree/master/tools/webcam). * A way to connect to your car. With a comma two or three, you need only a [car harness](https://comma.ai/shop/products/car-harness). With an EON Gold or PC, you also need a [black panda](https://comma.ai/shop/products/panda). diff --git a/RELEASES.md b/RELEASES.md index 12a90fba6c..3c73c67794 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,10 @@ +Version 0.8.12 (2021-12-XX) +======================== + * New alert sounds + * Honda Accord 2021 support thanks to csouers! + * Honda Accord Hybrid 2021 support thanks to csouers! + * Lexus RC 2020 support thanks to ErichMoraga! + Version 0.8.11 (2021-11-29) ======================== * Support for CAN FD on the red panda diff --git a/SConstruct b/SConstruct index 67acd3eac4..a66290aabf 100644 --- a/SConstruct +++ b/SConstruct @@ -60,7 +60,6 @@ if arch == "aarch64" and TICI: arch = "larch64" USE_WEBCAM = os.getenv("USE_WEBCAM") is not None -USE_FRAME_STREAM = os.getenv("USE_FRAME_STREAM") is not None lenv = { "PATH": os.environ['PATH'], @@ -352,7 +351,7 @@ if GetOption("clazy"): qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0] qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks) -Export('env', 'qt_env', 'arch', 'real_arch', 'SHARED', 'USE_WEBCAM', 'USE_FRAME_STREAM') +Export('env', 'qt_env', 'arch', 'real_arch', 'SHARED', 'USE_WEBCAM') SConscript(['selfdrive/common/SConscript']) Import('_common', '_gpucommon', '_gpu_libs') @@ -387,7 +386,7 @@ rednose_config = { }, } -if arch != "aarch64": +if arch not in ["aarch64", "larch64"]: rednose_config['to_build'].update({ 'gnss': ('#selfdrive/locationd/models/gnss_kf.py', True, []), 'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, []), diff --git a/cereal b/cereal index 032aca6ca3..c4a1c9fa00 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 032aca6ca38a342e26fb9cc986b7f72b91cd9b55 +Subproject commit c4a1c9fa00c5915fbdf7f6c29f5968dfc1be0630 diff --git a/common/api/__init__.py b/common/api/__init__.py index 5b1d66cf77..8b83dfc641 100644 --- a/common/api/__init__.py +++ b/common/api/__init__.py @@ -3,7 +3,7 @@ import os import requests from datetime import datetime, timedelta from common.basedir import PERSIST -from selfdrive.version import version +from selfdrive.version import get_version API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') @@ -34,13 +34,13 @@ class Api(): if isinstance(token, bytes): token = token.decode('utf8') return token - + def api_get(endpoint, method='GET', timeout=None, access_token=None, **params): headers = {} if access_token is not None: - headers['Authorization'] = "JWT "+access_token + headers['Authorization'] = "JWT " + access_token - headers['User-Agent'] = "openpilot-" + version + headers['User-Agent'] = "openpilot-" + get_version() return requests.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params) diff --git a/common/cython_hacks.py b/common/cython_hacks.py deleted file mode 100644 index d0e154746d..0000000000 --- a/common/cython_hacks.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -import sysconfig -from Cython.Distutils import build_ext - -def get_ext_filename_without_platform_suffix(filename): - name, ext = os.path.splitext(filename) - ext_suffix = sysconfig.get_config_var('EXT_SUFFIX') - - if ext_suffix == ext: - return filename - - ext_suffix = ext_suffix.replace(ext, '') - idx = name.find(ext_suffix) - - if idx == -1: - return filename - else: - return name[:idx] + ext - -class BuildExtWithoutPlatformSuffix(build_ext): - def get_ext_filename(self, ext_name): - filename = super().get_ext_filename(ext_name) - return get_ext_filename_without_platform_suffix(filename) diff --git a/docs/CARS.md b/docs/CARS.md index 30f3131c72..a4742603bf 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -7,8 +7,8 @@ | Acura | ILX 2016-19 | AcuraWatch Plus | openpilot | 25mph1 | 25mph | | Acura | RDX 2016-18 | AcuraWatch Plus | openpilot | 25mph1 | 12mph | | Acura | RDX 2019-21 | All | Stock | 0mph | 3mph | -| Honda | Accord 2018-20 | All | Stock | 0mph | 3mph | -| Honda | Accord Hybrid 2018-20 | All | Stock | 0mph | 3mph | +| Honda | Accord 2018-21 | All | Stock | 0mph | 3mph | +| Honda | Accord Hybrid 2018-21 | All | Stock | 0mph | 3mph | | Honda | Civic Hatchback 2017-21 | Honda Sensing | Stock | 0mph | 12mph | | Honda | Civic Coupe 2016-18 | Honda Sensing | openpilot | 0mph | 12mph | | Honda | Civic Coupe 2019-20 | All | Stock | 0mph | 2mph2 | @@ -34,9 +34,10 @@ | Lexus | ES Hybrid 2017-18 | LSS | Stock3| 0mph | 0mph | | Lexus | ES Hybrid 2019-21 | All | openpilot | 0mph | 0mph | | Lexus | IS 2017-2019 | All | Stock | 22mph | 0mph | -| Lexus | NX 2018 | All | Stock3| 0mph | 0mph | +| Lexus | NX 2018-2019 | All | Stock3| 0mph | 0mph | | Lexus | NX 2020 | All | openpilot | 0mph | 0mph | | Lexus | NX Hybrid 2018-19 | All | Stock3| 0mph | 0mph | +| Lexus | RC 2020 | All | Stock | 22mph | 0mph | | Lexus | RX 2016-18 | All | Stock3| 0mph | 0mph | | Lexus | RX 2020-21 | All | openpilot | 0mph | 0mph | | Lexus | RX Hybrid 2016-19 | All | Stock3| 0mph | 0mph | @@ -49,7 +50,7 @@ | Toyota | Camry 2021-22 | All | openpilot | 0mph4 | 0mph | | Toyota | Camry Hybrid 2018-20 | All | Stock | 0mph4 | 0mph | | Toyota | Camry Hybrid 2021-22 | All | openpilot | 0mph | 0mph | -| Toyota | C-HR 2017-20 | All | Stock | 0mph | 0mph | +| Toyota | C-HR 2017-21 | All | Stock | 0mph | 0mph | | Toyota | C-HR Hybrid 2017-19 | All | Stock | 0mph | 0mph | | Toyota | Corolla 2017-19 | All | Stock3| 20mph1 | 0mph | | Toyota | Corolla 2020-22 | All | openpilot | 0mph | 0mph | diff --git a/models/supercombo.dlc b/models/supercombo.dlc index 82891f98b8..7a92a33a67 100644 --- a/models/supercombo.dlc +++ b/models/supercombo.dlc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e2d3a3b513bf3d0ea4255fc41d57e84b141643fd3d47268c33862110387ea05 -size 56685051 +oid sha256:ebb75bbd4c19994752f038f181359dce358a2c95c4ad1760d2d1ee346a45503d +size 56684955 diff --git a/models/supercombo.onnx b/models/supercombo.onnx index 5ef1944f92..7aa47f83ad 100644 --- a/models/supercombo.onnx +++ b/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43d362484b134fce8d05ff0cb0971c53ceee64d96a4d97c1b6d19cc849f40588 -size 56707084 +oid sha256:6e004a9ff90304fd3dfd661f3f52886affa677d486ff78c131d8a4887757b5f1 +size 57552499 diff --git a/panda b/panda index 76dc825ad8..b0bc1b5197 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 76dc825ad8329bdc3fff97583ad88a7af81c7e2e +Subproject commit b0bc1b5197c63231c7af9e22929c8d41212631b6 diff --git a/release/files_common b/release/files_common index aeae351dfb..902242bcd7 100644 --- a/release/files_common +++ b/release/files_common @@ -36,7 +36,6 @@ common/filter_simple.py common/stat_live.py common/spinner.py common/text_window.py -common/cython_hacks.py common/SConscript common/kalman/.gitignore @@ -306,6 +305,8 @@ selfdrive/loggerd/omx_encoder.h selfdrive/loggerd/logger.cc selfdrive/loggerd/logger.h selfdrive/loggerd/loggerd.cc +selfdrive/loggerd/loggerd.h +selfdrive/loggerd/main.cc selfdrive/loggerd/bootlog.cc selfdrive/loggerd/raw_logger.cc selfdrive/loggerd/raw_logger.h @@ -366,8 +367,6 @@ selfdrive/camerad/snapshot/* selfdrive/camerad/include/* selfdrive/camerad/cameras/camera_common.h selfdrive/camerad/cameras/camera_common.cc -selfdrive/camerad/cameras/camera_frame_stream.cc -selfdrive/camerad/cameras/camera_frame_stream.h selfdrive/camerad/cameras/camera_qcom.cc selfdrive/camerad/cameras/camera_qcom.h selfdrive/camerad/cameras/camera_replay.cc diff --git a/release/files_tici b/release/files_tici index 29c8353b81..59cc41918f 100644 --- a/release/files_tici +++ b/release/files_tici @@ -4,7 +4,6 @@ selfdrive/timezoned.py selfdrive/assets/navigation/* selfdrive/assets/training_wide/* -selfdrive/assets/sounds_tici/* selfdrive/camerad/cameras/camera_qcom2.cc selfdrive/camerad/cameras/camera_qcom2.h diff --git a/release/verify.sh b/release/verify.sh new file mode 100755 index 0000000000..2ebd50a29d --- /dev/null +++ b/release/verify.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +RED="\033[0;31m" +GREEN="\033[0;32m" +CLEAR="\033[0m" + +BRANCHES="devel dashcam dashcam3 release2 release3" +for b in $BRANCHES; do + if git diff --quiet origin/$b origin/$b-staging && [ "$(git rev-parse origin/$b)" = "$(git rev-parse origin/$b-staging)" ]; then + printf "%-10s $GREEN ok $CLEAR\n" "$b" + else + printf "%-10s $RED mismatch $CLEAR\n" "$b" + fi +done diff --git a/scripts/apply-pr.sh b/scripts/apply-pr.sh new file mode 100755 index 0000000000..65805b8485 --- /dev/null +++ b/scripts/apply-pr.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ $# -eq 0 ]; then + echo "usage: $0 " + exit 1 +fi + +BASE="https://github.com/commaai/openpilot/pull/" +PR_NUM="$(echo $1 | grep -o -E '[0-9]+')" + +curl -L $BASE/$PR_NUM.patch | git apply diff --git a/selfdrive/assets/offroad/fcc.html b/selfdrive/assets/offroad/fcc.html index 7f03a72046..793bea533c 100644 --- a/selfdrive/assets/offroad/fcc.html +++ b/selfdrive/assets/offroad/fcc.html @@ -11,7 +11,7 @@

FCC ID: 2AOHHTURBOXSOMD845

Quectel/EG25-G
-

FCC ID: XMR201903EG25GM

+

FCC ID: XMR201903EG25G

This device complies with Part 15 of the FCC Rules. Operation is subject to the following two conditions: diff --git a/selfdrive/assets/sounds/disengage.wav b/selfdrive/assets/sounds/disengage.wav new file mode 100644 index 0000000000..9f4a16e627 Binary files /dev/null and b/selfdrive/assets/sounds/disengage.wav differ diff --git a/selfdrive/assets/sounds/disengaged.wav b/selfdrive/assets/sounds/disengaged.wav deleted file mode 100644 index 3aa8e51e69..0000000000 Binary files a/selfdrive/assets/sounds/disengaged.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/engage.wav b/selfdrive/assets/sounds/engage.wav new file mode 100644 index 0000000000..ff094bfb01 Binary files /dev/null and b/selfdrive/assets/sounds/engage.wav differ diff --git a/selfdrive/assets/sounds/engaged.wav b/selfdrive/assets/sounds/engaged.wav deleted file mode 100644 index 1451f937f3..0000000000 Binary files a/selfdrive/assets/sounds/engaged.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/error.wav b/selfdrive/assets/sounds/error.wav deleted file mode 100644 index e805181aeb..0000000000 Binary files a/selfdrive/assets/sounds/error.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/prompt.wav b/selfdrive/assets/sounds/prompt.wav new file mode 100644 index 0000000000..f35e66e144 Binary files /dev/null and b/selfdrive/assets/sounds/prompt.wav differ diff --git a/selfdrive/assets/sounds/prompt_distracted.wav b/selfdrive/assets/sounds/prompt_distracted.wav new file mode 100644 index 0000000000..557be172aa Binary files /dev/null and b/selfdrive/assets/sounds/prompt_distracted.wav differ diff --git a/selfdrive/assets/sounds/refuse.wav b/selfdrive/assets/sounds/refuse.wav new file mode 100644 index 0000000000..a21bb1a36e Binary files /dev/null and b/selfdrive/assets/sounds/refuse.wav differ diff --git a/selfdrive/assets/sounds/warning_1.wav b/selfdrive/assets/sounds/warning_1.wav deleted file mode 100644 index 43ca74cc5c..0000000000 Binary files a/selfdrive/assets/sounds/warning_1.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/warning_2.wav b/selfdrive/assets/sounds/warning_2.wav deleted file mode 100644 index 4909f11982..0000000000 Binary files a/selfdrive/assets/sounds/warning_2.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/warning_immediate.wav b/selfdrive/assets/sounds/warning_immediate.wav new file mode 100644 index 0000000000..b93f2cda05 Binary files /dev/null and b/selfdrive/assets/sounds/warning_immediate.wav differ diff --git a/selfdrive/assets/sounds/warning_repeat.wav b/selfdrive/assets/sounds/warning_repeat.wav deleted file mode 100644 index 9f6f672e28..0000000000 Binary files a/selfdrive/assets/sounds/warning_repeat.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/warning_soft.wav b/selfdrive/assets/sounds/warning_soft.wav new file mode 100644 index 0000000000..3651366bb1 Binary files /dev/null and b/selfdrive/assets/sounds/warning_soft.wav differ diff --git a/selfdrive/assets/sounds_tici/disengaged.wav b/selfdrive/assets/sounds_tici/disengaged.wav deleted file mode 100644 index 182adffa2c..0000000000 Binary files a/selfdrive/assets/sounds_tici/disengaged.wav and /dev/null differ diff --git a/selfdrive/assets/sounds_tici/engaged.wav b/selfdrive/assets/sounds_tici/engaged.wav deleted file mode 100644 index 5c4c8b4d1d..0000000000 Binary files a/selfdrive/assets/sounds_tici/engaged.wav and /dev/null differ diff --git a/selfdrive/assets/sounds_tici/error.wav b/selfdrive/assets/sounds_tici/error.wav deleted file mode 100644 index c4e280e9a6..0000000000 Binary files a/selfdrive/assets/sounds_tici/error.wav and /dev/null differ diff --git a/selfdrive/assets/sounds_tici/warning_1.wav b/selfdrive/assets/sounds_tici/warning_1.wav deleted file mode 100644 index c937b3be06..0000000000 Binary files a/selfdrive/assets/sounds_tici/warning_1.wav and /dev/null differ diff --git a/selfdrive/assets/sounds_tici/warning_2.wav b/selfdrive/assets/sounds_tici/warning_2.wav deleted file mode 100644 index 49188db881..0000000000 Binary files a/selfdrive/assets/sounds_tici/warning_2.wav and /dev/null differ diff --git a/selfdrive/assets/sounds_tici/warning_repeat.wav b/selfdrive/assets/sounds_tici/warning_repeat.wav deleted file mode 100644 index bb3bfd0d54..0000000000 Binary files a/selfdrive/assets/sounds_tici/warning_repeat.wav and /dev/null differ diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 779045bf97..5e462a42f3 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -30,7 +30,7 @@ from selfdrive.hardware import HARDWARE, PC from selfdrive.loggerd.config import ROOT from selfdrive.loggerd.xattr_cache import getxattr, setxattr from selfdrive.swaglog import cloudlog, SWAGLOG_DIR -from selfdrive.version import version, get_version, get_git_remote, get_git_branch, get_git_commit +from selfdrive.version import get_version, get_origin, get_short_branch, get_commit ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai') HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) @@ -176,9 +176,9 @@ def getMessage(service=None, timeout=1000): def getVersion(): return { "version": get_version(), - "remote": get_git_remote(), - "branch": get_git_branch(), - "commit": get_git_commit(), + "remote": get_origin(), + "branch": get_short_branch(), + "commit": get_commit(), } @@ -551,7 +551,7 @@ def main(): except socket.timeout: try: r = requests.get("http://api.commadotai.com/v1/me", allow_redirects=False, - headers={"User-Agent": f"openpilot-{version}"}, timeout=15.0) + headers={"User-Agent": f"openpilot-{get_version()}"}, timeout=15.0) if r.status_code == 302 and r.headers['Location'].startswith("http://u.web2go.com"): params.put_bool("PrimeRedirected", True) except Exception: diff --git a/selfdrive/athena/manage_athenad.py b/selfdrive/athena/manage_athenad.py index 7bb717e078..fa95eacd8e 100755 --- a/selfdrive/athena/manage_athenad.py +++ b/selfdrive/athena/manage_athenad.py @@ -6,7 +6,7 @@ from multiprocessing import Process from common.params import Params from selfdrive.manager.process import launcher from selfdrive.swaglog import cloudlog -from selfdrive.version import version, dirty +from selfdrive.version import get_version, get_dirty ATHENA_MGR_PID_PARAM = "AthenadPid" @@ -14,7 +14,7 @@ ATHENA_MGR_PID_PARAM = "AthenadPid" def main(): params = Params() dongle_id = params.get("DongleId").decode('utf-8') - cloudlog.bind_global(dongle_id=dongle_id, version=version, dirty=dirty) + cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=get_dirty()) try: while 1: diff --git a/selfdrive/boardd/.gitignore b/selfdrive/boardd/.gitignore index 1f653bde8f..e8daa2ef27 100644 --- a/selfdrive/boardd/.gitignore +++ b/selfdrive/boardd/.gitignore @@ -1,2 +1,3 @@ boardd boardd_api_impl.cpp +tests/test_boardd_usbprotocol diff --git a/selfdrive/boardd/SConscript b/selfdrive/boardd/SConscript index f2a1f3f7bd..07ded56e1b 100644 --- a/selfdrive/boardd/SConscript +++ b/selfdrive/boardd/SConscript @@ -1,6 +1,9 @@ Import('env', 'envCython', 'common', 'cereal', 'messaging') -env.Program('boardd', ['boardd.cc', 'panda.cc', 'pigeon.cc'], LIBS=['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj']) +libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'] +env.Program('boardd', ['boardd.cc', 'panda.cc', 'pigeon.cc'], LIBS=libs) env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) +if GetOption('test'): + env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc'], LIBS=libs) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index f9c4891d03..3f66efc9fe 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -62,7 +62,7 @@ std::atomic pigeon_active(false); ExitHandler do_exit; -std::string get_time_str(const struct tm &time) { +static std::string get_time_str(const struct tm &time) { char s[30] = {'\0'}; std::strftime(s, std::size(s), "%Y-%m-%d %H:%M:%S", &time); return s; @@ -70,11 +70,43 @@ std::string get_time_str(const struct tm &time) { bool check_all_connected(const std::vector &pandas) { for (const auto& panda : pandas) { - if (!panda->connected) return false; + if (!panda->connected) { + do_exit = true; + return false; + } } return true; } +enum class SyncTimeDir { TO_PANDA, FROM_PANDA }; + +void sync_time(Panda *panda, SyncTimeDir dir) { + if (!panda->has_rtc) return; + + setenv("TZ", "UTC", 1); + struct tm sys_time = util::get_time(); + struct tm rtc_time = panda->get_rtc(); + + if (dir == SyncTimeDir::TO_PANDA) { + if (util::time_valid(sys_time)) { + // Write time to RTC if it looks reasonable + double seconds = difftime(mktime(&rtc_time), mktime(&sys_time)); + if (std::abs(seconds) > 1.1) { + panda->set_rtc(sys_time); + LOGW("Updating panda RTC. dt = %.2f System: %s RTC: %s", + seconds, get_time_str(sys_time).c_str(), get_time_str(rtc_time).c_str()); + } + } + } else if (dir == SyncTimeDir::FROM_PANDA) { + if (!util::time_valid(sys_time) && util::time_valid(rtc_time)) { + const struct timeval tv = {mktime(&rtc_time), 0}; + settimeofday(&tv, 0); + LOGE("System time wrong, setting from RTC. System: %s RTC: %s", + get_time_str(sys_time).c_str(), get_time_str(rtc_time).c_str()); + } + } +} + bool safety_setter_thread(std::vector pandas) { LOGD("Starting safety setter thread"); @@ -168,19 +200,7 @@ Panda *usb_connect(std::string serial="", uint32_t index=0) { std::call_once(connected_once, &Panda::set_usb_power_mode, panda, cereal::PeripheralState::UsbPowerMode::CDP); #endif - if (panda->has_rtc) { - setenv("TZ","UTC",1); - struct tm sys_time = util::get_time(); - struct tm rtc_time = panda->get_rtc(); - - if (!util::time_valid(sys_time) && util::time_valid(rtc_time)) { - LOGE("System time wrong, setting from RTC. System: %s RTC: %s", - get_time_str(sys_time).c_str(), get_time_str(rtc_time).c_str()); - const struct timeval tv = {mktime(&rtc_time), 0}; - settimeofday(&tv, 0); - } - } - + sync_time(panda.get(), SyncTimeDir::FROM_PANDA); return panda.release(); } @@ -194,14 +214,8 @@ void can_send_thread(std::vector pandas, bool fake_send) { subscriber->setTimeout(100); // run as fast as messages come in - while (!do_exit) { - if (!check_all_connected(pandas)) { - do_exit = true; - break; - } - + while (!do_exit && check_all_connected(pandas)) { Message * msg = subscriber->receive(); - if (!msg) { if (errno == EINTR) { do_exit = true; @@ -239,12 +253,7 @@ void can_recv_thread(std::vector pandas) { uint64_t next_frame_time = nanos_since_boot() + dt; std::vector raw_can_data; - while (!do_exit) { - if (!check_all_connected(pandas)){ - do_exit = true; - break; - } - + while (!do_exit && check_all_connected(pandas)) { bool comms_healthy = true; raw_can_data.clear(); for (const auto& panda : pandas) { @@ -315,7 +324,7 @@ bool send_panda_states(PubMaster *pm, const std::vector &pandas, bool s for (uint32_t i=0; i pandas, bool spoofin LOGD("start panda state thread"); // run at 2hz - while (!do_exit) { - if(!check_all_connected(pandas)) { - do_exit = true; - break; - } - + while (!do_exit && check_all_connected(pandas)) { + // send out peripheralState send_peripheral_state(pm, peripheral_panda); ignition = send_panda_states(pm, pandas, spoofing_started); @@ -433,7 +438,7 @@ void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofin break; } - // clear VIN, CarParams, and set new safety on car start + // clear ignition-based params and set new safety on car start if (ignition && !ignition_last) { params.clearAll(CLEAR_ON_IGNITION_ON); if (!safety_future.valid() || safety_future.wait_for(0ms) == std::future_status::ready) { @@ -528,21 +533,8 @@ void peripheral_control_thread(Panda *panda) { } // Write to rtc once per minute when no ignition present - if ((panda->has_rtc) && !ignition && (cnt % 120 == 1)) { - // Write time to RTC if it looks reasonable - setenv("TZ","UTC",1); - struct tm sys_time = util::get_time(); - - if (util::time_valid(sys_time)) { - struct tm rtc_time = panda->get_rtc(); - double seconds = difftime(mktime(&rtc_time), mktime(&sys_time)); - - if (std::abs(seconds) > 1.1) { - panda->set_rtc(sys_time); - LOGW("Updating panda RTC. dt = %.2f System: %s RTC: %s", - seconds, get_time_str(sys_time).c_str(), get_time_str(rtc_time).c_str()); - } - } + if (!ignition && (cnt % 120 == 1)) { + sync_time(panda, SyncTimeDir::TO_PANDA); } } } diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index 78b123f698..9c59bba6fc 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -363,7 +363,8 @@ uint8_t Panda::len_to_dlc(uint8_t len) { } } -void Panda::can_send(capnp::List::Reader can_data_list) { +void Panda::pack_can_buffer(const capnp::List::Reader &can_data_list, + std::function write_func) { if (send.size() < (can_data_list.size() * CANPACKET_MAX_SIZE)) { send.resize(can_data_list.size() * CANPACKET_MAX_SIZE); } @@ -383,7 +384,7 @@ void Panda::can_send(capnp::List::Reader can_data_list) { } auto can_data = cmsg.getDat(); uint8_t data_len_code = len_to_dlc(can_data.size()); - assert(can_data.size() <= (hw_type == cereal::PandaState::PandaType::RED_PANDA) ? 64 : 8); + assert(can_data.size() <= ((hw_type == cereal::PandaState::PandaType::RED_PANDA) ? 64 : 8)); assert(can_data.size() == dlc_to_len[data_len_code]); can_header header; @@ -410,67 +411,58 @@ void Panda::can_send(capnp::List::Reader can_data_list) { ptr += copy_size + 1; counter++; } - usb_bulk_write(3, to_write, ptr, 5); + write_func(to_write, ptr); } } } +void Panda::can_send(capnp::List::Reader can_data_list) { + pack_can_buffer(can_data_list, [=](uint8_t* data, size_t size) { + usb_bulk_write(3, data, size, 5); + }); +} + bool Panda::can_receive(std::vector& out_vec) { uint8_t data[RECV_SIZE]; int recv = usb_bulk_read(0x81, (uint8_t*)data, RECV_SIZE); - - // Not sure if this can happen - if (recv < 0) recv = 0; - - if (recv == RECV_SIZE) { - LOGW("Receive buffer full"); - } - if (!comms_healthy) { return false; } + if (recv == RECV_SIZE) { + LOGW("Panda receive buffer full"); + } - static uint8_t tail[CANPACKET_MAX_SIZE]; - uint8_t tail_size = 0; - uint8_t counter = 0; - for (int i = 0; i < recv; i += USBPACKET_MAX_SIZE) { - // Check for counter every 64 bytes (length of USB packet) - if (counter != data[i]) { + return (recv <= 0) ? true : unpack_can_buffer(data, recv, out_vec); +} + +bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec) { + recv_buf.clear(); + for (int i = 0; i < size; i += USBPACKET_MAX_SIZE) { + if (data[i] != i / USBPACKET_MAX_SIZE) { LOGE("CAN: MALFORMED USB RECV PACKET"); - break; - } - counter++; - uint8_t chunk_len = ((recv - i) > USBPACKET_MAX_SIZE) ? 63 : (recv - i - 1); // as 1 is always reserved for counter - uint8_t chunk[USBPACKET_MAX_SIZE + CANPACKET_MAX_SIZE]; - memcpy(chunk, tail, tail_size); - memcpy(&chunk[tail_size], &data[i+1], chunk_len); - chunk_len += tail_size; - tail_size = 0; - uint8_t pos = 0; - while (pos < chunk_len) { - uint8_t data_len = dlc_to_len[(chunk[pos] >> 4)]; - uint8_t pckt_len = CANPACKET_HEAD_SIZE + data_len; - if (pckt_len <= (chunk_len - pos)) { - can_header header; - memcpy(&header, &chunk[pos], CANPACKET_HEAD_SIZE); - - can_frame &canData = out_vec.emplace_back(); - canData.busTime = 0; - canData.address = header.addr; - canData.src = header.bus + bus_offset; - - if (header.rejected) { canData.src += CANPACKET_REJECTED; } - if (header.returned) { canData.src += CANPACKET_RETURNED; } - canData.dat.assign((char*)&chunk[pos+CANPACKET_HEAD_SIZE], data_len); - - pos += pckt_len; - } else { - // Keep partial CAN packet until next USB packet - tail_size = (chunk_len - pos); - memcpy(tail, &chunk[pos], tail_size); - break; - } + comms_healthy = false; + return false; } + int chunk_len = std::min(USBPACKET_MAX_SIZE, (size - i)); + recv_buf.insert(recv_buf.end(), &data[i + 1], &data[i + chunk_len]); + } + + int pos = 0; + while (pos < recv_buf.size()) { + can_header header; + memcpy(&header, &recv_buf[pos], CANPACKET_HEAD_SIZE); + + can_frame &canData = out_vec.emplace_back(); + canData.busTime = 0; + canData.address = header.addr; + canData.src = header.bus + bus_offset; + if (header.rejected) { canData.src += CANPACKET_REJECTED; } + if (header.returned) { canData.src += CANPACKET_RETURNED; } + + const uint8_t data_len = dlc_to_len[header.data_len_code]; + canData.dat.assign((char *)&recv_buf[pos + CANPACKET_HEAD_SIZE], data_len); + + pos += CANPACKET_HEAD_SIZE + data_len; } return true; } diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index 4be9454cf5..9ac75c968a 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -17,7 +18,7 @@ #define PANDA_BUS_CNT 4 #define RECV_SIZE (0x4000U) #define USB_TX_SOFT_LIMIT (0x100U) -#define USBPACKET_MAX_SIZE (0x40U) +#define USBPACKET_MAX_SIZE (0x40) #define CANPACKET_HEAD_SIZE 5U #define CANPACKET_MAX_SIZE 72U #define CANPACKET_REJECTED (0xC0U) @@ -69,6 +70,7 @@ class Panda { libusb_device_handle *dev_handle = NULL; std::mutex usb_lock; std::vector send; + std::vector recv_buf; void handle_usb_issue(int err, const char func[]); void cleanup(); @@ -113,4 +115,11 @@ class Panda { uint8_t len_to_dlc(uint8_t len); void can_send(capnp::List::Reader can_data_list); bool can_receive(std::vector& out_vec); + +protected: + // for unit tests + Panda(uint32_t bus_offset) : bus_offset(bus_offset) {} + void pack_can_buffer(const capnp::List::Reader &can_data_list, + std::function write_func); + bool unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec); }; diff --git a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc new file mode 100644 index 0000000000..6a13cbd71f --- /dev/null +++ b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc @@ -0,0 +1,130 @@ +#define CATCH_CONFIG_MAIN +#define CATCH_CONFIG_ENABLE_BENCHMARKING +#include + +#include "catch2/catch.hpp" +#include "cereal/messaging/messaging.h" +#include "selfdrive/boardd/panda.h" + +const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U}; + +int random_int(int min, int max) { + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_int_distribution dist(min, max); + return dist(rng); +} + +struct PandaTest : public Panda { + PandaTest(uint32_t bus_offset, int can_list_size, cereal::PandaState::PandaType hw_type); + void test_can_send(); + void test_can_recv(); + + std::map test_data; + int can_list_size = 0; + int total_pakets_size = 0; + MessageBuilder msg; + capnp::List::Reader can_data_list; +}; + +PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState::PandaType hw_type) : can_list_size(can_list_size), Panda(bus_offset_) { + this->hw_type = hw_type; + int data_limit = ((hw_type == cereal::PandaState::PandaType::RED_PANDA) ? std::size(dlc_to_len) : 8); + // prepare test data + for (int i = 0; i < data_limit; ++i) { + std::random_device rd; + std::independent_bits_engine rbe(rd()); + + int data_len = dlc_to_len[i]; + std::string bytes(data_len, '\0'); + std::generate(bytes.begin(), bytes.end(), std::ref(rbe)); + test_data[data_len] = bytes; + } + + // generate can messages for this panda + auto can_list = msg.initEvent().initSendcan(can_list_size); + for (uint8_t i = 0; i < can_list_size; ++i) { + auto can = can_list[i]; + uint32_t id = random_int(0, std::size(dlc_to_len) - 1); + const std::string &dat = test_data[dlc_to_len[id]]; + can.setAddress(i); + can.setSrc(random_int(0, 3) + bus_offset); + can.setDat(kj::ArrayPtr((uint8_t *)dat.data(), dat.size())); + total_pakets_size += CANPACKET_HEAD_SIZE + dat.size(); + } + + can_data_list = can_list.asReader(); + INFO("test " << can_list_size << " packets, total size " << total_pakets_size); +} + +void PandaTest::test_can_send() { + std::vector unpacked_data; + this->pack_can_buffer(can_data_list, [&](uint8_t *chunk, size_t size) { + int size_left = size; + for (int i = 0, counter = 0; i < size; i += USBPACKET_MAX_SIZE, counter++) { + REQUIRE(chunk[i] == counter); + + const int len = std::min(USBPACKET_MAX_SIZE, size_left); + unpacked_data.insert(unpacked_data.end(), &chunk[i + 1], &chunk[i + len]); + size_left -= len; + } + }); + REQUIRE(unpacked_data.size() == total_pakets_size); + + int cnt = 0; + INFO("test can message integrity"); + for (int pos = 0, pckt_len = 0; pos < unpacked_data.size(); pos += pckt_len) { + can_header header; + memcpy(&header, &unpacked_data[pos], CANPACKET_HEAD_SIZE); + const uint8_t data_len = dlc_to_len[header.data_len_code]; + pckt_len = CANPACKET_HEAD_SIZE + data_len; + + REQUIRE(header.addr == cnt); + REQUIRE(test_data.find(data_len) != test_data.end()); + const std::string &dat = test_data[data_len]; + REQUIRE(memcmp(dat.data(), &unpacked_data[pos + 5], dat.size()) == 0); + ++cnt; + } + REQUIRE(cnt == can_list_size); +} + +void PandaTest::test_can_recv() { + std::vector frames; + this->pack_can_buffer(can_data_list, [&](uint8_t *data, size_t size) { + this->unpack_can_buffer(data, size, frames); + }); + + REQUIRE(frames.size() == can_list_size); + for (int i = 0; i < frames.size(); ++i) { + REQUIRE(frames[i].address == i); + REQUIRE(test_data.find(frames[i].dat.size()) != test_data.end()); + const std::string &dat = test_data[frames[i].dat.size()]; + REQUIRE(memcmp(dat.data(), frames[i].dat.data(), dat.size()) == 0); + } +} + +TEST_CASE("send/recv CAN 2.0 packets") { + auto bus_offset = GENERATE(0, 4); + auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); + PandaTest test(bus_offset, can_list_size, cereal::PandaState::PandaType::DOS); + + SECTION("can_send") { + test.test_can_send(); + } + SECTION("can_receive") { + test.test_can_recv(); + } +} + +TEST_CASE("send/recv CAN FD packets") { + auto bus_offset = GENERATE(0, 4); + auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); + PandaTest test(bus_offset, can_list_size, cereal::PandaState::PandaType::RED_PANDA); + + SECTION("can_send") { + test.test_can_send(); + } + SECTION("can_receive") { + test.test_can_recv(); + } +} diff --git a/selfdrive/camerad/SConscript b/selfdrive/camerad/SConscript index d018704d60..85bc756bc1 100644 --- a/selfdrive/camerad/SConscript +++ b/selfdrive/camerad/SConscript @@ -1,4 +1,4 @@ -Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'USE_WEBCAM', 'USE_FRAME_STREAM') +Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'USE_WEBCAM') libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon] @@ -18,15 +18,12 @@ else: env.Append(CFLAGS = '-DWEBCAM') env.Append(CPPPATH = ['/usr/include/opencv4', '/usr/local/include/opencv4']) else: - if USE_FRAME_STREAM: - cameras = ['cameras/camera_frame_stream.cc'] - else: - libs += ['avutil', 'avcodec', 'avformat', 'swscale', 'bz2', 'ssl', 'curl', 'crypto'] - # TODO: import replay_lib from root SConstruct - cameras = ['cameras/camera_replay.cc', - env.Object('camera-util', '#/selfdrive/ui/replay/util.cc'), - env.Object('camera-framereader', '#/selfdrive/ui/replay/framereader.cc'), - env.Object('camera-filereader', '#/selfdrive/ui/replay/filereader.cc')] + libs += ['avutil', 'avcodec', 'avformat', 'bz2', 'ssl', 'curl', 'crypto'] + # TODO: import replay_lib from root SConstruct + cameras = ['cameras/camera_replay.cc', + env.Object('camera-util', '#/selfdrive/ui/replay/util.cc'), + env.Object('camera-framereader', '#/selfdrive/ui/replay/framereader.cc'), + env.Object('camera-filereader', '#/selfdrive/ui/replay/filereader.cc')] if arch == "Darwin": del libs[libs.index('OpenCL')] diff --git a/selfdrive/camerad/cameras/camera_common.cc b/selfdrive/camerad/cameras/camera_common.cc index 4db6eaa121..b901dc39d3 100644 --- a/selfdrive/camerad/cameras/camera_common.cc +++ b/selfdrive/camerad/cameras/camera_common.cc @@ -28,8 +28,6 @@ #include "selfdrive/camerad/cameras/camera_replay.h" #endif -const int YUV_COUNT = 100; - class Debayer { public: Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s) { @@ -109,7 +107,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, vipc_server->create_buffers(rgb_type, UI_BUF_COUNT, true, rgb_width, rgb_height); rgb_stride = vipc_server->get_buffer(rgb_type)->stride; - vipc_server->create_buffers(yuv_type, YUV_COUNT, false, rgb_width, rgb_height); + vipc_server->create_buffers(yuv_type, YUV_BUFFER_COUNT, false, rgb_width, rgb_height); if (ci->bayer) { debayer = new Debayer(device_id, context, this, s); diff --git a/selfdrive/camerad/cameras/camera_common.h b/selfdrive/camerad/cameras/camera_common.h index e994fc2208..f55bbd8401 100644 --- a/selfdrive/camerad/cameras/camera_common.h +++ b/selfdrive/camerad/cameras/camera_common.h @@ -14,6 +14,7 @@ #include "selfdrive/common/queue.h" #include "selfdrive/common/swaglog.h" #include "selfdrive/common/visionimg.h" +#include "selfdrive/hardware/hw.h" #define CAMERA_ID_IMX298 0 #define CAMERA_ID_IMX179 1 @@ -26,7 +27,9 @@ #define CAMERA_ID_AR0231 8 #define CAMERA_ID_MAX 9 -#define UI_BUF_COUNT 4 +const int UI_BUF_COUNT = 4; +const int YUV_BUFFER_COUNT = Hardware::EON() ? 100 : 40; + enum CameraType { RoadCam = 0, diff --git a/selfdrive/camerad/cameras/camera_frame_stream.cc b/selfdrive/camerad/cameras/camera_frame_stream.cc deleted file mode 100644 index 8e6c69e361..0000000000 --- a/selfdrive/camerad/cameras/camera_frame_stream.cc +++ /dev/null @@ -1,92 +0,0 @@ -#include "selfdrive/camerad/cameras/camera_frame_stream.h" - -#include -#include - -#include - -#include "cereal/messaging/messaging.h" -#include "selfdrive/common/util.h" - -#define FRAME_WIDTH 1164 -#define FRAME_HEIGHT 874 - -extern ExitHandler do_exit; - -namespace { - -// TODO: make this more generic -CameraInfo cameras_supported[CAMERA_ID_MAX] = { - [CAMERA_ID_IMX298] = { - .frame_width = FRAME_WIDTH, - .frame_height = FRAME_HEIGHT, - .frame_stride = FRAME_WIDTH*3, - .bayer = false, - .bayer_flip = false, - }, - [CAMERA_ID_OV8865] = { - .frame_width = 1632, - .frame_height = 1224, - .frame_stride = 2040, // seems right - .bayer = false, - .bayer_flip = 3, - .hdr = false - }, -}; - -void camera_init(VisionIpcServer * v, CameraState *s, int camera_id, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type) { - assert(camera_id < std::size(cameras_supported)); - s->ci = cameras_supported[camera_id]; - assert(s->ci.frame_width != 0); - - s->camera_num = camera_id; - s->fps = fps; - s->buf.init(device_id, ctx, s, v, FRAME_BUF_COUNT, rgb_type, yuv_type); -} - -void run_frame_stream(CameraState &camera, const char* frame_pkt) { - SubMaster sm({frame_pkt}); - - size_t buf_idx = 0; - while (!do_exit) { - sm.update(1000); - if(sm.updated(frame_pkt)) { - auto msg = static_cast(sm[frame_pkt]); - auto frame = msg.get(frame_pkt).as(); - camera.buf.camera_bufs_metadata[buf_idx] = { - .frame_id = frame.get("frameId").as(), - .timestamp_eof = frame.get("timestampEof").as(), - .timestamp_sof = frame.get("timestampSof").as(), - }; - - cl_command_queue q = camera.buf.camera_bufs[buf_idx].copy_q; - cl_mem yuv_cl = camera.buf.camera_bufs[buf_idx].buf_cl; - - auto image = frame.get("image").as(); - clEnqueueWriteBuffer(q, yuv_cl, CL_TRUE, 0, image.size(), image.begin(), 0, NULL, NULL); - camera.buf.queue(buf_idx); - buf_idx = (buf_idx + 1) % FRAME_BUF_COUNT; - } - } -} - -} // namespace - -void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { - camera_init(v, &s->road_cam, CAMERA_ID_IMX298, 20, device_id, ctx, - VISION_STREAM_RGB_BACK, VISION_STREAM_YUV_BACK); - camera_init(v, &s->driver_cam, CAMERA_ID_OV8865, 10, device_id, ctx, - VISION_STREAM_RGB_FRONT, VISION_STREAM_YUV_FRONT); -} - -void cameras_open(MultiCameraState *s) {} -void cameras_close(MultiCameraState *s) {} -void camera_autoexposure(CameraState *s, float grey_frac) {} -void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) {} - -void cameras_run(MultiCameraState *s) { - std::thread t = start_process_thread(s, &s->road_cam, process_road_camera); - set_thread_name("frame_streaming"); - run_frame_stream(s->road_cam, "roadCameraState"); - t.join(); -} diff --git a/selfdrive/camerad/cameras/camera_frame_stream.h b/selfdrive/camerad/cameras/camera_frame_stream.h deleted file mode 100644 index 0d4d8cfb42..0000000000 --- a/selfdrive/camerad/cameras/camera_frame_stream.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "camera_common.h" - -#define FRAME_BUF_COUNT 16 - -typedef struct CameraState { - int camera_num; - CameraInfo ci; - - int fps; - float digital_gain; - - CameraBuf buf; -} CameraState; - -typedef struct MultiCameraState { - CameraState road_cam; - CameraState driver_cam; - - SubMaster *sm; - PubMaster *pm; -} MultiCameraState; diff --git a/selfdrive/camerad/cameras/camera_qcom.cc b/selfdrive/camerad/cameras/camera_qcom.cc index 33b5d00c95..11383de668 100644 --- a/selfdrive/camerad/cameras/camera_qcom.cc +++ b/selfdrive/camerad/cameras/camera_qcom.cc @@ -211,12 +211,12 @@ void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_i /*fps*/ 20, #endif device_id, ctx, - VISION_STREAM_RGB_BACK, VISION_STREAM_YUV_BACK); + VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD); camera_init(v, &s->driver_cam, CAMERA_ID_OV8865, 1, /*pixel_clock=*/72000000, /*line_length_pclk=*/1602, /*max_gain=*/510, 10, device_id, ctx, - VISION_STREAM_RGB_FRONT, VISION_STREAM_YUV_FRONT); + VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER); s->sm = new SubMaster({"driverState"}); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "thumbnail"}); diff --git a/selfdrive/camerad/cameras/camera_qcom2.cc b/selfdrive/camerad/cameras/camera_qcom2.cc index 03138e353a..3ea065ae73 100644 --- a/selfdrive/camerad/cameras/camera_qcom2.cc +++ b/selfdrive/camerad/cameras/camera_qcom2.cc @@ -709,13 +709,13 @@ static void camera_open(CameraState *s) { void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { camera_init(s, v, &s->road_cam, CAMERA_ID_AR0231, 1, 20, device_id, ctx, - VISION_STREAM_RGB_BACK, VISION_STREAM_YUV_BACK); // swap left/right + VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD); // swap left/right printf("road camera initted \n"); camera_init(s, v, &s->wide_road_cam, CAMERA_ID_AR0231, 0, 20, device_id, ctx, - VISION_STREAM_RGB_WIDE, VISION_STREAM_YUV_WIDE); + VISION_STREAM_RGB_WIDE, VISION_STREAM_WIDE_ROAD); printf("wide road camera initted \n"); camera_init(s, v, &s->driver_cam, CAMERA_ID_AR0231, 2, 20, device_id, ctx, - VISION_STREAM_RGB_FRONT, VISION_STREAM_YUV_FRONT); + VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER); printf("driver camera initted \n"); s->sm = new SubMaster({"driverState"}); diff --git a/selfdrive/camerad/cameras/camera_replay.cc b/selfdrive/camerad/cameras/camera_replay.cc index 6bc53d8c94..8ff1b6aacc 100644 --- a/selfdrive/camerad/cameras/camera_replay.cc +++ b/selfdrive/camerad/cameras/camera_replay.cc @@ -23,7 +23,7 @@ std::string get_url(std::string route_name, const std::string &camera, int segme } void camera_init(VisionIpcServer *v, CameraState *s, int camera_id, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type, const std::string &url) { - s->frame = new FrameReader(true); + s->frame = new FrameReader(); if (!s->frame->load(url)) { printf("failed to load stream from %s", url.c_str()); assert(0); @@ -98,9 +98,9 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { camera_init(v, &s->road_cam, CAMERA_ID_LGC920, 20, device_id, ctx, - VISION_STREAM_RGB_BACK, VISION_STREAM_YUV_BACK, get_url(road_camera_route, "fcamera", 0)); + VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD, get_url(road_camera_route, "fcamera", 0)); // camera_init(v, &s->driver_cam, CAMERA_ID_LGC615, 10, device_id, ctx, - // VISION_STREAM_RGB_FRONT, VISION_STREAM_YUV_FRONT, get_url(driver_camera_route, "dcamera", 0)); + // VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER, get_url(driver_camera_route, "dcamera", 0)); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "thumbnail"}); } diff --git a/selfdrive/camerad/cameras/camera_webcam.cc b/selfdrive/camerad/cameras/camera_webcam.cc index ff63257bd3..47da56042c 100644 --- a/selfdrive/camerad/cameras/camera_webcam.cc +++ b/selfdrive/camerad/cameras/camera_webcam.cc @@ -141,9 +141,9 @@ void driver_camera_thread(CameraState *s) { void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { camera_init(v, &s->road_cam, CAMERA_ID_LGC920, 20, device_id, ctx, - VISION_STREAM_RGB_BACK, VISION_STREAM_YUV_BACK); + VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD); camera_init(v, &s->driver_cam, CAMERA_ID_LGC615, 10, device_id, ctx, - VISION_STREAM_RGB_FRONT, VISION_STREAM_YUV_FRONT); + VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "thumbnail"}); } diff --git a/selfdrive/camerad/test/get_thumbnails_for_segment.py b/selfdrive/camerad/test/get_thumbnails_for_segment.py index 2ffb4bf650..ab9a7b308d 100755 --- a/selfdrive/camerad/test/get_thumbnails_for_segment.py +++ b/selfdrive/camerad/test/get_thumbnails_for_segment.py @@ -20,9 +20,13 @@ if __name__ == "__main__": mkdirs_exists_ok(out_path) r = Route(args.route) - lr = list(LogReader(r.qlog_paths()[args.segment])) + path = r.log_paths()[args.segment] or r.qlog_paths()[args.segment] + lr = list(LogReader(path)) for msg in tqdm(lr): if msg.which() == 'thumbnail': with open(os.path.join(out_path, f"{msg.thumbnail.frameId}.jpg"), 'wb') as f: f.write(msg.thumbnail.thumbnail) + elif msg.which() == 'navThumbnail': + with open(os.path.join(out_path, f"nav_{msg.navThumbnail.frameId}.jpg"), 'wb') as f: + f.write(msg.navThumbnail.thumbnail) diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 980b207fa7..786b00fa2b 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -1,7 +1,7 @@ import os from common.params import Params from common.basedir import BASEDIR -from selfdrive.version import comma_remote, tested_branch +from selfdrive.version import get_comma_remote, get_tested_branch from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from selfdrive.car.vin import get_vin, VIN_UNKNOWN from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car @@ -14,7 +14,7 @@ EventName = car.CarEvent.EventName def get_startup_event(car_recognized, controller_available, fw_seen): - if comma_remote and tested_branch: + if get_comma_remote() and get_tested_branch(): event = EventName.startup else: event = EventName.startupMaster diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index ba85a89c3e..ab3125b011 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -31,10 +31,13 @@ class CarState(CarStateBase): ret.espDisabled = (cp.vl["TRACTION_BUTTON"]["TRACTION_OFF"] == 1) - ret.wheelSpeeds.fl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"] - ret.wheelSpeeds.rr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"] - ret.wheelSpeeds.rl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"] - ret.wheelSpeeds.fr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"] + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"], + unit=1, + ) ret.vEgoRaw = (cp.vl["SPEED_1"]["SPEED_LEFT"] + cp.vl["SPEED_1"]["SPEED_RIGHT"]) / 2. ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.standstill = not ret.vEgoRaw > 0.001 diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 100d472409..a621ab16ab 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -10,10 +10,14 @@ WHEEL_RADIUS = 0.33 class CarState(CarStateBase): def update(self, cp): ret = car.CarState.new_message() - ret.wheelSpeeds.rr = cp.vl["WheelSpeed_CG1"]["WhlRr_W_Meas"] * WHEEL_RADIUS - ret.wheelSpeeds.rl = cp.vl["WheelSpeed_CG1"]["WhlRl_W_Meas"] * WHEEL_RADIUS - ret.wheelSpeeds.fr = cp.vl["WheelSpeed_CG1"]["WhlFr_W_Meas"] * WHEEL_RADIUS - ret.wheelSpeeds.fl = cp.vl["WheelSpeed_CG1"]["WhlFl_W_Meas"] * WHEEL_RADIUS + + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WheelSpeed_CG1"]["WhlFl_W_Meas"], + cp.vl["WheelSpeed_CG1"]["WhlFr_W_Meas"], + cp.vl["WheelSpeed_CG1"]["WhlRl_W_Meas"], + cp.vl["WheelSpeed_CG1"]["WhlRr_W_Meas"], + unit=WHEEL_RADIUS, + ) ret.vEgoRaw = mean([ret.wheelSpeeds.rr, ret.wheelSpeeds.rl, ret.wheelSpeeds.fr, ret.wheelSpeeds.fl]) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.standstill = not ret.vEgoRaw > 0.001 diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 00f25c5fdb..e4d6448348 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -1,6 +1,5 @@ from cereal import car from common.numpy_fast import mean -from selfdrive.config import Conversions as CV from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from selfdrive.car.interfaces import CarStateBase @@ -21,10 +20,12 @@ class CarState(CarStateBase): self.prev_cruise_buttons = self.cruise_buttons self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"] - ret.wheelSpeeds.fl = pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = pt_cp.vl["EBCMWheelSpdFront"]["FRWheelSpd"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = pt_cp.vl["EBCMWheelSpdRear"]["RLWheelSpd"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = pt_cp.vl["EBCMWheelSpdRear"]["RRWheelSpd"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"], + pt_cp.vl["EBCMWheelSpdFront"]["FRWheelSpd"], + pt_cp.vl["EBCMWheelSpdRear"]["RLWheelSpd"], + pt_cp.vl["EBCMWheelSpdRear"]["RRWheelSpd"], + ) ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr]) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.standstill = ret.vEgoRaw < 0.01 diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index 8bf3ac58f6..65a9d8fd44 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -5,7 +5,7 @@ from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from selfdrive.config import Conversions as CV from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, SPEED_FACTOR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL +from selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL TransmissionType = car.CarParams.TransmissionType @@ -107,7 +107,7 @@ def get_can_signals(CP, gearbox_msg, main_on_sig_msg): else: checks += [("CRUISE_PARAMS", 50)] - if CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): + if CP.carFingerprint in (CAR.ACCORD, CAR.ACCORD_2021, CAR.ACCORDH, CAR.ACCORDH_2021, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): signals += [("DRIVERS_DOOR_OPEN", "SCM_FEEDBACK", 1)] elif CP.carFingerprint == CAR.ODYSSEY_CHN: signals += [("DRIVERS_DOOR_OPEN", "SCM_BUTTONS", 1)] @@ -185,7 +185,7 @@ class CarState(CarStateBase): # ******************* parse out can ******************* # TODO: find wheels moving bit in dbc - if self.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): + if self.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORD_2021, CAR.ACCORDH, CAR.ACCORDH_2021, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 0.1 ret.doorOpen = bool(cp.vl["SCM_FEEDBACK"]["DRIVERS_DOOR_OPEN"]) elif self.CP.carFingerprint == CAR.ODYSSEY_CHN: @@ -213,16 +213,17 @@ class CarState(CarStateBase): self.brake_error = cp.vl["STANDSTILL"]["BRAKE_ERROR_1"] or cp.vl["STANDSTILL"]["BRAKE_ERROR_2"] ret.espDisabled = cp.vl["VSA_STATUS"]["ESP_DISABLED"] != 0 - speed_factor = SPEED_FACTOR.get(self.CP.carFingerprint, 1.) - ret.wheelSpeeds.fl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"] * CV.KPH_TO_MS * speed_factor - ret.wheelSpeeds.fr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"] * CV.KPH_TO_MS * speed_factor - ret.wheelSpeeds.rl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"] * CV.KPH_TO_MS * speed_factor - ret.wheelSpeeds.rr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"] * CV.KPH_TO_MS * speed_factor - v_wheel = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr)/4. + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"], + ) + v_wheel = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4.0 # blend in transmission speed at low speed, since it has more low speed accuracy v_weight = interp(v_wheel, v_weight_bp, v_weight_v) - ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * speed_factor + v_weight * v_wheel + ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE"] @@ -235,7 +236,7 @@ class CarState(CarStateBase): 250, cp.vl["SCM_FEEDBACK"]["LEFT_BLINKER"], cp.vl["SCM_FEEDBACK"]["RIGHT_BLINKER"]) ret.brakeHoldActive = cp.vl["VSA_STATUS"]["BRAKE_HOLD_ACTIVE"] == 1 - if self.CP.carFingerprint in (CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN, CAR.CRV_5G, CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, + if self.CP.carFingerprint in (CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN, CAR.CRV_5G, CAR.ACCORD, CAR.ACCORD_2021, CAR.ACCORDH, CAR.ACCORDH_2021, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): self.park_brake = cp.vl["EPB_STATUS"]["EPB_STATE"] != 0 else: diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 802ce980ab..5a5aea9f92 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -110,7 +110,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 1. ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] - elif candidate in (CAR.ACCORD, CAR.ACCORDH): + elif candidate in (CAR.ACCORD, CAR.ACCORD_2021, CAR.ACCORDH, CAR.ACCORDH_2021): stop_and_go = True ret.mass = 3279. * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 2.83 @@ -143,6 +143,7 @@ class CarInterface(CarInterfaceBase): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end tire_stiffness_factor = 0.444 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] + ret.wheelSpeedFactor = 1.025 elif candidate == CAR.CRV_5G: stop_and_go = True @@ -160,6 +161,7 @@ class CarInterface(CarInterfaceBase): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.64], [0.192]] tire_stiffness_factor = 0.677 + ret.wheelSpeedFactor = 1.025 elif candidate == CAR.CRV_HYBRID: stop_and_go = True @@ -170,6 +172,7 @@ class CarInterface(CarInterfaceBase): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end tire_stiffness_factor = 0.677 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] + ret.wheelSpeedFactor = 1.025 elif candidate == CAR.FIT: stop_and_go = False @@ -201,6 +204,7 @@ class CarInterface(CarInterfaceBase): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] tire_stiffness_factor = 0.5 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.025]] + ret.wheelSpeedFactor = 1.025 elif candidate == CAR.ACURA_RDX: stop_and_go = False diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 97e0ded663..340509c563 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -59,6 +59,8 @@ VISUAL_HUD = { class CAR: ACCORD = "HONDA ACCORD 2018" ACCORDH = "HONDA ACCORD HYBRID 2018" + ACCORD_2021 = "HONDA ACCORD 2021" + ACCORDH_2021 = "HONDA ACCORD HYBRID 2021" CIVIC = "HONDA CIVIC 2016" CIVIC_BOSCH = "HONDA CIVIC (BOSCH) 2019" CIVIC_BOSCH_DIESEL = "HONDA CIVIC SEDAN 1.6 DIESEL 2019" @@ -268,12 +270,10 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x18dab5f1, None): [ b'36161-TWA-A070\x00\x00', - b'36161-TWA-A330\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TWA-A080\x00\x00', b'36802-TWA-A070\x00\x00', - b'36802-TWA-A330\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ b'39990-TVA-A160\x00\x00', @@ -281,6 +281,78 @@ FW_VERSIONS = { b'39990-TVA-A340\x00\x00', ], }, + CAR.ACCORD_2021: { + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-6B2-C520\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28102-6B8-A700\x00\x00', + ], + (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ + b'46114-TVA-A320\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TVA-A020\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TVA-E520\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TVA-L420\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TVC-A230\x00\x00', + ], + (Ecu.hud, 0x18da61f1, None): [ + b'78209-TVA-A110\x00\x00', + ], + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-TVC-A910\x00\x00', + ], + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TVC-A330\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TVC-A330\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TVA-A340\x00\x00', + ], + (Ecu.unknown, 0x18da3af1, None): [ + b'39390-TVA-A120\x00\x00', + ], + }, + CAR.ACCORDH_2021: { + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TWD-J020\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TWA-A530\x00\x00', + b'57114-TWA-B520\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TWA-L420\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TWA-A030\x00\x00', + b'78109-TWA-A230\x00\x00', + ], + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-TWA-A910\x00\x00', + ], + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TWA-A330\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TWA-A330\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TVA-A340\x00\x00', + ], + (Ecu.unknown, 0x18da3af1, None): [ + b'39390-TVA-A120\x00\x00', + ], + }, CAR.CIVIC: { (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-5AA-A640\x00\x00', @@ -1324,7 +1396,9 @@ FW_VERSIONS = { DBC = { CAR.ACCORD: dbc_dict('honda_accord_2018_can_generated', None), + CAR.ACCORD_2021: dbc_dict('honda_accord_2018_can_generated', None), CAR.ACCORDH: dbc_dict('honda_accord_2018_can_generated', None), + CAR.ACCORDH_2021: dbc_dict('honda_accord_2018_can_generated', None), CAR.ACURA_ILX: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), CAR.ACURA_RDX: dbc_dict('acura_rdx_2018_can_generated', 'acura_ilx_2016_nidec'), CAR.ACURA_RDX_3G: dbc_dict('acura_rdx_2020_can_generated', None), @@ -1354,18 +1428,9 @@ STEER_THRESHOLD = { CAR.CRV_EU: 400, } -# TODO: is this real? -SPEED_FACTOR = { - # default is 1, overrides go here - CAR.CRV: 1.025, - CAR.CRV_5G: 1.025, - CAR.CRV_EU: 1.025, - CAR.CRV_HYBRID: 1.025, - CAR.HRV: 1.025, -} - HONDA_NIDEC_ALT_PCM_ACCEL = set([CAR.ODYSSEY]) HONDA_NIDEC_ALT_SCM_MESSAGES = set([CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN, CAR.PILOT, CAR.PILOT_2019, CAR.PASSPORT, CAR.RIDGELINE]) -HONDA_BOSCH = set([CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E]) -HONDA_BOSCH_ALT_BRAKE_SIGNAL = set([CAR.ACCORD, CAR.CRV_5G, CAR.ACURA_RDX_3G]) +HONDA_BOSCH = set([CAR.ACCORD, CAR.ACCORD_2021, CAR.ACCORDH, CAR.ACCORDH_2021, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G, + CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E]) +HONDA_BOSCH_ALT_BRAKE_SIGNAL = set([CAR.ACCORD, CAR.ACCORD_2021, CAR.CRV_5G, CAR.ACURA_RDX_3G]) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 4db775bea5..e889f24fc9 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -28,10 +28,12 @@ class CarState(CarStateBase): ret.seatbeltUnlatched = cp.vl["CGW1"]["CF_Gway_DrvSeatBeltSw"] == 0 - ret.wheelSpeeds.fl = cp.vl["WHL_SPD11"]["WHL_SPD_FL"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = cp.vl["WHL_SPD11"]["WHL_SPD_FR"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = cp.vl["WHL_SPD11"]["WHL_SPD_RL"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = cp.vl["WHL_SPD11"]["WHL_SPD_RR"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHL_SPD11"]["WHL_SPD_FL"], + cp.vl["WHL_SPD11"]["WHL_SPD_FR"], + cp.vl["WHL_SPD11"]["WHL_SPD_RL"], + cp.vl["WHL_SPD11"]["WHL_SPD_RR"], + ) ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 336c1ee6c2..487fcab8c7 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -78,6 +78,7 @@ class CarInterfaceBase(): ret.steerMaxBP = [0.] ret.steerMaxV = [1.] ret.minSteerSpeed = 0. + ret.wheelSpeedFactor = 1.0 ret.pcmCruise = True # openpilot's state is tied to the PCM's cruise state on most cars ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this @@ -210,6 +211,16 @@ class CarStateBase: v_ego_x = self.v_ego_kf.update(v_ego_raw) return float(v_ego_x[0]), float(v_ego_x[1]) + def get_wheel_speeds(self, fl, fr, rl, rr, unit=CV.KPH_TO_MS): + factor = unit * self.CP.wheelSpeedFactor + + wheelSpeeds = car.CarState.WheelSpeeds.new_message() + wheelSpeeds.fl = fl * factor + wheelSpeeds.fr = fr * factor + wheelSpeeds.rl = rl * factor + wheelSpeeds.rr = rr * factor + return wheelSpeeds + def update_blinker_from_lamp(self, blinker_time: int, left_blinker_lamp: bool, right_blinker_lamp: bool): """Update blinkers from lights. Enable output when light was seen within the last `blinker_time` iterations""" diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py index 4140a4e079..7ebf60c96b 100644 --- a/selfdrive/car/mazda/carstate.py +++ b/selfdrive/car/mazda/carstate.py @@ -20,10 +20,12 @@ class CarState(CarStateBase): def update(self, cp, cp_cam): ret = car.CarState.new_message() - ret.wheelSpeeds.fl = cp.vl["WHEEL_SPEEDS"]["FL"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = cp.vl["WHEEL_SPEEDS"]["FR"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = cp.vl["WHEEL_SPEEDS"]["RL"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = cp.vl["WHEEL_SPEEDS"]["RR"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHEEL_SPEEDS"]["FL"], + cp.vl["WHEEL_SPEEDS"]["FR"], + cp.vl["WHEEL_SPEEDS"]["RL"], + cp.vl["WHEEL_SPEEDS"]["RR"], + ) ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index ada3ea1ddb..58e1dd7033 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -21,7 +21,7 @@ class CAR: CX9 = "MAZDA CX-9" MAZDA3 = "MAZDA 3" MAZDA6 = "MAZDA 6" - CX9_2021 = "Mazda CX-9 2021" # No Steer Lockout + CX9_2021 = "MAZDA CX-9 2021" # No Steer Lockout class LKAS_LIMITS: STEER_THRESHOLD = 15 diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py index a4019b538c..05191feedf 100644 --- a/selfdrive/car/nissan/carstate.py +++ b/selfdrive/car/nissan/carstate.py @@ -35,11 +35,12 @@ class CarState(CarStateBase): elif self.CP.carFingerprint in [CAR.LEAF, CAR.LEAF_IC]: ret.brakePressed = bool(cp.vl["CRUISE_THROTTLE"]["USER_BRAKE_PRESSED"]) - ret.wheelSpeeds.fl = cp.vl["WHEEL_SPEEDS_FRONT"]["WHEEL_SPEED_FL"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = cp.vl["WHEEL_SPEEDS_FRONT"]["WHEEL_SPEED_FR"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RL"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RR"] * CV.KPH_TO_MS - + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHEEL_SPEEDS_FRONT"]["WHEEL_SPEED_FL"], + cp.vl["WHEEL_SPEEDS_FRONT"]["WHEEL_SPEED_FR"], + cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RL"], + cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RR"], + ) ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index e324037766..1f56f09ff7 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -23,10 +23,12 @@ class CarState(CarStateBase): else: ret.brakePressed = cp.vl["Brake_Status"]["Brake"] == 1 - ret.wheelSpeeds.fl = cp.vl["Wheel_Speeds"]["FL"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = cp.vl["Wheel_Speeds"]["FR"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = cp.vl["Wheel_Speeds"]["RL"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = cp.vl["Wheel_Speeds"]["RR"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["Wheel_Speeds"]["FL"], + cp.vl["Wheel_Speeds"]["FR"], + cp.vl["Wheel_Speeds"]["RL"], + cp.vl["Wheel_Speeds"]["RR"], + ) ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. # Kalman filter, even though Subaru raw wheel speed is heaviliy filtered by default ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 72cde0e537..c445528a83 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -5,35 +5,18 @@ from selfdrive.car.toyota.toyotacan import create_steer_command, create_ui_comma create_accel_command, create_acc_cancel_command, \ create_fcw_command, create_lta_steer_command from selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \ - MIN_ACC_SPEED, PEDAL_HYST_GAP, PEDAL_SCALE, CarControllerParams + MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams from opendbc.can.packer import CANPacker VisualAlert = car.CarControl.HUDControl.VisualAlert -def accel_hysteresis(accel, accel_steady, enabled): - - # for small accel oscillations within ACCEL_HYST_GAP, don't change the accel command - if not enabled: - # send 0 when disabled, otherwise acc faults - accel_steady = 0. - elif accel > accel_steady + CarControllerParams.ACCEL_HYST_GAP: - accel_steady = accel - CarControllerParams.ACCEL_HYST_GAP - elif accel < accel_steady - CarControllerParams.ACCEL_HYST_GAP: - accel_steady = accel + CarControllerParams.ACCEL_HYST_GAP - accel = accel_steady - - return accel, accel_steady - - class CarController(): def __init__(self, dbc_name, CP, VM): self.last_steer = 0 - self.accel_steady = 0. self.alert_active = False self.last_standstill = False self.standstill_req = False self.steer_rate_limited = False - self.use_interceptor = False self.packer = CANPacker(dbc_name) @@ -43,25 +26,22 @@ class CarController(): # *** compute control surfaces *** # gas and brake - interceptor_gas_cmd = 0. - pcm_accel_cmd = actuators.accel - - if CS.CP.enableGasInterceptor: - # handle hysteresis when around the minimum acc speed - if CS.out.vEgo < MIN_ACC_SPEED: - self.use_interceptor = True - elif CS.out.vEgo > MIN_ACC_SPEED + PEDAL_HYST_GAP: - self.use_interceptor = False - - if self.use_interceptor and active: - # only send negative accel when using interceptor. gas handles acceleration - # +0.18 m/s^2 offset to reduce ABS pump usage when OP is engaged - MAX_INTERCEPTOR_GAS = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED], [0.2, 0.5]) - interceptor_gas_cmd = clip(actuators.accel / PEDAL_SCALE, 0., MAX_INTERCEPTOR_GAS) - pcm_accel_cmd = 0.18 - max(0, -actuators.accel) - - pcm_accel_cmd, self.accel_steady = accel_hysteresis(pcm_accel_cmd, self.accel_steady, enabled) - pcm_accel_cmd = clip(pcm_accel_cmd, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) + if CS.CP.enableGasInterceptor and enabled: + MAX_INTERCEPTOR_GAS = 0.5 + # RAV4 has very sensitive has pedal + if CS.CP.carFingerprint in [CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER, CAR.HIGHLANDERH]: + PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0]) + elif CS.CP.carFingerprint in [CAR.COROLLA]: + PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0]) + else: + PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.4, 0.5, 0.0]) + # offset for creep and windbrake + pedal_offset = interp(CS.out.vEgo, [0.0, 2.3, MIN_ACC_SPEED + PEDAL_TRANSITION], [-.4, 0.0, 0.2]) + pedal_command = PEDAL_SCALE * (actuators.accel + pedal_offset) + interceptor_gas_cmd = clip(pedal_command, 0., MAX_INTERCEPTOR_GAS) + else: + interceptor_gas_cmd = 0. + pcm_accel_cmd = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) # steer torque new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX)) @@ -88,7 +68,6 @@ class CarController(): self.standstill_req = False self.last_steer = apply_steer - self.last_accel = pcm_accel_cmd self.last_standstill = CS.out.standstill can_sends = [] @@ -113,7 +92,7 @@ class CarController(): lead = lead or CS.out.vEgo < 12. # at low speed we always assume the lead is present do ACC can be engaged # Lexus IS uses a different cancellation message - if pcm_cancel_cmd and CS.CP.carFingerprint == CAR.LEXUS_IS: + if pcm_cancel_cmd and CS.CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]: can_sends.append(create_acc_cancel_command(self.packer)) elif CS.CP.openpilotLongitudinalControl: can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type)) diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index cacd5b7bed..7ce5907b9b 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -41,10 +41,12 @@ class CarState(CarStateBase): ret.gas = cp.vl["GAS_PEDAL"]["GAS_PEDAL"] ret.gasPressed = cp.vl["PCM_CRUISE"]["GAS_RELEASED"] == 0 - ret.wheelSpeeds.fl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"], + ) ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr]) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) @@ -79,7 +81,7 @@ class CarState(CarStateBase): ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD ret.steerWarning = cp.vl["EPS_STATUS"]["LKA_STATE"] not in [1, 5] - if self.CP.carFingerprint == CAR.LEXUS_IS: + if self.CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]: ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0 ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS else: @@ -93,7 +95,7 @@ class CarState(CarStateBase): # these cars are identified by an ACC_TYPE value of 2. # TODO: it is possible to avoid the lockout and gain stop and go if you # send your own ACC_CONTROL msg on startup with ACC_TYPE set to 1 - if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint != CAR.LEXUS_IS) or \ + if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in [CAR.LEXUS_IS, CAR.LEXUS_RC]) or \ (self.CP.carFingerprint in TSS2_CAR and self.acc_type == 1): self.low_speed_lockout = cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2 @@ -168,7 +170,7 @@ class CarState(CarStateBase): ("STEER_TORQUE_SENSOR", 50), ] - if CP.carFingerprint == CAR.LEXUS_IS: + if CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]: signals.append(("MAIN_ON", "DSU_CRUISE", 0)) signals.append(("SET_SPEED", "DSU_CRUISE", 0)) checks.append(("DSU_CRUISE", 5)) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index a1221ae8a4..97a20f9df4 100755 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -65,7 +65,6 @@ class CarInterface(CarInterfaceBase): ret.mass = 4387. * CV.LB_TO_KG + STD_CARGO_KG set_lat_tune(ret.lateralTuning, LatTunes.PID_B) - elif candidate == CAR.LEXUS_RXH: stop_and_go = True ret.wheelbase = 2.79 @@ -81,6 +80,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.5533 # not optimized yet ret.mass = 4387. * CV.LB_TO_KG + STD_CARGO_KG set_lat_tune(ret.lateralTuning, LatTunes.PID_D) + ret.wheelSpeedFactor = 1.035 elif candidate == CAR.LEXUS_RXH_TSS2: stop_and_go = True @@ -89,6 +89,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.444 # not optimized yet ret.mass = 4481.0 * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max set_lat_tune(ret.lateralTuning, LatTunes.PID_E) + ret.wheelSpeedFactor = 1.035 elif candidate in [CAR.CHR, CAR.CHRH]: stop_and_go = True @@ -186,6 +187,15 @@ class CarInterface(CarInterfaceBase): ret.mass = 3736.8 * CV.LB_TO_KG + STD_CARGO_KG set_lat_tune(ret.lateralTuning, LatTunes.PID_L) + elif candidate == CAR.LEXUS_RC: + ret.safetyConfigs[0].safetyParam = 77 + stop_and_go = False + ret.wheelbase = 2.73050 + ret.steerRatio = 13.3 + tire_stiffness_factor = 0.444 + ret.mass = 3736.8 * CV.LB_TO_KG + STD_CARGO_KG + set_lat_tune(ret.lateralTuning, LatTunes.PID_L) + elif candidate == CAR.LEXUS_CTH: ret.safetyConfigs[0].safetyParam = 100 stop_and_go = True @@ -206,7 +216,7 @@ class CarInterface(CarInterfaceBase): elif candidate == CAR.PRIUS_TSS2: stop_and_go = True ret.wheelbase = 2.70002 # from toyota online sepc. - ret.steerRatio = 13.4 # True steerRation from older prius + ret.steerRatio = 13.4 # True steerRatio from older prius tire_stiffness_factor = 0.6371 # hand-tune ret.mass = 3115. * CV.LB_TO_KG + STD_CARGO_KG set_lat_tune(ret.lateralTuning, LatTunes.PID_N) @@ -259,7 +269,8 @@ class CarInterface(CarInterfaceBase): if ret.enableGasInterceptor: set_long_tune(ret.longitudinalTuning, LongTunes.PEDAL) - elif candidate in [CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.RAV4_TSS2, CAR.RAV4H_TSS2, CAR.LEXUS_NX_TSS2]: + elif candidate in [CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.RAV4_TSS2, CAR.RAV4H_TSS2, CAR.LEXUS_NX_TSS2, + CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2]: set_long_tune(ret.longitudinalTuning, LongTunes.TSS2) ret.stoppingDecelRate = 0.3 # reach stopping target smoothly ret.startingAccelRate = 6.0 # release brakes fast diff --git a/selfdrive/car/toyota/tunes.py b/selfdrive/car/toyota/tunes.py index 3f210d48a7..15c8bbfcc6 100644 --- a/selfdrive/car/toyota/tunes.py +++ b/selfdrive/car/toyota/tunes.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 from enum import Enum -from selfdrive.car.toyota.values import MIN_ACC_SPEED, PEDAL_HYST_GAP class LongTunes(Enum): @@ -29,15 +28,8 @@ class LatTunes(Enum): ###### LONG ###### def set_long_tune(tune, name): - if name == LongTunes.PEDAL: - tune.deadzoneBP = [0.] - tune.deadzoneV = [0.] - tune.kpBP = [0., 5., MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_HYST_GAP, 35.] - tune.kpV = [1.2, 0.8, 0.765, 2.255, 1.5] - tune.kiBP = [0., MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_HYST_GAP, 35.] - tune.kiV = [0.18, 0.165, 0.489, 0.36] # Improved longitudinal tune - elif name == LongTunes.TSS2: + if name == LongTunes.TSS2 or name == LongTunes.PEDAL: tune.deadzoneBP = [0., 8.05] tune.deadzoneV = [.0, .14] tune.kpBP = [0., 5., 20.] diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 73198d925f..4e64a66749 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -6,9 +6,8 @@ from selfdrive.config import Conversions as CV Ecu = car.CarParams.Ecu MIN_ACC_SPEED = 19. * CV.MPH_TO_MS +PEDAL_TRANSITION = 10. * CV.MPH_TO_MS -PEDAL_HYST_GAP = 3. * CV.MPH_TO_MS -PEDAL_SCALE = 3.0 class CarControllerParams: ACCEL_HYST_GAP = 0.06 # don't change accel command for small oscilalitons within this value @@ -58,6 +57,7 @@ class CAR: LEXUS_NX = "LEXUS NX 2018" LEXUS_NXH = "LEXUS NX HYBRID 2018" LEXUS_NX_TSS2 = "LEXUS NX 2020" + LEXUS_RC = "LEXUS RC 2020" LEXUS_RX = "LEXUS RX 2016" LEXUS_RXH = "LEXUS RX HYBRID 2017" LEXUS_RX_TSS2 = "LEXUS RX 2020" @@ -442,6 +442,7 @@ FW_VERSIONS = { b'\x0189663F438000\x00\x00\x00\x00', ], (Ecu.esp, 0x7b0, None): [ + b'F152610012\x00\x00\x00\x00\x00\x00', b'F152610013\x00\x00\x00\x00\x00\x00', b'F152610014\x00\x00\x00\x00\x00\x00', b'F152610040\x00\x00\x00\x00\x00\x00', @@ -455,6 +456,7 @@ FW_VERSIONS = { b'8821FF402400 ', b'8821FF404000 ', b'8821FF404100 ', + b'8821FF405000 ', b'8821FF406000 ', b'8821FF407100 ', ], @@ -470,10 +472,12 @@ FW_VERSIONS = { b'8821FF402400 ', b'8821FF404000 ', b'8821FF404100 ', + b'8821FF405000 ', b'8821FF406000 ', b'8821FF407100 ', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646FF401700 ', b'8646FF402100 ', b'8646FF404000 ', b'8646FF406000 ', @@ -804,10 +808,10 @@ FW_VERSIONS = { b'\x018966353M7100\x00\x00\x00\x00', b'\x018966353Q2000\x00\x00\x00\x00', b'\x018966353Q2300\x00\x00\x00\x00', + b'\x018966353Q4000\x00\x00\x00\x00', b'\x018966353R1100\x00\x00\x00\x00', b'\x018966353R7100\x00\x00\x00\x00', b'\x018966353R8100\x00\x00\x00\x00', - b'\x018966353Q4000\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\x0232480000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -1204,15 +1208,18 @@ FW_VERSIONS = { b'\x018966333T5000\x00\x00\x00\x00', b'\x018966333T5100\x00\x00\x00\x00', b'\x018966333X6000\x00\x00\x00\x00', + b'\x01896633T07000\x00\x00\x00\x00', ], (Ecu.esp, 0x7b0, None): [ b'\x01F152606281\x00\x00\x00\x00\x00\x00', b'\x01F152606340\x00\x00\x00\x00\x00\x00', + b'\x01F152606461\x00\x00\x00\x00\x00\x00', b'\x01F15260E031\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B33252\x00\x00\x00\x00\x00\x00', b'8965B33590\x00\x00\x00\x00\x00\x00', + b'8965B33690\x00\x00\x00\x00\x00\x00', b'8965B48271\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ @@ -1224,6 +1231,7 @@ FW_VERSIONS = { b'\x028646F33030D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3303200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3304100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F3304300\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F4810200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, @@ -1288,6 +1296,7 @@ FW_VERSIONS = { b'\x01896637851000\x00\x00\x00\x00', b'\x01896637852000\x00\x00\x00\x00', b'\x01896637854000\x00\x00\x00\x00', + b'\x01896637878000\x00\x00\x00\x00', ], (Ecu.esp, 0x7b0, None): [ b'F152678130\x00\x00\x00\x00\x00\x00', @@ -1295,6 +1304,7 @@ FW_VERSIONS = { ], (Ecu.dsu, 0x791, None): [ b'881517803100\x00\x00\x00\x00', + b'881517803300\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B78060\x00\x00\x00\x00\x00\x00', @@ -1306,6 +1316,7 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'8646F7801100\x00\x00\x00\x00', + b'8646F7801300\x00\x00\x00\x00', ], }, CAR.LEXUS_NX_TSS2: { @@ -1358,6 +1369,26 @@ FW_VERSIONS = { b'8646F7801100\x00\x00\x00\x00', ], }, + CAR.LEXUS_RC: { + (Ecu.engine, 0x7e0, None): [ + b'\x0232484000\x00\x00\x00\x00\x00\x00\x00\x0052422000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.esp, 0x7b0, None): [ + b'F152624221\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881512409100\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B24081\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F2402200\x00\x00\x00\x00', + ], + }, CAR.LEXUS_RX: { (Ecu.engine, 0x700, None): [ b'\x01896630E36200\x00\x00\x00\x00', @@ -1558,6 +1589,7 @@ DBC = { CAR.RAV4: dbc_dict('toyota_rav4_2017_pt_generated', 'toyota_adas'), CAR.PRIUS: dbc_dict('toyota_prius_2017_pt_generated', 'toyota_adas'), CAR.COROLLA: dbc_dict('toyota_corolla_2017_pt_generated', 'toyota_adas'), + CAR.LEXUS_RC: dbc_dict('lexus_is_2018_pt_generated', 'toyota_adas'), CAR.LEXUS_RX: dbc_dict('lexus_rx_350_2016_pt_generated', 'toyota_adas'), CAR.LEXUS_RXH: dbc_dict('lexus_rx_hybrid_2017_pt_generated', 'toyota_adas'), CAR.LEXUS_RX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index f74e86c6cb..03130ec9d2 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -20,10 +20,12 @@ class CarState(CarStateBase): def update(self, pt_cp, cam_cp, ext_cp, trans_type): ret = car.CarState.new_message() # Update vehicle speed and acceleration from ABS wheel speeds. - ret.wheelSpeeds.fl = pt_cp.vl["ESP_19"]["ESP_VL_Radgeschw_02"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = pt_cp.vl["ESP_19"]["ESP_VR_Radgeschw_02"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = pt_cp.vl["ESP_19"]["ESP_HL_Radgeschw_02"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = pt_cp.vl["ESP_19"]["ESP_HR_Radgeschw_02"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + pt_cp.vl["ESP_19"]["ESP_VL_Radgeschw_02"], + pt_cp.vl["ESP_19"]["ESP_VR_Radgeschw_02"], + pt_cp.vl["ESP_19"]["ESP_HL_Radgeschw_02"], + pt_cp.vl["ESP_19"]["ESP_HR_Radgeschw_02"], + ) ret.vEgoRaw = float(np.mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr])) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 9669e0e297..5dcdad3b5e 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -152,6 +152,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906016A \xf1\x897697', b'\xf1\x8704E906016AD\xf1\x895758', + b'\xf1\x8704E906016CE\xf1\x899096', b'\xf1\x8704E906023AG\xf1\x891726', b'\xf1\x8704E906023BN\xf1\x894518', b'\xf1\x8704E906024K \xf1\x896811', @@ -192,6 +193,7 @@ FW_VERSIONS = { b'\xf1\x870CW300042F \xf1\x891604', b'\xf1\x870CW300043B \xf1\x891601', b'\xf1\x870CW300044S \xf1\x894530', + b'\xf1\x870CW300044T \xf1\x895245', b'\xf1\x870CW300045 \xf1\x894531', b'\xf1\x870CW300047D \xf1\x895261', b'\xf1\x870CW300048J \xf1\x890611', @@ -274,6 +276,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907567G \xf1\x890390\xf1\x82\00101', + b'\xf1\x875Q0907567J \xf1\x890396\xf1\x82\x0101', b'\xf1\x875Q0907572A \xf1\x890141\xf1\x82\00101', b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\00101', b'\xf1\x875Q0907572C \xf1\x890210\xf1\x82\00101', diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index 01bf9a8f4f..ea09ee0705 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -130,11 +130,13 @@ std::unordered_map keys = { {"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastGPSPosition", PERSISTENT}, + {"LastPowerDropDetected", CLEAR_ON_MANAGER_START}, {"LastUpdateException", PERSISTENT}, {"LastUpdateTime", PERSISTENT}, {"LiveParameters", PERSISTENT}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"NavSettingTime24h", PERSISTENT}, + {"NavdRender", PERSISTENT}, {"OpenpilotEnabledToggle", PERSISTENT}, {"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"Passive", PERSISTENT}, diff --git a/selfdrive/common/version.h b/selfdrive/common/version.h index 9f00fac4f9..aa786903c4 100644 --- a/selfdrive/common/version.h +++ b/selfdrive/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.8.11" +#define COMMA_VERSION "0.8.12" diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index bbd84d458f..d029650aaf 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -540,8 +540,9 @@ class Controls: if len(lat_plan.dPathPoints): # Check if we deviated from the path - left_deviation = actuators.steer > 0 and lat_plan.dPathPoints[0] < -0.1 - right_deviation = actuators.steer < 0 and lat_plan.dPathPoints[0] > 0.1 + # TODO use desired vs actual curvature + left_deviation = actuators.steer > 0 and lat_plan.dPathPoints[0] < -0.20 + right_deviation = actuators.steer < 0 and lat_plan.dPathPoints[0] > 0.20 if left_deviation or right_deviation: self.events.add(EventName.steerSaturated) diff --git a/selfdrive/controls/lib/alertmanager.py b/selfdrive/controls/lib/alertmanager.py index 42d9f457be..22d7f3eac4 100644 --- a/selfdrive/controls/lib/alertmanager.py +++ b/selfdrive/controls/lib/alertmanager.py @@ -41,6 +41,7 @@ class AlertManager: self.activealerts: Dict[str, AlertEntry] = defaultdict(AlertEntry) def reset(self) -> None: + self.alert: Optional[Alert] = None self.alert_type: str = "" self.alert_text_1: str = "" self.alert_text_2: str = "" @@ -74,13 +75,13 @@ class AlertManager: # clear current alert self.reset() - a = current_alert.alert - if a is not None: - self.alert_type = a.alert_type - self.audible_alert = a.audible_alert - self.visual_alert = a.visual_alert - self.alert_text_1 = a.alert_text_1 - self.alert_text_2 = a.alert_text_2 - self.alert_status = a.alert_status - self.alert_size = a.alert_size - self.alert_rate = a.alert_rate + self.alert = current_alert.alert + if self.alert is not None: + self.alert_type = self.alert.alert_type + self.audible_alert = self.alert.audible_alert + self.visual_alert = self.alert.visual_alert + self.alert_text_1 = self.alert.alert_text_1 + self.alert_text_2 = self.alert.alert_text_2 + self.alert_status = self.alert.alert_status + self.alert_size = self.alert.alert_size + self.alert_rate = self.alert.alert_rate diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index bd728159b7..d858dbfcbc 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -139,11 +139,10 @@ class Alert: class NoEntryAlert(Alert): - def __init__(self, alert_text_2, audible_alert=AudibleAlert.chimeError, - visual_alert=VisualAlert.none): + def __init__(self, alert_text_2, visual_alert=VisualAlert.none): super().__init__("openpilot Unavailable", alert_text_2, AlertStatus.normal, AlertSize.mid, Priority.LOW, visual_alert, - audible_alert, 3.) + AudibleAlert.refuse, 3.) class SoftDisableAlert(Alert): @@ -151,7 +150,7 @@ class SoftDisableAlert(Alert): super().__init__("TAKE CONTROL IMMEDIATELY", alert_text_2, AlertStatus.userPrompt, AlertSize.full, Priority.MID, VisualAlert.steerRequired, - AudibleAlert.chimeWarningRepeatInfinite, 2.), + AudibleAlert.warningSoft, 2.), class ImmediateDisableAlert(Alert): @@ -159,7 +158,7 @@ class ImmediateDisableAlert(Alert): super().__init__("TAKE CONTROL IMMEDIATELY", alert_text_2, AlertStatus.critical, AlertSize.full, Priority.HIGHEST, VisualAlert.steerRequired, - AudibleAlert.chimeWarningRepeatInfinite, 4.), + AudibleAlert.warningImmediate, 4.), class EngagementAlert(Alert): @@ -201,7 +200,7 @@ def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}", "", AlertStatus.userPrompt, AlertSize.small, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimePrompt, 0.4) + Priority.MID, VisualAlert.steerRequired, AudibleAlert.prompt, 0.4) def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: @@ -321,7 +320,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "BRAKE!", "Risk of Collision", AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.chimeWarningRepeatInfinite, 2.), + Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.warningSoft, 2.), }, EventName.ldw: { @@ -329,7 +328,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "TAKE CONTROL", "Lane Departure Detected", AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.ldw, AudibleAlert.chimePrompt, 3.), + Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.), }, # ********** events only containing alerts that display while engaged ********** @@ -360,7 +359,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "Steering Temporarily Unavailable", "", AlertStatus.userPrompt, AlertSize.small, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimePrompt, 1.), + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.), }, EventName.preDriverDistracted: { @@ -376,7 +375,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "KEEP EYES ON ROAD", "Driver Distracted", AlertStatus.userPrompt, AlertSize.mid, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2RepeatInfinite, .1), + Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1), }, EventName.driverDistracted: { @@ -384,7 +383,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical, AlertSize.full, - Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeatInfinite, .1), + Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1), }, EventName.preDriverUnresponsive: { @@ -400,7 +399,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "TOUCH STEERING WHEEL", "Driver Unresponsive", AlertStatus.userPrompt, AlertSize.mid, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2RepeatInfinite, .1), + Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1), }, EventName.driverUnresponsive: { @@ -408,7 +407,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "DISENGAGE IMMEDIATELY", "Driver Unresponsive", AlertStatus.critical, AlertSize.full, - Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeatInfinite, .1), + Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1), }, EventName.manualRestart: { @@ -452,7 +451,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "Car Detected in Blindspot", "", AlertStatus.userPrompt, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.chimePrompt, .1), + Priority.LOW, VisualAlert.none, AudibleAlert.prompt, .1), }, EventName.laneChange: { @@ -468,7 +467,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "TAKE CONTROL", "Turn Exceeds Steering Limit", AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimeWarning2RepeatInfinite, 1.), + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 1.), }, # Thrown when the fan is driven at >50% but is not rotating @@ -496,44 +495,44 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # ********** events that affect controls state transitions ********** EventName.pcmEnable: { - ET.ENABLE: EngagementAlert(AudibleAlert.chimeEngage), + ET.ENABLE: EngagementAlert(AudibleAlert.engage), }, EventName.buttonEnable: { - ET.ENABLE: EngagementAlert(AudibleAlert.chimeEngage), + ET.ENABLE: EngagementAlert(AudibleAlert.engage), }, EventName.pcmDisable: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), }, EventName.buttonCancel: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), }, EventName.brakeHold: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), ET.NO_ENTRY: NoEntryAlert("Brake Hold Active"), }, EventName.parkBrake: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), ET.NO_ENTRY: NoEntryAlert("Park Brake Engaged"), }, EventName.pedalPressed: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), ET.NO_ENTRY: NoEntryAlert("Pedal Pressed During Attempt", visual_alert=VisualAlert.brakePressed), }, EventName.wrongCarMode: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), ET.NO_ENTRY: wrong_car_mode_alert, }, EventName.wrongCruiseMode: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), ET.NO_ENTRY: NoEntryAlert("Enable Adaptive Cruise"), }, @@ -627,14 +626,12 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # ten times the regular interval, or the average interval is more than 10% too high. EventName.commIssue: { ET.SOFT_DISABLE: SoftDisableAlert("Communication Issue between Processes"), - ET.NO_ENTRY: NoEntryAlert("Communication Issue between Processes", - audible_alert=AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("Communication Issue between Processes"), }, # Thrown when manager detects a service exited unexpectedly while driving EventName.processNotRunning: { - ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device", - audible_alert=AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device"), }, EventName.radarFault: { @@ -670,15 +667,13 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.lowMemory: { ET.SOFT_DISABLE: SoftDisableAlert("Low Memory: Reboot Your Device"), ET.PERMANENT: NormalPermanentAlert("Low Memory", "Reboot your Device"), - ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device", - audible_alert=AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device"), }, EventName.highCpuUsage: { #ET.SOFT_DISABLE: SoftDisableAlert("System Malfunction: Reboot Your Device"), #ET.PERMANENT: NormalPermanentAlert("System Malfunction", "Reboot your Device"), - ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device", - audible_alert=AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device"), }, EventName.accFaulted: { @@ -782,7 +777,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "openpilot Canceled", "No close lead car", AlertStatus.normal, AlertSize.mid, - Priority.HIGH, VisualAlert.none, AudibleAlert.chimeDisengage, 3.), + Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.), ET.NO_ENTRY: NoEntryAlert("No Close Lead Car"), }, @@ -791,7 +786,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "openpilot Canceled", "Speed too low", AlertStatus.normal, AlertSize.mid, - Priority.HIGH, VisualAlert.none, AudibleAlert.chimeDisengage, 3.), + Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.), }, # When the car is driving faster than most cars in the training data the model outputs can be unpredictable @@ -800,7 +795,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "Speed Too High", "Model uncertain at this speed", AlertStatus.userPrompt, AlertSize.mid, - Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarning2RepeatInfinite, 4.), + Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 4.), ET.NO_ENTRY: NoEntryAlert("Slow down to engage"), }, diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 74b27f595d..0aa2359ae7 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -38,7 +38,7 @@ DESIRES = { } -class LateralPlanner(): +class LateralPlanner: def __init__(self, CP, use_lanelines=True, wide_camera=False): self.use_lanelines = use_lanelines self.LP = LanePlanner(wide_camera) @@ -55,8 +55,8 @@ class LateralPlanner(): self.prev_one_blinker = False self.desire = log.LateralPlan.Desire.none - self.path_xyz = np.zeros((TRAJECTORY_SIZE,3)) - self.path_xyz_stds = np.ones((TRAJECTORY_SIZE,3)) + self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3)) + self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3)) self.plan_yaw = np.zeros((TRAJECTORY_SIZE,)) self.t_idxs = np.arange(TRAJECTORY_SIZE) self.y_pts = np.zeros(TRAJECTORY_SIZE) @@ -67,12 +67,8 @@ class LateralPlanner(): def reset_mpc(self, x0=np.zeros(6)): self.x0 = x0 self.lat_mpc.reset(x0=self.x0) - self.desired_curvature = 0.0 - self.safe_desired_curvature = 0.0 - self.desired_curvature_rate = 0.0 - self.safe_desired_curvature_rate = 0.0 - def update(self, sm, CP): + def update(self, sm): v_ego = sm['carState'].vEgo active = sm['controlsState'].active measured_curvature = sm['controlsState'].curvature @@ -110,7 +106,7 @@ class LateralPlanner(): self.lane_change_direction = LaneChangeDirection.none torque_applied = sm['carState'].steeringPressed and \ - ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or + ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right)) blindspot_detected = ((sm['carState'].leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or @@ -124,7 +120,7 @@ class LateralPlanner(): # LaneChangeState.laneChangeStarting elif self.lane_change_state == LaneChangeState.laneChangeStarting: # fade out over .5s - self.lane_change_ll_prob = max(self.lane_change_ll_prob - 2*DT_MDL, 0.0) + self.lane_change_ll_prob = max(self.lane_change_ll_prob - 2 * DT_MDL, 0.0) # 98% certainty lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob @@ -167,14 +163,14 @@ class LateralPlanner(): self.LP.rll_prob *= self.lane_change_ll_prob if self.use_lanelines: d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz) - self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost) + self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, self.steer_rate_cost) else: d_path_xyz = self.path_xyz - path_cost = np.clip(abs(self.path_xyz[0,1]/self.path_xyz_stds[0,1]), 0.5, 1.5) * MPC_COST_LAT.PATH + path_cost = np.clip(abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5, 1.5) * MPC_COST_LAT.PATH # Heading cost is useful at low speed, otherwise end of plan can be off-heading heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0]) - self.lat_mpc.set_weights(path_cost, heading_cost, CP.steerRateCost) - y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:,1]) + self.lat_mpc.set_weights(path_cost, heading_cost, self.steer_rate_cost) + y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1]) heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw) self.y_pts = y_pts @@ -187,11 +183,10 @@ class LateralPlanner(): y_pts, heading_pts) # init state for next - self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1], self.lat_mpc.x_sol[:,3]) + self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1], self.lat_mpc.x_sol[:, 3]) - - # Check for infeasable MPC solution - mpc_nans = any(math.isnan(x) for x in self.lat_mpc.x_sol[:,3]) + # Check for infeasible MPC solution + mpc_nans = any(math.isnan(x) for x in self.lat_mpc.x_sol[:, 3]) t = sec_since_boot() if mpc_nans or self.lat_mpc.solution_status != 0: self.reset_mpc() @@ -212,8 +207,8 @@ class LateralPlanner(): plan_send.lateralPlan.laneWidth = float(self.LP.lane_width) plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts] plan_send.lateralPlan.psis = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 2]] - plan_send.lateralPlan.curvatures = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N,3]] - plan_send.lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N-1]] +[0.0] + plan_send.lateralPlan.curvatures = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 3]] + plan_send.lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0] plan_send.lateralPlan.lProb = float(self.LP.lll_prob) plan_send.lateralPlan.rProb = float(self.LP.rll_prob) plan_send.lateralPlan.dProb = float(self.LP.d_prob) diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index f59a6cc0d6..beacc518d0 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -10,20 +10,20 @@ LongCtrlState = car.CarControl.Actuators.LongControlState STOPPING_TARGET_SPEED_OFFSET = 0.01 # As per ISO 15622:2018 for all speeds -ACCEL_MIN_ISO = -3.5 # m/s^2 -ACCEL_MAX_ISO = 2.0 # m/s^2 +ACCEL_MIN_ISO = -3.5 # m/s^2 +ACCEL_MAX_ISO = 2.0 # m/s^2 -def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, v_pid, +def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_future, v_pid, output_accel, brake_pressed, cruise_standstill, min_speed_can): """Update longitudinal control state machine""" stopping_target_speed = min_speed_can + STOPPING_TARGET_SPEED_OFFSET stopping_condition = (v_ego < 2.0 and cruise_standstill) or \ (v_ego < CP.vEgoStopping and - ((v_pid < stopping_target_speed and v_target < stopping_target_speed) or + ((v_pid < stopping_target_speed and v_target_future < stopping_target_speed) or brake_pressed)) - starting_condition = v_target > CP.vEgoStarting and not cruise_standstill + starting_condition = v_target_future > CP.vEgoStarting and not cruise_standstill if not active: long_control_state = LongCtrlState.off @@ -75,13 +75,10 @@ class LongControl(): v_target_upper = interp(CP.longitudinalActuatorDelayUpperBound, T_IDXS[:CONTROL_N], long_plan.speeds) a_target_upper = 2 * (v_target_upper - long_plan.speeds[0])/CP.longitudinalActuatorDelayUpperBound - long_plan.accels[0] - - v_target = min(v_target_lower, v_target_upper) a_target = min(a_target_lower, a_target_upper) v_target_future = long_plan.speeds[-1] else: - v_target = 0.0 v_target_future = 0.0 a_target = 0.0 @@ -103,11 +100,11 @@ class LongControl(): # tracking objects and driving elif self.long_control_state == LongCtrlState.pid: - self.v_pid = v_target + self.v_pid = long_plan.speeds[0] # Toyota starts braking more when it thinks you want to stop # Freeze the integrator so we don't accelerate to compensate, and don't allow positive acceleration - prevent_overshoot = not CP.stoppingControl and CS.vEgo < 1.5 and v_target_future < 0.7 + prevent_overshoot = not CP.stoppingControl and CS.vEgo < 1.5 and v_target_future < 0.7 and v_target_future < self.v_pid deadzone = interp(CS.vEgo, CP.longitudinalTuning.deadzoneBP, CP.longitudinalTuning.deadzoneV) freeze_integrator = prevent_overshoot diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 577651f8d4..54d4b0be31 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -49,15 +49,16 @@ T_IDXS_LST = [index_function(idx, max_val=MAX_T, max_idx=N+1) for idx in range(N T_IDXS = np.array(T_IDXS_LST) T_DIFFS = np.diff(T_IDXS, prepend=[0.]) MIN_ACCEL = -3.5 -T_REACT = 1.8 -MAX_BRAKE = 9.81 - +T_REACT = 0.5 +T_FOLLOW = 1.45 +COMFORT_BRAKE = 2.0 +STOP_DISTANCE = 6.0 def get_stopped_equivalence_factor(v_lead): - return T_REACT * v_lead + (v_lead*v_lead) / (2 * MAX_BRAKE) + return v_lead**2 / (2 * COMFORT_BRAKE) - (T_FOLLOW - T_REACT) * v_lead def get_safe_obstacle_distance(v_ego): - return 2 * T_REACT * v_ego + (v_ego*v_ego) / (2 * MAX_BRAKE) + 4.0 + return (v_ego*v_ego) / (2 * COMFORT_BRAKE) + T_REACT * v_ego + STOP_DISTANCE def desired_follow_distance(v_ego, v_lead): return get_safe_obstacle_distance(v_ego) - get_stopped_equivalence_factor(v_lead) @@ -242,7 +243,7 @@ class LongitudinalMpc(): self.solver.cost_set(i, 'Zl', Zl) def set_weights_for_xva_policy(self): - W = np.asfortranarray(np.diag([0., 10., 1., 10., 0.0, 1.])) + W = np.asfortranarray(np.diag([0., 10., 1., 10., 1.])) for i in range(N): self.solver.cost_set(i, 'W', W) # Setting the slice without the copy make the array not contiguous, @@ -298,8 +299,9 @@ class LongitudinalMpc(): self.cruise_min_a = min_a self.cruise_max_a = max_a - def update(self, carstate, radarstate, v_cruise): + def update(self, carstate, radarstate, v_cruise, prev_accel_constraint=False): v_ego = self.x0[1] + a_ego = self.x0[2] self.status = radarstate.leadOne.status or radarstate.leadTwo.status lead_xv_0 = self.process_lead(radarstate.leadOne) @@ -317,17 +319,20 @@ class LongitudinalMpc(): # Fake an obstacle for cruise, this ensures smooth acceleration to set speed # when the leads are no factor. - cruise_lower_bound = v_ego + (3/4) * self.cruise_min_a * T_IDXS - cruise_upper_bound = v_ego + (3/4) * self.cruise_max_a * T_IDXS + v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05) + v_upper = v_ego + (T_IDXS * self.cruise_max_a * 1.05) v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), - cruise_lower_bound, - cruise_upper_bound) - cruise_obstacle = T_IDXS*v_cruise_clipped + get_safe_obstacle_distance(v_cruise_clipped) + v_lower, + v_upper) + cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) self.source = SOURCES[np.argmin(x_obstacles[0])] self.params[:,2] = np.min(x_obstacles, axis=1) - self.params[:,3] = np.copy(self.prev_a) + if prev_accel_constraint: + self.params[:,3] = np.copy(self.prev_a) + else: + self.params[:,3] = a_ego self.run() if (np.any(lead_xv_0[:,0] - self.x_sol[:,0] < CRASH_DISTANCE) and diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 051a68a741..1c1ab0e17b 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -14,7 +14,7 @@ from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N from selfdrive.swaglog import cloudlog LON_MPC_STEP = 0.2 # first step is 0.2s -AWARENESS_DECEL = -0.2 # car smoothly decel at .2m/s^2 when user is distracted +AWARENESS_DECEL = -0.2 # car smoothly decel at .2m/s^2 when user is distracted A_CRUISE_MIN = -1.2 A_CRUISE_MAX_VALS = [1.2, 1.2, 0.8, 0.6] A_CRUISE_MAX_BP = [0., 15., 25., 40.] @@ -35,13 +35,13 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): """ a_total_max = interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V) - a_y = v_ego**2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase) - a_x_allowed = math.sqrt(max(a_total_max**2 - a_y**2, 0.)) + a_y = v_ego ** 2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase) + a_x_allowed = math.sqrt(max(a_total_max ** 2 - a_y ** 2, 0.)) return [a_target[0], min(a_target[1], a_x_allowed)] -class Planner(): +class Planner: def __init__(self, CP, init_v=0.0, init_a=0.0): self.CP = CP self.mpc = LongitudinalMpc() @@ -50,14 +50,13 @@ class Planner(): self.v_desired = init_v self.a_desired = init_a - self.alpha = np.exp(-DT_MDL/2.0) + self.alpha = np.exp(-DT_MDL / 2.0) self.v_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N) self.j_desired_trajectory = np.zeros(CONTROL_N) - - def update(self, sm, CP): + def update(self, sm): v_ego = sm['carState'].vEgo a_ego = sm['carState'].aEgo @@ -68,10 +67,13 @@ class Planner(): long_control_state = sm['controlsState'].longControlState force_slow_decel = sm['controlsState'].forceDecel + prev_accel_constraint = True enabled = (long_control_state == LongCtrlState.pid) or (long_control_state == LongCtrlState.stopping) if not enabled or sm['carState'].gasPressed: self.v_desired = v_ego self.a_desired = a_ego + # Smoothly changing between accel trajectory is only relevant when OP is driving + prev_accel_constraint = False # Prevent divergence, smooth in current v_ego self.v_desired = self.alpha * self.v_desired + (1 - self.alpha) * v_ego @@ -88,12 +90,12 @@ class Planner(): accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired, self.a_desired) - self.mpc.update(sm['carState'], sm['radarState'], v_cruise) + self.mpc.update(sm['carState'], sm['radarState'], v_cruise, prev_accel_constraint=prev_accel_constraint) self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.a_solution) self.j_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution) - #TODO counter is only needed because radar is glitchy, remove once radar is gone + # TODO counter is only needed because radar is glitchy, remove once radar is gone self.fcw = self.mpc.crash_cnt > 5 if self.fcw: cloudlog.info("FCW triggered") @@ -101,7 +103,7 @@ class Planner(): # Interpolate 0.05 seconds and save as starting point for next iteration a_prev = self.a_desired self.a_desired = float(interp(DT_MDL, T_IDXS[:CONTROL_N], self.a_desired_trajectory)) - self.v_desired = self.v_desired + DT_MDL * (self.a_desired + a_prev)/2.0 + self.v_desired = self.v_desired + DT_MDL * (self.a_desired + a_prev) / 2.0 def publish(self, sm, pm): plan_send = messaging.new_message('longitudinalPlan') diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 88734b3c13..02f1c19a77 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -36,9 +36,9 @@ def plannerd_thread(sm=None, pm=None): sm.update() if sm.updated['modelV2']: - lateral_planner.update(sm, CP) + lateral_planner.update(sm) lateral_planner.publish(sm, pm) - longitudinal_planner.update(sm, CP) + longitudinal_planner.update(sm) longitudinal_planner.publish(sm, pm) diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index 5b3fa65228..6661847b7f 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -29,7 +29,7 @@ class TestFollowingDistance(unittest.TestCase): simulation_steady_state = run_following_distance_simulation(v_lead) correct_steady_state = desired_follow_distance(v_lead, v_lead) - self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(correct_steady_state*.1 + .3)) + self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(correct_steady_state*.1 + 1.0)) if __name__ == "__main__": diff --git a/selfdrive/crash.py b/selfdrive/crash.py index f85d7c0e6d..e42b7532b0 100644 --- a/selfdrive/crash.py +++ b/selfdrive/crash.py @@ -1,6 +1,6 @@ """Install exception handler for process crash.""" from selfdrive.swaglog import cloudlog -from selfdrive.version import version +from selfdrive.version import get_version import sentry_sdk from sentry_sdk.integrations.threading import ThreadingIntegration @@ -24,4 +24,4 @@ def bind_extra(**kwargs) -> None: def init() -> None: sentry_sdk.init("https://a8dc76b5bfb34908a601d67e2aa8bcf9@o33823.ingest.sentry.io/77924", default_integrations=False, integrations=[ThreadingIntegration(propagate_hub=True)], - release=version) + release=get_version()) diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index 5931fda640..f9b57130b1 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -66,7 +66,7 @@ if __name__ == "__main__": for p in psutil.process_iter(): if p == psutil.Process(): continue - matched = any([l for l in p.cmdline() if any([pn for pn in monitored_proc_names if re.match(r'.*{}.*'.format(pn), l, re.M | re.I)])]) + matched = any(l for l in p.cmdline() if any(pn for pn in monitored_proc_names if re.match(r'.*{}.*'.format(pn), l, re.M | re.I))) if matched: k = ' '.join(p.cmdline()) print('Add monitored proc:', k) diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index 8b01a73b0d..ca238b4293 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -7,18 +7,31 @@ import time from cereal import car, log import cereal.messaging as messaging +from common.realtime import DT_CTRL from selfdrive.car.honda.interface import CarInterface from selfdrive.controls.lib.events import ET, EVENTS, Events from selfdrive.controls.lib.alertmanager import AlertManager EventName = car.CarEvent.EventName -def cycle_alerts(duration=2000, is_metric=False): - alerts = list(EVENTS.keys()) - print(alerts) +def cycle_alerts(duration=200, is_metric=False): + # all alerts + #alerts = list(EVENTS.keys()) - alerts = [EventName.preDriverDistracted, EventName.promptDriverDistracted, EventName.driverDistracted] - #alerts = [EventName.preLaneChangeLeft, EventName.preLaneChangeRight] + # this plays each type of audible alert + alerts = [ + (EventName.buttonEnable, ET.ENABLE), + (EventName.buttonCancel, ET.USER_DISABLE), + (EventName.wrongGear, ET.NO_ENTRY), + + (EventName.vehicleModelInvalid, ET.SOFT_DISABLE), + (EventName.accFaulted, ET.IMMEDIATE_DISABLE), + + # DM sequence + (EventName.preDriverDistracted, ET.WARNING), + (EventName.promptDriverDistracted, ET.WARNING), + (EventName.driverDistracted, ET.WARNING), + ] CP = CarInterface.get_params("HONDA CIVIC 2016") sm = messaging.SubMaster(['deviceState', 'pandaStates', 'roadCameraState', 'modelV2', 'liveCalibration', @@ -30,43 +43,45 @@ def cycle_alerts(duration=2000, is_metric=False): AM = AlertManager() frame = 0 - idx, last_alert_millis = 0, 0 - while 1: - if frame % duration == 0: - idx = (idx + 1) % len(alerts) - events.clear() - events.add(alerts[idx]) - - + while True: current_alert_types = [ET.PERMANENT, ET.USER_DISABLE, ET.IMMEDIATE_DISABLE, ET.SOFT_DISABLE, ET.PRE_ENABLE, ET.NO_ENTRY, ET.ENABLE, ET.WARNING] - a = events.create_alerts(current_alert_types, [CP, sm, is_metric]) - AM.add_many(frame, a) - AM.process_alerts(frame) - - dat = messaging.new_message() - dat.init('controlsState') - dat.controlsState.alertText1 = AM.alert_text_1 - dat.controlsState.alertText2 = AM.alert_text_2 - dat.controlsState.alertSize = AM.alert_size - dat.controlsState.alertStatus = AM.alert_status - dat.controlsState.alertBlinkingRate = AM.alert_rate - dat.controlsState.alertType = AM.alert_type - dat.controlsState.alertSound = AM.audible_alert - pm.send('controlsState', dat) - - dat = messaging.new_message() - dat.init('deviceState') - dat.deviceState.started = True - pm.send('deviceState', dat) - - dat = messaging.new_message('pandaStates', 1) - dat.pandaStates[0].ignitionLine = True - dat.pandaStates[0].pandaType = log.PandaState.PandaType.uno - pm.send('pandaStates', dat) - - time.sleep(0.01) + + for alert, et in alerts: + events.clear() + events.add(alert) + + a = events.create_alerts([et, ], [CP, sm, is_metric]) + AM.add_many(frame, a) + AM.process_alerts(frame) + print(AM.alert) + for _ in range(duration): + dat = messaging.new_message() + dat.init('controlsState') + dat.controlsState.enabled = True + + dat.controlsState.alertText1 = AM.alert_text_1 + dat.controlsState.alertText2 = AM.alert_text_2 + dat.controlsState.alertSize = AM.alert_size + dat.controlsState.alertStatus = AM.alert_status + dat.controlsState.alertBlinkingRate = AM.alert_rate + dat.controlsState.alertType = AM.alert_type + dat.controlsState.alertSound = AM.audible_alert + pm.send('controlsState', dat) + + dat = messaging.new_message() + dat.init('deviceState') + dat.deviceState.started = True + pm.send('deviceState', dat) + + dat = messaging.new_message('pandaStates', 1) + dat.pandaStates[0].ignitionLine = True + dat.pandaStates[0].pandaType = log.PandaState.PandaType.uno + pm.send('pandaStates', dat) + + frame += 1 + time.sleep(DT_CTRL) if __name__ == '__main__': cycle_alerts() diff --git a/selfdrive/hardware/base.py b/selfdrive/hardware/base.py index a1b67cb38b..06c86f0c26 100644 --- a/selfdrive/hardware/base.py +++ b/selfdrive/hardware/base.py @@ -1,7 +1,7 @@ from abc import abstractmethod from collections import namedtuple -ThermalConfig = namedtuple('ThermalConfig', ['cpu', 'gpu', 'mem', 'bat', 'ambient']) +ThermalConfig = namedtuple('ThermalConfig', ['cpu', 'gpu', 'mem', 'bat', 'ambient', 'pmic']) class HardwareBase: @staticmethod diff --git a/selfdrive/hardware/eon/hardware.py b/selfdrive/hardware/eon/hardware.py index 2296c26e6d..fa275c5a9c 100644 --- a/selfdrive/hardware/eon/hardware.py +++ b/selfdrive/hardware/eon/hardware.py @@ -369,7 +369,7 @@ class Android(HardwareBase): os.system('LD_LIBRARY_PATH="" svc power shutdown') def get_thermal_config(self): - return ThermalConfig(cpu=((5, 7, 10, 12), 10), gpu=((16,), 10), mem=(2, 10), bat=(29, 1000), ambient=(25, 1)) + return ThermalConfig(cpu=((5, 7, 10, 12), 10), gpu=((16,), 10), mem=(2, 10), bat=(29, 1000), ambient=(25, 1), pmic=((22,), 1000)) def set_screen_brightness(self, percentage): with open("/sys/class/leds/lcd-backlight/brightness", "w") as f: diff --git a/selfdrive/hardware/pc/hardware.py b/selfdrive/hardware/pc/hardware.py index b2be6ca91c..8231bbc2f9 100644 --- a/selfdrive/hardware/pc/hardware.py +++ b/selfdrive/hardware/pc/hardware.py @@ -78,7 +78,7 @@ class Pc(HardwareBase): print("SHUTDOWN!") def get_thermal_config(self): - return ThermalConfig(cpu=((None,), 1), gpu=((None,), 1), mem=(None, 1), bat=(None, 1), ambient=(None, 1)) + return ThermalConfig(cpu=((None,), 1), gpu=((None,), 1), mem=(None, 1), bat=(None, 1), ambient=(None, 1), pmic=((None,), 1)) def set_screen_brightness(self, percentage): pass diff --git a/selfdrive/hardware/tici/amplifier.py b/selfdrive/hardware/tici/amplifier.py index 99b6d0598a..a8b2798630 100755 --- a/selfdrive/hardware/tici/amplifier.py +++ b/selfdrive/hardware/tici/amplifier.py @@ -31,17 +31,18 @@ BASE_CONFIG = [ AmpConfig("Enable PLL2", 0b1, 0x1A, 7, 0b10000000), AmpConfig("DAI1: I2S mode", 0b00100, 0x14, 2, 0b01111100), AmpConfig("DAI2: I2S mode", 0b00100, 0x1C, 2, 0b01111100), - AmpConfig("Right speaker output volume", 0x1a, 0x3E, 0, 0b00011111), + AmpConfig("Right speaker output volume", 0x1c, 0x3E, 0, 0b00011111), AmpConfig("DAI1 Passband filtering: music mode", 0b1, 0x18, 7, 0b10000000), AmpConfig("DAI1 voice mode gain (DV1G)", 0b00, 0x2F, 4, 0b00110000), AmpConfig("DAI1 attenuation (DV1)", 0x0, 0x2F, 0, 0b00001111), AmpConfig("DAI2 attenuation (DV2)", 0x0, 0x31, 0, 0b00001111), AmpConfig("DAI2: DC blocking", 0b1, 0x20, 0, 0b00000001), AmpConfig("DAI2: High sample rate", 0b0, 0x20, 3, 0b00001000), - AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000), + AmpConfig("ALC enable", 0b1, 0x43, 7, 0b10000000), AmpConfig("ALC/excursion limiter release time", 0b101, 0x43, 4, 0b01110000), + AmpConfig("ALC multiband enable", 0b1, 0x43, 3, 0b00001000), AmpConfig("DAI1 EQ enable", 0b0, 0x49, 0, 0b00000001), - AmpConfig("DAI2 EQ enable", 0b0, 0x49, 1, 0b00000010), + AmpConfig("DAI2 EQ enable", 0b1, 0x49, 1, 0b00000010), AmpConfig("DAI2 EQ clip detection disabled", 0b1, 0x32, 4, 0b00010000), AmpConfig("DAI2 EQ attenuation", 0x5, 0x32, 0, 0b00001111), AmpConfig("Excursion limiter upper corner freq", 0b100, 0x41, 4, 0b01110000), @@ -62,11 +63,11 @@ BASE_CONFIG = [ AmpConfig("Zero-crossing detection disabled", 0b0, 0x49, 5, 0b00100000), ] -BASE_CONFIG += configs_from_eq_params(0x84, EQParams(0x65C4, 0xC07C, 0x3D66, 0x07D9, 0x120F)) +BASE_CONFIG += configs_from_eq_params(0x84, EQParams(0x274F, 0xC0FF, 0x3BF9, 0x0B3C, 0x1656)) BASE_CONFIG += configs_from_eq_params(0x8E, EQParams(0x1009, 0xC6BF, 0x2952, 0x1C97, 0x30DF)) -BASE_CONFIG += configs_from_eq_params(0x98, EQParams(0x2822, 0xC1C7, 0x3B50, 0x0EF8, 0x180A)) -BASE_CONFIG += configs_from_eq_params(0xA2, EQParams(0x1009, 0xC5C2, 0x271F, 0x1A87, 0x32A6)) -BASE_CONFIG += configs_from_eq_params(0xAC, EQParams(0x2000, 0xCA1E, 0x4000, 0x2287, 0x0000)) +BASE_CONFIG += configs_from_eq_params(0x98, EQParams(0x0F75, 0xCBE5, 0x0ED2, 0x2528, 0x3E42)) +BASE_CONFIG += configs_from_eq_params(0xA2, EQParams(0x091F, 0x3D4C, 0xCE11, 0x1266, 0x2807)) +BASE_CONFIG += configs_from_eq_params(0xAC, EQParams(0x0A9E, 0x3F20, 0xE573, 0x0A8B, 0x3A3B)) class Amplifier: AMP_I2C_BUS = 0 diff --git a/selfdrive/hardware/tici/hardware.h b/selfdrive/hardware/tici/hardware.h index abd7e9297a..549170412d 100644 --- a/selfdrive/hardware/tici/hardware.h +++ b/selfdrive/hardware/tici/hardware.h @@ -9,8 +9,8 @@ class HardwareTici : public HardwareNone { public: - static constexpr float MAX_VOLUME = 1.0; - static constexpr float MIN_VOLUME = 0.4; + static constexpr float MAX_VOLUME = 0.9; + static constexpr float MIN_VOLUME = 0.15; static bool TICI() { return true; } static std::string get_os_version() { return "AGNOS " + util::read_file("/VERSION"); diff --git a/selfdrive/hardware/tici/hardware.py b/selfdrive/hardware/tici/hardware.py index ca2705c801..5b7596df26 100644 --- a/selfdrive/hardware/tici/hardware.py +++ b/selfdrive/hardware/tici/hardware.py @@ -275,7 +275,7 @@ class Tici(HardwareBase): os.system("sudo poweroff") def get_thermal_config(self): - return ThermalConfig(cpu=((1, 2, 3, 4, 5, 6, 7, 8), 1000), gpu=((48,49), 1000), mem=(15, 1000), bat=(None, 1), ambient=(65, 1000)) + return ThermalConfig(cpu=((1, 2, 3, 4, 5, 6, 7, 8), 1000), gpu=((48,49), 1000), mem=(15, 1000), bat=(None, 1), ambient=(65, 1000), pmic=((35, 36), 1000)) def set_screen_brightness(self, percentage): try: diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 8bdc30a8d2..8e8641e7d5 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -18,6 +18,7 @@ const double MIN_STD_SANITY_CHECK = 1e-5; // m or rad const double VALID_TIME_SINCE_RESET = 1.0; // s const double VALID_POS_STD = 50.0; // m const double MAX_RESET_TRACKER = 5.0; +const double SANE_GPS_UNCERTAINTY = 1500.0; // m static VectorXd floatlist2vector(const capnp::List::Reader& floatlist) { VectorXd res(floatlist.size()); @@ -130,16 +131,16 @@ void Localizer::build_live_location(cereal::LiveLocationKalman::Builder& fix) { Vector3d nans = Vector3d(NAN, NAN, NAN); // write measurements to msg - init_measurement(fix.initPositionGeodetic(), fix_pos_geo_vec, nans, this->last_gps_fix > 0); - init_measurement(fix.initPositionECEF(), fix_ecef, fix_ecef_std, this->last_gps_fix > 0); - init_measurement(fix.initVelocityECEF(), vel_ecef, vel_ecef_std, this->last_gps_fix > 0); - init_measurement(fix.initVelocityNED(), ned_vel, nans, this->last_gps_fix > 0); + init_measurement(fix.initPositionGeodetic(), fix_pos_geo_vec, nans, this->gps_mode); + init_measurement(fix.initPositionECEF(), fix_ecef, fix_ecef_std, this->gps_mode); + init_measurement(fix.initVelocityECEF(), vel_ecef, vel_ecef_std, this->gps_mode); + init_measurement(fix.initVelocityNED(), ned_vel, nans, this->gps_mode); init_measurement(fix.initVelocityDevice(), vel_device, vel_device_std, true); init_measurement(fix.initAccelerationDevice(), accDevice, accDeviceErr, true); - init_measurement(fix.initOrientationECEF(), orientation_ecef, orientation_ecef_std, this->last_gps_fix > 0); - init_measurement(fix.initCalibratedOrientationECEF(), calibrated_orientation_ecef, nans, this->calibrated && this->last_gps_fix > 0); - init_measurement(fix.initOrientationNED(), orientation_ned, nans, this->last_gps_fix > 0); - init_measurement(fix.initCalibratedOrientationNED(), calibrated_orientation_ned, nans, this->calibrated && this->last_gps_fix > 0); + init_measurement(fix.initOrientationECEF(), orientation_ecef, orientation_ecef_std, this->gps_mode); + init_measurement(fix.initCalibratedOrientationECEF(), calibrated_orientation_ecef, nans, this->calibrated && this->gps_mode); + init_measurement(fix.initOrientationNED(), orientation_ned, nans, this->gps_mode); + init_measurement(fix.initCalibratedOrientationNED(), calibrated_orientation_ned, nans, this->calibrated && this->gps_mode); init_measurement(fix.initAngularVelocityDevice(), angVelocityDevice, angVelocityDeviceErr, true); init_measurement(fix.initVelocityCalibrated(), vel_calib, vel_calib_std, this->calibrated); init_measurement(fix.initAngularVelocityCalibrated(), ang_vel_calib, ang_vel_calib_std, this->calibrated); @@ -243,27 +244,39 @@ void Localizer::handle_sensors(double current_time, const capnp::List observe current obs with reasonable STD + this->kf->predict(current_time); + + VectorXd current_x = this->kf->get_x(); + VectorXd ecef_pos = current_x.segment(STATE_ECEF_POS_START); + VectorXd ecef_vel = current_x.segment(STATE_ECEF_VELOCITY_START); + MatrixXdr ecef_pos_R = this->kf->get_fake_gps_pos_cov(); + MatrixXdr ecef_vel_R = this->kf->get_fake_gps_vel_cov(); + + this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); + this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_VEL, { ecef_vel }, { ecef_vel_R }); +} + void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::Reader& log) { // ignore the message if the fix is invalid - if (log.getFlags() % 2 == 0) { - return; - } - - // Sanity checks - if ((log.getVerticalAccuracy() <= 0) || (log.getSpeedAccuracy() <= 0) || (log.getBearingAccuracyDeg() <= 0)) { - return; - } - if ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK)) { - return; - } + bool gps_invalid_flag = (log.getFlags() % 2 == 0); + bool gps_unreasonable = (Vector2d(log.getAccuracy(), log.getVerticalAccuracy()).norm() >= SANE_GPS_UNCERTAINTY); + bool gps_accuracy_insane = ((log.getVerticalAccuracy() <= 0) || (log.getSpeedAccuracy() <= 0) || (log.getBearingAccuracyDeg() <= 0)); + bool gps_lat_lng_alt_insane = ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK)); + bool gps_vel_insane = (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK); - if (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK) { + if (gps_invalid_flag || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane){ + this->determine_gps_mode(current_time); return; } // Process message this->last_gps_fix = current_time; + this->gps_mode = true; Geodetic geodetic = { log.getLatitude(), log.getLongitude(), log.getAltitude() }; this->converter = std::make_unique(geodetic); @@ -273,7 +286,7 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R MatrixXdr ecef_vel_R = Vector3d::Constant(std::pow(log.getSpeedAccuracy() * 10.0, 2)).asDiagonal(); this->unix_timestamp_millis = log.getTimestamp(); - double gps_est_error = (this->kf->get_x().head(3) - ecef_pos).norm(); + double gps_est_error = (this->kf->get_x().segment(STATE_ECEF_POS_START) - ecef_pos).norm(); VectorXd orientation_ecef = quat2euler(vector2quat(this->kf->get_x().segment(STATE_ECEF_ORIENTATION_START))); VectorXd orientation_ned = ned_euler_from_ecef({ ecef_pos(0), ecef_pos(1), ecef_pos(2) }, orientation_ecef); @@ -290,11 +303,11 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R if (ecef_vel.norm() > 5.0 && orientation_error.norm() > 1.0) { LOGE("Locationd vs ubloxLocation orientation difference too large, kalman reset"); - this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos); + this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_ORIENTATION_FROM_GPS, { initial_pose_ecef_quat }); } else if (gps_est_error > 100.0) { LOGE("Locationd vs ubloxLocation position difference too large, kalman reset"); - this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos); + this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); } this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); @@ -358,7 +371,8 @@ void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibra void Localizer::reset_kalman(double current_time) { VectorXd init_x = this->kf->get_initial_x(); - this->reset_kalman(current_time, init_x.segment<4>(3), init_x.head(3)); + MatrixXdr init_P = this->kf->get_initial_P(); + this->reset_kalman(current_time, init_x, init_P); } void Localizer::finite_check(double current_time) { @@ -390,13 +404,27 @@ void Localizer::update_reset_tracker() { } } -void Localizer::reset_kalman(double current_time, VectorXd init_orient, VectorXd init_pos) { +void Localizer::reset_kalman(double current_time, VectorXd init_orient, VectorXd init_pos, VectorXd init_vel, MatrixXdr init_pos_R, MatrixXdr init_vel_R) { // too nonlinear to init on completely wrong - VectorXd init_x = this->kf->get_initial_x(); + VectorXd current_x = this->kf->get_x(); + MatrixXdr current_P = this->kf->get_P(); MatrixXdr init_P = this->kf->get_initial_P(); - init_x.segment<4>(3) = init_orient; - init_x.head(3) = init_pos; + MatrixXdr reset_orientation_P = this->kf->get_reset_orientation_P(); + int non_ecef_state_err_len = init_P.rows() - (STATE_ECEF_POS_ERR_LEN + STATE_ECEF_ORIENTATION_ERR_LEN + STATE_ECEF_VELOCITY_ERR_LEN); + + current_x.segment(STATE_ECEF_ORIENTATION_START) = init_orient; + current_x.segment(STATE_ECEF_VELOCITY_START) = init_vel; + current_x.segment(STATE_ECEF_POS_START) = init_pos; + + init_P.block(STATE_ECEF_POS_ERR_START, STATE_ECEF_POS_ERR_START).diagonal() = init_pos_R.diagonal(); + init_P.block(STATE_ECEF_ORIENTATION_ERR_START, STATE_ECEF_ORIENTATION_ERR_START).diagonal() = reset_orientation_P.diagonal(); + init_P.block(STATE_ECEF_VELOCITY_ERR_START, STATE_ECEF_VELOCITY_ERR_START).diagonal() = init_vel_R.diagonal(); + init_P.block(STATE_ANGULAR_VELOCITY_ERR_START, STATE_ANGULAR_VELOCITY_ERR_START, non_ecef_state_err_len, non_ecef_state_err_len).diagonal() = current_P.block(STATE_ANGULAR_VELOCITY_ERR_START, STATE_ANGULAR_VELOCITY_ERR_START, non_ecef_state_err_len, non_ecef_state_err_len).diagonal(); + + this->reset_kalman(current_time, current_x, init_P); +} +void Localizer::reset_kalman(double current_time, VectorXd init_x, MatrixXdr init_P) { this->kf->init_state(init_x, init_P, current_time); this->last_reset_time = current_time; this->reset_tracker += 1.0; @@ -447,6 +475,22 @@ bool Localizer::isGpsOK() { return this->kf->get_filter_time() - this->last_gps_fix < 1.0; } +void Localizer::determine_gps_mode(double current_time) { + // 1. If the pos_std is greater than what's not acceptible and localizer is in gps-mode, reset to no-gps-mode + // 2. If the pos_std is greater than what's not acceptible and localizer is in no-gps-mode, fake obs + // 3. If the pos_std is smaller than what's not acceptible, let gps-mode be whatever it is + VectorXd current_pos_std = this->kf->get_P().block(STATE_ECEF_POS_ERR_START, STATE_ECEF_POS_ERR_START).diagonal().array().sqrt(); + if (current_pos_std.norm() > SANE_GPS_UNCERTAINTY){ + if (this->gps_mode){ + this->gps_mode = false; + this->reset_kalman(current_time); + } + else{ + this->input_fake_gps_observations(current_time); + } + } +} + int Localizer::locationd_thread() { const std::initializer_list service_list = { "gpsLocationExternal", "sensorEvents", "cameraOdometry", "liveCalibration", "carState" }; diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index 60fed112cf..9bc864bf6d 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -27,11 +27,13 @@ public: int locationd_thread(); void reset_kalman(double current_time = NAN); - void reset_kalman(double current_time, Eigen::VectorXd init_orient, Eigen::VectorXd init_pos); + void reset_kalman(double current_time, Eigen::VectorXd init_orient, Eigen::VectorXd init_pos, Eigen::VectorXd init_vel, MatrixXdr init_pos_R, MatrixXdr init_vel_R); + void reset_kalman(double current_time, Eigen::VectorXd init_x, MatrixXdr init_P); void finite_check(double current_time = NAN); void time_check(double current_time = NAN); void update_reset_tracker(); bool isGpsOK(); + void determine_gps_mode(double current_time); kj::ArrayPtr get_message_bytes(MessageBuilder& msg_builder, uint64_t logMonoTime, bool inputsOK, bool sensorsOK, bool gpsOK); @@ -49,6 +51,8 @@ public: void handle_cam_odo(double current_time, const cereal::CameraOdometry::Reader& log); void handle_live_calib(double current_time, const cereal::LiveCalibrationData::Reader& log); + void input_fake_gps_observations(double current_time); + private: std::unique_ptr kf; @@ -67,4 +71,5 @@ private: double last_gps_fix = 0; double reset_tracker = 0.0; bool device_fell = false; + bool gps_mode = false; }; diff --git a/selfdrive/locationd/models/live_kf.cc b/selfdrive/locationd/models/live_kf.cc index 541c0f7730..5ff0f26995 100755 --- a/selfdrive/locationd/models/live_kf.cc +++ b/selfdrive/locationd/models/live_kf.cc @@ -28,11 +28,14 @@ std::vector> get_vec_mapmat(std::vector& mat_ve } LiveKalman::LiveKalman() { - this->dim_state = 26; - this->dim_state_err = 25; + this->dim_state = live_initial_x.rows(); + this->dim_state_err = live_initial_P_diag.rows(); this->initial_x = live_initial_x; this->initial_P = live_initial_P_diag.asDiagonal(); + this->fake_gps_pos_cov = live_fake_gps_pos_cov_diag.asDiagonal(); + this->fake_gps_vel_cov = live_fake_gps_vel_cov_diag.asDiagonal(); + this->reset_orientation_P = live_reset_orientation_diag.asDiagonal(); this->Q = live_Q_diag.asDiagonal(); for (auto& pair : live_obs_noise_diag) { this->obs_noise[pair.first] = pair.second.asDiagonal(); @@ -87,6 +90,10 @@ std::optional LiveKalman::predict_and_observe(double t, int kind, std: return r; } +void LiveKalman::predict(double t) { + this->filter->predict(t); +} + Eigen::VectorXd LiveKalman::get_initial_x() { return this->initial_x; } @@ -95,6 +102,18 @@ MatrixXdr LiveKalman::get_initial_P() { return this->initial_P; } +MatrixXdr LiveKalman::get_fake_gps_pos_cov() { + return this->fake_gps_pos_cov; +} + +MatrixXdr LiveKalman::get_fake_gps_vel_cov() { + return this->fake_gps_vel_cov; +} + +MatrixXdr LiveKalman::get_reset_orientation_P() { + return this->reset_orientation_P; +} + MatrixXdr LiveKalman::H(VectorXd in) { assert(in.size() == 6); Matrix res; diff --git a/selfdrive/locationd/models/live_kf.h b/selfdrive/locationd/models/live_kf.h index 4cd4756c9f..06ec3854cb 100755 --- a/selfdrive/locationd/models/live_kf.h +++ b/selfdrive/locationd/models/live_kf.h @@ -36,9 +36,13 @@ public: std::optional predict_and_update_odo_speed(std::vector speed, double t, int kind); std::optional predict_and_update_odo_trans(std::vector trans, double t, int kind); std::optional predict_and_update_odo_rot(std::vector rot, double t, int kind); + void predict(double t); Eigen::VectorXd get_initial_x(); MatrixXdr get_initial_P(); + MatrixXdr get_fake_gps_pos_cov(); + MatrixXdr get_fake_gps_vel_cov(); + MatrixXdr get_reset_orientation_P(); MatrixXdr H(Eigen::VectorXd in); @@ -52,6 +56,9 @@ private: Eigen::VectorXd initial_x; MatrixXdr initial_P; + MatrixXdr fake_gps_pos_cov; + MatrixXdr fake_gps_vel_cov; + MatrixXdr reset_orientation_P; MatrixXdr Q; // process noise std::unordered_map obs_noise; }; diff --git a/selfdrive/locationd/models/live_kf.py b/selfdrive/locationd/models/live_kf.py index 75415a2845..fa52945932 100755 --- a/selfdrive/locationd/models/live_kf.py +++ b/selfdrive/locationd/models/live_kf.py @@ -26,10 +26,8 @@ class States(): ECEF_VELOCITY = slice(7, 10) # ecef velocity in m/s ANGULAR_VELOCITY = slice(10, 13) # roll, pitch and yaw rates in device frame in radians/s GYRO_BIAS = slice(13, 16) # roll, pitch and yaw biases - ODO_SCALE = slice(16, 17) # odometer scale - ACCELERATION = slice(17, 20) # Acceleration in device frame in m/s**2 - IMU_OFFSET = slice(20, 23) # imu offset angles in radians - ACC_BIAS = slice(23, 26) + ACCELERATION = slice(16, 19) # Acceleration in device frame in m/s**2 + ACC_BIAS = slice(19, 22) # Acceletometer bias in m/s**2 # Error-state has different slices because it is an ESKF ECEF_POS_ERR = slice(0, 3) @@ -37,10 +35,8 @@ class States(): ECEF_VELOCITY_ERR = slice(6, 9) ANGULAR_VELOCITY_ERR = slice(9, 12) GYRO_BIAS_ERR = slice(12, 15) - ODO_SCALE_ERR = slice(15, 16) - ACCELERATION_ERR = slice(16, 19) - IMU_OFFSET_ERR = slice(19, 22) - ACC_BIAS_ERR = slice(22, 25) + ACCELERATION_ERR = slice(15, 18) + ACC_BIAS_ERR = slice(18, 21) class LiveKalman(): @@ -51,38 +47,37 @@ class LiveKalman(): 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0]) # state covariance - initial_P_diag = np.array([1e3**2, 1e3**2, 1e3**2, - 0.5**2, 0.5**2, 0.5**2, + initial_P_diag = np.array([10**2, 10**2, 10**2, + 0.01**2, 0.01**2, 0.01**2, 10**2, 10**2, 10**2, 1**2, 1**2, 1**2, 1**2, 1**2, 1**2, - 0.02**2, 100**2, 100**2, 100**2, - 0.01**2, 0.01**2, 0.01**2, 0.01**2, 0.01**2, 0.01**2]) + # state covariance when resetting midway in a segment + reset_orientation_diag = np.array([1**2, 1**2, 1**2]) + + # fake observation covariance, to ensure the uncertainty estimate of the filter is under control + fake_gps_pos_cov_diag = np.array([1000**2, 1000**2, 1000**2]) + fake_gps_vel_cov_diag = np.array([10**2, 10**2, 10**2]) + # process noise Q_diag = np.array([0.03**2, 0.03**2, 0.03**2, 0.001**2, 0.001**2, 0.001**2, 0.01**2, 0.01**2, 0.01**2, 0.1**2, 0.1**2, 0.1**2, (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2, - (0.02 / 100)**2, 3**2, 3**2, 3**2, - (0.05 / 60)**2, (0.05 / 60)**2, (0.05 / 60)**2, 0.005**2, 0.005**2, 0.005**2]) - obs_noise_diag = {ObservationKind.ODOMETRIC_SPEED: np.array([0.2**2]), - ObservationKind.PHONE_GYRO: np.array([0.025**2, 0.025**2, 0.025**2]), + obs_noise_diag = {ObservationKind.PHONE_GYRO: np.array([0.025**2, 0.025**2, 0.025**2]), ObservationKind.PHONE_ACCEL: np.array([.5**2, .5**2, .5**2]), ObservationKind.CAMERA_ODO_ROTATION: np.array([0.05**2, 0.05**2, 0.05**2]), - ObservationKind.IMU_FRAME: np.array([0.05**2, 0.05**2, 0.05**2]), ObservationKind.NO_ROT: np.array([0.005**2, 0.005**2, 0.005**2]), ObservationKind.NO_ACCEL: np.array([0.05**2, 0.05**2, 0.05**2]), ObservationKind.ECEF_POS: np.array([5**2, 5**2, 5**2]), @@ -105,7 +100,6 @@ class LiveKalman(): vroll, vpitch, vyaw = omega roll_bias, pitch_bias, yaw_bias = state[States.GYRO_BIAS, :] acceleration = state[States.ACCELERATION, :] - imu_angles = state[States.IMU_OFFSET, :] acc_bias = state[States.ACC_BIAS, :] dt = sp.Symbol('dt') @@ -140,7 +134,6 @@ class LiveKalman(): omega_err = state_err[States.ANGULAR_VELOCITY_ERR, :] acceleration_err = state_err[States.ACCELERATION_ERR, :] - # Time derivative of the state error as a function of state error and state quat_err_matrix = euler_rotate(quat_err[0], quat_err[1], quat_err[2]) q_err_dot = quat_err_matrix * quat_rot * (omega + omega_err) @@ -183,7 +176,6 @@ class LiveKalman(): # # Observation functions # - # imu_rot = euler_rotate(*imu_angles) h_gyro_sym = sp.Matrix([ vroll + roll_bias, vpitch + pitch_bias, @@ -194,19 +186,12 @@ class LiveKalman(): h_acc_sym = (gravity + acceleration + acc_bias) h_acc_stationary_sym = acceleration h_phone_rot_sym = sp.Matrix([vroll, vpitch, vyaw]) - - speed = sp.sqrt(vx**2 + vy**2 + vz**2 + 1e-6) - h_speed_sym = sp.Matrix([speed]) - h_pos_sym = sp.Matrix([x, y, z]) h_vel_sym = sp.Matrix([vx, vy, vz]) h_orientation_sym = q - h_imu_frame_sym = sp.Matrix(imu_angles) - h_relative_motion = sp.Matrix(quat_rot.T * v) - obs_eqs = [[h_speed_sym, ObservationKind.ODOMETRIC_SPEED, None], - [h_gyro_sym, ObservationKind.PHONE_GYRO, None], + obs_eqs = [[h_gyro_sym, ObservationKind.PHONE_GYRO, None], [h_phone_rot_sym, ObservationKind.NO_ROT, None], [h_acc_sym, ObservationKind.PHONE_ACCEL, None], [h_pos_sym, ObservationKind.ECEF_POS, None], @@ -214,12 +199,11 @@ class LiveKalman(): [h_orientation_sym, ObservationKind.ECEF_ORIENTATION_FROM_GPS, None], [h_relative_motion, ObservationKind.CAMERA_ODO_TRANSLATION, None], [h_phone_rot_sym, ObservationKind.CAMERA_ODO_ROTATION, None], - [h_imu_frame_sym, ObservationKind.IMU_FRAME, None], [h_acc_stationary_sym, ObservationKind.NO_ACCEL, None]] # this returns a sympy routine for the jacobian of the observation function of the local vel in_vec = sp.MatrixSymbol('in_vec', 6, 1) # roll, pitch, yaw, vx, vy, vz - h = euler_rotate(in_vec[0], in_vec[1], in_vec[2]).T*(sp.Matrix([in_vec[3], in_vec[4], in_vec[5]])) + h = euler_rotate(in_vec[0], in_vec[1], in_vec[2]).T * (sp.Matrix([in_vec[3], in_vec[4], in_vec[5]])) extra_routines = [('H', h.jacobian(in_vec), [in_vec])] gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state_err, eskf_params, extra_routines=extra_routines) @@ -241,6 +225,9 @@ class LiveKalman(): live_kf_header += f"static const Eigen::VectorXd live_initial_x = {numpy2eigenstring(LiveKalman.initial_x)};\n" live_kf_header += f"static const Eigen::VectorXd live_initial_P_diag = {numpy2eigenstring(LiveKalman.initial_P_diag)};\n" + live_kf_header += f"static const Eigen::VectorXd live_fake_gps_pos_cov_diag = {numpy2eigenstring(LiveKalman.fake_gps_pos_cov_diag)};\n" + live_kf_header += f"static const Eigen::VectorXd live_fake_gps_vel_cov_diag = {numpy2eigenstring(LiveKalman.fake_gps_vel_cov_diag)};\n" + live_kf_header += f"static const Eigen::VectorXd live_reset_orientation_diag = {numpy2eigenstring(LiveKalman.reset_orientation_diag)};\n" live_kf_header += f"static const Eigen::VectorXd live_Q_diag = {numpy2eigenstring(LiveKalman.Q_diag)};\n" live_kf_header += "static const std::unordered_map> live_obs_noise_diag = {\n" for kind, noise in LiveKalman.obs_noise_diag.items(): diff --git a/selfdrive/locationd/models/loc_kf.py b/selfdrive/locationd/models/loc_kf.py index 2739494225..adb9098d4d 100755 --- a/selfdrive/locationd/models/loc_kf.py +++ b/selfdrive/locationd/models/loc_kf.py @@ -41,14 +41,17 @@ class States(): CLOCK_BIAS = slice(13, 14) # clock bias in light-meters, CLOCK_DRIFT = slice(14, 15) # clock drift in light-meters/s, GYRO_BIAS = slice(15, 18) # roll, pitch and yaw biases - ODO_SCALE = slice(18, 19) # odometer scale + ODO_SCALE_UNUSED = slice(18, 19) # odometer scale ACCELERATION = slice(19, 22) # Acceleration in device frame in m/s**2 - FOCAL_SCALE = slice(22, 23) # focal length scale + FOCAL_SCALE_UNUSED = slice(22, 23) # focal length scale IMU_OFFSET = slice(23, 26) # imu offset angles in radians GLONASS_BIAS = slice(26, 27) # GLONASS bias in m expressed as bias + freq_num*freq_slope GLONASS_FREQ_SLOPE = slice(27, 28) # GLONASS bias in m expressed as bias + freq_num*freq_slope CLOCK_ACCELERATION = slice(28, 29) # clock acceleration in light-meters/s**2, - ACCELEROMETER_SCALE = slice(29, 30) # scale of mems accelerometer + ACCELEROMETER_SCALE_UNUSED = slice(29, 30) # scale of mems accelerometer + ACCELEROMETER_BIAS = slice(30, 33) # bias of mems accelerometer + # We curently do not use ACCELEROMETER_SCALE to avoid instability due to too many free variables (ACCELEROMETER_SCALE, ACCELEROMETER_BIAS, IMU_OFFSET). + # From experiments we see that ACCELEROMETER_BIAS is more correct than ACCELEROMETER_SCALE # Error-state has different slices because it is an ESKF ECEF_POS_ERR = slice(0, 3) @@ -58,14 +61,15 @@ class States(): CLOCK_BIAS_ERR = slice(12, 13) CLOCK_DRIFT_ERR = slice(13, 14) GYRO_BIAS_ERR = slice(14, 17) - ODO_SCALE_ERR = slice(17, 18) + ODO_SCALE_ERR_UNUSED = slice(17, 18) ACCELERATION_ERR = slice(18, 21) - FOCAL_SCALE_ERR = slice(21, 22) + FOCAL_SCALE_ERR_UNUSED = slice(21, 22) IMU_OFFSET_ERR = slice(22, 25) GLONASS_BIAS_ERR = slice(25, 26) GLONASS_FREQ_SLOPE_ERR = slice(26, 27) CLOCK_ACCELERATION_ERR = slice(27, 28) - ACCELEROMETER_SCALE_ERR = slice(28, 29) + ACCELEROMETER_SCALE_ERR_UNUSED = slice(28, 29) + ACCELEROMETER_BIAS_ERR = slice(29, 32) class LocKalman(): @@ -82,7 +86,8 @@ class LocKalman(): 0, 0, 0, 0, 0, 0, - 1], dtype=np.float64) + 1, + 0, 0, 0], dtype=np.float64) # state covariance P_initial = np.diag([1e16, 1e16, 1e16, @@ -97,7 +102,8 @@ class LocKalman(): (0.01)**2, (0.01)**2, (0.01)**2, 10**2, 1**2, 0.2**2, - 0.05**2]) + 0.05**2, + 0.05**2, 0.05**2, 0.05**2]) # process noise Q = np.diag([0.03**2, 0.03**2, 0.03**2, @@ -112,7 +118,8 @@ class LocKalman(): (0.05 / 60)**2, (0.05 / 60)**2, (0.05 / 60)**2, (.1)**2, (.01)**2, 0.005**2, - (0.02 / 100)**2]) + (0.02 / 100)**2, + (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2]) # measurements that need to pass mahalanobis distance outlier rejector maha_test_kinds = [ObservationKind.ORB_FEATURES] # , ObservationKind.PSEUDORANGE, ObservationKind.PSEUDORANGE_RATE] @@ -145,16 +152,14 @@ class LocKalman(): cb = state[States.CLOCK_BIAS, :] cd = state[States.CLOCK_DRIFT, :] roll_bias, pitch_bias, yaw_bias = state[States.GYRO_BIAS, :] - odo_scale = state[States.ODO_SCALE, :] acceleration = state[States.ACCELERATION, :] - focal_scale = state[States.FOCAL_SCALE, :] imu_angles = state[States.IMU_OFFSET, :] imu_angles[0, 0] = 0 imu_angles[2, 0] = 0 glonass_bias = state[States.GLONASS_BIAS, :] glonass_freq_slope = state[States.GLONASS_FREQ_SLOPE, :] ca = state[States.CLOCK_ACCELERATION, :] - accel_scale = state[States.ACCELEROMETER_SCALE, :] + accel_bias = state[States.ACCELEROMETER_BIAS, :] dt = sp.Symbol('dt') @@ -251,13 +256,10 @@ class LocKalman(): sat_pos_freq_sym = sp.MatrixSymbol('sat_pos', 4, 1) sat_pos_vel_sym = sp.MatrixSymbol('sat_pos_vel', 6, 1) # sat_los_sym = sp.MatrixSymbol('sat_los', 3, 1) - orb_epos_sym = sp.MatrixSymbol('orb_epos_sym', 3, 1) # expand extra args sat_x, sat_y, sat_z, glonass_freq = sat_pos_freq_sym sat_vx, sat_vy, sat_vz = sat_pos_vel_sym[3:] - # los_x, los_y, los_z = sat_los_sym - orb_x, orb_y, orb_z = orb_epos_sym h_pseudorange_sym = sp.Matrix([ sp.sqrt( @@ -290,37 +292,21 @@ class LocKalman(): pos = sp.Matrix([x, y, z]) # add 1 for stability, prevent division by 0 gravity = quat_rot.T * ((EARTH_GM / ((x**2 + y**2 + z**2 + 1)**(3.0 / 2.0))) * pos) - h_acc_sym = imu_rot * (accel_scale[0] * (gravity + acceleration)) + h_acc_sym = imu_rot * (gravity + acceleration + accel_bias) + h_acc_stationary_sym = acceleration h_phone_rot_sym = sp.Matrix([vroll, vpitch, vyaw]) - - speed = sp.sqrt(vx**2 + vy**2 + vz**2) - h_speed_sym = sp.Matrix([speed * odo_scale]) - - # orb stuff - orb_pos_sym = sp.Matrix([orb_x - x, orb_y - y, orb_z - z]) - orb_pos_rot_sym = quat_rot.T * orb_pos_sym - s = orb_pos_rot_sym[0] - h_orb_point_sym = sp.Matrix([(1 / s) * (orb_pos_rot_sym[1]), - (1 / s) * (orb_pos_rot_sym[2])]) - - h_pos_sym = sp.Matrix([x, y, z]) - h_imu_frame_sym = sp.Matrix(imu_angles) - h_relative_motion = sp.Matrix(quat_rot.T * v) - obs_eqs = [[h_speed_sym, ObservationKind.ODOMETRIC_SPEED, None], - [h_gyro_sym, ObservationKind.PHONE_GYRO, None], + obs_eqs = [[h_gyro_sym, ObservationKind.PHONE_GYRO, None], [h_phone_rot_sym, ObservationKind.NO_ROT, None], [h_acc_sym, ObservationKind.PHONE_ACCEL, None], [h_pseudorange_sym, ObservationKind.PSEUDORANGE_GPS, sat_pos_freq_sym], [h_pseudorange_glonass_sym, ObservationKind.PSEUDORANGE_GLONASS, sat_pos_freq_sym], [h_pseudorange_rate_sym, ObservationKind.PSEUDORANGE_RATE_GPS, sat_pos_vel_sym], [h_pseudorange_rate_sym, ObservationKind.PSEUDORANGE_RATE_GLONASS, sat_pos_vel_sym], - [h_pos_sym, ObservationKind.ECEF_POS, None], [h_relative_motion, ObservationKind.CAMERA_ODO_TRANSLATION, None], [h_phone_rot_sym, ObservationKind.CAMERA_ODO_ROTATION, None], - [h_imu_frame_sym, ObservationKind.IMU_FRAME, None], - [h_orb_point_sym, ObservationKind.ORB_POINT, orb_epos_sym]] + [h_acc_stationary_sym, ObservationKind.NO_ACCEL, None]] # MSCKF configuration if N > 0: @@ -368,7 +354,8 @@ class LocKalman(): ObservationKind.CAMERA_ODO_ROTATION: np.diag([0.05**2, 0.05**2, 0.05**2]), ObservationKind.IMU_FRAME: np.diag([0.05**2, 0.05**2, 0.05**2]), ObservationKind.NO_ROT: np.diag([0.0025**2, 0.0025**2, 0.0025**2]), - ObservationKind.ECEF_POS: np.diag([5**2, 5**2, 5**2])} + ObservationKind.ECEF_POS: np.diag([5**2, 5**2, 5**2]), + ObservationKind.NO_ACCEL: np.diag([0.0025**2, 0.0025**2, 0.0025**2])} # MSCKF stuff self.N = N @@ -382,7 +369,7 @@ class LocKalman(): self.computer = LstSqComputer(generated_dir, N) self.max_tracks = max_tracks - self.quaternion_idxs = [3,] + [(self.dim_main + i*self.dim_augment + 3)for i in range(self.N)] + self.quaternion_idxs = [3, ] + [(self.dim_main + i * self.dim_augment + 3)for i in range(self.N)] # init filter self.filter = EKF_sym(generated_dir, name, Q, x_initial, P_initial, self.dim_main, self.dim_main_err, @@ -440,14 +427,10 @@ class LocKalman(): r = self.predict_and_update_pseudorange(data, t, kind) elif kind == ObservationKind.PSEUDORANGE_RATE_GPS or kind == ObservationKind.PSEUDORANGE_RATE_GLONASS: r = self.predict_and_update_pseudorange_rate(data, t, kind) - elif kind == ObservationKind.ORB_POINT: - r = self.predict_and_update_orb(data, t, kind) elif kind == ObservationKind.ORB_FEATURES: r = self.predict_and_update_orb_features(data, t, kind) elif kind == ObservationKind.MSCKF_TEST: r = self.predict_and_update_msckf_test(data, t, kind) - elif kind == ObservationKind.ODOMETRIC_SPEED: - r = self.predict_and_update_odo_speed(data, t, kind) else: r = self.filter.predict_and_update_batch(t, kind, data, self.get_R(kind, len(data))) # Normalize quats @@ -487,21 +470,6 @@ class LocKalman(): z[i, :] = z_i return self.filter.predict_and_update_batch(t, kind, z, R, sat_pos_vel) - def predict_and_update_orb(self, orb, t, kind): - true_pos = orb[:, 2:] - z = orb[:, :2] - R = np.zeros((len(orb), 2, 2)) - for i, _ in enumerate(z): - R[i, :, :] = np.diag([10**2, 10**2]) - return self.filter.predict_and_update_batch(t, kind, z, R, true_pos) - - def predict_and_update_odo_speed(self, speed, t, kind): - z = np.array(speed) - R = np.zeros((len(speed), 1, 1)) - for i, _ in enumerate(z): - R[i, :, :] = np.diag([0.2**2]) - return self.filter.predict_and_update_batch(t, kind, z, R) - def predict_and_update_odo_trans(self, trans, t, kind): z = trans[:, :3] R = np.zeros((len(trans), 3, 3)) diff --git a/selfdrive/loggerd/SConscript b/selfdrive/loggerd/SConscript index 7e41c9d3ee..2adcfb846c 100644 --- a/selfdrive/loggerd/SConscript +++ b/selfdrive/loggerd/SConscript @@ -24,8 +24,8 @@ if arch == "Darwin": del libs[libs.index('OpenCL')] env['FRAMEWORKS'] = ['OpenCL'] -env.Program(src, LIBS=libs) +env.Program('loggerd', ['main.cc'] + src, LIBS=libs) env.Program('bootlog.cc', LIBS=libs) if GetOption('test'): - env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_logger.cc', env.Object('logger_util', '#/selfdrive/ui/replay/util.cc')], LIBS=[libs] + ['curl', 'crypto']) + env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_loggerd.cc', 'tests/test_logger.cc', env.Object('logger_util', '#/selfdrive/ui/replay/util.cc')] + src, LIBS=[libs] + ['curl', 'crypto', 'bz2']) diff --git a/selfdrive/loggerd/bootlog.cc b/selfdrive/loggerd/bootlog.cc index 481b3ee47c..5209958377 100644 --- a/selfdrive/loggerd/bootlog.cc +++ b/selfdrive/loggerd/bootlog.cc @@ -56,6 +56,7 @@ static kj::Array build_boot_log() { } int main(int argc, char** argv) { + clear_locks(LOG_ROOT); const std::string path = LOG_ROOT + "/boot/" + logger_get_route_name() + ".bz2"; LOGW("bootlog to %s", path.c_str()); diff --git a/selfdrive/loggerd/logger.cc b/selfdrive/loggerd/logger.cc index a73fefb8c9..81cfd131f6 100644 --- a/selfdrive/loggerd/logger.cc +++ b/selfdrive/loggerd/logger.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -266,3 +267,15 @@ void lh_close(LoggerHandle* h) { } pthread_mutex_unlock(&h->lock); } + +int clear_locks_fn(const char* fpath, const struct stat *sb, int tyupeflag) { + const char* dot = strrchr(fpath, '.'); + if (dot && strcmp(dot, ".lock") == 0) { + unlink(fpath); + } + return 0; +} + +void clear_locks(const std::string log_root) { + ftw(log_root.c_str(), clear_locks_fn, 16); +} diff --git a/selfdrive/loggerd/logger.h b/selfdrive/loggerd/logger.h index bdda9d6917..e85d7810e0 100644 --- a/selfdrive/loggerd/logger.h +++ b/selfdrive/loggerd/logger.h @@ -96,3 +96,4 @@ void logger_log(LoggerState *s, uint8_t* data, size_t data_size, bool in_qlog); void lh_log(LoggerHandle* h, uint8_t* data, size_t data_size, bool in_qlog); void lh_close(LoggerHandle* h); +void clear_locks(const std::string log_root); diff --git a/selfdrive/loggerd/loggerd.cc b/selfdrive/loggerd/loggerd.cc index f6d89b8091..e3444f3315 100644 --- a/selfdrive/loggerd/loggerd.cc +++ b/selfdrive/loggerd/loggerd.cc @@ -1,152 +1,43 @@ -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cereal/messaging/messaging.h" -#include "cereal/services.h" -#include "cereal/visionipc/visionipc.h" -#include "cereal/visionipc/visionipc_client.h" -#include "selfdrive/camerad/cameras/camera_common.h" -#include "selfdrive/common/params.h" -#include "selfdrive/common/swaglog.h" -#include "selfdrive/common/timing.h" -#include "selfdrive/common/util.h" -#include "selfdrive/hardware/hw.h" - -#include "selfdrive/loggerd/encoder.h" -#include "selfdrive/loggerd/logger.h" -#if defined(QCOM) || defined(QCOM2) -#include "selfdrive/loggerd/omx_encoder.h" -#define Encoder OmxEncoder -#else -#include "selfdrive/loggerd/raw_logger.h" -#define Encoder RawLogger -#endif - -namespace { - -constexpr int MAIN_FPS = 20; -const int MAIN_BITRATE = Hardware::TICI() ? 10000000 : 5000000; -const int DCAM_BITRATE = Hardware::TICI() ? MAIN_BITRATE : 2500000; - -#define NO_CAMERA_PATIENCE 500 // fall back to time-based rotation if all cameras are dead - -const bool LOGGERD_TEST = getenv("LOGGERD_TEST"); -const int SEGMENT_LENGTH = LOGGERD_TEST ? atoi(getenv("LOGGERD_SEGMENT_LENGTH")) : 60; +#include "selfdrive/loggerd/loggerd.h" ExitHandler do_exit; -const LogCameraInfo cameras_logged[] = { - { - .type = RoadCam, - .stream_type = VISION_STREAM_YUV_BACK, - .filename = "fcamera.hevc", - .frame_packet_name = "roadCameraState", - .fps = MAIN_FPS, - .bitrate = MAIN_BITRATE, - .is_h265 = true, - .downscale = false, - .has_qcamera = true, - .trigger_rotate = true, - .enable = true, - .record = true, - }, - { - .type = DriverCam, - .stream_type = VISION_STREAM_YUV_FRONT, - .filename = "dcamera.hevc", - .frame_packet_name = "driverCameraState", - .fps = MAIN_FPS, // on EONs, more compressed this way - .bitrate = DCAM_BITRATE, - .is_h265 = true, - .downscale = false, - .has_qcamera = false, - .trigger_rotate = Hardware::TICI(), - .enable = !Hardware::PC(), - .record = Params().getBool("RecordFront"), - }, - { - .type = WideRoadCam, - .stream_type = VISION_STREAM_YUV_WIDE, - .filename = "ecamera.hevc", - .frame_packet_name = "wideRoadCameraState", - .fps = MAIN_FPS, - .bitrate = MAIN_BITRATE, - .is_h265 = true, - .downscale = false, - .has_qcamera = false, - .trigger_rotate = true, - .enable = Hardware::TICI(), - .record = Hardware::TICI(), - }, -}; -const LogCameraInfo qcam_info = { - .filename = "qcamera.ts", - .fps = MAIN_FPS, - .bitrate = 256000, - .is_h265 = false, - .downscale = true, - .frame_width = Hardware::TICI() ? 526 : 480, - .frame_height = Hardware::TICI() ? 330 : 360 // keep pixel count the same? -}; - -struct LoggerdState { - Context *ctx; - LoggerState logger = {}; - char segment_path[4096]; - std::mutex rotate_lock; - std::condition_variable rotate_cv; - std::atomic rotate_segment; - std::atomic last_camera_seen_tms; - std::atomic ready_to_rotate; // count of encoders ready to rotate - int max_waiting = 0; - double last_rotate_tms = 0.; // last rotate time in ms - - // Sync logic for startup - std::atomic encoders_ready = 0; - std::atomic start_frame_id = 0; - bool camera_ready[WideRoadCam + 1] = {}; - bool camera_synced[WideRoadCam + 1] = {}; -}; -LoggerdState s; - // Handle initial encoder syncing by waiting for all encoders to reach the same frame id -bool sync_encoders(LoggerdState *state, CameraType cam_type, uint32_t frame_id) { - if (state->camera_synced[cam_type]) return true; +bool sync_encoders(LoggerdState *s, CameraType cam_type, uint32_t frame_id) { + if (s->camera_synced[cam_type]) return true; - if (state->max_waiting > 1 && state->encoders_ready != state->max_waiting) { + if (s->max_waiting > 1 && s->encoders_ready != s->max_waiting) { // add a small margin to the start frame id in case one of the encoders already dropped the next frame - update_max_atomic(state->start_frame_id, frame_id + 2); - if (std::exchange(state->camera_ready[cam_type], true) == false) { - ++state->encoders_ready; + update_max_atomic(s->start_frame_id, frame_id + 2); + if (std::exchange(s->camera_ready[cam_type], true) == false) { + ++s->encoders_ready; LOGE("camera %d encoder ready", cam_type); } return false; } else { - if (state->max_waiting == 1) update_max_atomic(state->start_frame_id, frame_id); - bool synced = frame_id >= state->start_frame_id; - state->camera_synced[cam_type] = synced; - if (!synced) LOGE("camera %d waiting for frame %d, cur %d", cam_type, (int)state->start_frame_id, frame_id); + if (s->max_waiting == 1) update_max_atomic(s->start_frame_id, frame_id); + bool synced = frame_id >= s->start_frame_id; + s->camera_synced[cam_type] = synced; + if (!synced) LOGE("camera %d waiting for frame %d, cur %d", cam_type, (int)s->start_frame_id, frame_id); return synced; } } -void encoder_thread(const LogCameraInfo &cam_info) { +bool trigger_rotate_if_needed(LoggerdState *s, int cur_seg, uint32_t frame_id) { + const int frames_per_seg = SEGMENT_LENGTH * MAIN_FPS; + if (cur_seg >= 0 && frame_id >= ((cur_seg + 1) * frames_per_seg) + s->start_frame_id) { + // trigger rotate and wait until the main logger has rotated to the new segment + ++s->ready_to_rotate; + std::unique_lock lk(s->rotate_lock); + s->rotate_cv.wait(lk, [&] { + return s->rotate_segment > cur_seg || do_exit; + }); + return !do_exit; + } + return false; +} + +void encoder_thread(LoggerdState *s, const LogCameraInfo &cam_info) { set_thread_name(cam_info.filename); int cur_seg = -1; @@ -183,37 +74,29 @@ void encoder_thread(const LogCameraInfo &cam_info) { if (buf == nullptr) continue; if (cam_info.trigger_rotate) { - s.last_camera_seen_tms = millis_since_boot(); - if (!sync_encoders(&s, cam_info.type, extra.frame_id)) { + s->last_camera_seen_tms = millis_since_boot(); + if (!sync_encoders(s, cam_info.type, extra.frame_id)) { continue; } // check if we're ready to rotate - const int frames_per_seg = SEGMENT_LENGTH * MAIN_FPS; - if (cur_seg >= 0 && extra.frame_id >= ((cur_seg+1) * frames_per_seg) + s.start_frame_id) { - // trigger rotate and wait until the main logger has rotated to the new segment - ++s.ready_to_rotate; - std::unique_lock lk(s.rotate_lock); - s.rotate_cv.wait(lk, [&] { - return s.rotate_segment > cur_seg || do_exit; - }); - if (do_exit) break; - } + trigger_rotate_if_needed(s, cur_seg, extra.frame_id); + if (do_exit) break; } // rotate the encoder if the logger is on a newer segment - if (s.rotate_segment > cur_seg) { - cur_seg = s.rotate_segment; + if (s->rotate_segment > cur_seg) { + cur_seg = s->rotate_segment; - LOGW("camera %d rotate encoder to %s", cam_info.type, s.segment_path); + LOGW("camera %d rotate encoder to %s", cam_info.type, s->segment_path); for (auto &e : encoders) { e->encoder_close(); - e->encoder_open(s.segment_path); + e->encoder_open(s->segment_path); } if (lh) { lh_close(lh); } - lh = logger_get_handle(&s.logger); + lh = logger_get_handle(&s->logger); } // encode a frame @@ -266,68 +149,42 @@ void encoder_thread(const LogCameraInfo &cam_info) { } } -int clear_locks_fn(const char* fpath, const struct stat *sb, int tyupeflag) { - const char* dot = strrchr(fpath, '.'); - if (dot && strcmp(dot, ".lock") == 0) { - unlink(fpath); - } - return 0; -} - -void clear_locks() { - ftw(LOG_ROOT.c_str(), clear_locks_fn, 16); -} - -void logger_rotate() { +void logger_rotate(LoggerdState *s) { { - std::unique_lock lk(s.rotate_lock); + std::unique_lock lk(s->rotate_lock); int segment = -1; - int err = logger_next(&s.logger, LOG_ROOT.c_str(), s.segment_path, sizeof(s.segment_path), &segment); + int err = logger_next(&s->logger, LOG_ROOT.c_str(), s->segment_path, sizeof(s->segment_path), &segment); assert(err == 0); - s.rotate_segment = segment; - s.ready_to_rotate = 0; - s.last_rotate_tms = millis_since_boot(); + s->rotate_segment = segment; + s->ready_to_rotate = 0; + s->last_rotate_tms = millis_since_boot(); } - s.rotate_cv.notify_all(); - LOGW((s.logger.part == 0) ? "logging to %s" : "rotated to %s", s.segment_path); + s->rotate_cv.notify_all(); + LOGW((s->logger.part == 0) ? "logging to %s" : "rotated to %s", s->segment_path); } -void rotate_if_needed() { - if (s.ready_to_rotate == s.max_waiting) { - logger_rotate(); +void rotate_if_needed(LoggerdState *s) { + if (s->ready_to_rotate == s->max_waiting) { + logger_rotate(s); } double tms = millis_since_boot(); - if ((tms - s.last_rotate_tms) > SEGMENT_LENGTH * 1000 && - (tms - s.last_camera_seen_tms) > NO_CAMERA_PATIENCE && + if ((tms - s->last_rotate_tms) > SEGMENT_LENGTH * 1000 && + (tms - s->last_camera_seen_tms) > NO_CAMERA_PATIENCE && !LOGGERD_TEST) { LOGW("no camera packet seen. auto rotating"); - logger_rotate(); + logger_rotate(s); } } -} // namespace - -int main(int argc, char** argv) { - if (Hardware::EON()) { - setpriority(PRIO_PROCESS, 0, -20); - } else if (Hardware::TICI()) { - int ret; - ret = set_core_affinity({0, 1, 2, 3}); - assert(ret == 0); - // TODO: why does this impact camerad timings? - //ret = set_realtime_priority(1); - //assert(ret == 0); - } - - clear_locks(); - +void loggerd_thread() { // setup messaging typedef struct QlogState { int counter, freq; } QlogState; std::unordered_map qlog_states; + LoggerdState s; s.ctx = Context::create(); Poller * poller = Poller::create(); @@ -343,7 +200,7 @@ int main(int argc, char** argv) { // init logger logger_init(&s.logger, "rlog", true); - logger_rotate(); + logger_rotate(&s); Params().put("CurrentRoute", s.logger.route_name); // init encoders @@ -351,7 +208,7 @@ int main(int argc, char** argv) { std::vector encoder_threads; for (const auto &cam : cameras_logged) { if (cam.enable) { - encoder_threads.push_back(std::thread(encoder_thread, cam)); + encoder_threads.push_back(std::thread(encoder_thread, &s, cam)); if (cam.trigger_rotate) s.max_waiting++; } } @@ -370,7 +227,7 @@ int main(int argc, char** argv) { bytes_count += msg->getSize(); delete msg; - rotate_if_needed(); + rotate_if_needed(&s); if ((++msg_count % 1000) == 0) { double seconds = (millis_since_boot() - start_ts) / 1000.0; @@ -397,6 +254,4 @@ int main(int argc, char** argv) { for (auto &[sock, qs] : qlog_states) delete sock; delete poller; delete s.ctx; - - return 0; } diff --git a/selfdrive/loggerd/loggerd.h b/selfdrive/loggerd/loggerd.h new file mode 100644 index 0000000000..977e03b8d3 --- /dev/null +++ b/selfdrive/loggerd/loggerd.h @@ -0,0 +1,120 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cereal/messaging/messaging.h" +#include "cereal/services.h" +#include "cereal/visionipc/visionipc.h" +#include "cereal/visionipc/visionipc_client.h" +#include "selfdrive/camerad/cameras/camera_common.h" +#include "selfdrive/common/params.h" +#include "selfdrive/common/swaglog.h" +#include "selfdrive/common/timing.h" +#include "selfdrive/common/util.h" +#include "selfdrive/hardware/hw.h" + +#include "selfdrive/loggerd/encoder.h" +#include "selfdrive/loggerd/logger.h" +#if defined(QCOM) || defined(QCOM2) +#include "selfdrive/loggerd/omx_encoder.h" +#define Encoder OmxEncoder +#else +#include "selfdrive/loggerd/raw_logger.h" +#define Encoder RawLogger +#endif + +constexpr int MAIN_FPS = 20; +const int MAIN_BITRATE = Hardware::TICI() ? 10000000 : 5000000; +const int DCAM_BITRATE = Hardware::TICI() ? MAIN_BITRATE : 2500000; + +#define NO_CAMERA_PATIENCE 500 // fall back to time-based rotation if all cameras are dead + +const bool LOGGERD_TEST = getenv("LOGGERD_TEST"); +const int SEGMENT_LENGTH = LOGGERD_TEST ? atoi(getenv("LOGGERD_SEGMENT_LENGTH")) : 60; + +const LogCameraInfo cameras_logged[] = { + { + .type = RoadCam, + .stream_type = VISION_STREAM_ROAD, + .filename = "fcamera.hevc", + .frame_packet_name = "roadCameraState", + .fps = MAIN_FPS, + .bitrate = MAIN_BITRATE, + .is_h265 = true, + .downscale = false, + .has_qcamera = true, + .trigger_rotate = true, + .enable = true, + .record = true, + }, + { + .type = DriverCam, + .stream_type = VISION_STREAM_DRIVER, + .filename = "dcamera.hevc", + .frame_packet_name = "driverCameraState", + .fps = MAIN_FPS, // on EONs, more compressed this way + .bitrate = DCAM_BITRATE, + .is_h265 = true, + .downscale = false, + .has_qcamera = false, + .trigger_rotate = Hardware::TICI(), + .enable = !Hardware::PC(), + .record = Params().getBool("RecordFront"), + }, + { + .type = WideRoadCam, + .stream_type = VISION_STREAM_WIDE_ROAD, + .filename = "ecamera.hevc", + .frame_packet_name = "wideRoadCameraState", + .fps = MAIN_FPS, + .bitrate = MAIN_BITRATE, + .is_h265 = true, + .downscale = false, + .has_qcamera = false, + .trigger_rotate = true, + .enable = Hardware::TICI(), + .record = Hardware::TICI(), + }, +}; +const LogCameraInfo qcam_info = { + .filename = "qcamera.ts", + .fps = MAIN_FPS, + .bitrate = 256000, + .is_h265 = false, + .downscale = true, + .frame_width = Hardware::TICI() ? 526 : 480, + .frame_height = Hardware::TICI() ? 330 : 360 // keep pixel count the same? +}; + +struct LoggerdState { + Context *ctx; + LoggerState logger = {}; + char segment_path[4096]; + std::mutex rotate_lock; + std::condition_variable rotate_cv; + std::atomic rotate_segment; + std::atomic last_camera_seen_tms; + std::atomic ready_to_rotate; // count of encoders ready to rotate + int max_waiting = 0; + double last_rotate_tms = 0.; // last rotate time in ms + + // Sync logic for startup + std::atomic encoders_ready = 0; + std::atomic start_frame_id = 0; + bool camera_ready[WideRoadCam + 1] = {}; + bool camera_synced[WideRoadCam + 1] = {}; +}; + +bool sync_encoders(LoggerdState *s, CameraType cam_type, uint32_t frame_id); +bool trigger_rotate_if_needed(LoggerdState *s, int cur_seg, uint32_t frame_id); +void rotate_if_needed(LoggerdState *s); +void loggerd_thread(); diff --git a/selfdrive/loggerd/main.cc b/selfdrive/loggerd/main.cc new file mode 100644 index 0000000000..04da0f67e8 --- /dev/null +++ b/selfdrive/loggerd/main.cc @@ -0,0 +1,20 @@ +#include "selfdrive/loggerd/loggerd.h" + +#include + +int main(int argc, char** argv) { + if (Hardware::EON()) { + setpriority(PRIO_PROCESS, 0, -20); + } else if (Hardware::TICI()) { + int ret; + ret = set_core_affinity({0, 1, 2, 3}); + assert(ret == 0); + // TODO: why does this impact camerad timings? + //ret = set_realtime_priority(1); + //assert(ret == 0); + } + + loggerd_thread(); + + return 0; +} diff --git a/selfdrive/loggerd/tests/test_loggerd.cc b/selfdrive/loggerd/tests/test_loggerd.cc new file mode 100644 index 0000000000..849b350ee4 --- /dev/null +++ b/selfdrive/loggerd/tests/test_loggerd.cc @@ -0,0 +1,111 @@ +#include + +#include "catch2/catch.hpp" +#include "selfdrive/loggerd/loggerd.h" + +int random_int(int min, int max) { + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_int_distribution dist(min, max); + return dist(rng); +} + +int get_synced_frame_id(LoggerdState *s, CameraType cam_type, int start_frame_id) { + int frame_id = start_frame_id; + while (!sync_encoders(s, cam_type, frame_id)) { + ++frame_id; + usleep(0); + } + return frame_id; +}; + +TEST_CASE("sync_encoders") { + const int max_waiting = GENERATE(1, 2, 3); + + for (int test_cnt = 0; test_cnt < 10; ++test_cnt) { + LoggerdState s{.max_waiting = max_waiting}; + std::vector start_frames; + std::vector> futures; + + for (int i = 0; i < max_waiting; ++i) { + int start_frame_id = random_int(0, 20); + start_frames.push_back(start_frame_id); + futures.emplace_back(std::async(std::launch::async, get_synced_frame_id, &s, (CameraType)i, start_frame_id)); + } + + // get results + int synced_frame_id = 0; + for (int i = 0; i < max_waiting; ++i) { + if (i == 0) { + synced_frame_id = futures[i].get(); + // require synced_frame_id equal start_frame_id if max_waiting == 1 + if (max_waiting == 1) { + REQUIRE(synced_frame_id == start_frames[0]); + } + } else { + REQUIRE(futures[i].get() == synced_frame_id); + } + } + } +} + +const int MAX_SEGMENT_CNT = 100; + +std::pair encoder_thread(LoggerdState *s) { + int cur_seg = 0; + uint32_t frame_id = s->start_frame_id; + + while (cur_seg < MAX_SEGMENT_CNT) { + ++frame_id; + if (trigger_rotate_if_needed(s, cur_seg, frame_id)) { + cur_seg = s->rotate_segment; + } + util::sleep_for(0); + } + + return {cur_seg, frame_id}; +} + +TEST_CASE("trigger_rotate") { + const int encoders = GENERATE(1, 2, 3); + const int start_frame_id = random_int(0, 20); + + LoggerdState s{ + .max_waiting = encoders, + .start_frame_id = start_frame_id, + }; + + std::vector>> futures; + for (int i = 0; i < encoders; ++i) { + futures.emplace_back(std::async(std::launch::async, encoder_thread, &s)); + } + + while (s.rotate_segment < MAX_SEGMENT_CNT) { + rotate_if_needed(&s); + util::sleep_for(10); + } + + for (auto &f : futures) { + auto [encoder_seg, frame_id] = f.get(); + REQUIRE(encoder_seg == MAX_SEGMENT_CNT); + REQUIRE(frame_id == start_frame_id + encoder_seg * (SEGMENT_LENGTH * MAIN_FPS)); + } +} + +TEST_CASE("clear_locks") { + std::vector dirs; + for (int i = 0; i < 10; ++i) { + std::string &path = dirs.emplace_back(LOG_ROOT + "/" + std::to_string(i)); + REQUIRE(util::create_directories(path, 0775)); + std::ofstream{path + "/.lock"}; + REQUIRE(util::file_exists(path + "/.lock")); + } + + clear_locks(LOG_ROOT); + + for (const auto &dir : dirs) { + std::string lock_file = dir + "/.lock"; + REQUIRE(util::file_exists(lock_file) == false); + rmdir(dir.c_str()); + } +} diff --git a/selfdrive/loggerd/tests/test_loggerd.py b/selfdrive/loggerd/tests/test_loggerd.py index 7ee97f5118..021eea5c83 100755 --- a/selfdrive/loggerd/tests/test_loggerd.py +++ b/selfdrive/loggerd/tests/test_loggerd.py @@ -18,7 +18,7 @@ from selfdrive.hardware import PC, TICI from selfdrive.loggerd.config import ROOT from selfdrive.manager.process_config import managed_processes from selfdrive.test.helpers import with_processes -from selfdrive.version import version as VERSION +from selfdrive.version import get_version from tools.lib.logreader import LogReader SentinelType = log.Sentinel.SentinelType @@ -95,7 +95,7 @@ class TestLoggerd(unittest.TestCase): initData = lr[0].initData self.assertTrue(initData.dirty != bool(os.environ["CLEAN"])) - self.assertEqual(initData.version, VERSION) + self.assertEqual(initData.version, get_version()) if os.path.isfile("/proc/cmdline"): with open("/proc/cmdline") as f: diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index 66f836ce6b..d6429092ea 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -12,9 +12,9 @@ from common.spinner import Spinner from common.text_window import TextWindow from selfdrive.hardware import TICI from selfdrive.swaglog import cloudlog, add_file_handler -from selfdrive.version import dirty +from selfdrive.version import get_dirty -MAX_CACHE_SIZE = 2e9 +MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 CACHE_DIR = Path("/data/scons_cache" if TICI else "/tmp/scons_cache") TOTAL_SCONS_NODES = 2405 @@ -69,7 +69,7 @@ def build(spinner, dirty=False): else: # Build failed log errors errors = [line.decode('utf8', 'replace') for line in compile_output - if any([err in line for err in [b'error: ', b'not found, needed by target']])] + if any(err in line for err in [b'error: ', b'not found, needed by target'])] error_s = "\n".join(errors) add_file_handler(cloudlog) cloudlog.error("scons build failed\n" + error_s) @@ -98,4 +98,4 @@ def build(spinner, dirty=False): if __name__ == "__main__" and not PREBUILT: spinner = Spinner() spinner.update_progress(0, 100) - build(spinner, dirty) + build(spinner, get_dirty()) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 6c71c2a377..d0d32874b0 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -18,17 +18,20 @@ from selfdrive.manager.process import ensure_running from selfdrive.manager.process_config import managed_processes from selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from selfdrive.swaglog import cloudlog, add_file_handler -from selfdrive.version import dirty, get_git_commit, version, origin, branch, commit, \ - terms_version, training_version, comma_remote, \ - get_git_branch, get_git_remote +from selfdrive.version import get_dirty, get_commit, get_version, get_origin, get_short_branch, \ + terms_version, training_version, get_comma_remote + sys.path.append(os.path.join(BASEDIR, "pyextra")) -def manager_init(): +def manager_init(): # update system time from panda set_time(cloudlog) + # save boot log + subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "selfdrive/loggerd")) + params = Params() params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) @@ -67,12 +70,12 @@ def manager_init(): print("WARNING: failed to make /dev/shm") # set version params - params.put("Version", version) + params.put("Version", get_version()) params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) - params.put("GitCommit", get_git_commit(default="")) - params.put("GitBranch", get_git_branch(default="")) - params.put("GitRemote", get_git_remote(default="")) + params.put("GitCommit", get_commit(default="")) + params.put("GitBranch", get_short_branch(default="")) + params.put("GitRemote", get_origin(default="")) # set dongle id reg_res = register(show_spinner=True) @@ -83,16 +86,16 @@ def manager_init(): raise Exception(f"Registration failed for device {serial}") os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog - if not dirty: + if not get_dirty(): os.environ['CLEAN'] = '1' - cloudlog.bind_global(dongle_id=dongle_id, version=version, dirty=dirty, + cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=get_dirty(), device=HARDWARE.get_device_type()) - if comma_remote and not (os.getenv("NOLOG") or os.getenv("NOCRASH") or PC): + if get_comma_remote() and not (os.getenv("NOLOG") or os.getenv("NOCRASH") or PC): crash.init() crash.bind_user(id=dongle_id) - crash.bind_extra(dirty=dirty, origin=origin, branch=branch, commit=commit, + crash.bind_extra(dirty=get_dirty(), origin=get_origin(), branch=get_short_branch(), commit=get_commit(), device=HARDWARE.get_device_type()) @@ -102,8 +105,13 @@ def manager_prepare(): def manager_cleanup(): + # send signals to kill all procs for p in managed_processes.values(): - p.stop() + p.stop(block=False) + + # ensure all are killed + for p in managed_processes.values(): + p.stop(block=True) cloudlog.info("everything is dead") @@ -112,9 +120,6 @@ def manager_thread(): cloudlog.info("manager start") cloudlog.info({"environ": os.environ}) - # save boot log - subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "selfdrive/loggerd")) - params = Params() ignore = [] diff --git a/selfdrive/modeld/dmonitoringmodeld.cc b/selfdrive/modeld/dmonitoringmodeld.cc index 6ae6e780c8..c85c05c9d1 100644 --- a/selfdrive/modeld/dmonitoringmodeld.cc +++ b/selfdrive/modeld/dmonitoringmodeld.cc @@ -39,7 +39,7 @@ int main(int argc, char **argv) { DMonitoringModelState model; dmonitoring_init(&model); - VisionIpcClient vipc_client = VisionIpcClient("camerad", VISION_STREAM_YUV_FRONT, true); + VisionIpcClient vipc_client = VisionIpcClient("camerad", VISION_STREAM_DRIVER, true); while (!do_exit && !vipc_client.connect(false)) { util::sleep_for(100); } diff --git a/selfdrive/modeld/modeld.cc b/selfdrive/modeld/modeld.cc index 3d3e435909..9013e63617 100644 --- a/selfdrive/modeld/modeld.cc +++ b/selfdrive/modeld/modeld.cc @@ -153,7 +153,7 @@ int main(int argc, char **argv) { model_init(&model, device_id, context); LOGW("models loaded, modeld starting"); - VisionIpcClient vipc_client = VisionIpcClient("camerad", wide_camera ? VISION_STREAM_YUV_WIDE : VISION_STREAM_YUV_BACK, true, device_id, context); + VisionIpcClient vipc_client = VisionIpcClient("camerad", wide_camera ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD, true, device_id, context); while (!do_exit && !vipc_client.connect(false)) { util::sleep_for(100); } diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index ac19d27540..cefc2166ec 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -42,8 +42,7 @@ class Plant(): from selfdrive.car.honda.values import CAR from selfdrive.car.honda.interface import CarInterface - self.CP = CarInterface.get_params(CAR.CIVIC) - self.planner = Planner(self.CP, init_v=self.speed) + self.planner = Planner(CarInterface.get_params(CAR.CIVIC), init_v=self.speed) def current_time(self): return float(self.rk.frame) / self.rate @@ -97,7 +96,7 @@ class Plant(): sm = {'radarState': radar.radarState, 'carState': car_state.carState, 'controlsState': control.controlsState} - self.planner.update(sm, self.CP) + self.planner.update(sm) self.speed = self.planner.v_desired self.acceleration = self.planner.a_desired fcw = self.planner.fcw diff --git a/selfdrive/test/model_test.py b/selfdrive/test/model_test.py deleted file mode 100755 index b8a4ac0a86..0000000000 --- a/selfdrive/test/model_test.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 -import os -import numpy as np - -from tools.lib.logreader import LogReader -from tools.lib.framereader import FrameReader -from tools.lib.cache import cache_path_for_file_path -from selfdrive.test.process_replay.model_replay import model_replay - - -if __name__ == "__main__": - lr = LogReader(os.path.expanduser('~/rlog.bz2')) - fr = FrameReader(os.path.expanduser('~/fcamera.hevc')) - desire = np.load(os.path.expanduser('~/desire.npy')) - calib = np.load(os.path.expanduser('~/calib.npy')) - - try: - msgs = model_replay(list(lr), fr, desire=desire, calib=calib) - finally: - cache_path = cache_path_for_file_path(os.path.expanduser('~/fcamera.hevc')) - if os.path.isfile(cache_path): - os.remove(cache_path) - - output_size = len(np.frombuffer(msgs[0].model.rawPredictions, dtype=np.float32)) - output_data = np.zeros((len(msgs), output_size), dtype=np.float32) - for i, msg in enumerate(msgs): - output_data[i] = np.frombuffer(msg.model.rawPredictions, dtype=np.float32) - np.save(os.path.expanduser('~/modeldata.npy'), output_data) - - print("Finished replay") diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index b3c52879ce..32a5717852 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -7,22 +7,31 @@ from typing import Any from tqdm import tqdm import cereal.messaging as messaging -from cereal import log from cereal.visionipc.visionipc_pyx import VisionIpcServer, VisionStreamType # pylint: disable=no-name-in-module, import-error from common.spinner import Spinner from common.timeout import Timeout -from common.transformations.camera import get_view_frame_from_road_frame, eon_f_frame_size, tici_f_frame_size -from selfdrive.hardware import PC +from common.transformations.camera import get_view_frame_from_road_frame, eon_f_frame_size, tici_f_frame_size, \ + eon_d_frame_size, tici_d_frame_size +from selfdrive.hardware import PC, TICI from selfdrive.manager.process_config import managed_processes from selfdrive.test.openpilotci import BASE_URL, get_url from selfdrive.test.process_replay.compare_logs import compare_logs, save_log from selfdrive.test.process_replay.test_processes import format_diff -from selfdrive.version import get_git_commit +from selfdrive.version import get_commit from tools.lib.framereader import FrameReader from tools.lib.logreader import LogReader -TEST_ROUTE = "99c94dc769b5d96e|2019-08-03--14-19-59" +if TICI: + TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" +else: + TEST_ROUTE = "303055c0002aefd1|2021-11-22--18-36-32" +CACHE_DIR = os.getenv("CACHE_DIR", None) + +packet_from_camera = {"roadCameraState":"modelV2", "driverCameraState":"driverState"} + +def get_log_fn(ref_commit): + return "%s_%s_%s.bz2" % (TEST_ROUTE, "model_tici" if TICI else "model", ref_commit) def replace_calib(msg, calib): msg = msg.as_builder() @@ -30,67 +39,78 @@ def replace_calib(msg, calib): msg.liveCalibration.extrinsicMatrix = get_view_frame_from_road_frame(*calib, 1.22).flatten().tolist() return msg - -def model_replay(lr, fr, desire=None, calib=None): - +def process_frame(msg, pm, sm, log_msgs, vipc_server, spinner, frs, frame_idxs, last_desire): + if msg.which() == "roadCameraState" and last_desire is not None: + dat = messaging.new_message('lateralPlan') + dat.lateralPlan.desire = last_desire + pm.send('lateralPlan', dat) + + f = msg.as_builder() + pm.send(msg.which(), f) + + img = frs[msg.which()].get(frame_idxs[msg.which()], pix_fmt="yuv420p")[0] + if msg.which == "roadCameraState": + vipc_server.send(VisionStreamType.VISION_STREAM_ROAD, img.flatten().tobytes(), f.roadCameraState.frameId, + f.roadCameraState.timestampSof, f.roadCameraState.timestampEof) + else: + vipc_server.send(VisionStreamType.VISION_STREAM_DRIVER, img.flatten().tobytes(), f.driverCameraState.frameId, + f.driverCameraState.timestampSof, f.driverCameraState.timestampEof) + with Timeout(seconds=15): + log_msgs.append(messaging.recv_one(sm.sock[packet_from_camera[msg.which()]])) + + frame_idxs[msg.which()] += 1 + if frame_idxs[msg.which()] >= frs[msg.which()].frame_count: + return None + update_spinner(spinner, frame_idxs['roadCameraState'], frs['roadCameraState'].frame_count, + frame_idxs['driverCameraState'], frs['driverCameraState'].frame_count) + return 0 + +def update_spinner(s, fidx, fcnt, didx, dcnt): + s.update("replaying models: road %d/%d, driver %d/%d" % (fidx, fcnt, didx, dcnt)) + +def model_replay(lr_list, frs): spinner = Spinner() spinner.update("starting model replay") - vipc_server = None - pm = messaging.PubMaster(['roadCameraState', 'liveCalibration', 'lateralPlan']) - sm = messaging.SubMaster(['modelV2']) + vipc_server = VisionIpcServer("camerad") + vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, *(tici_f_frame_size if TICI else eon_f_frame_size)) + vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, *(tici_d_frame_size if TICI else eon_d_frame_size)) + vipc_server.start_listener() + + pm = messaging.PubMaster(['roadCameraState', 'driverCameraState', 'liveCalibration', 'lateralPlan']) + sm = messaging.SubMaster(['modelV2', 'driverState']) - # TODO: add dmonitoringmodeld try: managed_processes['modeld'].start() + managed_processes['dmonitoringmodeld'].start() time.sleep(5) sm.update(1000) - desires_by_index = {v:k for k,v in log.LateralPlan.Desire.schema.enumerants.items()} + last_desire = None + log_msgs = [] + frame_idxs = dict.fromkeys(['roadCameraState','driverCameraState'], 0) cal = [msg for msg in lr if msg.which() == "liveCalibration"] for msg in cal[:5]: - pm.send(msg.which(), replace_calib(msg, calib)) + pm.send(msg.which(), replace_calib(msg, None)) - log_msgs = [] - frame_idx = 0 - for msg in tqdm(lr): + for msg in tqdm(lr_list): if msg.which() == "liveCalibration": - pm.send(msg.which(), replace_calib(msg, calib)) - elif msg.which() == "roadCameraState": - if desire is not None: - for i in desire[frame_idx].nonzero()[0]: - dat = messaging.new_message('lateralPlan') - dat.lateralPlan.desire = desires_by_index[i] - pm.send('lateralPlan', dat) - - f = msg.as_builder() - pm.send(msg.which(), f) - - img = fr.get(frame_idx, pix_fmt="yuv420p")[0] - if vipc_server is None: - w, h = {int(3*w*h/2): (w, h) for (w, h) in [tici_f_frame_size, eon_f_frame_size]}[len(img)] - vipc_server = VisionIpcServer("camerad") - vipc_server.create_buffers(VisionStreamType.VISION_STREAM_YUV_BACK, 40, False, w, h) - vipc_server.start_listener() - time.sleep(1) # wait for modeld to connect - - vipc_server.send(VisionStreamType.VISION_STREAM_YUV_BACK, img.flatten().tobytes(), f.roadCameraState.frameId, - f.roadCameraState.timestampSof, f.roadCameraState.timestampEof) - - with Timeout(seconds=15): - log_msgs.append(messaging.recv_one(sm.sock['modelV2'])) - - spinner.update("modeld replay %d/%d" % (frame_idx, fr.frame_count)) - - frame_idx += 1 - if frame_idx >= fr.frame_count: + last_calib = list(msg.liveCalibration.rpyCalib) + pm.send(msg.which(), replace_calib(msg, last_calib)) + elif msg.which() == "lateralPlan": + last_desire = msg.lateralPlan.desire + elif msg.which() in ["roadCameraState", "driverCameraState"]: + ret = process_frame(msg, pm, sm, log_msgs, vipc_server, spinner, frs, frame_idxs, last_desire) + if ret is None: break + except KeyboardInterrupt: pass finally: spinner.close() managed_processes['modeld'].stop() + managed_processes['dmonitoringmodeld'].stop() return log_msgs @@ -98,26 +118,40 @@ if __name__ == "__main__": update = "--update" in sys.argv + if TICI: + os.system('sudo mount -o remount,size=200M /tmp') # c3 hevcs are 75M each + replay_dir = os.path.dirname(os.path.abspath(__file__)) ref_commit_fn = os.path.join(replay_dir, "model_replay_ref_commit") - lr = LogReader(get_url(TEST_ROUTE, 0)) - fr = FrameReader(get_url(TEST_ROUTE, 0, log_type="fcamera")) + segnum = 0 + frs = {} + if CACHE_DIR: + lr = LogReader(os.path.join(CACHE_DIR, '%s--%d--rlog.bz2' % (TEST_ROUTE.replace('|', '_'), segnum))) + frs['roadCameraState'] = FrameReader(os.path.join(CACHE_DIR, '%s--%d--fcamera.hevc' % (TEST_ROUTE.replace('|', '_'), segnum))) + frs['driverCameraState'] = FrameReader(os.path.join(CACHE_DIR, '%s--%d--dcamera.hevc' % (TEST_ROUTE.replace('|', '_'), segnum))) + else: + lr = LogReader(get_url(TEST_ROUTE, segnum)) + frs['roadCameraState'] = FrameReader(get_url(TEST_ROUTE, segnum, log_type="fcamera")) + frs['driverCameraState'] = FrameReader(get_url(TEST_ROUTE, segnum, log_type="dcamera")) - log_msgs = model_replay(list(lr), fr) + lr_list = list(lr) + log_msgs = model_replay(lr_list, frs) failed = False if not update: ref_commit = open(ref_commit_fn).read().strip() - log_fn = "%s_%s_%s.bz2" % (TEST_ROUTE, "model", ref_commit) + log_fn = get_log_fn(ref_commit) cmp_log = LogReader(BASE_URL + log_fn) ignore = ['logMonoTime', 'valid', 'modelV2.frameDropPerc', - 'modelV2.modelExecutionTime'] + 'modelV2.modelExecutionTime', + 'driverState.modelExecutionTime', + 'driverState.dspExecutionTime'] tolerance = None if not PC else 1e-3 results: Any = {TEST_ROUTE: {}} - results[TEST_ROUTE]["modeld"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore) + results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore) diff1, diff2, failed = format_diff(results, ref_commit) print(diff2) @@ -135,8 +169,8 @@ if __name__ == "__main__": print("Uploading new refs") - new_commit = get_git_commit() - log_fn = "%s_%s_%s.bz2" % (TEST_ROUTE, "model", new_commit) + new_commit = get_commit() + log_fn = get_log_fn(new_commit) save_log(log_fn, log_msgs) try: upload_file(log_fn, os.path.basename(log_fn)) diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index c006ea47a4..93b61856de 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -46abfc14629d16f84ee45255ac876dceea2e7a11 +ed6d7e8f5e7e2c141b35814bb1c9b965f5e7dde8 diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 451a7cfc50..ccdbc2754f 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -341,6 +341,15 @@ def replay_process(cfg, lr, fingerprint=None): else: return cpp_replay_process(cfg, lr, fingerprint) +def setup_env(): + params = Params() + params.clear_all() + params.put_bool("OpenpilotEnabledToggle", True) + params.put_bool("Passive", False) + params.put_bool("CommunityFeaturesToggle", True) + + os.environ['NO_RADAR_SLEEP'] = "1" + os.environ["SIMULATION"] = "1" def python_replay_process(cfg, lr, fingerprint=None): sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub] @@ -356,21 +365,14 @@ def python_replay_process(cfg, lr, fingerprint=None): all_msgs = sorted(lr, key=lambda msg: msg.logMonoTime) pub_msgs = [msg for msg in all_msgs if msg.which() in list(cfg.pub_sub.keys())] - params = Params() - params.clear_all() - params.put_bool("OpenpilotEnabledToggle", True) - params.put_bool("Passive", False) - params.put_bool("CommunityFeaturesToggle", True) - - os.environ['NO_RADAR_SLEEP'] = "1" - os.environ["SIMULATION"] = "1" - + setup_env() # TODO: remove after getting new route for civic & accord migration = { "HONDA CIVIC 2016 TOURING": "HONDA CIVIC 2016", "HONDA ACCORD 2018 SPORT 2T": "HONDA ACCORD 2018", "HONDA ACCORD 2T 2018": "HONDA ACCORD 2018", + "Mazda CX-9 2021": "MAZDA CX-9 2021", } if fingerprint is not None: @@ -383,7 +385,7 @@ def python_replay_process(cfg, lr, fingerprint=None): if msg.which() == 'carParams': car_fingerprint = migration.get(msg.carParams.carFingerprint, msg.carParams.carFingerprint) if len(msg.carParams.carFw) and (car_fingerprint in FW_VERSIONS): - params.put("CarParamsCache", msg.carParams.as_builder().to_bytes()) + Params().put("CarParamsCache", msg.carParams.as_builder().to_bytes()) else: os.environ['SKIP_FW_QUERY'] = "1" os.environ['FINGERPRINT'] = car_fingerprint @@ -401,7 +403,7 @@ def python_replay_process(cfg, lr, fingerprint=None): can_sock = None cfg.init_callback(all_msgs, fsm, can_sock, fingerprint) - CP = car.CarParams.from_bytes(params.get("CarParams", block=True)) + CP = car.CarParams.from_bytes(Params().get("CarParams", block=True)) # wait for started process to be ready if 'can' in list(cfg.pub_sub.keys()): @@ -446,6 +448,8 @@ def cpp_replay_process(cfg, lr, fingerprint=None): pub_msgs = [msg for msg in all_msgs if msg.which() in list(cfg.pub_sub.keys())] log_msgs = [] + setup_env() + managed_processes[cfg.proc_name].prepare() managed_processes[cfg.proc_name].start() diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 9021aaec7a..640e6864da 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -567866c06976f8f95fa7d1670b51d8723e663ea1 \ No newline at end of file +4d55b3974e099f2bd6e77ea12e0e0830af051041 \ No newline at end of file diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index 0e8a0eaf11..5326075240 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -39,8 +39,8 @@ def replay_service(s, msgs): vs = None def replay_cameras(lr, frs): cameras = [ - ("roadCameraState", DT_MDL, eon_f_frame_size, VisionStreamType.VISION_STREAM_YUV_BACK), - ("driverCameraState", DT_DMON, eon_d_frame_size, VisionStreamType.VISION_STREAM_YUV_FRONT), + ("roadCameraState", DT_MDL, eon_f_frame_size, VisionStreamType.VISION_STREAM_ROAD), + ("driverCameraState", DT_DMON, eon_d_frame_size, VisionStreamType.VISION_STREAM_DRIVER), ] def replay_camera(s, stream, dt, vipc_server, fr, size): diff --git a/selfdrive/test/process_replay/update_refs.py b/selfdrive/test/process_replay/update_refs.py index fe5212d2c6..96feb0d534 100755 --- a/selfdrive/test/process_replay/update_refs.py +++ b/selfdrive/test/process_replay/update_refs.py @@ -6,7 +6,7 @@ from selfdrive.test.openpilotci import upload_file, get_url from selfdrive.test.process_replay.compare_logs import save_log from selfdrive.test.process_replay.process_replay import replay_process, CONFIGS from selfdrive.test.process_replay.test_processes import segments -from selfdrive.version import get_git_commit +from selfdrive.version import get_commit from tools.lib.logreader import LogReader if __name__ == "__main__": @@ -16,7 +16,7 @@ if __name__ == "__main__": process_replay_dir = os.path.dirname(os.path.abspath(__file__)) ref_commit_fn = os.path.join(process_replay_dir, "ref_commit") - ref_commit = get_git_commit() + ref_commit = get_commit() if ref_commit is None: raise Exception("couldn't get ref commit") with open(ref_commit_fn, "w") as f: diff --git a/selfdrive/test/test_models.py b/selfdrive/test/test_models.py index a55ad35a12..fc1f919b83 100755 --- a/selfdrive/test/test_models.py +++ b/selfdrive/test/test_models.py @@ -10,6 +10,7 @@ from cereal import log, car from common.params import Params from selfdrive.car.fingerprints import all_known_cars from selfdrive.car.car_helpers import interfaces +from selfdrive.car.gm.values import CAR as GM from selfdrive.car.honda.values import HONDA_BOSCH, CAR as HONDA from selfdrive.car.chrysler.values import CAR as CHRYSLER from selfdrive.car.hyundai.values import CAR as HYUNDAI @@ -37,6 +38,11 @@ ignore_carstate_check = [ CHRYSLER.PACIFICA_2017_HYBRID, ] +ignore_addr_checks_valid = [ + GM.BUICK_REGAL, + HYUNDAI.GENESIS_G70_2020, +] + @parameterized_class(('car_model'), [(car,) for i, car in enumerate(sorted(all_known_cars())) if i % NUM_JOBS == JOB_ID]) class TestCarModel(unittest.TestCase): @@ -50,35 +56,35 @@ class TestCarModel(unittest.TestCase): else: raise Exception(f"missing test route for car {cls.car_model}") + params = Params() + params.clear_all() + for seg in [2, 1, 0]: try: lr = LogReader(get_url(ROUTES[cls.car_model], seg)) - break except Exception: - lr = None - - if lr is None: - raise Exception("Route not found. Is it uploaded?") - - params = Params() - params.clear_all() - - can_msgs = [] - fingerprint = {i: dict() for i in range(3)} - for msg in lr: - if msg.which() == "can": - for m in msg.can: - if m.src < 128: - fingerprint[m.src][m.address] = len(m.dat) - can_msgs.append(msg) - elif msg.which() == "carParams": - if msg.carParams.openpilotLongitudinalControl: - params.put_bool("DisableRadar", True) + continue + + can_msgs = [] + fingerprint = {i: dict() for i in range(3)} + for msg in lr: + if msg.which() == "can": + for m in msg.can: + if m.src < 64: + fingerprint[m.src][m.address] = len(m.dat) + can_msgs.append(msg) + elif msg.which() == "carParams": + if msg.carParams.openpilotLongitudinalControl: + params.put_bool("DisableRadar", True) + + if len(can_msgs): + break + else: + raise Exception("Route not found or no CAN msgs found. Is it uploaded?") cls.can_msgs = sorted(can_msgs, key=lambda msg: msg.logMonoTime) cls.CarInterface, cls.CarController, cls.CarState = interfaces[cls.car_model] - cls.CP = cls.CarInterface.get_params(cls.car_model, fingerprint, []) assert cls.CP @@ -108,6 +114,8 @@ class TestCarModel(unittest.TestCase): self.assertTrue(len(self.CP.lateralTuning.lqr.a)) elif tuning == 'indi': self.assertTrue(len(self.CP.lateralTuning.indi.outerLoopGainV)) + else: + raise Exception("unkown tuning") def test_car_interface(self): # TODO: also check for checkusm and counter violations from can parser @@ -141,14 +149,28 @@ class TestCarModel(unittest.TestCase): if self.CP.dashcamOnly: self.skipTest("no need to check panda safety for dashcamOnly") + start_ts = self.can_msgs[0].logMonoTime + failed_addrs = Counter() for can in self.can_msgs: + # update panda timer + t = (can.logMonoTime - start_ts) / 1e3 + self.safety.set_timer(int(t)) + + # run all msgs through the safety RX hook for msg in can.can: - if msg.src >= 128: + if msg.src >= 64: continue + to_send = package_can_msg([msg.address, 0, msg.dat, msg.src]) if self.safety.safety_rx_hook(to_send) != 1: failed_addrs[hex(msg.address)] += 1 + + # ensure all msgs defined in the addr checks are valid + if self.car_model not in ignore_addr_checks_valid: + self.safety.safety_tick_current_rx_checks() + if t > 1e6: + self.assertTrue(self.safety.addr_checks_valid()) self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}") def test_panda_safety_carstate(self): @@ -164,7 +186,7 @@ class TestCarModel(unittest.TestCase): CC = car.CarControl.new_message() for can in self.can_msgs: for msg in can.can: - if msg.src >= 128: + if msg.src >= 64: continue to_send = package_can_msg([msg.address, 0, msg.dat, msg.src]) ret = self.safety.safety_rx_hook(to_send) @@ -197,6 +219,12 @@ class TestCarModel(unittest.TestCase): if failed_checks['brakePressed'] < 150: del failed_checks['brakePressed'] + # TODO: use the same signal in panda and carState + # tolerate a small delay between the button press and PCM entering a cruise state + if self.car_model == HONDA.ACCORD_2021: + if failed_checks['controlsAllowed'] < 500: + del failed_checks['controlsAllowed'] + self.assertFalse(len(failed_checks), f"panda safety doesn't agree with CarState: {failed_checks}") if __name__ == "__main__": diff --git a/selfdrive/test/test_routes.py b/selfdrive/test/test_routes.py index 5344f8fd75..696fe0de04 100755 --- a/selfdrive/test/test_routes.py +++ b/selfdrive/test/test_routes.py @@ -53,7 +53,9 @@ routes = [ TestRoute("81722949a62ea724|2019-04-06--15-19-25", HONDA.ODYSSEY_CHN), TestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T TestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T + TestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD_2021), # 2.0T TestRoute("07585b0da3c88459|2021-05-26--18-52-04", HONDA.ACCORDH), + TestRoute("f29e2b57a55e7ad5|2021-03-24--20-52-38", HONDA.ACCORDH_2021), TestRoute("1ad763dd22ef1a0e|2020-02-29--18-37-03", HONDA.CRV_5G), TestRoute("0a96f86fcfe35964|2020-02-05--07-25-51", HONDA.ODYSSEY), TestRoute("d83f36766f8012a5|2020-02-05--18-42-21", HONDA.CIVIC_BOSCH_DIESEL), @@ -122,6 +124,7 @@ routes = [ TestRoute("25057fa6a5a63dfb|2020-03-04--08-44-23", TOYOTA.LEXUS_CTH), TestRoute("f49e8041283f2939|2019-05-30--11-51-51", TOYOTA.LEXUS_ESH_TSS2), TestRoute("37041c500fd30100|2020-12-30--12-17-24", TOYOTA.LEXUS_ESH), + TestRoute("32696cea52831b02|2021-11-19--18-13-30", TOYOTA.LEXUS_RC), TestRoute("886fcd8408d570e9|2020-01-29--05-11-22", TOYOTA.LEXUS_RX), TestRoute("886fcd8408d570e9|2020-01-29--02-18-55", TOYOTA.LEXUS_RX), TestRoute("d27ad752e9b08d4f|2021-05-26--19-39-51", TOYOTA.LEXUS_RXH), diff --git a/selfdrive/test/test_valgrind_replay.py b/selfdrive/test/test_valgrind_replay.py old mode 100644 new mode 100755 index 2c7870a337..238b822ec9 --- a/selfdrive/test/test_valgrind_replay.py +++ b/selfdrive/test/test_valgrind_replay.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import os import threading import time @@ -53,9 +54,11 @@ class TestValgrind(unittest.TestCase): # Run valgrind on a process command = "valgrind --leak-check=full " + arg p = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True, preexec_fn=os.setsid) # pylint: disable=W1509 - while not self.done: + + while not self.replay_done: time.sleep(0.1) + # Kill valgrind and extract leak output os.killpg(os.getpgid(p.pid), signal.SIGINT) _, err = p.communicate() error_msg = str(err, encoding='utf-8') @@ -91,18 +94,24 @@ class TestValgrind(unittest.TestCase): if config.wait_for_response: sm.update(100) - self.done = True + self.replay_done = True def test_config(self): open(os.path.join(BASEDIR, "selfdrive/test/valgrind_logs.txt"), "w").close() for cfg in CONFIGS: - self.done = False + self.leak = None + self.replay_done = False + r, n = cfg.segment.rsplit("--", 1) lr = LogReader(get_url(r, n)) self.replay_process(cfg, lr) - time.sleep(1) # Wait for the logs to get written + + while self.leak is None: + time.sleep(0.1) # Wait for the valgrind to finish + self.assertFalse(self.leak) + if __name__ == "__main__": unittest.main() diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index f68a0accb8..eb3e594269 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -22,7 +22,7 @@ from selfdrive.hardware import EON, TICI, PC, HARDWARE from selfdrive.loggerd.config import get_available_percent from selfdrive.swaglog import cloudlog from selfdrive.thermald.power_monitoring import PowerMonitoring -from selfdrive.version import tested_branch, terms_version, training_version +from selfdrive.version import get_tested_branch, terms_version, training_version ThermalStatus = log.DeviceState.ThermalStatus NetworkType = log.DeviceState.NetworkType @@ -66,6 +66,7 @@ def read_thermal(thermal_config): dat.deviceState.gpuTempC = [read_tz(z) / thermal_config.gpu[1] for z in thermal_config.gpu[0]] dat.deviceState.memoryTempC = read_tz(thermal_config.mem[0]) / thermal_config.mem[1] dat.deviceState.ambientTempC = read_tz(thermal_config.ambient[0]) / thermal_config.ambient[1] + dat.deviceState.pmicTempC = [read_tz(z) / thermal_config.pmic[1] for z in thermal_config.pmic[0]] return dat @@ -342,7 +343,7 @@ def thermald_thread(): last_update_exception = params.get("LastUpdateException", encoding='utf8') if update_failed_count > 15 and last_update_exception is not None: - if tested_branch: + if get_tested_branch(): extra_text = "Ensure the software is correctly installed" else: extra_text = last_update_exception @@ -406,6 +407,8 @@ def thermald_thread(): power_monitor.calculate(peripheralState, startup_conditions["ignition"]) msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used() msg.deviceState.carBatteryCapacityUwh = max(0, power_monitor.get_car_battery_capacity()) + current_power_draw = HARDWARE.get_current_power_draw() # pylint: disable=assignment-from-none + msg.deviceState.powerDrawW = current_power_draw if current_power_draw is not None else 0 # Check if we need to disable charging (handled by boardd) msg.deviceState.chargingDisabled = power_monitor.should_disable_charging(startup_conditions["ignition"], in_car, off_ts) diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index fb7a1a2c99..a301725bae 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -15,7 +15,7 @@ from common.file_helpers import mkdirs_exists_ok from selfdrive.hardware import TICI, HARDWARE from selfdrive.loggerd.config import ROOT from selfdrive.swaglog import cloudlog -from selfdrive.version import branch, commit, dirty, origin, version +from selfdrive.version import get_branch, get_commit, get_dirty, get_origin, get_version MAX_SIZE = 100000 * 10 # mal size is 40-100k, allow up to 1M if TICI: @@ -109,7 +109,7 @@ def report_tombstone_android(fn): clean_path = executable.replace('./', '').replace('/', '_') date = datetime.datetime.now().strftime("%Y-%m-%d--%H-%M-%S") - new_fn = f"{date}_{commit[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] + new_fn = f"{date}_{get_commit(default='nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] crashlog_dir = os.path.join(ROOT, "crash") mkdirs_exists_ok(crashlog_dir) @@ -183,7 +183,7 @@ def report_tombstone_apport(fn): clean_path = path.replace('/', '_') date = datetime.datetime.now().strftime("%Y-%m-%d--%H-%M-%S") - new_fn = f"{date}_{commit[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] + new_fn = f"{date}_{get_commit(default='nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] crashlog_dir = os.path.join(ROOT, "crash") mkdirs_exists_ok(crashlog_dir) @@ -203,14 +203,14 @@ def main(): sentry_sdk.utils.MAX_STRING_LENGTH = 8192 sentry_sdk.init("https://a40f22e13cbc4261873333c125fc9d38@o33823.ingest.sentry.io/157615", - default_integrations=False, release=version) + default_integrations=False, release=get_version()) dongle_id = Params().get("DongleId", encoding='utf-8') sentry_sdk.set_user({"id": dongle_id}) - sentry_sdk.set_tag("dirty", dirty) - sentry_sdk.set_tag("origin", origin) - sentry_sdk.set_tag("branch", branch) - sentry_sdk.set_tag("commit", commit) + sentry_sdk.set_tag("dirty", get_dirty()) + sentry_sdk.set_tag("origin", get_origin()) + sentry_sdk.set_tag("branch", get_branch()) + sentry_sdk.set_tag("commit", get_commit()) sentry_sdk.set_tag("device", HARDWARE.get_device_type()) while True: diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index bc21022dd9..da16c12d14 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -116,7 +116,7 @@ if arch in ['x86_64', 'Darwin'] or GetOption('extras'): replay_lib_src = ["replay/replay.cc", "replay/camera.cc", "replay/filereader.cc", "replay/logreader.cc", "replay/framereader.cc", "replay/route.cc", "replay/util.cc"] replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=base_libs) - replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'swscale', 'yuv'] + qt_libs + replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs qt_env.Program("replay/replay", ["replay/main.cc"], LIBS=replay_libs) qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11']) @@ -126,5 +126,8 @@ if arch in ['x86_64', 'Darwin'] or GetOption('extras'): # navd if maps: - navd_src = ["navd/main.cc", "navd/route_engine.cc"] + navd_src = ["navd/main.cc", "navd/route_engine.cc", "navd/map_renderer.cc"] qt_env.Program("navd/_navd", navd_src, LIBS=qt_libs + ['common', 'json11']) + + if GetOption('extras'): + qt_env.SharedLibrary("navd/map_renderer", ["navd/map_renderer.cc"], LIBS=qt_libs + ['common', 'messaging']) diff --git a/selfdrive/ui/navd/main.cc b/selfdrive/ui/navd/main.cc index cc8d9c339b..7ae0393a47 100644 --- a/selfdrive/ui/navd/main.cc +++ b/selfdrive/ui/navd/main.cc @@ -5,9 +5,11 @@ #include #include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/navd/route_engine.h" - -RouteEngine* route_engine = nullptr; +#include "selfdrive/ui/navd/map_renderer.h" +#include "selfdrive/hardware/hw.h" +#include "selfdrive/common/params.h" void sigHandler(int s) { qInfo() << "Shutting down"; @@ -30,7 +32,14 @@ int main(int argc, char *argv[]) { parser.process(app); const QStringList args = parser.positionalArguments(); - route_engine = new RouteEngine(); + + RouteEngine* route_engine = new RouteEngine(); + + if (Params().getBool("NavdRender")) { + MapRenderer * m = new MapRenderer(get_mapbox_settings()); + QObject::connect(route_engine, &RouteEngine::positionUpdated, m, &MapRenderer::updatePosition); + QObject::connect(route_engine, &RouteEngine::routeUpdated, m, &MapRenderer::updateRoute); + } return app.exec(); } diff --git a/selfdrive/ui/navd/map_renderer.cc b/selfdrive/ui/navd/map_renderer.cc new file mode 100644 index 0000000000..1d78d68dfe --- /dev/null +++ b/selfdrive/ui/navd/map_renderer.cc @@ -0,0 +1,200 @@ +#include "selfdrive/ui/navd/map_renderer.h" + +#include +#include +#include + +#include "selfdrive/ui/qt/maps/map_helpers.h" +#include "selfdrive/common/timing.h" + +const float ZOOM = 13.5; // Don't go below 13 or features will start to disappear +const int WIDTH = 256; +const int HEIGHT = WIDTH; + +const int NUM_VIPC_BUFFERS = 4; + +MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool enable_vipc) : m_settings(settings) { + QSurfaceFormat fmt; + fmt.setRenderableType(QSurfaceFormat::OpenGLES); + + ctx = std::make_unique(); + ctx->setFormat(fmt); + ctx->create(); + assert(ctx->isValid()); + + surface = std::make_unique(); + surface->setFormat(ctx->format()); + surface->create(); + + ctx->makeCurrent(surface.get()); + assert(QOpenGLContext::currentContext() == ctx.get()); + + gl_functions.reset(ctx->functions()); + gl_functions->initializeOpenGLFunctions(); + + QOpenGLFramebufferObjectFormat fbo_format; + fbo.reset(new QOpenGLFramebufferObject(WIDTH, HEIGHT, fbo_format)); + + m_map.reset(new QMapboxGL(nullptr, m_settings, fbo->size(), 1)); + m_map->setCoordinateZoom(QMapbox::Coordinate(0, 0), ZOOM); + m_map->setStyleUrl("mapbox://styles/commaai/ckvmksrpd4n0a14pfdo5heqzr"); + m_map->createRenderer(); + + m_map->resize(fbo->size()); + m_map->setFramebufferObject(fbo->handle(), fbo->size()); + gl_functions->glViewport(0, 0, WIDTH, HEIGHT); + + if (enable_vipc) { + qWarning() << "Enabling navd map rendering"; + vipc_server.reset(new VisionIpcServer("navd")); + vipc_server->create_buffers(VisionStreamType::VISION_STREAM_RGB_MAP, NUM_VIPC_BUFFERS, true, WIDTH, HEIGHT); + vipc_server->start_listener(); + + pm.reset(new PubMaster({"navThumbnail"})); + } +} + +void MapRenderer::updatePosition(QMapbox::Coordinate position, float bearing) { + if (m_map.isNull()) { + return; + } + + m_map->setCoordinate(position); + m_map->setBearing(bearing); + update(); +} + +bool MapRenderer::loaded() { + return m_map->isFullyLoaded(); +} + +void MapRenderer::update() { + gl_functions->glClear(GL_COLOR_BUFFER_BIT); + m_map->render(); + gl_functions->glFlush(); + + sendVipc(); +} + +void MapRenderer::sendVipc() { + if (!vipc_server || !loaded()) { + return; + } + + QImage cap = fbo->toImage().convertToFormat(QImage::Format_RGB888, Qt::AutoColor); + uint64_t ts = nanos_since_boot(); + VisionBuf* buf = vipc_server->get_buffer(VisionStreamType::VISION_STREAM_RGB_MAP); + VisionIpcBufExtra extra = { + .frame_id = frame_id, + .timestamp_sof = ts, + .timestamp_eof = ts, + }; + + assert(cap.sizeInBytes() == buf->len); + memcpy(buf->addr, cap.bits(), buf->len); + vipc_server->send(buf, &extra); + + if (frame_id % 100 == 0) { + // Write jpeg into buffer + QByteArray buffer_bytes; + QBuffer buffer(&buffer_bytes); + buffer.open(QIODevice::WriteOnly); + cap.save(&buffer, "JPG", 50); + + kj::Array buffer_kj = kj::heapArray((const capnp::byte*)buffer_bytes.constData(), buffer_bytes.size()); + + // Send thumbnail + MessageBuilder msg; + auto thumbnaild = msg.initEvent().initNavThumbnail(); + thumbnaild.setFrameId(frame_id); + thumbnaild.setTimestampEof(ts); + thumbnaild.setThumbnail(buffer_kj); + pm->send("navThumbnail", msg); + } + + frame_id++; +} + +uint8_t* MapRenderer::getImage() { + QImage cap = fbo->toImage().convertToFormat(QImage::Format_RGB888, Qt::AutoColor); + uint8_t* buf = new uint8_t[cap.sizeInBytes()]; + memcpy(buf, cap.bits(), cap.sizeInBytes()); + + return buf; +} + +void MapRenderer::updateRoute(QList coordinates) { + if (m_map.isNull()) return; + initLayers(); + + auto route_points = coordinate_list_to_collection(coordinates); + QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {}); + QVariantMap navSource; + navSource["type"] = "geojson"; + navSource["data"] = QVariant::fromValue(feature); + m_map->updateSource("navSource", navSource); + m_map->setLayoutProperty("navLayer", "visibility", "visible"); +} + +void MapRenderer::initLayers() { + if (!m_map->layerExists("navLayer")) { + QVariantMap nav; + nav["id"] = "navLayer"; + nav["type"] = "line"; + nav["source"] = "navSource"; + m_map->addLayer(nav, "road-intersection"); + m_map->setPaintProperty("navLayer", "line-color", QColor("blue")); + m_map->setPaintProperty("navLayer", "line-width", 3); + m_map->setLayoutProperty("navLayer", "line-cap", "round"); + } +} + +MapRenderer::~MapRenderer() { +} + +extern "C" { + MapRenderer* map_renderer_init() { + char *argv[] = { + (char*)"navd", + nullptr + }; + int argc = 0; + QApplication *app = new QApplication(argc, argv); + assert(app); + + QMapboxGLSettings settings; + settings.setApiBaseUrl(MAPS_HOST); + settings.setAccessToken(get_mapbox_token()); + + return new MapRenderer(settings, false); + } + + void map_renderer_update_position(MapRenderer *inst, float lat, float lon, float bearing) { + inst->updatePosition({lat, lon}, bearing); + QApplication::processEvents(); + } + + void map_renderer_update_route(MapRenderer *inst, char* polyline) { + inst->updateRoute(polyline_to_coordinate_list(QString::fromUtf8(polyline))); + } + + void map_renderer_update(MapRenderer *inst) { + inst->update(); + } + + void map_renderer_process(MapRenderer *inst) { + QApplication::processEvents(); + } + + bool map_renderer_loaded(MapRenderer *inst) { + return inst->loaded(); + } + + uint8_t * map_renderer_get_image(MapRenderer *inst) { + return inst->getImage(); + } + + void map_renderer_free_image(MapRenderer *inst, uint8_t * buf) { + delete[] buf; + } +} diff --git a/selfdrive/ui/navd/map_renderer.h b/selfdrive/ui/navd/map_renderer.h new file mode 100644 index 0000000000..1746e76695 --- /dev/null +++ b/selfdrive/ui/navd/map_renderer.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cereal/visionipc/visionipc_server.h" +#include "cereal/messaging/messaging.h" + + +class MapRenderer : public QObject { + Q_OBJECT + +public: + MapRenderer(const QMapboxGLSettings &, bool enable_vipc=true); + uint8_t* getImage(); + void update(); + bool loaded(); + ~MapRenderer(); + + +private: + std::unique_ptr ctx; + std::unique_ptr surface; + std::unique_ptr gl_functions; + std::unique_ptr fbo; + + std::unique_ptr vipc_server; + std::unique_ptr pm; + void sendVipc(); + + QMapboxGLSettings m_settings; + QScopedPointer m_map; + + void initLayers(); + + uint32_t frame_id = 0; + +public slots: + void updatePosition(QMapbox::Coordinate position, float bearing); + void updateRoute(QList coordinates); +}; diff --git a/selfdrive/ui/navd/map_renderer.py b/selfdrive/ui/navd/map_renderer.py new file mode 100755 index 0000000000..28d006841b --- /dev/null +++ b/selfdrive/ui/navd/map_renderer.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# You might need to uninstall the PyQt5 pip package to avoid conflicts + +import os +import time +from cffi import FFI + +from common.ffi_wrapper import suffix +from common.basedir import BASEDIR + +HEIGHT = WIDTH = 256 + + +def get_ffi(): + lib = os.path.join(BASEDIR, "selfdrive", "ui", "navd", "libmap_renderer" + suffix()) + + ffi = FFI() + ffi.cdef(""" +void* map_renderer_init(); +void map_renderer_update_position(void *inst, float lat, float lon, float bearing); +void map_renderer_update_route(void *inst, char *polyline); +void map_renderer_update(void *inst); +void map_renderer_process(void *inst); +bool map_renderer_loaded(void *inst); +uint8_t* map_renderer_get_image(void *inst); +void map_renderer_free_image(void *inst, uint8_t *buf); +""") + return ffi, ffi.dlopen(lib) + + +def wait_ready(lib, renderer): + while not lib.map_renderer_loaded(renderer): + lib.map_renderer_update(renderer) + + # The main qt app is not execed, so we need to periodically process events for e.g. network requests + lib.map_renderer_process(renderer) + + time.sleep(0.01) + + +def get_image(lib, renderer): + buf = lib.map_renderer_get_image(renderer) + r = list(buf[0:3 * WIDTH * HEIGHT]) + lib.map_renderer_free_image(renderer, buf) + + # Convert to numpy + r = np.asarray(r) + return r.reshape((WIDTH, HEIGHT, 3)) + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + import numpy as np + + ffi, lib = get_ffi() + renderer = lib.map_renderer_init() + wait_ready(lib, renderer) + + geometry = r"{yxk}@|obn~Eg@@eCFqc@J{RFw@?kA@gA?q|@Riu@NuJBgi@ZqVNcRBaPBkG@iSD{I@_H@cH?gG@mG@gG?aD@{LDgDDkVVyQLiGDgX@q_@@qI@qKhS{R~[}NtYaDbGoIvLwNfP_b@|f@oFnF_JxHel@bf@{JlIuxAlpAkNnLmZrWqFhFoh@jd@kX|TkJxH_RnPy^|[uKtHoZ~Um`DlkCorC``CuShQogCtwB_ThQcr@fk@sVrWgRhVmSb\\oj@jxA{Qvg@u]tbAyHzSos@xjBeKbWszAbgEc~@~jCuTrl@cYfo@mRn\\_m@v}@ij@jp@om@lk@y|A`pAiXbVmWzUod@xj@wNlTw}@|uAwSn\\kRfYqOdS_IdJuK`KmKvJoOhLuLbHaMzGwO~GoOzFiSrEsOhD}PhCqw@vJmnAxSczA`Vyb@bHk[fFgl@pJeoDdl@}}@zIyr@hG}X`BmUdBcM^aRR}Oe@iZc@mR_@{FScHxAn_@vz@zCzH~GjPxAhDlB~DhEdJlIbMhFfG|F~GlHrGjNjItLnGvQ~EhLnBfOn@p`@AzAAvn@CfC?fc@`@lUrArStCfSxEtSzGxM|ElFlBrOzJlEbDnC~BfDtCnHjHlLvMdTnZzHpObOf^pKla@~G|a@dErg@rCbj@zArYlj@ttJ~AfZh@r]LzYg@`TkDbj@gIdv@oE|i@kKzhA{CdNsEfOiGlPsEvMiDpLgBpHyB`MkB|MmArPg@|N?|P^rUvFz~AWpOCdAkB|PuB`KeFfHkCfGy@tAqC~AsBPkDs@uAiAcJwMe@s@eKkPMoXQux@EuuCoH?eI?Kas@}Dy@wAUkMOgDL" + lib.map_renderer_update_route(renderer, geometry.encode()) + + POSITIONS = [ + (32.71569271952601, -117.16384270868463, 0), (32.71569271952601, -117.16384270868463, 45), # San Diego + (52.378641991483136, 4.902623379456488, 0), (52.378641991483136, 4.902623379456488, 45), # Amsterdam + ] + plt.figure() + + for i, pos in enumerate(POSITIONS): + t = time.time() + lib.map_renderer_update_position(renderer, *pos) + wait_ready(lib, renderer) + + print(f"{pos} took {time.time() - t:.2f} s") + + plt.subplot(2, 2, i + 1) + plt.imshow(get_image(lib, renderer)) + + plt.show() diff --git a/selfdrive/ui/navd/route_engine.cc b/selfdrive/ui/navd/route_engine.cc index 30d070dc73..f3cb7df3d9 100644 --- a/selfdrive/ui/navd/route_engine.cc +++ b/selfdrive/ui/navd/route_engine.cc @@ -91,7 +91,6 @@ static void parse_banner(cereal::NavInstruction::Builder &instruction, const QMa } } } - } RouteEngine::RouteEngine() { @@ -99,14 +98,17 @@ RouteEngine::RouteEngine() { pm = new PubMaster({"navInstruction", "navRoute"}); // Timers - timer = new QTimer(this); - QObject::connect(timer, SIGNAL(timeout()), this, SLOT(timerUpdate())); - timer->start(1000); + route_timer = new QTimer(this); + QObject::connect(route_timer, SIGNAL(timeout()), this, SLOT(routeUpdate())); + route_timer->start(1000); + + msg_timer = new QTimer(this); + QObject::connect(msg_timer, SIGNAL(timeout()), this, SLOT(msgUpdate())); + msg_timer->start(50); // Build routing engine QVariantMap parameters; - QString token = MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN; - parameters["mapbox.access_token"] = token; + parameters["mapbox.access_token"] = get_mapbox_token(); parameters["mapbox.directions_api_url"] = MAPS_HOST + "/directions/v5/mapbox/"; geoservice_provider = new QGeoServiceProvider("mapbox", parameters); @@ -124,12 +126,14 @@ RouteEngine::RouteEngine() { } } -void RouteEngine::timerUpdate() { - sm->update(0); +void RouteEngine::msgUpdate() { + sm->update(1000); if (!sm->updated("liveLocationKalman")) { + active = false; return; } + if (sm->updated("managerState")) { for (auto const &p : (*sm)["managerState"].getManagerState().getProcesses()) { if (p.getName() == "ui" && p.getRunning()) { @@ -153,6 +157,15 @@ void RouteEngine::timerUpdate() { if (localizer_valid) { last_bearing = RAD2DEG(orientation.getValue()[2]); last_position = QMapbox::Coordinate(pos.getValue()[0], pos.getValue()[1]); + emit positionUpdated(*last_position, *last_bearing); + } + + active = true; +} + +void RouteEngine::routeUpdate() { + if (!active) { + return; } recomputeRoute(); @@ -261,6 +274,10 @@ bool RouteEngine::shouldRecompute() { } void RouteEngine::recomputeRoute() { + if (!last_position) { + return; + } + auto new_destination = coordinate_from_param("NavDestination"); if (!new_destination) { clearRoute(); @@ -308,6 +325,9 @@ void RouteEngine::routeCalculated(QGeoRouteReply *reply) { route = reply->routes().at(0); segment = route.firstRouteSegment(); + + auto path = route.path(); + emit routeUpdated(path); } else { qWarning() << "Got empty route response"; } diff --git a/selfdrive/ui/navd/route_engine.h b/selfdrive/ui/navd/route_engine.h index 9a6c990417..33cbc79107 100644 --- a/selfdrive/ui/navd/route_engine.h +++ b/selfdrive/ui/navd/route_engine.h @@ -23,7 +23,8 @@ public: SubMaster *sm; PubMaster *pm; - QTimer* timer; + QTimer* msg_timer; + QTimer* route_timer; std::optional ui_pid; @@ -41,6 +42,7 @@ public: bool localizer_valid = false; // Route recompute + bool active = false; int recompute_backoff = 0; int recompute_countdown = 0; void calculateRoute(QMapbox::Coordinate destination); @@ -48,8 +50,13 @@ public: bool shouldRecompute(); private slots: - void timerUpdate(); + void routeUpdate(); + void msgUpdate(); void routeCalculated(QGeoRouteReply *reply); void recomputeRoute(); void sendRoute(); + +signals: + void positionUpdated(QMapbox::Coordinate position, float bearing); + void routeUpdated(QList coordinates); }; diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc index 6339884aec..8ac9b41313 100644 --- a/selfdrive/ui/qt/api.cc +++ b/selfdrive/ui/qt/api.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -62,18 +63,20 @@ QString create_jwt(const QJsonObject &payloads, int expiry) { } // namespace CommaApi HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) { - networkAccessManager = new QNetworkAccessManager(this); - networkTimer = new QTimer(this); networkTimer->setSingleShot(true); networkTimer->setInterval(timeout); connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout); } -bool HttpRequest::active() { +bool HttpRequest::active() const { return reply != nullptr; } +bool HttpRequest::timeout() const { + return reply && reply->error() == QNetworkReply::OperationCanceledError; +} + void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method) { if (active()) { qDebug() << "HttpRequest is active"; @@ -97,9 +100,9 @@ void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Meth } if (method == HttpRequest::Method::GET) { - reply = networkAccessManager->get(request); + reply = nam()->get(request); } else if (method == HttpRequest::Method::DELETE) { - reply = networkAccessManager->deleteResource(request); + reply = nam()->deleteResource(request); } networkTimer->start(); @@ -110,29 +113,31 @@ void HttpRequest::requestTimeout() { reply->abort(); } -// This function should always emit something void HttpRequest::requestFinished() { - bool success = false; - if (reply->error() != QNetworkReply::OperationCanceledError) { - networkTimer->stop(); - QString response = reply->readAll(); - - if (reply->error() == QNetworkReply::NoError) { - success = true; - emit receivedResponse(response); - } else { - emit failedResponse(reply->errorString()); + networkTimer->stop(); + if (reply->error() == QNetworkReply::NoError) { + emit requestDone(reply->readAll(), true); + } else { + QString error; + if (reply->error() == QNetworkReply::OperationCanceledError) { + nam()->clearAccessCache(); + nam()->clearConnectionCache(); + error = "Request timed out"; + } else { if (reply->error() == QNetworkReply::ContentAccessDenied || reply->error() == QNetworkReply::AuthenticationRequiredError) { qWarning() << ">> Unauthorized. Authenticate with tools/lib/auth.py <<"; } + error = reply->errorString(); } - } else { - networkAccessManager->clearAccessCache(); - networkAccessManager->clearConnectionCache(); - emit timeoutResponse("timeout"); + emit requestDone(error, false); } - emit requestDone(success); + reply->deleteLater(); reply = nullptr; } + +QNetworkAccessManager *HttpRequest::nam() { + static QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(qApp); + return networkAccessManager; +} diff --git a/selfdrive/ui/qt/api.h b/selfdrive/ui/qt/api.h index 1c7c333ea0..8f74678109 100644 --- a/selfdrive/ui/qt/api.h +++ b/selfdrive/ui/qt/api.h @@ -27,23 +27,21 @@ public: explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000); void sendRequest(const QString &requestURL, const Method method = Method::GET); - bool active(); + bool active() const; + bool timeout() const; + +signals: + void requestDone(const QString &response, bool success); protected: QNetworkReply *reply = nullptr; private: - QNetworkAccessManager *networkAccessManager = nullptr; + static QNetworkAccessManager *nam(); QTimer *networkTimer = nullptr; bool create_jwt; private slots: void requestTimeout(); void requestFinished(); - -signals: - void requestDone(bool success); - void receivedResponse(const QString &response); - void failedResponse(const QString &errorString); - void timeoutResponse(const QString &errorString); }; diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 5484c5d743..1e89264952 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -178,6 +178,6 @@ void OffroadHome::refresh() { update_notif->setVisible(updateAvailable); alert_notif->setVisible(alerts); if (alerts) { - alert_notif->setText(QString::number(alerts) + " ALERT" + (alerts > 1 ? "S" : "")); + alert_notif->setText(QString::number(alerts) + (alerts > 1 ? " ALERTS" : " ALERT")); } } diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index b74f5bf29d..c62089cb25 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -22,9 +22,6 @@ #include "selfdrive/common/util.h" #include "cereal/messaging/messaging.h" -const QString MAPBOX_TOKEN = util::getenv("MAPBOX_TOKEN").c_str(); -const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str(); - class MapInstructions : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index 422ae99b6b..f87e80403f 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -4,7 +4,25 @@ #include #include "selfdrive/common/params.h" +#include "selfdrive/hardware/hw.h" +#include "selfdrive/ui/qt/api.h" +QString get_mapbox_token() { + // Valid for 4 weeks since we can't swap tokens on the fly + return MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN; +} + +QMapboxGLSettings get_mapbox_settings() { + QMapboxGLSettings settings; + + if (!Hardware::PC()) { + settings.setCacheDatabasePath(MAPS_CACHE_PATH); + } + settings.setApiBaseUrl(MAPS_HOST); + settings.setAccessToken(get_mapbox_token()); + + return settings; +} QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in) { return QGeoCoordinate(in.first, in.second); @@ -83,6 +101,48 @@ QMapbox::CoordinatesCollections coordinate_list_to_collection(QList polyline_to_coordinate_list(const QString &polylineString) { + QList path; + if (polylineString.isEmpty()) + return path; + + QByteArray data = polylineString.toLatin1(); + + bool parsingLatitude = true; + + int shift = 0; + int value = 0; + + QGeoCoordinate coord(0, 0); + + for (int i = 0; i < data.length(); ++i) { + unsigned char c = data.at(i) - 63; + + value |= (c & 0x1f) << shift; + shift += 5; + + // another chunk + if (c & 0x20) + continue; + + int diff = (value & 1) ? ~(value >> 1) : (value >> 1); + + if (parsingLatitude) { + coord.setLatitude(coord.latitude() + (double)diff/1e6); + } else { + coord.setLongitude(coord.longitude() + (double)diff/1e6); + path.append(coord); + } + + parsingLatitude = !parsingLatitude; + + value = 0; + shift = 0; + } + + return path; +} + static QGeoCoordinate sub(QGeoCoordinate v, QGeoCoordinate w) { return QGeoCoordinate(v.latitude() - w.latitude(), v.longitude() - w.longitude()); } diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h index 53a44f42d8..1c8cacbebc 100644 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -5,12 +5,17 @@ #include #include +#include "selfdrive/common/util.h" #include "common/transformations/coordinates.hpp" #include "common/transformations/orientation.hpp" #include "cereal/messaging/messaging.h" -#define RAD2DEG(x) ((x) * 180.0 / M_PI) +const QString MAPBOX_TOKEN = util::getenv("MAPBOX_TOKEN").c_str(); +const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str(); +const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db"; +QString get_mapbox_token(); +QMapboxGLSettings get_mapbox_settings(); QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in); QMapbox::CoordinatesCollections model_to_collection( const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, @@ -19,6 +24,7 @@ QMapbox::CoordinatesCollections model_to_collection( QMapbox::CoordinatesCollections coordinate_to_collection(QMapbox::Coordinate c); QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader &coordinate_list); QMapbox::CoordinatesCollections coordinate_list_to_collection(QList coordinate_list); +QList polyline_to_coordinate_list(const QString &polylineString); float minimum_distance(QGeoCoordinate a, QGeoCoordinate b, QGeoCoordinate p); std::optional coordinate_from_param(std::string param); diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc index 60a7812265..e130a9a1ec 100644 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -127,8 +127,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { { QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations"; RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true); - QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &MapPanel::parseResponse); - QObject::connect(repeater, &RequestRepeater::failedResponse, this, &MapPanel::failedResponse); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &MapPanel::parseResponse); } // Destination set while offline @@ -137,8 +136,8 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { RequestRepeater* repeater = new RequestRepeater(this, url, "", 10, true); HttpRequest* deleter = new HttpRequest(this); - QObject::connect(repeater, &RequestRepeater::receivedResponse, [=](QString resp) { - if (resp != "null") { + QObject::connect(repeater, &RequestRepeater::requestDone, [=](const QString &resp, bool success) { + if (success && resp != "null") { if (params.get("NavDestination").empty()) { qWarning() << "Setting NavDestination from /next" << resp; params.put("NavDestination", resp.toStdString()); @@ -183,7 +182,12 @@ void MapPanel::updateCurrentRoute() { current_widget->setVisible(dest.size() && !doc.isNull()); } -void MapPanel::parseResponse(const QString &response) { +void MapPanel::parseResponse(const QString &response, bool success) { + if (!success) { + stack->setCurrentIndex(1); + return; + } + QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8()); if (doc.isNull()) { qDebug() << "JSON Parse failed on navigation locations"; @@ -283,10 +287,6 @@ void MapPanel::parseResponse(const QString &response) { repaint(); } -void MapPanel::failedResponse(const QString &response) { - stack->setCurrentIndex(1); -} - void MapPanel::navigateTo(const QJsonObject &place) { QJsonDocument doc(place); params.put("NavDestination", doc.toJson().toStdString()); diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h index ec409c4962..03720edee7 100644 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -17,8 +17,7 @@ public: explicit MapPanel(QWidget* parent = nullptr); void navigateTo(const QJsonObject &place); - void parseResponse(const QString &response); - void failedResponse(const QString &response); + void parseResponse(const QString &response, bool success); void updateCurrentRoute(); void clear(); diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index f988d193e3..6f03e2ed4a 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -12,7 +12,7 @@ DriverViewWindow::DriverViewWindow(QWidget* parent) : QWidget(parent) { layout = new QStackedLayout(this); layout->setStackingMode(QStackedLayout::StackAll); - cameraView = new CameraViewWidget(VISION_STREAM_RGB_FRONT, true, this); + cameraView = new CameraViewWidget("camerad", VISION_STREAM_RGB_FRONT, true, this); layout->addWidget(cameraView); scene = new DriverViewScene(this); diff --git a/selfdrive/ui/qt/offroad/networking.h b/selfdrive/ui/qt/offroad/networking.h index b5a0c2ba2e..037ef82f67 100644 --- a/selfdrive/ui/qt/offroad/networking.h +++ b/selfdrive/ui/qt/offroad/networking.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index aa03ac63db..e6593f7679 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -145,7 +145,7 @@ void DeclinePage::showEvent(QShowEvent *event) { QObject::connect(back_btn, &QPushButton::clicked, this, &DeclinePage::getBack); - QPushButton *uninstall_btn = new QPushButton("Decline, uninstall " + getBrand()); + QPushButton *uninstall_btn = new QPushButton(QString("Decline, uninstall %1").arg(getBrand())); uninstall_btn->setStyleSheet("background-color: #B73D3D"); buttons->addWidget(uninstall_btn); QObject::connect(uninstall_btn, &QPushButton::clicked, [=]() { diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 7caa29c428..e5438f7c85 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -109,74 +109,53 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { } } -DevicePanel::DevicePanel(QWidget* parent) : ListWidget(parent) { - setSpacing(50); +DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { Params params = Params(); - addItem(new LabelControl("Dongle ID", getDongleId().value_or("N/A"))); - QString serial = QString::fromStdString(params.get("HardwareSerial", false)); - addItem(new LabelControl("Serial", serial)); + setSpacing(50); + addItem(new LabelControl("Dongle ID", getDongleId().value_or("N/A"))); + addItem(new LabelControl("Serial", params.get("HardwareSerial").c_str())); // offroad-only buttons auto dcamBtn = new ButtonControl("Driver Camera", "PREVIEW", - "Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)"); + "Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)"); connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); }); + addItem(dcamBtn); - QString resetCalibDesc = "openpilot requires the device to be mounted within 4° left or right and within 5° up or down. openpilot is continuously calibrating, resetting is rarely required."; - auto resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", resetCalibDesc); - connect(resetCalibBtn, &ButtonControl::clicked, [=]() { + auto resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", " "); + connect(resetCalibBtn, &ButtonControl::showDescription, this, &DevicePanel::updateCalibDescription); + connect(resetCalibBtn, &ButtonControl::clicked, [&]() { if (ConfirmationDialog::confirm("Are you sure you want to reset calibration?", this)) { - Params().remove("CalibrationParams"); + params.remove("CalibrationParams"); } }); - connect(resetCalibBtn, &ButtonControl::showDescription, [=]() { - QString desc = resetCalibDesc; - std::string calib_bytes = Params().get("CalibrationParams"); - if (!calib_bytes.empty()) { - try { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(calib_bytes.data(), calib_bytes.size())); - auto calib = cmsg.getRoot().getLiveCalibration(); - if (calib.getCalStatus() != 0) { - double pitch = calib.getRpyCalib()[1] * (180 / M_PI); - double yaw = calib.getRpyCalib()[2] * (180 / M_PI); - desc += QString(" Your device is pointed %1° %2 and %3° %4.") - .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "up" : "down", - QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "right" : "left"); - } - } catch (kj::Exception) { - qInfo() << "invalid CalibrationParams"; - } - } - resetCalibBtn->setDescription(desc); - }); + addItem(resetCalibBtn); - ButtonControl *retrainingBtn = nullptr; if (!params.getBool("Passive")) { - retrainingBtn = new ButtonControl("Review Training Guide", "REVIEW", "Review the rules, features, and limitations of openpilot"); + auto retrainingBtn = new ButtonControl("Review Training Guide", "REVIEW", "Review the rules, features, and limitations of openpilot"); connect(retrainingBtn, &ButtonControl::clicked, [=]() { if (ConfirmationDialog::confirm("Are you sure you want to review the training guide?", this)) { emit reviewTrainingGuide(); } }); + addItem(retrainingBtn); } - ButtonControl *regulatoryBtn = nullptr; if (Hardware::TICI()) { - regulatoryBtn = new ButtonControl("Regulatory", "VIEW", ""); + auto regulatoryBtn = new ButtonControl("Regulatory", "VIEW", ""); connect(regulatoryBtn, &ButtonControl::clicked, [=]() { const std::string txt = util::read_file("../assets/offroad/fcc.html"); RichTextDialog::alert(QString::fromStdString(txt), this); }); + addItem(regulatoryBtn); } - for (auto btn : {dcamBtn, resetCalibBtn, retrainingBtn, regulatoryBtn}) { - if (btn) { - connect(parent, SIGNAL(offroadTransition(bool)), btn, SLOT(setEnabled(bool))); - addItem(btn); + QObject::connect(parent, &SettingsWindow::offroadTransition, [=](bool offroad) { + for (auto btn : findChildren()) { + btn->setEnabled(offroad); } - } + }); // power buttons QHBoxLayout *power_layout = new QHBoxLayout(); @@ -185,34 +164,12 @@ DevicePanel::DevicePanel(QWidget* parent) : ListWidget(parent) { QPushButton *reboot_btn = new QPushButton("Reboot"); reboot_btn->setObjectName("reboot_btn"); power_layout->addWidget(reboot_btn); - QObject::connect(reboot_btn, &QPushButton::clicked, [=]() { - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { - if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) { - // Check engaged again in case it changed while the dialog was open - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { - Params().putBool("DoReboot", true); - } - } - } else { - ConfirmationDialog::alert("Disengage to Reboot", this); - } - }); + QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot); QPushButton *poweroff_btn = new QPushButton("Power Off"); poweroff_btn->setObjectName("poweroff_btn"); power_layout->addWidget(poweroff_btn); - QObject::connect(poweroff_btn, &QPushButton::clicked, [=]() { - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { - if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) { - // Check engaged again in case it changed while the dialog was open - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { - Params().putBool("DoShutdown", true); - } - } - } else { - ConfirmationDialog::alert("Disengage to Power Off", this); - } - }); + QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff); setStyleSheet(R"( QPushButton { @@ -227,6 +184,56 @@ DevicePanel::DevicePanel(QWidget* parent) : ListWidget(parent) { addItem(power_layout); } +void DevicePanel::updateCalibDescription() { + QString desc = + "openpilot requires the device to be mounted within 4° left or right and " + "within 5° up or down. openpilot is continuously calibrating, resetting is rarely required."; + std::string calib_bytes = Params().get("CalibrationParams"); + if (!calib_bytes.empty()) { + try { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(calib_bytes.data(), calib_bytes.size())); + auto calib = cmsg.getRoot().getLiveCalibration(); + if (calib.getCalStatus() != 0) { + double pitch = calib.getRpyCalib()[1] * (180 / M_PI); + double yaw = calib.getRpyCalib()[2] * (180 / M_PI); + desc += QString(" Your device is pointed %1° %2 and %3° %4.") + .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "up" : "down", + QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "right" : "left"); + } + } catch (kj::Exception) { + qInfo() << "invalid CalibrationParams"; + } + } + qobject_cast(sender())->setDescription(desc); +} + +void DevicePanel::reboot() { + if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) { + // Check engaged again in case it changed while the dialog was open + if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + Params().putBool("DoReboot", true); + } + } + } else { + ConfirmationDialog::alert("Disengage to Reboot", this); + } +} + +void DevicePanel::poweroff() { + if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) { + // Check engaged again in case it changed while the dialog was open + if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + Params().putBool("DoShutdown", true); + } + } + } else { + ConfirmationDialog::alert("Disengage to Power Off", this); + } +} + SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { gitBranchLbl = new LabelControl("Git Branch"); gitCommitLbl = new LabelControl("Git Commit"); @@ -246,9 +253,9 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { auto uninstallBtn = new ButtonControl("Uninstall " + getBrand(), "UNINSTALL"); - connect(uninstallBtn, &ButtonControl::clicked, [=]() { + connect(uninstallBtn, &ButtonControl::clicked, [&]() { if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) { - Params().putBool("DoUninstall", true); + params.putBool("DoUninstall", true); } }); connect(parent, SIGNAL(offroadTransition(bool)), uninstallBtn, SLOT(setEnabled(bool))); diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index a2c7e01d8f..fe88180662 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -38,10 +38,15 @@ private: class DevicePanel : public ListWidget { Q_OBJECT public: - explicit DevicePanel(QWidget* parent = nullptr); + explicit DevicePanel(SettingsWindow *parent); signals: void reviewTrainingGuide(); void showDriverView(); + +private slots: + void poweroff(); + void reboot(); + void updateCalibDescription(); }; class TogglesPanel : public ListWidget { diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index e7a233000c..5abcf61047 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -5,9 +5,9 @@ #include "selfdrive/common/timing.h" #include "selfdrive/ui/paint.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/api.h" #ifdef ENABLE_MAPS #include "selfdrive/ui/qt/maps/map.h" +#include "selfdrive/ui/qt/maps/map_helpers.h" #endif OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { @@ -68,19 +68,7 @@ void OnroadWindow::offroadTransition(bool offroad) { #ifdef ENABLE_MAPS if (!offroad) { if (map == nullptr && (QUIState::ui_state.has_prime || !MAPBOX_TOKEN.isEmpty())) { - QMapboxGLSettings settings; - - // Valid for 4 weeks since we can't swap tokens on the fly - QString token = MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN; - - if (!Hardware::PC()) { - settings.setCacheDatabasePath("/data/mbgl-cache.db"); - } - settings.setApiBaseUrl(MAPS_HOST); - settings.setCacheDatabaseMaximumSize(20 * 1024 * 1024); - settings.setAccessToken(token.trimmed()); - - MapWindow * m = new MapWindow(settings); + MapWindow * m = new MapWindow(get_mapbox_settings()); m->setFixedWidth(topWidget(this)->width() / 2); QObject::connect(this, &OnroadWindow::offroadTransitionSignal, m, &MapWindow::offroadTransition); split->addWidget(m, 0, Qt::AlignRight); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index ee44973cf2..1076ed01e6 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -29,7 +29,7 @@ class NvgWindow : public CameraViewWidget { Q_OBJECT public: - explicit NvgWindow(VisionStreamType type, QWidget* parent = 0) : CameraViewWidget(type, true, parent) {} + explicit NvgWindow(VisionStreamType type, QWidget* parent = 0) : CameraViewWidget("camerad", type, true, parent) {} protected: void paintGL() override; diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc index c11698d6f1..d2c0f9bc30 100644 --- a/selfdrive/ui/qt/request_repeater.cc +++ b/selfdrive/ui/qt/request_repeater.cc @@ -15,10 +15,10 @@ RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, con if (!cacheKey.isEmpty()) { prevResp = QString::fromStdString(params.get(cacheKey.toStdString())); if (!prevResp.isEmpty()) { - QTimer::singleShot(500, [=]() { emit receivedResponse(prevResp); }); + QTimer::singleShot(500, [=]() { emit requestDone(prevResp, true); }); } - QObject::connect(this, &HttpRequest::receivedResponse, [=](const QString &resp) { - if (resp != prevResp) { + QObject::connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) { + if (success && resp != prevResp) { params.put(cacheKey.toStdString(), resp.toStdString()); prevResp = resp; } diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index a855813688..bd494327cc 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -169,7 +169,7 @@ QWidget * Setup::network_setup() { // setup timer for testing internet connection HttpRequest *request = new HttpRequest(this, false, 2500); - QObject::connect(request, &HttpRequest::requestDone, [=](bool success) { + QObject::connect(request, &HttpRequest::requestDone, [=](const QString &, bool success) { cont->setEnabled(success); if (success) { const bool cell = networking->wifi->currentNetworkType() == NetworkType::CELL; diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 1daabd7d1e..f09b1b1796 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -94,11 +94,10 @@ mat4 get_fit_view_transform(float widget_aspect_ratio, float frame_aspect_ratio) } // namespace -CameraViewWidget::CameraViewWidget(VisionStreamType type, bool zoom, QWidget* parent) : - stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { +CameraViewWidget::CameraViewWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) : + stream_name(stream_name), stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); connect(this, &CameraViewWidget::vipcThreadConnected, this, &CameraViewWidget::vipcConnected, Qt::BlockingQueuedConnection); - connect(this, &CameraViewWidget::vipcThreadFrameReceived, this, &CameraViewWidget::vipcFrameReceived); } CameraViewWidget::~CameraViewWidget() { @@ -114,7 +113,7 @@ CameraViewWidget::~CameraViewWidget() { void CameraViewWidget::initializeGL() { initializeOpenGLFunctions(); - program = new QOpenGLShaderProgram(context()); + program = std::make_unique(context()); bool ret = program->addShaderFromSourceCode(QOpenGLShader::Vertex, frame_vertex_shader); assert(ret); ret = program->addShaderFromSourceCode(QOpenGLShader::Fragment, frame_fragment_shader); @@ -152,17 +151,22 @@ void CameraViewWidget::initializeGL() { } void CameraViewWidget::showEvent(QShowEvent *event) { - latest_frame = nullptr; - vipc_thread = new QThread(); - connect(vipc_thread, &QThread::started, [=]() { vipcThread(); }); - connect(vipc_thread, &QThread::finished, vipc_thread, &QObject::deleteLater); - vipc_thread->start(); + latest_texture_id = -1; + if (!vipc_thread) { + vipc_thread = new QThread(); + connect(vipc_thread, &QThread::started, [=]() { vipcThread(); }); + connect(vipc_thread, &QThread::finished, vipc_thread, &QObject::deleteLater); + vipc_thread->start(); + } } void CameraViewWidget::hideEvent(QHideEvent *event) { - vipc_thread->requestInterruption(); - vipc_thread->quit(); - vipc_thread->wait(); + if (vipc_thread) { + vipc_thread->requestInterruption(); + vipc_thread->quit(); + vipc_thread->wait(); + vipc_thread = nullptr; + } } void CameraViewWidget::updateFrameMat(int w, int h) { @@ -195,18 +199,17 @@ void CameraViewWidget::updateFrameMat(int w, int h) { } void CameraViewWidget::paintGL() { - if (!latest_frame) { + if (latest_texture_id == -1) { glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); return; } - std::unique_lock lk(texture_lock); glViewport(0, 0, width(), height()); glBindVertexArray(frame_vao); glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture[latest_frame->idx]->frame_tex); + glBindTexture(GL_TEXTURE_2D, texture[latest_texture_id]->frame_tex); glUseProgram(program->programId()); glUniform1i(program->uniformLocation("uTexture"), 0); @@ -234,17 +237,12 @@ void CameraViewWidget::vipcConnected(VisionIpcClient *vipc_client) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); assert(glGetError() == GL_NO_ERROR); } - latest_frame = nullptr; + latest_texture_id = -1; stream_width = vipc_client->buffers[0].width; stream_height = vipc_client->buffers[0].height; updateFrameMat(width(), height()); } -void CameraViewWidget::vipcFrameReceived(VisionBuf *buf) { - latest_frame = buf; - update(); -} - void CameraViewWidget::vipcThread() { VisionStreamType cur_stream_type = stream_type; std::unique_ptr vipc_client; @@ -271,7 +269,7 @@ void CameraViewWidget::vipcThread() { while (!QThread::currentThread()->isInterruptionRequested()) { if (!vipc_client || cur_stream_type != stream_type) { cur_stream_type = stream_type; - vipc_client.reset(new VisionIpcClient("camerad", cur_stream_type, true)); + vipc_client.reset(new VisionIpcClient(stream_name, cur_stream_type, true)); } if (!vipc_client->connected) { @@ -293,8 +291,6 @@ void CameraViewWidget::vipcThread() { if (VisionBuf *buf = vipc_client->recv(nullptr, 1000)) { if (!Hardware::EON()) { - std::unique_lock lk(texture_lock); - void *texture_buffer = gl_buffer->map(QOpenGLBuffer::WriteOnly); memcpy(texture_buffer, buf->addr, buf->len); gl_buffer->unmap(); @@ -304,13 +300,15 @@ void CameraViewWidget::vipcThread() { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, buf->width, buf->height, GL_RGB, GL_UNSIGNED_BYTE, 0); glBindTexture(GL_TEXTURE_2D, 0); assert(glGetError() == GL_NO_ERROR); - - emit vipcThreadFrameReceived(buf); - - glFlush(); - } else { - emit vipcThreadFrameReceived(buf); + // use glFinish to ensure that the texture has been uploaded. + glFinish(); } + latest_texture_id = buf->idx; + // Schedule update. update() will be invoked on the gui thread. + QMetaObject::invokeMethod(this, "update"); + + // TODO: remove later, it's only connected by DriverView. + emit vipcThreadFrameReceived(buf); } } } diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 90622c6b77..146528cd23 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -15,7 +15,7 @@ class CameraViewWidget : public QOpenGLWidget, protected QOpenGLFunctions { public: using QOpenGLWidget::QOpenGLWidget; - explicit CameraViewWidget(VisionStreamType stream_type, bool zoom, QWidget* parent = nullptr); + explicit CameraViewWidget(std::string stream_name, VisionStreamType stream_type, bool zoom, QWidget* parent = nullptr); ~CameraViewWidget(); void setStreamType(VisionStreamType type) { stream_type = type; } void setBackgroundColor(const QColor &color) { bg = color; } @@ -36,21 +36,20 @@ protected: void vipcThread(); bool zoomed_view; - VisionBuf *latest_frame = nullptr; + std::atomic latest_texture_id = -1; GLuint frame_vao, frame_vbo, frame_ibo; mat4 frame_mat; std::unique_ptr texture[UI_BUF_COUNT]; - QOpenGLShaderProgram *program; + std::unique_ptr program; QColor bg = QColor("#000000"); + std::string stream_name; int stream_width = 0; int stream_height = 0; std::atomic stream_type; QThread *vipc_thread = nullptr; - std::mutex texture_lock; protected slots: void vipcConnected(VisionIpcClient *vipc_client); - void vipcFrameReceived(VisionBuf *buf); }; diff --git a/selfdrive/ui/qt/widgets/drive_stats.cc b/selfdrive/ui/qt/widgets/drive_stats.cc index bd1dc71766..f4c8f502a3 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.cc +++ b/selfdrive/ui/qt/widgets/drive_stats.cc @@ -48,7 +48,7 @@ DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { if (auto dongleId = getDongleId()) { QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats"; RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_DriveStats", 30); - QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &DriveStats::parseResponse); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &DriveStats::parseResponse); } setStyleSheet(R"( @@ -76,7 +76,9 @@ void DriveStats::updateStats() { update(json["week"].toObject(), week_); } -void DriveStats::parseResponse(const QString& response) { +void DriveStats::parseResponse(const QString& response, bool success) { + if (!success) return; + QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8()); if (doc.isNull()) { qDebug() << "JSON Parse failed on getting past drives statistics"; diff --git a/selfdrive/ui/qt/widgets/drive_stats.h b/selfdrive/ui/qt/widgets/drive_stats.h index 40ecbdeaf2..9446294516 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.h +++ b/selfdrive/ui/qt/widgets/drive_stats.h @@ -21,5 +21,5 @@ private: } all_, week_; private slots: - void parseResponse(const QString &response); + void parseResponse(const QString &response, bool success); }; diff --git a/selfdrive/ui/qt/widgets/keyboard.cc b/selfdrive/ui/qt/widgets/keyboard.cc index e6e60e4097..1c59686535 100644 --- a/selfdrive/ui/qt/widgets/keyboard.cc +++ b/selfdrive/ui/qt/widgets/keyboard.cc @@ -126,7 +126,7 @@ Keyboard::Keyboard(QWidget *parent) : QFrame(parent) { std::vector> specials = { {"[","]","{","}","#","%","^","*","+","="}, {"_","\\","|","~","<",">","€","£","¥","•"}, - {"123",".",",","?","!","`",BACKSPACE_KEY}, + {"123",".",",","?","!","'",BACKSPACE_KEY}, {"ABC"," ",".",ENTER_KEY}, }; main_layout->addWidget(new KeyboardLayout(this, specials)); diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index 665466df5e..ab6dc67d36 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -161,7 +161,7 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) { if (auto dongleId = getDongleId()) { QString url = CommaApi::BASE_URL + "/v1/devices/" + *dongleId + "/owner"; RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_Owner", 6); - QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &PrimeUserWidget::replyFinished); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &PrimeUserWidget::replyFinished); } } @@ -291,14 +291,14 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) { QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/"; RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_Device", 5); - QObject::connect(repeater, &RequestRepeater::failedResponse, this, &SetupWidget::show); - QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &SetupWidget::replyFinished); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &SetupWidget::replyFinished); } hide(); // Only show when first request comes back } -void SetupWidget::replyFinished(const QString &response) { +void SetupWidget::replyFinished(const QString &response, bool success) { show(); + if (!success) return; QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); if (doc.isNull()) { diff --git a/selfdrive/ui/qt/widgets/prime.h b/selfdrive/ui/qt/widgets/prime.h index 6044f06104..f7470fe441 100644 --- a/selfdrive/ui/qt/widgets/prime.h +++ b/selfdrive/ui/qt/widgets/prime.h @@ -68,5 +68,5 @@ private: PrimeUserWidget *primeUser; private slots: - void replyFinished(const QString &response); + void replyFinished(const QString &response, bool success); }; diff --git a/selfdrive/ui/qt/widgets/ssh_keys.cc b/selfdrive/ui/qt/widgets/ssh_keys.cc index c6af2f5cd1..7630e65731 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.cc +++ b/selfdrive/ui/qt/widgets/ssh_keys.cc @@ -41,23 +41,22 @@ void SshControl::refresh() { void SshControl::getUserKeys(const QString &username) { HttpRequest *request = new HttpRequest(this, false); - QObject::connect(request, &HttpRequest::receivedResponse, [=](const QString &resp) { - if (!resp.isEmpty()) { - params.put("GithubUsername", username.toStdString()); - params.put("GithubSshKeys", resp.toStdString()); + QObject::connect(request, &HttpRequest::requestDone, [=](const QString &resp, bool success) { + if (success) { + if (!resp.isEmpty()) { + params.put("GithubUsername", username.toStdString()); + params.put("GithubSshKeys", resp.toStdString()); + } else { + ConfirmationDialog::alert(QString("Username '%1' has no keys on GitHub").arg(username), this); + } } else { - ConfirmationDialog::alert("Username '" + username + "' has no keys on GitHub", this); + if (request->timeout()) { + ConfirmationDialog::alert("Request timed out", this); + } else { + ConfirmationDialog::alert(QString("Username '%1' doesn't exist on GitHub").arg(username), this); + } } - refresh(); - request->deleteLater(); - }); - QObject::connect(request, &HttpRequest::failedResponse, [=] { - ConfirmationDialog::alert("Username '" + username + "' doesn't exist on GitHub", this); - refresh(); - request->deleteLater(); - }); - QObject::connect(request, &HttpRequest::timeoutResponse, [=] { - ConfirmationDialog::alert("Request timed out", this); + refresh(); request->deleteLater(); }); diff --git a/selfdrive/ui/replay/camera.h b/selfdrive/ui/replay/camera.h index 8b1fa7620b..21e02292d5 100644 --- a/selfdrive/ui/replay/camera.h +++ b/selfdrive/ui/replay/camera.h @@ -32,9 +32,9 @@ protected: void cameraThread(Camera &cam); Camera cameras_[MAX_CAMERAS] = { - {.type = RoadCam, .rgb_type = VISION_STREAM_RGB_BACK, .yuv_type = VISION_STREAM_YUV_BACK}, - {.type = DriverCam, .rgb_type = VISION_STREAM_RGB_FRONT, .yuv_type = VISION_STREAM_YUV_FRONT}, - {.type = WideRoadCam, .rgb_type = VISION_STREAM_RGB_WIDE, .yuv_type = VISION_STREAM_YUV_WIDE}, + {.type = RoadCam, .rgb_type = VISION_STREAM_RGB_BACK, .yuv_type = VISION_STREAM_ROAD}, + {.type = DriverCam, .rgb_type = VISION_STREAM_RGB_FRONT, .yuv_type = VISION_STREAM_DRIVER}, + {.type = WideRoadCam, .rgb_type = VISION_STREAM_RGB_WIDE, .yuv_type = VISION_STREAM_WIDE_ROAD}, }; std::atomic publishing_ = 0; std::unique_ptr vipc_server_; diff --git a/selfdrive/ui/replay/filereader.cc b/selfdrive/ui/replay/filereader.cc index d110255497..84dc76694b 100644 --- a/selfdrive/ui/replay/filereader.cc +++ b/selfdrive/ui/replay/filereader.cc @@ -1,12 +1,7 @@ #include "selfdrive/ui/replay/filereader.h" -#include - -#include -#include #include #include -#include #include "selfdrive/common/util.h" #include "selfdrive/ui/replay/util.h" @@ -31,7 +26,7 @@ std::string FileReader::read(const std::string &file, std::atomic *abort) } else if (is_remote) { result = download(file, abort); if (cache_to_local_ && !result.empty()) { - std::ofstream fs(local_file, fs.binary | fs.out); + std::ofstream fs(local_file, std::ios::binary | std::ios::out); fs.write(result.data(), result.size()); } } @@ -39,23 +34,13 @@ std::string FileReader::read(const std::string &file, std::atomic *abort) } std::string FileReader::download(const std::string &url, std::atomic *abort) { - std::string result; - size_t remote_file_size = 0; for (int i = 0; i <= max_retries_ && !(abort && *abort); ++i) { - if (i > 0) { - std::cout << "download failed, retrying" << i << std::endl; - } - if (remote_file_size <= 0) { - remote_file_size = getRemoteFileSize(url); + std::string result = httpGet(url, chunk_size_, abort); + if (!result.empty()) { + return result; } - if (remote_file_size > 0 && !(abort && *abort)) { - std::ostringstream oss; - result.resize(remote_file_size); - oss.rdbuf()->pubsetbuf(result.data(), result.size()); - int chunks = chunk_size_ > 0 ? std::max(1, (int)std::nearbyint(remote_file_size / (float)chunk_size_)) : 1; - if (httpMultiPartDownload(url, oss, chunks, remote_file_size, abort)) { - return result; - } + if (i != max_retries_) { + std::cout << "download failed, retrying " << i + 1 << std::endl; } } return {}; diff --git a/selfdrive/ui/replay/filereader.h b/selfdrive/ui/replay/filereader.h index 06ce14e9f2..34aa91e858 100644 --- a/selfdrive/ui/replay/filereader.h +++ b/selfdrive/ui/replay/filereader.h @@ -5,14 +5,14 @@ class FileReader { public: - FileReader(bool cache_to_local, int chunk_size = -1, int max_retries = 3) - : cache_to_local_(cache_to_local), chunk_size_(chunk_size), max_retries_(max_retries) {} + FileReader(bool cache_to_local, size_t chunk_size = 0, int retries = 3) + : cache_to_local_(cache_to_local), chunk_size_(chunk_size), max_retries_(retries) {} virtual ~FileReader() {} std::string read(const std::string &file, std::atomic *abort = nullptr); private: std::string download(const std::string &url, std::atomic *abort); - int chunk_size_; + size_t chunk_size_; int max_retries_; bool cache_to_local_; }; diff --git a/selfdrive/ui/replay/framereader.cc b/selfdrive/ui/replay/framereader.cc index fdbfc0672c..32af922f1f 100644 --- a/selfdrive/ui/replay/framereader.cc +++ b/selfdrive/ui/replay/framereader.cc @@ -27,43 +27,46 @@ enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat * if (*p == *hw_pix_fmt) return *p; } printf("Please run replay with the --no-cuda flag!\n"); - assert(0); - return AV_PIX_FMT_NONE; + // fallback to YUV420p + *hw_pix_fmt = AV_PIX_FMT_NONE; + return AV_PIX_FMT_YUV420P; } } // namespace -FrameReader::FrameReader(bool local_cache, int chunk_size, int retries) : FileReader(local_cache, chunk_size, retries) { - input_ctx = avformat_alloc_context(); - sws_frame.reset(av_frame_alloc()); -} +FrameReader::FrameReader() {} FrameReader::~FrameReader() { - for (auto &f : frames_) { - av_packet_unref(&f.pkt); + for (AVPacket *pkt : packets) { + av_packet_free(&pkt); } if (decoder_ctx) avcodec_free_context(&decoder_ctx); if (input_ctx) avformat_close_input(&input_ctx); if (hw_device_ctx) av_buffer_unref(&hw_device_ctx); - if (rgb_sws_ctx_) sws_freeContext(rgb_sws_ctx_); - if (yuv_sws_ctx_) sws_freeContext(yuv_sws_ctx_); - if (avio_ctx_) { av_freep(&avio_ctx_->buffer); avio_context_free(&avio_ctx_); } } -bool FrameReader::load(const std::string &url, bool no_cuda, std::atomic *abort) { - std::string content = read(url, abort); - if (content.empty()) return false; +bool FrameReader::load(const std::string &url, bool no_cuda, std::atomic *abort, bool local_cache, int chunk_size, int retries) { + FileReader f(local_cache, chunk_size, retries); + std::string data = f.read(url, abort); + if (data.empty()) return false; + + return load((std::byte *)data.data(), data.size(), no_cuda, abort); +} + +bool FrameReader::load(const std::byte *data, size_t size, bool no_cuda, std::atomic *abort) { + input_ctx = avformat_alloc_context(); + if (!input_ctx) return false; struct buffer_data bd = { - .data = (uint8_t *)content.data(), + .data = (const uint8_t*)data, .offset = 0, - .size = content.size(), + .size = size, }; const int avio_ctx_buffer_size = 64 * 1024; unsigned char *avio_ctx_buffer = (unsigned char *)av_malloc(avio_ctx_buffer_size); @@ -71,11 +74,11 @@ bool FrameReader::load(const std::string &url, bool no_cuda, std::atomic * input_ctx->pb = avio_ctx_; input_ctx->probesize = 10 * 1024 * 1024; // 10MB - int ret = avformat_open_input(&input_ctx, url.c_str(), NULL, NULL); + int ret = avformat_open_input(&input_ctx, nullptr, nullptr, nullptr); if (ret != 0) { char err_str[1024] = {0}; av_strerror(ret, err_str, std::size(err_str)); - printf("Error loading video - %s - %s\n", err_str, url.c_str()); + printf("Error loading video - %s\n", err_str); return false; } @@ -96,36 +99,31 @@ bool FrameReader::load(const std::string &url, bool no_cuda, std::atomic * width = (decoder_ctx->width + 3) & ~3; height = decoder_ctx->height; - if (!no_cuda) { + if (has_cuda_device && !no_cuda) { if (!initHardwareDecoder(AV_HWDEVICE_TYPE_CUDA)) { printf("No CUDA capable device was found. fallback to CPU decoding.\n"); + } else { + nv12toyuv_buffer.resize(getYUVSize()); } } - rgb_sws_ctx_ = sws_getContext(decoder_ctx->width, decoder_ctx->height, sws_src_format, - width, height, AV_PIX_FMT_BGR24, - SWS_BILINEAR, NULL, NULL, NULL); - if (!rgb_sws_ctx_) return false; - yuv_sws_ctx_ = sws_getContext(decoder_ctx->width, decoder_ctx->height, sws_src_format, - width, height, AV_PIX_FMT_YUV420P, - SWS_BILINEAR, NULL, NULL, NULL); - if (!yuv_sws_ctx_) return false; - - ret = avcodec_open2(decoder_ctx, decoder, NULL); + ret = avcodec_open2(decoder_ctx, decoder, nullptr); if (ret < 0) return false; - frames_.reserve(60 * 20); // 20fps, one minute + packets.reserve(60 * 20); // 20fps, one minute while (!(abort && *abort)) { - Frame &frame = frames_.emplace_back(); - ret = av_read_frame(input_ctx, &frame.pkt); + AVPacket *pkt = av_packet_alloc(); + ret = av_read_frame(input_ctx, pkt); if (ret < 0) { - frames_.pop_back(); + av_packet_free(&pkt); valid_ = (ret == AVERROR_EOF); break; } + packets.push_back(pkt); // some stream seems to contian no keyframes - key_frames_count_ += frame.pkt.flags & AV_PKT_FLAG_KEY; + key_frames_count_ += pkt->flags & AV_PKT_FLAG_KEY; } + valid_ = valid_ && !packets.empty(); return valid_; } @@ -145,21 +143,12 @@ bool FrameReader::initHardwareDecoder(AVHWDeviceType hw_device_type) { int ret = av_hwdevice_ctx_create(&hw_device_ctx, hw_device_type, nullptr, nullptr, 0); if (ret < 0) { + hw_pix_fmt = AV_PIX_FMT_NONE; + has_cuda_device = false; printf("Failed to create specified HW device %d.\n", ret); return false; } - // get sws source format - AVHWFramesConstraints *hw_frames_const = av_hwdevice_get_hwframe_constraints(hw_device_ctx, nullptr); - assert(hw_frames_const != 0); - for (AVPixelFormat *p = hw_frames_const->valid_sw_formats; *p != AV_PIX_FMT_NONE; p++) { - if (sws_isSupportedInput(*p)) { - sws_src_format = *p; - break; - } - } - av_hwframe_constraints_free(&hw_frames_const); - decoder_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx); decoder_ctx->opaque = &hw_pix_fmt; decoder_ctx->get_format = get_hw_format; @@ -168,35 +157,29 @@ bool FrameReader::initHardwareDecoder(AVHWDeviceType hw_device_type) { bool FrameReader::get(int idx, uint8_t *rgb, uint8_t *yuv) { assert(rgb || yuv); - if (!valid_ || idx < 0 || idx >= frames_.size()) { + if (!valid_ || idx < 0 || idx >= packets.size()) { return false; } return decode(idx, rgb, yuv); } bool FrameReader::decode(int idx, uint8_t *rgb, uint8_t *yuv) { - auto get_keyframe = [=](int idx) { - for (int i = idx; i >= 0 && key_frames_count_ > 1; --i) { - if (frames_[i].pkt.flags & AV_PKT_FLAG_KEY) return i; - } - return idx; - }; - int from_idx = idx; - if (idx > 0 && !frames_[idx].decoded && !frames_[idx - 1].decoded) { - // find the previous keyframe - from_idx = get_keyframe(idx); + if (idx != prev_idx + 1 && key_frames_count_ > 1) { + // seeking to the nearest key frame + for (int i = idx; i >= 0; --i) { + if (packets[i]->flags & AV_PKT_FLAG_KEY) { + from_idx = i; + break; + } + } } + prev_idx = idx; for (int i = from_idx; i <= idx; ++i) { - Frame &frame = frames_[i]; - if ((!frame.decoded || i == idx) && !frame.failed) { - AVFrame *f = decodeFrame(&frame.pkt); - frame.decoded = f != nullptr; - frame.failed = !frame.decoded; - if (frame.decoded && i == idx) { - return copyBuffers(f, rgb, yuv); - } + AVFrame *f = decodeFrame(packets[i]); + if (f && i == idx) { + return copyBuffers(f, rgb, yuv); } } return false; @@ -228,27 +211,26 @@ AVFrame *FrameReader::decodeFrame(AVPacket *pkt) { } bool FrameReader::copyBuffers(AVFrame *f, uint8_t *rgb, uint8_t *yuv) { - if (yuv) { - if (sws_src_format == AV_PIX_FMT_NV12) { - // libswscale crash if height is not 16 bytes aligned for NV12->YUV420 conversion - assert(sws_src_format == AV_PIX_FMT_NV12); + if (hw_pix_fmt == AV_PIX_FMT_CUDA) { + uint8_t *y = yuv ? yuv : nv12toyuv_buffer.data(); + uint8_t *u = y + width * height; + uint8_t *v = u + (width / 2) * (height / 2); + libyuv::NV12ToI420(f->data[0], f->linesize[0], f->data[1], f->linesize[1], + y, width, u, width / 2, v, width / 2, width, height); + libyuv::I420ToRGB24(y, width, u, width / 2, v, width / 2, + rgb, width * 3, width, height); + } else { + if (yuv) { uint8_t *u = yuv + width * height; uint8_t *v = u + (width / 2) * (height / 2); - libyuv::NV12ToI420(f->data[0], f->linesize[0], - f->data[1], f->linesize[1], - yuv, width, - u, width / 2, - v, width / 2, - width, height); - } else { - av_image_fill_arrays(sws_frame->data, sws_frame->linesize, yuv, AV_PIX_FMT_YUV420P, width, height, 1); - int ret = sws_scale(yuv_sws_ctx_, (const uint8_t **)f->data, f->linesize, 0, f->height, sws_frame->data, sws_frame->linesize); - if (ret < 0) return false; + memcpy(yuv, f->data[0], width * height); + memcpy(u, f->data[1], width / 2 * height / 2); + memcpy(v, f->data[2], width / 2 * height / 2); } + libyuv::I420ToRGB24(f->data[0], f->linesize[0], + f->data[1], f->linesize[1], + f->data[2], f->linesize[2], + rgb, width * 3, width, height); } - - // images is going to be written to output buffers, no alignment (align = 1) - av_image_fill_arrays(sws_frame->data, sws_frame->linesize, rgb, AV_PIX_FMT_BGR24, width, height, 1); - int ret = sws_scale(rgb_sws_ctx_, (const uint8_t **)f->data, f->linesize, 0, f->height, sws_frame->data, sws_frame->linesize); - return ret >= 0; + return true; } diff --git a/selfdrive/ui/replay/framereader.h b/selfdrive/ui/replay/framereader.h index c7b04138cf..d572b727e5 100644 --- a/selfdrive/ui/replay/framereader.h +++ b/selfdrive/ui/replay/framereader.h @@ -9,23 +9,22 @@ extern "C" { #include #include -#include -#include } struct AVFrameDeleter { void operator()(AVFrame* frame) const { av_frame_free(&frame); } }; -class FrameReader : protected FileReader { +class FrameReader { public: - FrameReader(bool local_cache = false, int chunk_size = -1, int retries = 0); + FrameReader(); ~FrameReader(); - bool load(const std::string &url, bool no_cuda = false, std::atomic *abort = nullptr); + bool load(const std::string &url, bool no_cuda = false, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); + bool load(const std::byte *data, size_t size, bool no_cuda = false, std::atomic *abort = nullptr); bool get(int idx, uint8_t *rgb, uint8_t *yuv); int getRGBSize() const { return width * height * 3; } int getYUVSize() const { return width * height * 3 / 2; } - size_t getFrameCount() const { return frames_.size(); } + size_t getFrameCount() const { return packets.size(); } bool valid() const { return valid_; } int width = 0, height = 0; @@ -36,15 +35,8 @@ private: AVFrame * decodeFrame(AVPacket *pkt); bool copyBuffers(AVFrame *f, uint8_t *rgb, uint8_t *yuv); - struct Frame { - AVPacket pkt = {}; - int decoded = false; - bool failed = false; - }; - std::vector frames_; - AVPixelFormat sws_src_format = AV_PIX_FMT_YUV420P; - SwsContext *rgb_sws_ctx_ = nullptr, *yuv_sws_ctx_ = nullptr; - std::unique_ptrav_frame_, sws_frame, hw_frame; + std::vector packets; + std::unique_ptrav_frame_, hw_frame; AVFormatContext *input_ctx = nullptr; AVCodecContext *decoder_ctx = nullptr; int key_frames_count_ = 0; @@ -53,4 +45,7 @@ private: AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE; AVBufferRef *hw_device_ctx = nullptr; + std::vector nv12toyuv_buffer; + int prev_idx = -1; + inline static std::atomic has_cuda_device = true; }; diff --git a/selfdrive/ui/replay/logreader.cc b/selfdrive/ui/replay/logreader.cc index 7e7a943f88..8e2836a4ff 100644 --- a/selfdrive/ui/replay/logreader.cc +++ b/selfdrive/ui/replay/logreader.cc @@ -1,6 +1,7 @@ #include "selfdrive/ui/replay/logreader.h" #include +#include #include "selfdrive/ui/replay/util.h" Event::Event(const kj::ArrayPtr &amsg, bool frame) : reader(amsg), frame(frame) { @@ -26,7 +27,7 @@ Event::Event(const kj::ArrayPtr &amsg, bool frame) : reader(a // class LogReader -LogReader::LogReader(bool local_cache, int chunk_size, int retries, size_t memory_pool_block_size) : FileReader(local_cache, chunk_size, retries) { +LogReader::LogReader(size_t memory_pool_block_size) { #ifdef HAS_MEMORY_RESOURCE const size_t buf_size = sizeof(Event) * memory_pool_block_size; pool_buffer_ = ::operator new(buf_size); @@ -39,19 +40,32 @@ LogReader::~LogReader() { for (Event *e : events) { delete e; } + #ifdef HAS_MEMORY_RESOURCE delete mbr_; ::operator delete(pool_buffer_); #endif } -bool LogReader::load(const std::string &file, std::atomic *abort) { - raw_ = decompressBZ2(read(file, abort)); - if (raw_.empty()) return false; +bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, int chunk_size, int retries) { + FileReader f(local_cache, chunk_size, retries); + std::string data = f.read(url, abort); + if (data.empty()) return false; + + return load((std::byte*)data.data(), data.size(), abort); +} + +bool LogReader::load(const std::byte *data, size_t size, std::atomic *abort) { + raw_ = decompressBZ2(data, size); + if (raw_.empty()) { + std::cout << "failed to decompress log" << std::endl; + return false; + } + + try { + kj::ArrayPtr words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); + while (words.size() > 0) { - kj::ArrayPtr words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); - while (words.size() > 0) { - try { #ifdef HAS_MEMORY_RESOURCE Event *evt = new (mbr_) Event(words); #else @@ -62,20 +76,26 @@ bool LogReader::load(const std::string &file, std::atomic *abort) { if (evt->which == cereal::Event::ROAD_ENCODE_IDX || evt->which == cereal::Event::DRIVER_ENCODE_IDX || evt->which == cereal::Event::WIDE_ROAD_ENCODE_IDX) { + #ifdef HAS_MEMORY_RESOURCE Event *frame_evt = new (mbr_) Event(words, true); #else Event *frame_evt = new Event(words, true); #endif + events.push_back(frame_evt); } words = kj::arrayPtr(evt->reader.getEnd(), words.end()); events.push_back(evt); - } catch (const kj::Exception &e) { - return false; } + } catch (const kj::Exception &e) { + std::cout << "failed to parse log : " << e.getDescription().cStr() << std::endl; + if (events.empty()) return false; + + std::cout << "read " << events.size() << " events from corrupt log" << std::endl; } + std::sort(events.begin(), events.end(), Event::lessThan()); return true; } diff --git a/selfdrive/ui/replay/logreader.h b/selfdrive/ui/replay/logreader.h index 5bb613d9de..33d7ea82f2 100644 --- a/selfdrive/ui/replay/logreader.h +++ b/selfdrive/ui/replay/logreader.h @@ -46,11 +46,12 @@ public: bool frame; }; -class LogReader : protected FileReader { +class LogReader { public: - LogReader(bool local_cache = false, int chunk_size = -1, int retries = 0, size_t memory_pool_block_size = DEFAULT_EVENT_MEMORY_POOL_BLOCK_SIZE); + LogReader(size_t memory_pool_block_size = DEFAULT_EVENT_MEMORY_POOL_BLOCK_SIZE); ~LogReader(); - bool load(const std::string &file, std::atomic *abort = nullptr); + bool load(const std::string &url, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); + bool load(const std::byte *data, size_t size, std::atomic *abort = nullptr); std::vector events; diff --git a/selfdrive/ui/replay/route.cc b/selfdrive/ui/replay/route.cc index 9ee1d387c4..23c27073cb 100644 --- a/selfdrive/ui/replay/route.cc +++ b/selfdrive/ui/replay/route.cc @@ -35,10 +35,8 @@ bool Route::load() { bool Route::loadFromServer() { QEventLoop loop; HttpRequest http(nullptr, !Hardware::PC()); - QObject::connect(&http, &HttpRequest::failedResponse, [&] { loop.exit(0); }); - QObject::connect(&http, &HttpRequest::timeoutResponse, [&] { loop.exit(0); }); - QObject::connect(&http, &HttpRequest::receivedResponse, [&](const QString &json) { - loop.exit(loadFromJson(json)); + QObject::connect(&http, &HttpRequest::requestDone, [&](const QString &json, bool success) { + loop.exit(success ? loadFromJson(json) : 0); }); http.sendRequest("https://api.commadotai.com/v1/route/" + route_.str + "/files"); return loop.exec(); @@ -118,11 +116,11 @@ void Segment::loadFile(int id, const std::string file) { const bool local_cache = !(flags & REPLAY_FLAG_NO_FILE_CACHE); bool success = false; if (id < MAX_CAMERAS) { - frames[id] = std::make_unique(local_cache, 20 * 1024 * 1024, 3); - success = frames[id]->load(file, flags & REPLAY_FLAG_NO_CUDA, &abort_); + frames[id] = std::make_unique(); + success = frames[id]->load(file, flags & REPLAY_FLAG_NO_CUDA, &abort_, local_cache, 20 * 1024 * 1024, 3); } else { - log = std::make_unique(local_cache, -1, 3); - success = log->load(file, &abort_); + log = std::make_unique(); + success = log->load(file, &abort_, local_cache, 0, 3); } if (!success) { diff --git a/selfdrive/ui/replay/tests/test_replay.cc b/selfdrive/ui/replay/tests/test_replay.cc index f2a2171c82..5b9b7cdeb7 100644 --- a/selfdrive/ui/replay/tests/test_replay.cc +++ b/selfdrive/ui/replay/tests/test_replay.cc @@ -1,7 +1,5 @@ #include #include -#include -#include #include "catch2/catch.hpp" #include "selfdrive/common/util.h" @@ -16,19 +14,15 @@ TEST_CASE("httpMultiPartDownload") { char filename[] = "/tmp/XXXXXX"; close(mkstemp(filename)); + const size_t chunk_size = 5 * 1024 * 1024; std::string content; - auto file_size = getRemoteFileSize(TEST_RLOG_URL); - REQUIRE(file_size > 0); - SECTION("5 connections, download to file") { - std::ofstream of(filename, of.binary | of.out); - REQUIRE(httpMultiPartDownload(TEST_RLOG_URL, of, 5, file_size)); + SECTION("download to file") { + REQUIRE(httpDownload(TEST_RLOG_URL, filename, chunk_size)); content = util::read_file(filename); } - SECTION("5 connection, download to buffer") { - std::ostringstream oss; - content.resize(file_size); - oss.rdbuf()->pubsetbuf(content.data(), content.size()); - REQUIRE(httpMultiPartDownload(TEST_RLOG_URL, oss, 5, file_size)); + SECTION("download to buffer") { + content = httpGet(TEST_RLOG_URL, chunk_size); + REQUIRE(!content.empty()); } REQUIRE(content.size() == 9112651); REQUIRE(sha256(content) == TEST_RLOG_CHECKSUM); @@ -56,6 +50,17 @@ TEST_CASE("FileReader") { } } +TEST_CASE("LogReader") { + SECTION("corrupt log") { + FileReader reader(true); + std::string corrupt_content = reader.read(TEST_RLOG_URL); + corrupt_content.resize(corrupt_content.length() / 2); + LogReader log; + REQUIRE(log.load((std::byte *)corrupt_content.data(), corrupt_content.size())); + REQUIRE(log.events.size() > 0); + } +} + TEST_CASE("Segment") { auto flags = GENERATE(REPLAY_FLAG_DCAM | REPLAY_FLAG_ECAM, REPLAY_FLAG_QCAMERA); Route demo_route(DEMO_ROUTE); diff --git a/selfdrive/ui/replay/util.cc b/selfdrive/ui/replay/util.cc index b4fcbf7e6b..d6791465f2 100644 --- a/selfdrive/ui/replay/util.cc +++ b/selfdrive/ui/replay/util.cc @@ -4,8 +4,10 @@ #include #include +#include #include -#include +#include +#include #include #include #include @@ -22,21 +24,34 @@ struct CURLGlobalInitializer { ~CURLGlobalInitializer() { curl_global_cleanup(); } }; +template struct MultiPartWriter { + T *buf; + size_t *total_written; size_t offset; size_t end; - size_t written; - std::ostream *os; + + size_t write(char *data, size_t size, size_t count) { + size_t bytes = size * count; + if ((offset + bytes) > end) return 0; + + if constexpr (std::is_same::value) { + memcpy(buf->data() + offset, data, bytes); + } else if constexpr (std::is_same::value) { + buf->seekp(offset); + buf->write(data, bytes); + } + + offset += bytes; + *total_written += bytes; + return bytes; + } }; +template size_t write_cb(char *data, size_t size, size_t count, void *userp) { - MultiPartWriter *w = (MultiPartWriter *)userp; - w->os->seekp(w->offset); - size_t bytes = size * count; - w->os->write(data, bytes); - w->offset += bytes; - w->written += bytes; - return bytes; + auto w = (MultiPartWriter *)userp; + return w->write(data, size, count); } size_t dumy_write_cb(char *data, size_t size, size_t count, void *userp) { return size * count; } @@ -64,12 +79,12 @@ size_t getRemoteFileSize(const std::string &url) { CURLcode res = curl_easy_perform(curl); double content_length = -1; if (res == CURLE_OK) { - res = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); + curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); } else { std::cout << "Download failed: error code: " << res << std::endl; } curl_easy_cleanup(curl); - return content_length > 0 ? content_length : 0; + return content_length > 0 ? (size_t)content_length : 0; } std::string getUrlWithoutQuery(const std::string &url) { @@ -81,30 +96,32 @@ void enableHttpLogging(bool enable) { enable_http_logging = enable; } -bool httpMultiPartDownload(const std::string &url, std::ostream &os, int parts, size_t content_length, std::atomic *abort) { +template +bool httpDownload(const std::string &url, T &buf, size_t chunk_size, size_t content_length, std::atomic *abort) { static CURLGlobalInitializer curl_initializer; - static std::mutex lock; - static uint64_t total_written = 0, prev_total_written = 0; - static double last_print_ts = 0; - os.seekp(content_length - 1); - os.write("\0", 1); + int parts = 1; + if (chunk_size > 0 && content_length > 10 * 1024 * 1024) { + parts = std::nearbyint(content_length / (float)chunk_size); + parts = std::clamp(parts, 1, 5); + } CURLM *cm = curl_multi_init(); - - std::map writers; + size_t written = 0; + std::map> writers; const int part_size = content_length / parts; for (int i = 0; i < parts; ++i) { CURL *eh = curl_easy_init(); writers[eh] = { - .os = &os, + .buf = &buf, + .total_written = &written, .offset = (size_t)(i * part_size), - .end = i == parts - 1 ? content_length - 1 : (i + 1) * part_size - 1, + .end = i == parts - 1 ? content_length : (i + 1) * part_size, }; - curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb); + curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb); curl_easy_setopt(eh, CURLOPT_WRITEDATA, (void *)(&writers[eh])); curl_easy_setopt(eh, CURLOPT_URL, url.c_str()); - curl_easy_setopt(eh, CURLOPT_RANGE, util::string_format("%d-%d", writers[eh].offset, writers[eh].end).c_str()); + curl_easy_setopt(eh, CURLOPT_RANGE, util::string_format("%d-%d", writers[eh].offset, writers[eh].end - 1).c_str()); curl_easy_setopt(eh, CURLOPT_HTTPGET, 1); curl_easy_setopt(eh, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1); @@ -112,27 +129,22 @@ bool httpMultiPartDownload(const std::string &url, std::ostream &os, int parts, curl_multi_add_handle(cm, eh); } - int still_running = 1; size_t prev_written = 0; + double last_print = millis_since_boot(); + int still_running = 1; while (still_running > 0 && !(abort && *abort)) { curl_multi_wait(cm, nullptr, 0, 1000, nullptr); curl_multi_perform(cm, &still_running); - size_t written = std::accumulate(writers.begin(), writers.end(), 0, [=](int v, auto &w) { return v + w.second.written; }); - int cur_written = written - prev_written; - prev_written = written; - - std::lock_guard lk(lock); - double ts = millis_since_boot(); - total_written += cur_written; - if ((ts - last_print_ts) > 2 * 1000) { - if (enable_http_logging && last_print_ts > 0) { - size_t average = (total_written - prev_total_written) / ((ts - last_print_ts) / 1000.); + if (enable_http_logging) { + if (double ts = millis_since_boot(); (ts - last_print) > 2 * 1000) { + size_t average = (written - prev_written) / ((ts - last_print) / 1000.); int progress = std::min(100, 100.0 * (double)written / (double)content_length); - std::cout << "downloading " << getUrlWithoutQuery(url) << " - " << progress << "% (" << formattedDataSize(average) << "/s)" << std::endl; + std::cout << "downloading " << getUrlWithoutQuery(url) << " - " << progress + << "% (" << formattedDataSize(average) << "/s)" << std::endl; + last_print = ts; + prev_written = written; } - prev_total_written = total_written; - last_print_ts = ts; } } @@ -155,29 +167,59 @@ bool httpMultiPartDownload(const std::string &url, std::ostream &os, int parts, } } - for (auto &[e, w] : writers) { + for (const auto &[e, w] : writers) { curl_multi_remove_handle(cm, e); curl_easy_cleanup(e); } - curl_multi_cleanup(cm); + return complete == parts; } +std::string httpGet(const std::string &url, size_t chunk_size, std::atomic *abort) { + size_t size = getRemoteFileSize(url); + if (size == 0) return {}; + + std::string result(size, '\0'); + return httpDownload(url, result, chunk_size, size, abort) ? result : ""; +} + +bool httpDownload(const std::string &url, const std::string &file, size_t chunk_size, std::atomic *abort) { + size_t size = getRemoteFileSize(url); + if (size == 0) return false; + + std::ofstream of(file, std::ios::binary | std::ios::out); + of.seekp(size - 1).write("\0", 1); + return httpDownload(url, of, chunk_size, size, abort); +} + std::string decompressBZ2(const std::string &in) { - if (in.empty()) return {}; + return decompressBZ2((std::byte *)in.data(), in.size()); +} + +std::string decompressBZ2(const std::byte *in, size_t in_size) { + if (in_size == 0) return {}; bz_stream strm = {}; int bzerror = BZ2_bzDecompressInit(&strm, 0, 0); assert(bzerror == BZ_OK); - strm.next_in = (char *)in.data(); - strm.avail_in = in.size(); - std::string out(in.size() * 5, '\0'); + strm.next_in = (char *)in; + strm.avail_in = in_size; + std::string out(in_size * 5, '\0'); do { strm.next_out = (char *)(&out[strm.total_out_lo32]); strm.avail_out = out.size() - strm.total_out_lo32; + + const char *prev_write_pos = strm.next_out; bzerror = BZ2_bzDecompress(&strm); + if (bzerror == BZ_OK && prev_write_pos == strm.next_out) { + // content is corrupt + bzerror = BZ_STREAM_END; + std::cout << "decompressBZ2 error : content is corrupt" << std::endl; + break; + } + if (bzerror == BZ_OK && strm.avail_in > 0 && strm.avail_out == 0) { out.resize(out.size() * 2); } diff --git a/selfdrive/ui/replay/util.h b/selfdrive/ui/replay/util.h index 30a26c4314..726e65cb94 100644 --- a/selfdrive/ui/replay/util.h +++ b/selfdrive/ui/replay/util.h @@ -1,13 +1,14 @@ #pragma once #include -#include #include std::string sha256(const std::string &str); void precise_nano_sleep(long sleep_ns); std::string decompressBZ2(const std::string &in); +std::string decompressBZ2(const std::byte *in, size_t in_size); void enableHttpLogging(bool enable); std::string getUrlWithoutQuery(const std::string &url); size_t getRemoteFileSize(const std::string &url); -bool httpMultiPartDownload(const std::string &url, std::ostream &os, int parts, size_t content_length, std::atomic *abort = nullptr); +std::string httpGet(const std::string &url, size_t chunk_size = 0, std::atomic *abort = nullptr); +bool httpDownload(const std::string &url, const std::string &file, size_t chunk_size = 0, std::atomic *abort = nullptr); diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc index df8b4fa6ac..766c37a51c 100644 --- a/selfdrive/ui/soundd/sound.cc +++ b/selfdrive/ui/soundd/sound.cc @@ -1,5 +1,9 @@ #include "selfdrive/ui/soundd/sound.h" +#include +#include +#include + #include "cereal/messaging/messaging.h" #include "selfdrive/common/util.h" @@ -7,14 +11,15 @@ // TODO: detect when we can't display the UI Sound::Sound(QObject *parent) : sm({"carState", "controlsState", "deviceState"}) { - const QString sound_asset_path = Hardware::TICI() ? "../../assets/sounds_tici/" : "../../assets/sounds/"; + qInfo() << "default audio device: " << QAudioDeviceInfo::defaultOutputDevice().deviceName(); + for (auto &[alert, fn, loops] : sound_list) { QSoundEffect *s = new QSoundEffect(this); QObject::connect(s, &QSoundEffect::statusChanged, [=]() { assert(s->status() != QSoundEffect::Error); }); s->setVolume(Hardware::MIN_VOLUME); - s->setSource(QUrl::fromLocalFile(sound_asset_path + fn)); + s->setSource(QUrl::fromLocalFile("../../assets/sounds/" + fn)); sounds[alert] = {s, loops}; } @@ -42,8 +47,9 @@ void Sound::update() { // scale volume with speed if (sm.updated("carState")) { - float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 0.f, 20.f, - Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); + float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 11.f, 20.f, 0.f, 1.0f); + volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); + volume = util::map_val(volume, 0.f, 1.f, Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); for (auto &[s, loops] : sounds) { s->setVolume(std::round(100 * volume) / 100); } @@ -59,7 +65,7 @@ void Sound::setAlert(const Alert &alert) { for (auto &[s, loops] : sounds) { // Only stop repeating sounds if (s->loopsRemaining() > 1 || s->loopsRemaining() == QSoundEffect::Infinite) { - s->stop(); + s->setLoopCount(0); } } diff --git a/selfdrive/ui/soundd/sound.h b/selfdrive/ui/soundd/sound.h index cbc7b87078..0990591093 100644 --- a/selfdrive/ui/soundd/sound.h +++ b/selfdrive/ui/soundd/sound.h @@ -7,14 +7,16 @@ const std::tuple sound_list[] = { // AudibleAlert, file name, loop count - {AudibleAlert::CHIME_DISENGAGE, "disengaged.wav", 0}, - {AudibleAlert::CHIME_ENGAGE, "engaged.wav", 0}, - {AudibleAlert::CHIME_WARNING1, "warning_1.wav", 0}, - {AudibleAlert::CHIME_WARNING_REPEAT, "warning_repeat.wav", 10}, - {AudibleAlert::CHIME_WARNING_REPEAT_INFINITE, "warning_repeat.wav", QSoundEffect::Infinite}, - {AudibleAlert::CHIME_WARNING2_REPEAT_INFINITE, "warning_2.wav", QSoundEffect::Infinite}, - {AudibleAlert::CHIME_ERROR, "error.wav", 0}, - {AudibleAlert::CHIME_PROMPT, "error.wav", 0}, + {AudibleAlert::ENGAGE, "engage.wav", 0}, + {AudibleAlert::DISENGAGE, "disengage.wav", 0}, + {AudibleAlert::REFUSE, "refuse.wav", 0}, + + {AudibleAlert::PROMPT, "prompt.wav", 0}, + {AudibleAlert::PROMPT_REPEAT, "prompt.wav", QSoundEffect::Infinite}, + {AudibleAlert::PROMPT_DISTRACTED, "prompt_distracted.wav", 0}, + + {AudibleAlert::WARNING_SOFT, "warning_soft.wav", QSoundEffect::Infinite}, + {AudibleAlert::WARNING_IMMEDIATE, "warning_immediate.wav", 10}, }; class Sound : public QObject { diff --git a/selfdrive/ui/tests/test_sound.cc b/selfdrive/ui/tests/test_sound.cc index 62aa35cb2b..43599f3828 100644 --- a/selfdrive/ui/tests/test_sound.cc +++ b/selfdrive/ui/tests/test_sound.cc @@ -9,6 +9,7 @@ class TestSound : public Sound { public: TestSound() : Sound() { for (auto i = sounds.constBegin(); i != sounds.constEnd(); ++i) { + sound_stats[i.key()] = {0, 0}; QObject::connect(i.value().first, &QSoundEffect::playingChanged, [=, s = i.value().first, a = i.key()]() { if (s->isPlaying()) { sound_stats[a].first++; @@ -23,7 +24,11 @@ public: }; void controls_thread(int loop_cnt) { - PubMaster pm({"controlsState"}); + PubMaster pm({"controlsState", "deviceState"}); + MessageBuilder deviceStateMsg; + auto deviceState = deviceStateMsg.initEvent().initDeviceState(); + deviceState.setStarted(true); + const int DT_CTRL = 10; // ms for (int i = 0; i < loop_cnt; ++i) { for (auto &[alert, fn, loops] : sound_list) { @@ -34,6 +39,7 @@ void controls_thread(int loop_cnt) { cs.setAlertSound(alert); cs.setAlertType(fn.toStdString()); pm.send("controlsState", msg); + pm.send("deviceState", deviceStateMsg); QThread::msleep(DT_CTRL); } } diff --git a/selfdrive/ui/tests/test_soundd.py b/selfdrive/ui/tests/test_soundd.py index 98f17d620f..1f0947335f 100755 --- a/selfdrive/ui/tests/test_soundd.py +++ b/selfdrive/ui/tests/test_soundd.py @@ -15,14 +15,14 @@ AudibleAlert = car.CarControl.HUDControl.AudibleAlert SOUNDS = { # sound: total writes AudibleAlert.none: 0, - AudibleAlert.chimeEngage: 173, - AudibleAlert.chimeDisengage: 173, - AudibleAlert.chimeError: 173, - AudibleAlert.chimePrompt: 173, - AudibleAlert.chimeWarning1: 163, - AudibleAlert.chimeWarningRepeat: 468, - AudibleAlert.chimeWarningRepeatInfinite: 468, - AudibleAlert.chimeWarning2RepeatInfinite: 470, + AudibleAlert.engage: 197, + AudibleAlert.disengage: 230, + AudibleAlert.refuse: 189, + AudibleAlert.prompt: 230, + AudibleAlert.promptRepeat: 468, + AudibleAlert.promptDistracted: 187, + AudibleAlert.warningSoft: 499, + AudibleAlert.warningImmediate: 496, } def get_total_writes(): @@ -51,22 +51,24 @@ class TestSoundd(unittest.TestCase): print(f"testing {alert_sounds[sound]}") start_writes = get_total_writes() - for _ in range(int(9 / DT_CTRL)): + for i in range(int(10 / DT_CTRL)): msg = messaging.new_message('deviceState') msg.deviceState.started = True pm.send('deviceState', msg) msg = messaging.new_message('controlsState') - msg.controlsState.alertSound = sound - msg.controlsState.alertType = str(sound) - msg.controlsState.alertText1 = "Testing Sounds" - msg.controlsState.alertText2 = f"playing {alert_sounds[sound]}" - msg.controlsState.alertSize = log.ControlsState.AlertSize.mid + if i < int(6 / DT_CTRL): + msg.controlsState.alertSound = sound + msg.controlsState.alertType = str(sound) + msg.controlsState.alertText1 = "Testing Sounds" + msg.controlsState.alertText2 = f"playing {alert_sounds[sound]}" + msg.controlsState.alertSize = log.ControlsState.AlertSize.mid pm.send('controlsState', msg) time.sleep(DT_CTRL) - tolerance = (expected_writes % 100) * 2 + tolerance = expected_writes / 10 actual_writes = get_total_writes() - start_writes + #print(f" expected {expected_writes} writes, got {actual_writes}") assert abs(expected_writes - actual_writes) <= tolerance, f"{alert_sounds[sound]}: expected {expected_writes} writes, got {actual_writes}" if __name__ == "__main__": diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index fab68d3818..4c94a83038 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -78,7 +78,7 @@ struct Alert { // car is started, but controls is lagging or died return {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, - AudibleAlert::CHIME_WARNING_REPEAT}; + AudibleAlert::WARNING_IMMEDIATE}; } } return {}; @@ -192,7 +192,7 @@ private: int awake_timeout = 0; float accel_prev = 0; float gyro_prev = 0; - float last_brightness = 0; + int last_brightness = 0; FirstOrderFilter brightness_filter; QTimer *timer; diff --git a/selfdrive/ui/watch3.cc b/selfdrive/ui/watch3.cc index e5e2864ccb..73c8e11318 100644 --- a/selfdrive/ui/watch3.cc +++ b/selfdrive/ui/watch3.cc @@ -17,12 +17,20 @@ int main(int argc, char *argv[]) { QVBoxLayout *layout = new QVBoxLayout(&w); layout->setMargin(0); layout->setSpacing(0); - layout->addWidget(new CameraViewWidget(VISION_STREAM_RGB_BACK, false)); - QHBoxLayout *hlayout = new QHBoxLayout(); - layout->addLayout(hlayout); - hlayout->addWidget(new CameraViewWidget(VISION_STREAM_RGB_FRONT, false)); - hlayout->addWidget(new CameraViewWidget(VISION_STREAM_RGB_WIDE, false)); + { + QHBoxLayout *hlayout = new QHBoxLayout(); + layout->addLayout(hlayout); + hlayout->addWidget(new CameraViewWidget("navd", VISION_STREAM_RGB_MAP, false)); + hlayout->addWidget(new CameraViewWidget("camerad", VISION_STREAM_RGB_BACK, false)); + } + + { + QHBoxLayout *hlayout = new QHBoxLayout(); + layout->addLayout(hlayout); + hlayout->addWidget(new CameraViewWidget("camerad", VISION_STREAM_RGB_FRONT, false)); + hlayout->addWidget(new CameraViewWidget("camerad", VISION_STREAM_RGB_WIDE, false)); + } return a.exec(); } diff --git a/selfdrive/version.py b/selfdrive/version.py index 7a2234e3ec..2ddfc93c69 100644 --- a/selfdrive/version.py +++ b/selfdrive/version.py @@ -2,6 +2,7 @@ import os import subprocess from typing import List, Optional +from functools import lru_cache from common.basedir import BASEDIR from selfdrive.swaglog import cloudlog @@ -9,9 +10,16 @@ from selfdrive.swaglog import cloudlog TESTED_BRANCHES = ['devel', 'release2-staging', 'release3-staging', 'dashcam-staging', 'release2', 'release3', 'dashcam'] +training_version: bytes = b"0.2.0" +terms_version: bytes = b"2" + + +def cache(user_function, /): + return lru_cache(maxsize=None)(user_function) + def run_cmd(cmd: List[str]) -> str: - return subprocess.check_output(cmd, encoding='utf8').strip() + return subprocess.check_output(cmd, encoding='utf8').strip() def run_cmd_default(cmd: List[str], default: Optional[str] = None) -> Optional[str]: @@ -21,19 +29,23 @@ def run_cmd_default(cmd: List[str], default: Optional[str] = None) -> Optional[s return default -def get_git_commit(branch: str = "HEAD", default: Optional[str] = None) -> Optional[str]: +@cache +def get_commit(branch: str = "HEAD", default: Optional[str] = None) -> Optional[str]: return run_cmd_default(["git", "rev-parse", branch], default=default) -def get_git_branch(default: Optional[str] = None) -> Optional[str]: +@cache +def get_short_branch(default: Optional[str] = None) -> Optional[str]: return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], default=default) -def get_git_full_branchname(default: Optional[str] = None) -> Optional[str]: +@cache +def get_branch(default: Optional[str] = None) -> Optional[str]: return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], default=default) -def get_git_remote(default: Optional[str] = None) -> Optional[str]: +@cache +def get_origin(default: Optional[str] = None) -> Optional[str]: try: local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"]) tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"]) @@ -42,55 +54,68 @@ def get_git_remote(default: Optional[str] = None) -> Optional[str]: return run_cmd_default(["git", "config", "--get", "remote.origin.url"], default=default) -def get_version(): +@cache +def get_version() -> str: with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "common", "version.h")) as _versionf: version = _versionf.read().split('"')[1] return version -version = get_version() -prebuilt = os.path.exists(os.path.join(BASEDIR, 'prebuilt')) -training_version: bytes = b"0.2.0" -terms_version: bytes = b"2" +@cache +def get_prebuilt() -> bool: + return os.path.exists(os.path.join(BASEDIR, 'prebuilt')) -dirty: bool = True -comma_remote: bool = False -tested_branch: bool = False -origin = get_git_remote() -branch = get_git_full_branchname() -commit = get_git_commit() -if (origin is not None) and (branch is not None): - try: - comma_remote = origin.startswith('git@github.com:commaai') or origin.startswith('https://github.com/commaai') - tested_branch = get_git_branch() in TESTED_BRANCHES +@cache +def get_comma_remote() -> bool: + origin = get_origin() + if origin is None: + return False + + return origin.startswith('git@github.com:commaai') or origin.startswith('https://github.com/commaai') - dirty = False +@cache +def get_tested_branch() -> bool: + return get_short_branch() in TESTED_BRANCHES + + +@cache +def get_dirty() -> bool: + origin = get_origin() + branch = get_branch() + if (origin is None) or (branch is None): + return True + + dirty = False + try: # Actually check dirty files - if not prebuilt: + if not get_prebuilt(): # This is needed otherwise touched files might show up as modified try: subprocess.check_call(["git", "update-index", "--refresh"]) except subprocess.CalledProcessError: pass + dirty = (subprocess.call(["git", "diff-index", "--quiet", branch, "--"]) != 0) # Log dirty files - if dirty and comma_remote: + if dirty and get_comma_remote(): try: dirty_files = run_cmd(["git", "diff-index", branch, "--"]) - cloudlog.event("dirty comma branch", version=version, dirty=dirty, origin=origin, branch=branch, - dirty_files=dirty_files, commit=commit, origin_commit=get_git_commit(branch)) + cloudlog.event("dirty comma branch", version=get_version(), dirty=dirty, origin=origin, branch=branch, + dirty_files=dirty_files, commit=get_commit(), origin_commit=get_commit(branch)) except subprocess.CalledProcessError: pass - dirty = dirty or (not comma_remote) + dirty = dirty or (not get_comma_remote()) dirty = dirty or ('master' in branch) except subprocess.CalledProcessError: - dirty = True cloudlog.exception("git subprocess failed while checking dirty") + dirty = True + + return dirty if __name__ == "__main__": @@ -100,8 +125,9 @@ if __name__ == "__main__": params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) - print("Dirty: %s" % dirty) - print("Version: %s" % version) - print("Remote: %s" % origin) - print("Branch: %s" % branch) - print("Prebuilt: %s" % prebuilt) + print("Dirty: %s" % get_dirty()) + print("Version: %s" % get_version()) + print("Origin: %s" % get_origin()) + print("Branch: %s" % get_branch()) + print("Short branch: %s" % get_short_branch()) + print("Prebuilt: %s" % get_prebuilt()) diff --git a/tools/lib/lazy_property.py b/tools/lib/lazy_property.py deleted file mode 100644 index 85c038f287..0000000000 --- a/tools/lib/lazy_property.py +++ /dev/null @@ -1,12 +0,0 @@ -class lazy_property(object): - """Defines a property whose value will be computed only once and as needed. - - This can only be used on instance methods. - """ - def __init__(self, func): - self._func = func - - def __get__(self, obj_self, cls): - value = self._func(obj_self) - setattr(obj_self, self._func.__name__, value) - return value diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 01c666fd16..1c9cc81e32 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -13,7 +13,7 @@ from cereal import log as capnp_log # this is an iterator itself, and uses private variables from LogReader class MultiLogIterator(object): - def __init__(self, log_paths, wraparound=True): + def __init__(self, log_paths, wraparound=False): self._log_paths = log_paths self._wraparound = wraparound @@ -26,7 +26,6 @@ class MultiLogIterator(object): def _log_reader(self, i): if self._log_readers[i] is None and self._log_paths[i] is not None: log_path = self._log_paths[i] - print("LogReader:%s" % log_path) self._log_readers[i] = LogReader(log_path) return self._log_readers[i] diff --git a/tools/plotjuggler/README.md b/tools/plotjuggler/README.md index f462cadece..8ab150e821 100644 --- a/tools/plotjuggler/README.md +++ b/tools/plotjuggler/README.md @@ -1,10 +1,12 @@ # PlotJuggler -We've extended [PlotJuggler](https://github.com/facontidavide/PlotJuggler) to plot all of your openpilot logs. Check out our plugins: https://github.com/commaai/PlotJuggler. +[PlotJuggler](https://github.com/facontidavide/PlotJuggler) is a tool to quickly visualize time series data, and we've written plugins to parse openpilot logs. Check out our plugins: https://github.com/commaai/PlotJuggler. ## Installation -Once you've cloned and are in openpilot, download PlotJuggler and install our plugins with this command: +**NOTE: this is Ubuntu only for now. Pull requests for macOS support are welcome.** + +Once you've cloned and are in openpilot, this command will download PlotJuggler and install our plugins: `cd tools/plotjuggler && ./install.sh` @@ -12,9 +14,9 @@ Once you've cloned and are in openpilot, download PlotJuggler and install our pl ``` $ ./juggle.py -h -usage: juggle.py [-h] [--qlog] [--can] [--stream] [--layout [LAYOUT]] [route_name] [segment_number] [segment_count] +usage: juggle.py [-h] [--demo] [--qlog] [--can] [--stream] [--layout [LAYOUT]] [route_name] [segment_number] [segment_count] -PlotJuggler plugin for reading openpilot logs +A helper to run PlotJuggler on openpilot routes positional arguments: route_name The route name to plot (cabana share URL accepted) (default: None) @@ -23,9 +25,10 @@ positional arguments: optional arguments: -h, --help show this help message and exit + --demo Use the demo route instead of providing one (default: False) --qlog Use qlogs (default: False) --can Parse CAN data (default: False) - --stream Start PlotJuggler without a route to stream data using Cereal (default: False) + --stream Start PlotJuggler in streaming mode (default: False) --layout [LAYOUT] Run PlotJuggler with a pre-defined layout (default: None) ``` @@ -47,11 +50,15 @@ If streaming to PlotJuggler from a replay on your PC, simply run: `./juggle.py - For a quick demo, go through the installation step and run this command: -`./juggle.py "https://commadataci.blob.core.windows.net/openpilotci/d83f36766f8012a5/2020-02-05--18-42-21/0/rlog.bz2" --layout=layouts/demo.xml` +`./juggle.py --demo --qlog --layout=layouts/demo.xml` + +## Layouts + +If you create a layout that's useful for others, consider upstreaming it. -## Tuning +### Tuning -Use this layout to generate plots for tuning PRs. Also see tuning wiki and tuning PR template. +Use this layout to improve your car's tuning and generate plots for tuning PRs. Also see the tuning wiki and tuning PR template. `--layout layouts/tuning.xml` diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 151e4a49c4..d488750701 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -16,6 +16,8 @@ from urllib.parse import urlparse, parse_qs juggle_dir = os.path.dirname(os.path.realpath(__file__)) +DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" + def load_segment(segment_name): print(f"Loading {segment_name}") if segment_name is None: @@ -94,12 +96,13 @@ def juggle_route(route_name, segment_number, segment_count, qlog, can, layout): start_juggler(tempfile.name, dbc, layout) def get_arg_parser(): - parser = argparse.ArgumentParser(description="PlotJuggler plugin for reading openpilot logs", + parser = argparse.ArgumentParser(description="A helper to run PlotJuggler on openpilot routes", formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") parser.add_argument("--qlog", action="store_true", help="Use qlogs") parser.add_argument("--can", action="store_true", help="Parse CAN data") - parser.add_argument("--stream", action="store_true", help="Start PlotJuggler without a route to stream data using Cereal") + parser.add_argument("--stream", action="store_true", help="Start PlotJuggler in streaming mode") parser.add_argument("--layout", nargs='?', help="Run PlotJuggler with a pre-defined layout") parser.add_argument("route_name", nargs='?', help="The route name to plot (cabana share URL accepted)") parser.add_argument("segment_number", type=int, nargs='?', help="The index of the segment to plot") @@ -116,4 +119,5 @@ if __name__ == "__main__": if args.stream: start_juggler(layout=args.layout) else: - juggle_route(args.route_name.strip(), args.segment_number, args.segment_count, args.qlog, args.can, args.layout) + route = DEMO_ROUTE if args.demo else args.route_name.strip() + juggle_route(route, args.segment_number, args.segment_count, args.qlog, args.can, args.layout) diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index dd16493abf..6d68e75fe7 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -6,6 +6,7 @@ from tqdm import tqdm os.environ['FILEREADER_CACHE'] = '1' +from common.basedir import BASEDIR from common.realtime import config_realtime_process, Ratekeeper, DT_CTRL from selfdrive.boardd.boardd import can_capnp_to_can_list from tools.lib.logreader import LogReader @@ -81,6 +82,10 @@ def connect(): if __name__ == "__main__": + if PandaJungle is None: + print("\33[31m", "WARNING: cannot connect to jungles. Clone the jungle library to enable support:", "\033[0m") + print("\033[34m", f"cd {BASEDIR} && git clone https://github.com/commaai/panda_jungle", "\033[0m") + print("Loading log...") ROUTE = "77611a1fac303767/2020-03-24--09-50-38" REPLAY_SEGS = list(range(10, 16)) # route has 82 segments available diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim index cff02565f1..f6cf0d9b0b 100644 --- a/tools/sim/Dockerfile.sim +++ b/tools/sim/Dockerfile.sim @@ -62,4 +62,4 @@ COPY ./selfdrive $HOME/openpilot/selfdrive COPY ./tools $HOME/openpilot/tools WORKDIR $HOME/openpilot -RUN USE_FRAME_STREAM=1 scons -j$(nproc) +RUN scons -j$(nproc) diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index 2873aa7223..9924a40b11 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -1,19 +1,25 @@ #!/usr/bin/env python3 import argparse -import carla # pylint: disable=import-error import math -import numpy as np -import time import threading -from cereal import log +import time +import os from multiprocessing import Process, Queue from typing import Any +import carla # pylint: disable=import-error +import numpy as np +import pyopencl as cl +import pyopencl.array as cl_array +from lib.can import can_function + import cereal.messaging as messaging -from common.params import Params +from cereal import log +from cereal.visionipc.visionipc_pyx import VisionIpcServer, VisionStreamType # pylint: disable=no-name-in-module, import-error +from common.basedir import BASEDIR from common.numpy_fast import clip -from common.realtime import Ratekeeper, DT_DMON -from lib.can import can_function +from common.params import Params +from common.realtime import DT_DMON, Ratekeeper from selfdrive.car.honda.values import CruiseButtons from selfdrive.test.helpers import set_params_enabled @@ -21,18 +27,18 @@ parser = argparse.ArgumentParser(description='Bridge between CARLA and openpilot parser.add_argument('--joystick', action='store_true') parser.add_argument('--low_quality', action='store_true') parser.add_argument('--town', type=str, default='Town04_Opt') -parser.add_argument('--spawn_point', dest='num_selected_spawn_point', - type=int, default=16) +parser.add_argument('--spawn_point', dest='num_selected_spawn_point', type=int, default=16) args = parser.parse_args() -W, H = 1164, 874 +W, H = 1928, 1208 REPEAT_COUNTER = 5 PRINT_DECIMATION = 100 STEER_RATIO = 15. pm = messaging.PubMaster(['roadCameraState', 'sensorEvents', 'can', "gpsLocationExternal"]) -sm = messaging.SubMaster(['carControl','controlsState']) +sm = messaging.SubMaster(['carControl', 'controlsState']) + class VehicleState: def __init__(self): @@ -40,8 +46,9 @@ class VehicleState: self.angle = 0 self.bearing_deg = 0.0 self.vel = carla.Vector3D() - self.cruise_button= 0 - self.is_engaged=False + self.cruise_button = 0 + self.is_engaged = False + def steer_rate_limit(old, new): # Rate limiting to 0.5 degrees per step @@ -53,21 +60,56 @@ def steer_rate_limit(old, new): else: return new -frame_id = 0 -def cam_callback(image): - global frame_id - img = np.frombuffer(image.raw_data, dtype=np.dtype("uint8")) - img = np.reshape(img, (H, W, 4)) - img = img[:, :, [0, 1, 2]].copy() - - dat = messaging.new_message('roadCameraState') - dat.roadCameraState = { - "frameId": image.frame, - "image": img.tobytes(), - "transform": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] - } - pm.send('roadCameraState', dat) - frame_id += 1 + +class Camerad: + def __init__(self): + self.frame_id = 0 + self.vipc_server = VisionIpcServer("camerad") + + # TODO: remove RGB buffers once the last RGB vipc subscriber is removed + self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_RGB_BACK, 4, True, W, H) + self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, W, H) + self.vipc_server.start_listener() + + # set up for pyopencl rgb to yuv conversion + self.ctx = cl.create_some_context() + self.queue = cl.CommandQueue(self.ctx) + cl_arg = f" -DHEIGHT={H} -DWIDTH={W} -DRGB_STRIDE={W*3} -DUV_WIDTH={W // 2} -DUV_HEIGHT={H // 2} -DRGB_SIZE={W * H} -DCL_DEBUG " + + # TODO: move rgb_to_yuv.cl to local dir once the frame stream camera is removed + kernel_fn = os.path.join(BASEDIR, "selfdrive", "camerad", "transforms", "rgb_to_yuv.cl") + prg = cl.Program(self.ctx, open(kernel_fn).read()).build(cl_arg) + self.krnl = prg.rgb_to_yuv + self.Wdiv4 = W // 4 if (W % 4 == 0) else (W + (4 - W % 4)) // 4 + self.Hdiv4 = H // 4 if (H % 4 == 0) else (H + (4 - H % 4)) // 4 + + def cam_callback(self, image): + img = np.frombuffer(image.raw_data, dtype=np.dtype("uint8")) + img = np.reshape(img, (H, W, 4)) + img = img[:, :, [0, 1, 2]].copy() + + # convert RGB frame to YUV + rgb = np.reshape(img, (H, W * 3)) + rgb_cl = cl_array.to_device(self.queue, rgb) + yuv_cl = cl_array.empty_like(rgb_cl) + self.krnl(self.queue, (np.int32(self.Wdiv4), np.int32(self.Hdiv4)), None, rgb_cl.data, yuv_cl.data).wait() + yuv = np.resize(yuv_cl.get(), np.int32((rgb.size / 2))) + eof = self.frame_id * 0.05 + + # TODO: remove RGB send once the last RGB vipc subscriber is removed + self.vipc_server.send(VisionStreamType.VISION_STREAM_RGB_BACK, img.tobytes(), self.frame_id, eof, eof) + self.vipc_server.send(VisionStreamType.VISION_STREAM_ROAD, yuv.data.tobytes(), self.frame_id, eof, eof) + + dat = messaging.new_message('roadCameraState') + dat.roadCameraState = { + "frameId": image.frame, + "transform": [1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0] + } + pm.send('roadCameraState', dat) + self.frame_id += 1 + def imu_callback(imu, vehicle_state): vehicle_state.bearing_deg = math.degrees(imu.compass) @@ -83,6 +125,7 @@ def imu_callback(imu, vehicle_state): dat.sensorEvents[1].gyroUncalibrated.v = [imu.gyroscope.x, imu.gyroscope.y, imu.gyroscope.z] pm.send('sensorEvents', dat) + def panda_state_function(exit_event: threading.Event): pm = messaging.PubMaster(['pandaStates']) while not exit_event.is_set(): @@ -97,6 +140,7 @@ def panda_state_function(exit_event: threading.Event): pm.send('pandaStates', dat) time.sleep(0.5) + def peripheral_state_function(exit_event: threading.Event): pm = messaging.PubMaster(['peripheralState']) while not exit_event.is_set(): @@ -112,20 +156,21 @@ def peripheral_state_function(exit_event: threading.Event): pm.send('peripheralState', dat) time.sleep(0.5) + def gps_callback(gps, vehicle_state): dat = messaging.new_message('gpsLocationExternal') # transform vel from carla to NED # north is -Y in CARLA velNED = [ - -vehicle_state.vel.y, # north/south component of NED is negative when moving south - vehicle_state.vel.x, # positive when moving east, which is x in carla + -vehicle_state.vel.y, # north/south component of NED is negative when moving south + vehicle_state.vel.x, # positive when moving east, which is x in carla vehicle_state.vel.z, ] dat.gpsLocationExternal = { "timestamp": int(time.time() * 1000), - "flags": 1, # valid fix + "flags": 1, # valid fix "accuracy": 1.0, "verticalAccuracy": 1.0, "speedAccuracy": 0.1, @@ -141,8 +186,9 @@ def gps_callback(gps, vehicle_state): pm.send('gpsLocationExternal', dat) + def fake_driver_monitoring(exit_event: threading.Event): - pm = messaging.PubMaster(['driverState','driverMonitoringState']) + pm = messaging.PubMaster(['driverState', 'driverMonitoringState']) while not exit_event.is_set(): # dmonitoringmodeld output dat = messaging.new_message('driverState') @@ -160,28 +206,35 @@ def fake_driver_monitoring(exit_event: threading.Event): time.sleep(DT_DMON) + def can_function_runner(vs: VehicleState, exit_event: threading.Event): i = 0 while not exit_event.is_set(): can_function(pm, vs.speed, vs.angle, i, vs.cruise_button, vs.is_engaged) time.sleep(0.01) - i+=1 + i += 1 def bridge(q): - # setup CARLA client = carla.Client("127.0.0.1", 2000) client.set_timeout(10.0) world = client.load_world(args.town) + settings = world.get_settings() + settings.synchronous_mode = True # Enables synchronous mode + settings.fixed_delta_seconds = 0.05 + world.apply_settings(settings) + + world.set_weather(carla.WeatherParameters.ClearSunset) + if args.low_quality: world.unload_map_layer(carla.MapLayer.Foliage) world.unload_map_layer(carla.MapLayer.Buildings) world.unload_map_layer(carla.MapLayer.ParkedVehicles) - world.unload_map_layer(carla.MapLayer.Particles) world.unload_map_layer(carla.MapLayer.Props) world.unload_map_layer(carla.MapLayer.StreetLights) + world.unload_map_layer(carla.MapLayer.Particles) blueprint_library = world.get_blueprint_library() @@ -209,11 +262,12 @@ def bridge(q): blueprint = blueprint_library.find('sensor.camera.rgb') blueprint.set_attribute('image_size_x', str(W)) blueprint.set_attribute('image_size_y', str(H)) - blueprint.set_attribute('fov', '70') + blueprint.set_attribute('fov', '40') blueprint.set_attribute('sensor_tick', '0.05') transform = carla.Transform(carla.Location(x=0.8, z=1.13)) camera = world.spawn_actor(blueprint, transform, attach_to=vehicle) - camera.listen(cam_callback) + camerad = Camerad() + camera.listen(camerad.cam_callback) vehicle_state = VehicleState() @@ -244,7 +298,6 @@ def bridge(q): brake_ease_out_counter = REPEAT_COUNTER steer_ease_out_counter = REPEAT_COUNTER - vc = carla.VehicleControl(throttle=0, steer=0, brake=0, reverse=False) is_openpilot_engaged = False @@ -253,12 +306,11 @@ def bridge(q): throttle_manual = steer_manual = brake_manual = 0 old_steer = old_brake = old_throttle = 0 - throttle_manual_multiplier = 0.7 #keyboard signal is always 1 - brake_manual_multiplier = 0.7 #keyboard signal is always 1 - steer_manual_multiplier = 45 * STEER_RATIO #keyboard signal is always 1 - + throttle_manual_multiplier = 0.7 # keyboard signal is always 1 + brake_manual_multiplier = 0.7 # keyboard signal is always 1 + steer_manual_multiplier = 45 * STEER_RATIO # keyboard signal is always 1 - while 1: + while True: # 1. Read the throttle, steer and brake from op or manual controls # 2. Set instructions in Carla # 3. Send current carstate to op via can @@ -282,7 +334,6 @@ def bridge(q): brake_manual = float(m[1]) is_openpilot_engaged = False elif m[0] == "reverse": - #in_reverse = not in_reverse cruise_button = CruiseButtons.CANCEL is_openpilot_engaged = False elif m[0] == "cruise": @@ -302,19 +353,16 @@ def bridge(q): steer_out = steer_manual * steer_manual_multiplier brake_out = brake_manual * brake_manual_multiplier - #steer_out = steer_out - # steer_out = steer_rate_limit(old_steer, steer_out) old_steer = steer_out old_throttle = throttle_out old_brake = brake_out - # print('message',old_throttle, old_steer, old_brake) - if is_openpilot_engaged: sm.update(0) + # TODO gas and brake is deprecated - throttle_op = clip(sm['carControl'].actuators.accel/1.6, 0.0, 1.0) - brake_op = clip(-sm['carControl'].actuators.accel/4.0, 0.0, 1.0) + throttle_op = clip(sm['carControl'].actuators.accel / 1.6, 0.0, 1.0) + brake_op = clip(-sm['carControl'].actuators.accel / 4.0, 0.0, 1.0) steer_op = sm['carControl'].actuators.steeringAngleDeg throttle_out = throttle_op @@ -325,24 +373,24 @@ def bridge(q): old_steer = steer_out else: - if throttle_out==0 and old_throttle>0: - if throttle_ease_out_counter>0: + if throttle_out == 0 and old_throttle > 0: + if throttle_ease_out_counter > 0: throttle_out = old_throttle throttle_ease_out_counter += -1 else: throttle_ease_out_counter = REPEAT_COUNTER old_throttle = 0 - if brake_out==0 and old_brake>0: - if brake_ease_out_counter>0: + if brake_out == 0 and old_brake > 0: + if brake_ease_out_counter > 0: brake_out = old_brake brake_ease_out_counter += -1 else: brake_ease_out_counter = REPEAT_COUNTER old_brake = 0 - if steer_out==0 and old_steer!=0: - if steer_ease_out_counter>0: + if steer_out == 0 and old_steer != 0: + if steer_ease_out_counter > 0: steer_out = old_steer steer_ease_out_counter += -1 else: @@ -350,30 +398,32 @@ def bridge(q): old_steer = 0 # --------------Step 2------------------------------- - steer_carla = steer_out / (max_steer_angle * STEER_RATIO * -1) - steer_carla = np.clip(steer_carla, -1,1) + steer_carla = np.clip(steer_carla, -1, 1) steer_out = steer_carla * (max_steer_angle * STEER_RATIO * -1) old_steer = steer_carla * (max_steer_angle * STEER_RATIO * -1) - vc.throttle = throttle_out/0.6 + vc.throttle = throttle_out / 0.6 vc.steer = steer_carla vc.brake = brake_out vehicle.apply_control(vc) # --------------Step 3------------------------------- vel = vehicle.get_velocity() - speed = math.sqrt(vel.x**2 + vel.y**2 + vel.z**2) # in m/s + speed = math.sqrt(vel.x**2 + vel.y**2 + vel.z**2) # in m/s vehicle_state.speed = speed vehicle_state.vel = vel vehicle_state.angle = steer_out vehicle_state.cruise_button = cruise_button vehicle_state.is_engaged = is_openpilot_engaged - if rk.frame%PRINT_DECIMATION == 0: + if rk.frame % PRINT_DECIMATION == 0: print("frame: ", "engaged:", is_openpilot_engaged, "; throttle: ", round(vc.throttle, 3), "; steer(c/deg): ", round(vc.steer, 3), round(steer_out, 3), "; brake: ", round(vc.brake, 3)) + if rk.frame % 5 == 0: + world.tick() + rk.keep_time() # Clean up resources in the opposite order they were created. @@ -385,6 +435,7 @@ def bridge(q): camera.destroy() vehicle.destroy() + def bridge_keep_alive(q: Any): while 1: try: @@ -393,6 +444,7 @@ def bridge_keep_alive(q: Any): except RuntimeError: print("Restarting bridge...") + if __name__ == "__main__": # make sure params are in a good state set_params_enabled() diff --git a/tools/sim/launch_openpilot.sh b/tools/sim/launch_openpilot.sh index e10d688c2f..b94a453b7b 100755 --- a/tools/sim/launch_openpilot.sh +++ b/tools/sim/launch_openpilot.sh @@ -5,8 +5,7 @@ export NOBOARD="1" export SIMULATION="1" export FINGERPRINT="HONDA CIVIC 2016" -# TODO: remove this once the bridge uses visionipc -export BLOCK="loggerd" +export BLOCK="camerad,loggerd" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" cd ../../selfdrive/manager && ./manager.py diff --git a/tools/sim/lib/can.py b/tools/sim/lib/can.py index 97754ab0b8..4873947d4b 100755 --- a/tools/sim/lib/can.py +++ b/tools/sim/lib/can.py @@ -62,7 +62,7 @@ def can_function(pm, speed, angle, idx, cruise_button, is_engaged): msg.append(packer.make_can_msg("CRUISE", 0, {}, idx)) msg.append(packer.make_can_msg("SCM_FEEDBACK", 0, {"MAIN_ON": 1}, idx)) msg.append(packer.make_can_msg("POWERTRAIN_DATA", 0, {"ACC_STATUS": int(is_engaged)}, idx)) - msg.append(packer.make_can_msg("HUD_SETTING", 0, {}, idx)) + msg.append(packer.make_can_msg("HUD_SETTING", 0, {})) # *** cam bus *** msg.append(packer.make_can_msg("STEERING_CONTROL", 2, {}, idx)) diff --git a/tools/sim/start_carla.sh b/tools/sim/start_carla.sh index df6e2a3fa3..6418dacaee 100755 --- a/tools/sim/start_carla.sh +++ b/tools/sim/start_carla.sh @@ -24,4 +24,4 @@ docker run \ -v /tmp/.X11-unix:/tmp/.X11-unix:rw \ -it \ carlasim/carla:0.9.12 \ - /bin/bash ./CarlaUE4.sh -opengl -nosound -RenderOffScreen -quality-level=Epic + /bin/bash ./CarlaUE4.sh -opengl -nosound -RenderOffScreen -benchmark -fps=20 -quality-level=High