Compare commits
33 Commits
dragonpilot
..
cp
| Author | SHA1 | Date | |
|---|---|---|---|
| 55ae5a17ea | |||
| be0f6fcb8b | |||
| a523b38c76 | |||
| 65f95e824b | |||
| 72a5031e69 | |||
| c8fe2f26c6 | |||
| 5d3546b8da | |||
| 0ab1092298 | |||
| 665c5541be | |||
| 320197a59f | |||
| db0ff4b591 | |||
| d29df3bc7d | |||
| b0eab6e8de | |||
| 81f8f4d434 | |||
| 8776533f03 | |||
| f7fd65ef80 | |||
| 55cfe8ca74 | |||
| c5ebcbcb97 | |||
| d6899edd97 | |||
| 771e7acd61 | |||
| 0423d12c9c | |||
| b94c3eb6d9 | |||
| 01e1c03d49 | |||
| 8962bb1acc | |||
| 77a8919349 | |||
| 9d35822092 | |||
| 7c74dbd3a0 | |||
| 5662c0a7c0 | |||
| 99ef1b188d | |||
| 42ef9dfe8a | |||
| a345912302 | |||
| 1bc2b23ec1 | |||
| bd2ed6664a |
+19
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
Checks: '
|
||||||
|
bugprone-*,
|
||||||
|
-bugprone-integer-division,
|
||||||
|
-bugprone-narrowing-conversions,
|
||||||
|
performance-*,
|
||||||
|
clang-analyzer-*,
|
||||||
|
misc-*,
|
||||||
|
-misc-unused-parameters,
|
||||||
|
modernize-*,
|
||||||
|
-modernize-avoid-c-arrays,
|
||||||
|
-modernize-deprecated-headers,
|
||||||
|
-modernize-use-auto,
|
||||||
|
-modernize-use-using,
|
||||||
|
-modernize-use-nullptr,
|
||||||
|
-modernize-use-trailing-return-type,
|
||||||
|
'
|
||||||
|
CheckOptions:
|
||||||
|
...
|
||||||
@@ -13,6 +13,27 @@
|
|||||||
*.o-*
|
*.o-*
|
||||||
*.os
|
*.os
|
||||||
*.os-*
|
*.os-*
|
||||||
|
*.so
|
||||||
|
*.a
|
||||||
|
|
||||||
venv/
|
venv/
|
||||||
.venv/
|
.venv/
|
||||||
|
|
||||||
|
notebooks
|
||||||
|
phone
|
||||||
|
massivemap
|
||||||
|
neos
|
||||||
|
installer
|
||||||
|
chffr/app2
|
||||||
|
chffr/backend/env
|
||||||
|
selfdrive/nav
|
||||||
|
selfdrive/baseui
|
||||||
|
selfdrive/test/simulator2
|
||||||
|
**/cache_data
|
||||||
|
xx/plus
|
||||||
|
xx/community
|
||||||
|
xx/projects
|
||||||
|
!xx/projects/eon_testing_master
|
||||||
|
!xx/projects/map3d
|
||||||
|
xx/ops
|
||||||
|
xx/junk
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ end_of_line = lf
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.{py,pyx,pxd}]
|
[{*.py, *.pyx, *.pxd}]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
CI / testing:
|
CI / testing:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: "{.github/**,**/test_*,**/test/**,Jenkinsfile}"
|
- any-glob-to-all-files: "{.github/**,**/test_*,Jenkinsfile}"
|
||||||
|
|
||||||
car:
|
car:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
@@ -12,7 +12,7 @@ simulation:
|
|||||||
|
|
||||||
ui:
|
ui:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: '{selfdrive/assets/**,selfdrive/ui/**,system/ui/**}'
|
- any-glob-to-all-files: 'selfdrive/ui/**'
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
name: 'automatically cache based on current runner'
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
path:
|
||||||
|
description: 'path to cache'
|
||||||
|
required: true
|
||||||
|
key:
|
||||||
|
description: 'key'
|
||||||
|
required: true
|
||||||
|
restore-keys:
|
||||||
|
description: 'restore-keys'
|
||||||
|
required: true
|
||||||
|
save:
|
||||||
|
description: 'whether to save the cache'
|
||||||
|
default: 'false'
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
cache-hit:
|
||||||
|
description: 'cache hit occurred'
|
||||||
|
value: ${{ (contains(runner.name, 'nsc') && steps.ns-cache.outputs.cache-hit) ||
|
||||||
|
(!contains(runner.name, 'nsc') && inputs.save != 'false' && steps.gha-cache.outputs.cache-hit) ||
|
||||||
|
(!contains(runner.name, 'nsc') && inputs.save == 'false' && steps.gha-cache-ro.outputs.cache-hit) }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: setup namespace cache
|
||||||
|
id: ns-cache
|
||||||
|
if: ${{ contains(runner.name, 'nsc') }}
|
||||||
|
uses: namespacelabs/nscloud-cache-action@v1
|
||||||
|
with:
|
||||||
|
path: ${{ inputs.path }}
|
||||||
|
|
||||||
|
- name: setup github cache
|
||||||
|
id: gha-cache
|
||||||
|
if: ${{ !contains(runner.name, 'nsc') && inputs.save != 'false' }}
|
||||||
|
uses: 'actions/cache@v4'
|
||||||
|
with:
|
||||||
|
path: ${{ inputs.path }}
|
||||||
|
key: ${{ inputs.key }}
|
||||||
|
restore-keys: ${{ inputs.restore-keys }}
|
||||||
|
|
||||||
|
- name: setup github cache
|
||||||
|
id: gha-cache-ro
|
||||||
|
if: ${{ !contains(runner.name, 'nsc') && inputs.save == 'false' }}
|
||||||
|
uses: 'actions/cache/restore@v4'
|
||||||
|
with:
|
||||||
|
path: ${{ inputs.path }}
|
||||||
|
key: ${{ inputs.key }}
|
||||||
|
restore-keys: ${{ inputs.restore-keys }}
|
||||||
|
|
||||||
|
# make the directory manually in case we didn't get a hit, so it doesn't fail on future steps
|
||||||
|
- id: scons-cache-setup
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ inputs.path }}
|
||||||
|
sudo chmod -R 777 ${{ inputs.path }}
|
||||||
|
sudo chown -R $USER ${{ inputs.path }}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
name: "PR review"
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, reopened, synchronize, edited, edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
labeler:
|
||||||
|
name: review
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: false
|
||||||
|
|
||||||
|
# Label PRs
|
||||||
|
- uses: actions/labeler@v5.0.0
|
||||||
|
with:
|
||||||
|
dot: true
|
||||||
|
configuration-path: .github/labeler.yaml
|
||||||
|
|
||||||
|
# Check PR target branch
|
||||||
|
- name: check branch
|
||||||
|
uses: Vankka/pr-target-branch-action@def32ec9d93514138d6ac0132ee62e120a72aed5
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
target: /^(?!master$).*/
|
||||||
|
exclude: /commaai:.*/
|
||||||
|
change-to: ${{ github.base_ref }}
|
||||||
|
already-exists-action: close_this
|
||||||
|
already-exists-comment: "Your PR should be made against the `master` branch"
|
||||||
|
|
||||||
|
# Welcome comment
|
||||||
|
- name: "First timers PR"
|
||||||
|
uses: actions/first-interaction@v1
|
||||||
|
if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot'
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
pr-message: |
|
||||||
|
<!-- _(run_id **${{ github.run_id }}**)_ -->
|
||||||
|
Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following:
|
||||||
|
* Convert your PR to a draft unless it's ready to review
|
||||||
|
* Read the [contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md)
|
||||||
|
* Before marking as "ready for review", ensure:
|
||||||
|
* the goal is clearly stated in the description
|
||||||
|
* all the tests are passing
|
||||||
|
* the change is [something we merge](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md#what-gets-merged)
|
||||||
|
* include a route or your device' dongle ID if relevant
|
||||||
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: badges
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 * * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
BASE_IMAGE: openpilot-base
|
||||||
|
DOCKER_REGISTRY: ghcr.io/commaai
|
||||||
|
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/bash -c
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
badges:
|
||||||
|
name: create badges
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- name: Push badges
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "scons -j$(nproc) && python3 selfdrive/ui/translations/create_badges.py"
|
||||||
|
|
||||||
|
rm .gitattributes
|
||||||
|
|
||||||
|
git checkout --orphan badges
|
||||||
|
git rm -rf --cached .
|
||||||
|
git config user.email "badge-researcher@comma.ai"
|
||||||
|
git config user.name "Badge Researcher"
|
||||||
|
|
||||||
|
git add translation_badge.svg
|
||||||
|
git commit -m "Add/Update badges"
|
||||||
|
git push -f origin HEAD
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
name: weekly CI test report
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '37 9 * * 1' # 9:37AM UTC -> 2:37AM PST every monday
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
ci_runs:
|
||||||
|
description: 'The amount of runs to trigger in CI test report'
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
CI_RUNS: ${{ github.event.inputs.ci_runs || '50' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup:
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
ci_runs: ${{ steps.ci_runs_setup.outputs.matrix }}
|
||||||
|
steps:
|
||||||
|
- id: ci_runs_setup
|
||||||
|
name: CI_RUNS=${{ env.CI_RUNS }}
|
||||||
|
run: |
|
||||||
|
matrix=$(python3 -c "import json; print(json.dumps({ 'run_number' : list(range(${{ env.CI_RUNS }})) }))")
|
||||||
|
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
ci_matrix_run:
|
||||||
|
needs: [ setup ]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix: ${{fromJSON(needs.setup.outputs.ci_runs)}}
|
||||||
|
uses: commaai/openpilot/.github/workflows/ci_weekly_run.yaml@master
|
||||||
|
with:
|
||||||
|
run_number: ${{ matrix.run_number }}
|
||||||
|
|
||||||
|
report:
|
||||||
|
needs: [ci_matrix_run]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- name: Get job results
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
id: get-job-results
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const jobs = await github
|
||||||
|
.paginate("GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt}/jobs", {
|
||||||
|
owner: "commaai",
|
||||||
|
repo: "${{ github.event.repository.name }}",
|
||||||
|
run_id: "${{ github.run_id }}",
|
||||||
|
attempt: "${{ github.run_attempt }}",
|
||||||
|
})
|
||||||
|
var report = {}
|
||||||
|
jobs.slice(1, jobs.length-1).forEach(job => {
|
||||||
|
if (job.conclusion === "skipped") return;
|
||||||
|
const jobName = job.name.split(" / ")[2];
|
||||||
|
const runRegex = /\((.*?)\)/;
|
||||||
|
const run = job.name.match(runRegex)[1];
|
||||||
|
report[jobName] = report[jobName] || { successes: [], failures: [], canceled: [] };
|
||||||
|
switch (job.conclusion) {
|
||||||
|
case "success":
|
||||||
|
report[jobName].successes.push({ "run_number": run, "link": job.html_url}); break;
|
||||||
|
case "failure":
|
||||||
|
report[jobName].failures.push({ "run_number": run, "link": job.html_url }); break;
|
||||||
|
case "canceled":
|
||||||
|
report[jobName].canceled.push({ "run_number": run, "link": job.html_url }); break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return JSON.stringify({"jobs": report});
|
||||||
|
|
||||||
|
- name: Add job results to summary
|
||||||
|
env:
|
||||||
|
JOB_RESULTS: ${{ fromJSON(steps.get-job-results.outputs.result) }}
|
||||||
|
run: |
|
||||||
|
cat <<EOF >> template.html
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Job</th>
|
||||||
|
<th>✅ Passing</th>
|
||||||
|
<th>❌ Failure Details</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for key in jobs.keys() %}<tr>
|
||||||
|
<td>{% for i in range(5) %}{% if i+1 <= (5 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }}) %}🟩{% else %}🟥{% endif %}{% endfor%}</td>
|
||||||
|
<td>{{ key }}</td>
|
||||||
|
<td>{{ 100 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }} }}%</td>
|
||||||
|
<td>{% if jobs[key]["failures"]|length > 0 %}<details>{% for failure in jobs[key]["failures"] %}<a href="{{ failure['link'] }}">Log for run #{{ failure['run_number'] }}</a><br>{% endfor %}</details>{% else %}{% endif %}</td>
|
||||||
|
</td>
|
||||||
|
</tr>{% endfor %}
|
||||||
|
</table>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
pip install jinja2-cli
|
||||||
|
echo $JOB_RESULTS | jinja2 template.html > report.html
|
||||||
|
echo "# CI Test Report - ${{ env.CI_RUNS }} Runs" >> $GITHUB_STEP_SUMMARY
|
||||||
|
cat report.html >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
name: weekly CI test run
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
run_number:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-run-${{ inputs.run_number }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
selfdrive_tests:
|
||||||
|
uses: commaai/openpilot/.github/workflows/selfdrive_tests.yaml@master
|
||||||
|
with:
|
||||||
|
run_number: ${{ inputs.run_number }}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
name: 'compile openpilot'
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- shell: bash
|
||||||
|
name: Build openpilot with all flags
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "scons -j$(nproc)"
|
||||||
|
${{ env.RUN }} "release/check-dirty.sh"
|
||||||
|
- shell: bash
|
||||||
|
name: Cleanup scons cache and rebuild
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \
|
||||||
|
scons -j$(nproc) --cache-populate"
|
||||||
|
- name: Save scons cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
with:
|
||||||
|
path: .ci_cache/scons_cache
|
||||||
|
key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
name: docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
run_number:
|
||||||
|
default: '1'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
concurrency:
|
||||||
|
group: docs-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docs:
|
||||||
|
name: build docs
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: commaai/timeout@v1
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
# Build
|
||||||
|
- name: Build docs
|
||||||
|
run: |
|
||||||
|
# TODO: can we install just the "docs" dependency group without the normal deps?
|
||||||
|
pip install mkdocs
|
||||||
|
mkdocs build
|
||||||
|
|
||||||
|
# Push to docs.comma.ai
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
|
||||||
|
with:
|
||||||
|
path: openpilot-docs
|
||||||
|
ssh-key: ${{ secrets.OPENPILOT_DOCS_KEY }}
|
||||||
|
repository: commaai/openpilot-docs
|
||||||
|
- name: Push
|
||||||
|
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
|
||||||
|
source release/identity.sh
|
||||||
|
|
||||||
|
cd openpilot-docs
|
||||||
|
git checkout --orphan tmp
|
||||||
|
git rm -rf .
|
||||||
|
|
||||||
|
# copy over docs
|
||||||
|
cp -r ../docs_site/ docs/
|
||||||
|
|
||||||
|
# GitHub pages config
|
||||||
|
touch docs/.nojekyll
|
||||||
|
echo -n docs.comma.ai > docs/CNAME
|
||||||
|
|
||||||
|
git add -f .
|
||||||
|
git commit -m "build docs"
|
||||||
|
|
||||||
|
# docs live in different repo to not bloat openpilot's full clone size
|
||||||
|
git push -f origin tmp:gh-pages
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
name: jenkins scan
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created, edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# TODO: gc old branches in a separate job in this workflow
|
||||||
|
scan-comments:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.issue.pull_request }}
|
||||||
|
steps:
|
||||||
|
- name: Check for trigger phrase
|
||||||
|
id: check_comment
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const triggerPhrase = "trigger-jenkins";
|
||||||
|
const comment = context.payload.comment.body;
|
||||||
|
const commenter = context.payload.comment.user.login;
|
||||||
|
|
||||||
|
const { data: permissions } = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
username: commenter
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasWriteAccess = permissions.permission === 'write' || permissions.permission === 'admin';
|
||||||
|
|
||||||
|
return (hasWriteAccess && comment.includes(triggerPhrase));
|
||||||
|
result-encoding: json
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
if: steps.check_comment.outputs.result == 'true'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: refs/pull/${{ github.event.issue.number }}/head
|
||||||
|
|
||||||
|
- name: Push to tmp-jenkins branch
|
||||||
|
if: steps.check_comment.outputs.result == 'true'
|
||||||
|
run: |
|
||||||
|
git config --global user.name "github-actions[bot]"
|
||||||
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git checkout -b tmp-jenkins-${{ github.event.issue.number }}
|
||||||
|
GIT_LFS_SKIP_PUSH=1 git push -f origin tmp-jenkins-${{ github.event.issue.number }}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
name: prebuilt
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 * * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
BUILD: selfdrive/test/docker_build.sh prebuilt
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_prebuilt:
|
||||||
|
name: build prebuilt
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
|
env:
|
||||||
|
PUSH_IMAGE: true
|
||||||
|
permissions:
|
||||||
|
checks: read
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Wait for green check mark
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||||
|
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
wait-interval: 30
|
||||||
|
running-workflow-name: 'build prebuilt'
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
check-regexp: ^((?!.*(build master-ci).*).)*$
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- run: git lfs pull
|
||||||
|
- name: Build and Push docker image
|
||||||
|
run: |
|
||||||
|
$DOCKER_LOGIN
|
||||||
|
eval "$BUILD"
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
name: release
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 9 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_masterci:
|
||||||
|
name: build master-ci
|
||||||
|
env:
|
||||||
|
TARGET_DIR: /tmp/openpilot
|
||||||
|
ImageOS: ubuntu20
|
||||||
|
container:
|
||||||
|
image: ghcr.io/commaai/openpilot-base:latest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
|
permissions:
|
||||||
|
checks: read
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Install wait-on-check-action dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libyaml-dev
|
||||||
|
- name: Wait for green check mark
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||||
|
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
wait-interval: 30
|
||||||
|
running-workflow-name: 'build master-ci'
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
check-regexp: ^((?!.*(build prebuilt).*).)*$
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Pull LFS
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
git lfs pull
|
||||||
|
- name: Build master-ci
|
||||||
|
run: |
|
||||||
|
release/build_devel.sh
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
export PYTHONPATH=$TARGET_DIR
|
||||||
|
cd $TARGET_DIR
|
||||||
|
scons -j$(nproc)
|
||||||
|
pytest -n logical selfdrive/car/tests/test_car_interfaces.py
|
||||||
|
- name: Push master-ci
|
||||||
|
run: |
|
||||||
|
unset TARGET_DIR
|
||||||
|
BRANCH=__nightly release/build_devel.sh
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
name: repo maintenance
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 14 * * 1" # every Monday at 2am UTC (6am PST)
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
BASE_IMAGE: openpilot-base
|
||||||
|
BUILD: selfdrive/test/docker_build.sh base
|
||||||
|
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update_translations:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- name: Update translations
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "python3 selfdrive/ui/update_translations.py --vanish"
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
|
||||||
|
with:
|
||||||
|
author: Vehicle Researcher <user@comma.ai>
|
||||||
|
commit-message: "Update translations"
|
||||||
|
title: "[bot] Update translations"
|
||||||
|
body: "Automatic PR from repo-maintenance -> update_translations"
|
||||||
|
branch: "update-translations"
|
||||||
|
base: "master"
|
||||||
|
delete-branch: true
|
||||||
|
labels: bot
|
||||||
|
|
||||||
|
package_updates:
|
||||||
|
name: package_updates
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ghcr.io/commaai/openpilot-base:latest
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: uv lock
|
||||||
|
run: |
|
||||||
|
python3 -m ensurepip --upgrade
|
||||||
|
pip3 install uv
|
||||||
|
uv lock --upgrade
|
||||||
|
- name: bump submodules
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
git -c submodule."tinygrad".update=none submodule update --remote
|
||||||
|
git add .
|
||||||
|
- name: update car docs
|
||||||
|
run: |
|
||||||
|
scons -j$(nproc) --minimal opendbc_repo
|
||||||
|
PYTHONPATH=. python selfdrive/car/docs.py
|
||||||
|
git add docs/CARS.md
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
|
||||||
|
with:
|
||||||
|
author: Vehicle Researcher <user@comma.ai>
|
||||||
|
token: ${{ secrets.ACTIONS_CREATE_PR_PAT }}
|
||||||
|
commit-message: Update Python packages
|
||||||
|
title: '[bot] Update Python packages'
|
||||||
|
branch: auto-package-updates
|
||||||
|
base: master
|
||||||
|
delete-branch: true
|
||||||
|
body: 'Automatic PR from repo-maintenance -> package_updates'
|
||||||
|
labels: bot
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
name: selfdrive
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
run_number:
|
||||||
|
default: '1'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: selfdrive-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
PYTHONWARNINGS: error
|
||||||
|
BASE_IMAGE: openpilot-base
|
||||||
|
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
|
||||||
|
|
||||||
|
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
BUILD: selfdrive/test/docker_build.sh base
|
||||||
|
|
||||||
|
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||||
|
|
||||||
|
PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5 --hypothesis-seed 0 -n logical
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_release:
|
||||||
|
name: build release
|
||||||
|
runs-on:
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||||
|
env:
|
||||||
|
STRIPPED_DIR: /tmp/releasepilot
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Getting LFS files
|
||||||
|
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e
|
||||||
|
with:
|
||||||
|
timeout_minutes: 2
|
||||||
|
max_attempts: 3
|
||||||
|
command: git lfs pull
|
||||||
|
- name: Build devel
|
||||||
|
timeout-minutes: 1
|
||||||
|
run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- name: Check submodules
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
|
timeout-minutes: 3
|
||||||
|
run: release/check-submodules.sh
|
||||||
|
- name: Build openpilot and run checks
|
||||||
|
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache
|
||||||
|
run: |
|
||||||
|
cd $STRIPPED_DIR
|
||||||
|
${{ env.RUN }} "python3 system/manager/build.py"
|
||||||
|
- name: Run tests
|
||||||
|
timeout-minutes: 1
|
||||||
|
run: |
|
||||||
|
cd $STRIPPED_DIR
|
||||||
|
${{ env.RUN }} "release/check-dirty.sh"
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on:
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Setup docker push
|
||||||
|
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
|
||||||
|
run: |
|
||||||
|
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
|
||||||
|
$DOCKER_LOGIN
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- uses: ./.github/workflows/compile-openpilot
|
||||||
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
build_mac:
|
||||||
|
name: build macOS
|
||||||
|
runs-on: ${{ github.repository == 'commaai/openpilot' && 'namespace-profile-macos-8x14' || 'macos-latest' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Homebrew cache
|
||||||
|
uses: ./.github/workflows/auto-cache
|
||||||
|
with:
|
||||||
|
path: ~/Library/Caches/Homebrew
|
||||||
|
- name: Install dependencies
|
||||||
|
run: ./tools/mac_setup.sh
|
||||||
|
env:
|
||||||
|
# package install has DeprecationWarnings
|
||||||
|
PYTHONWARNINGS: default
|
||||||
|
- run: git lfs pull
|
||||||
|
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
||||||
|
- name: Getting scons cache
|
||||||
|
uses: ./.github/workflows/auto-cache
|
||||||
|
with:
|
||||||
|
path: /tmp/scons_cache
|
||||||
|
- name: Building openpilot
|
||||||
|
run: . .venv/bin/activate && scons -j$(nproc)
|
||||||
|
|
||||||
|
static_analysis:
|
||||||
|
name: static analysis
|
||||||
|
runs-on:
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||||
|
env:
|
||||||
|
PYTHONWARNINGS: default
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- name: Static analysis
|
||||||
|
timeout-minutes: 1
|
||||||
|
run: ${{ env.RUN }} "scripts/lint/lint.sh"
|
||||||
|
|
||||||
|
unit_tests:
|
||||||
|
name: unit tests
|
||||||
|
runs-on:
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- name: Build openpilot
|
||||||
|
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||||
|
- name: Run unit tests
|
||||||
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && 1 || 20 }}
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "$PYTEST --collect-only -m 'not slow' &> /dev/null && \
|
||||||
|
MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \
|
||||||
|
./selfdrive/ui/tests/create_test_translations.sh && \
|
||||||
|
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
|
||||||
|
chmod -R 777 /tmp/comma_download_cache"
|
||||||
|
- name: "Upload coverage to Codecov"
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.job }}
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
|
process_replay:
|
||||||
|
name: process replay
|
||||||
|
runs-on:
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- name: Cache test routes
|
||||||
|
id: dependency-cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .ci_cache/comma_download_cache
|
||||||
|
key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_processes.py') }}
|
||||||
|
- name: Build openpilot
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "scons -j$(nproc)"
|
||||||
|
- name: Run replay
|
||||||
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.dependency-cache.outputs.cache-hit == 'true') && 1 || 20 }}
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
|
||||||
|
chmod -R 777 /tmp/comma_download_cache && \
|
||||||
|
coverage combine && \
|
||||||
|
coverage xml"
|
||||||
|
- name: Print diff
|
||||||
|
id: print-diff
|
||||||
|
if: always()
|
||||||
|
run: cat selfdrive/test/process_replay/diff.txt
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
name: process_replay_diff.txt
|
||||||
|
path: selfdrive/test/process_replay/diff.txt
|
||||||
|
- name: Upload reference logs
|
||||||
|
if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }}
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "unset PYTHONWARNINGS && AZURE_TOKEN='$AZURE_TOKEN' python3 selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
|
||||||
|
- name: Run regen
|
||||||
|
if: false
|
||||||
|
timeout-minutes: 4
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \
|
||||||
|
chmod -R 777 /tmp/comma_download_cache"
|
||||||
|
- name: "Upload coverage to Codecov"
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.job }}
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
|
test_cars:
|
||||||
|
name: cars
|
||||||
|
runs-on:
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
job: [0, 1, 2, 3]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- name: Cache test routes
|
||||||
|
id: routes-cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .ci_cache/comma_download_cache
|
||||||
|
key: car_models-${{ hashFiles('selfdrive/car/tests/test_models.py', 'opendbc/car/tests/routes.py') }}-${{ matrix.job }}
|
||||||
|
- name: Build openpilot
|
||||||
|
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||||
|
- name: Test car models
|
||||||
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.routes-cache.outputs.cache-hit == 'true') && 1 || 20 }}
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "MAX_EXAMPLES=1 $PYTEST selfdrive/car/tests/test_models.py && \
|
||||||
|
chmod -R 777 /tmp/comma_download_cache"
|
||||||
|
env:
|
||||||
|
NUM_JOBS: 4
|
||||||
|
JOB_ID: ${{ matrix.job }}
|
||||||
|
- name: "Upload coverage to Codecov"
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.job }}-${{ matrix.job }}
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
|
car_docs_diff:
|
||||||
|
name: PR comments
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
#if: github.event_name == 'pull_request'
|
||||||
|
if: false # TODO: run this in opendbc?
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
ref: ${{ github.event.pull_request.base.ref }}
|
||||||
|
- run: git lfs pull
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- name: Get base car info
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "scons -j$(nproc) && python3 selfdrive/debug/dump_car_docs.py --path /tmp/openpilot_cache/base_car_docs"
|
||||||
|
sudo chown -R $USER:$USER ${{ github.workspace }}
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
path: current
|
||||||
|
- run: cd current && git lfs pull
|
||||||
|
- name: Save car docs diff
|
||||||
|
id: save_diff
|
||||||
|
run: |
|
||||||
|
cd current
|
||||||
|
${{ env.RUN }} "scons -j$(nproc)"
|
||||||
|
output=$(${{ env.RUN }} "python3 selfdrive/debug/print_docs_diff.py --path /tmp/openpilot_cache/base_car_docs")
|
||||||
|
output="${output//$'\n'/'%0A'}"
|
||||||
|
echo "::set-output name=diff::$output"
|
||||||
|
- name: Find comment
|
||||||
|
if: ${{ env.AZURE_TOKEN != '' }}
|
||||||
|
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e
|
||||||
|
id: fc
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
body-includes: This PR makes changes to
|
||||||
|
- name: Update comment
|
||||||
|
if: ${{ steps.save_diff.outputs.diff != '' && env.AZURE_TOKEN != '' }}
|
||||||
|
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043
|
||||||
|
with:
|
||||||
|
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
body: "${{ steps.save_diff.outputs.diff }}"
|
||||||
|
edit-mode: replace
|
||||||
|
- name: Delete comment
|
||||||
|
if: ${{ steps.fc.outputs.comment-id != '' && steps.save_diff.outputs.diff == '' && env.AZURE_TOKEN != '' }}
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
github.rest.issues.deleteComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
comment_id: ${{ steps.fc.outputs.comment-id }}
|
||||||
|
})
|
||||||
|
|
||||||
|
simulator_driving:
|
||||||
|
name: simulator driving
|
||||||
|
runs-on:
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||||
|
if: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- name: Build openpilot
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "scons -j$(nproc)"
|
||||||
|
- name: Driving test
|
||||||
|
timeout-minutes: 1
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
|
||||||
|
source selfdrive/test/setup_vsound.sh && \
|
||||||
|
CI=1 pytest -s tools/sim/tests/test_metadrive_bridge.py"
|
||||||
|
|
||||||
|
create_ui_report:
|
||||||
|
# This job name needs to be the same as UI_JOB_NAME in ui_preview.yaml
|
||||||
|
name: Create UI Report
|
||||||
|
runs-on:
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||||
|
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- name: caching frames
|
||||||
|
id: frames-cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .ci_cache/comma_download_cache
|
||||||
|
key: ui_screenshots_test_${{ hashFiles('selfdrive/ui/tests/test_ui/run.py') }}
|
||||||
|
- name: Build openpilot
|
||||||
|
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||||
|
- name: Create Test Report
|
||||||
|
timeout-minutes: ${{ ((steps.frames-cache.outputs.cache-hit == 'true') && 1 || 3) }}
|
||||||
|
run: >
|
||||||
|
${{ env.RUN }} "PYTHONWARNINGS=ignore &&
|
||||||
|
source selfdrive/test/setup_xvfb.sh &&
|
||||||
|
CACHE_ROOT=/tmp/comma_download_cache python3 selfdrive/ui/tests/test_ui/run.py &&
|
||||||
|
chmod -R 777 /tmp/comma_download_cache"
|
||||||
|
- name: Upload Test Report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||||
|
path: selfdrive/ui/tests/test_ui/report_1/screenshots
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: 'openpilot env setup, with retry on failure'
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
docker_hub_pat:
|
||||||
|
description: 'Auth token for Docker Hub, required for BuildJet jobs'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
sleep_time:
|
||||||
|
description: 'Time to sleep between retries'
|
||||||
|
required: false
|
||||||
|
default: 30
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- id: setup1
|
||||||
|
uses: ./.github/workflows/setup
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
is_retried: true
|
||||||
|
- if: steps.setup1.outcome == 'failure'
|
||||||
|
shell: bash
|
||||||
|
run: sleep ${{ inputs.sleep_time }}
|
||||||
|
- id: setup2
|
||||||
|
if: steps.setup1.outcome == 'failure'
|
||||||
|
uses: ./.github/workflows/setup
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
is_retried: true
|
||||||
|
- if: steps.setup2.outcome == 'failure'
|
||||||
|
shell: bash
|
||||||
|
run: sleep ${{ inputs.sleep_time }}
|
||||||
|
- id: setup3
|
||||||
|
if: steps.setup2.outcome == 'failure'
|
||||||
|
uses: ./.github/workflows/setup
|
||||||
|
with:
|
||||||
|
is_retried: true
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
name: 'openpilot env setup'
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
is_retried:
|
||||||
|
description: 'A mock param that asserts that we use the setup-with-retry instead of this action directly'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
# assert that this action is retried using the setup-with-retry
|
||||||
|
- shell: bash
|
||||||
|
if: ${{ inputs.is_retried == 'false' }}
|
||||||
|
run: |
|
||||||
|
echo "You should not run this action directly. Use setup-with-retry instead"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- shell: bash
|
||||||
|
name: No retries!
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.run_attempt }}" -gt 1 ]; then
|
||||||
|
echo -e "\033[0;31m##################################################"
|
||||||
|
echo -e "\033[0;31m Retries not allowed! Fix the flaky test! "
|
||||||
|
echo -e "\033[0;31m##################################################\033[0m"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# do this after checkout to ensure our custom LFS config is used to pull from GitLab
|
||||||
|
- shell: bash
|
||||||
|
run: git lfs pull
|
||||||
|
|
||||||
|
# build cache
|
||||||
|
- id: date
|
||||||
|
shell: bash
|
||||||
|
run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
||||||
|
- shell: bash
|
||||||
|
run: echo "$CACHE_COMMIT_DATE"
|
||||||
|
- id: scons-cache
|
||||||
|
uses: ./.github/workflows/auto-cache
|
||||||
|
with:
|
||||||
|
path: .ci_cache/scons_cache
|
||||||
|
key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}
|
||||||
|
scons-${{ runner.arch }}
|
||||||
|
# as suggested here: https://github.com/moby/moby/issues/32816#issuecomment-910030001
|
||||||
|
- id: normalize-file-permissions
|
||||||
|
shell: bash
|
||||||
|
name: Normalize file permissions to ensure a consistent docker build cache
|
||||||
|
run: |
|
||||||
|
find . -type f -executable -not -perm 755 -exec chmod 755 {} \;
|
||||||
|
find . -type f -not -executable -not -perm 644 -exec chmod 644 {} \;
|
||||||
|
# build our docker image
|
||||||
|
- shell: bash
|
||||||
|
run: eval ${{ env.BUILD }}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
name: stale
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
DAYS_BEFORE_PR_CLOSE: 2
|
||||||
|
DAYS_BEFORE_PR_STALE: 9
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
exempt-all-milestones: true
|
||||||
|
|
||||||
|
# pull request config
|
||||||
|
stale-pr-message: 'This PR has had no activity for ${{ env.DAYS_BEFORE_PR_STALE }} days. It will be automatically closed in ${{ env.DAYS_BEFORE_PR_CLOSE }} days if there is no activity.'
|
||||||
|
close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.'
|
||||||
|
stale-pr-label: stale
|
||||||
|
delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo
|
||||||
|
exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale
|
||||||
|
days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }}
|
||||||
|
days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }}
|
||||||
|
|
||||||
|
# issue config
|
||||||
|
days-before-issue-stale: -1 # ignore issues for now
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
name: "ui preview"
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request_target:
|
||||||
|
types: [assigned, opened, synchronize, reopened, edited]
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
paths:
|
||||||
|
- 'selfdrive/ui/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
UI_JOB_NAME: "Create UI Report"
|
||||||
|
REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||||
|
SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }}
|
||||||
|
BRANCH_NAME: "openpilot/pr-${{ github.event.number }}"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
preview:
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
|
name: preview
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
actions: read
|
||||||
|
steps:
|
||||||
|
- name: Waiting for ui generation to start
|
||||||
|
run: sleep 30
|
||||||
|
|
||||||
|
- name: Waiting for ui generation to end
|
||||||
|
uses: lewagon/wait-on-check-action@v1.3.4
|
||||||
|
with:
|
||||||
|
ref: ${{ env.SHA }}
|
||||||
|
check-name: ${{ env.UI_JOB_NAME }}
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
allowed-conclusions: success
|
||||||
|
wait-interval: 20
|
||||||
|
|
||||||
|
- name: Getting workflow run ID
|
||||||
|
id: get_run_id
|
||||||
|
run: |
|
||||||
|
echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?<number>[0-9]+)") | .number')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Getting proposed ui
|
||||||
|
id: download-artifact
|
||||||
|
uses: dawidd6/action-download-artifact@v6
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run_id: ${{ steps.get_run_id.outputs.run_id }}
|
||||||
|
search_artifacts: true
|
||||||
|
name: report-1-${{ env.REPORT_NAME }}
|
||||||
|
path: ${{ github.workspace }}/pr_ui
|
||||||
|
|
||||||
|
- name: Getting master ui
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: commaai/ci-artifacts
|
||||||
|
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||||
|
path: ${{ github.workspace }}/master_ui
|
||||||
|
ref: openpilot_master_ui
|
||||||
|
|
||||||
|
- name: Saving new master ui
|
||||||
|
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||||
|
working-directory: ${{ github.workspace }}/master_ui
|
||||||
|
run: |
|
||||||
|
git checkout --orphan=new_master_ui
|
||||||
|
git rm -rf *
|
||||||
|
git branch -D openpilot_master_ui
|
||||||
|
git branch -m openpilot_master_ui
|
||||||
|
git config user.name "GitHub Actions Bot"
|
||||||
|
git config user.email "<>"
|
||||||
|
mv ${{ github.workspace }}/pr_ui/*.png .
|
||||||
|
git add .
|
||||||
|
git commit -m "screenshots for commit ${{ env.SHA }}"
|
||||||
|
git push origin openpilot_master_ui --force
|
||||||
|
|
||||||
|
- name: Finding diff
|
||||||
|
if: github.event_name == 'pull_request_target'
|
||||||
|
id: find_diff
|
||||||
|
run: >-
|
||||||
|
sudo apt-get install -y imagemagick
|
||||||
|
|
||||||
|
scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device')
|
||||||
|
A=($scenes)
|
||||||
|
|
||||||
|
DIFF=""
|
||||||
|
TABLE="<details><summary>All Screenshots</summary>"
|
||||||
|
TABLE="${TABLE}<table>"
|
||||||
|
|
||||||
|
for ((i=0; i<${#A[*]}; i=i+1));
|
||||||
|
do
|
||||||
|
|
||||||
|
if ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then
|
||||||
|
convert ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png -transparent black mask.png
|
||||||
|
composite mask.png ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png
|
||||||
|
convert -delay 100 ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif
|
||||||
|
|
||||||
|
mv ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_master_ref.png
|
||||||
|
|
||||||
|
DIFF="${DIFF}<details open>"
|
||||||
|
DIFF="${DIFF}<summary>${A[$i]} : \$\${\\color{red}\\text{DIFFERENT}}\$\$</summary>"
|
||||||
|
DIFF="${DIFF}<table>"
|
||||||
|
|
||||||
|
DIFF="${DIFF}<tr>"
|
||||||
|
DIFF="${DIFF} <td> master <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_master_ref.png\"> </td>"
|
||||||
|
DIFF="${DIFF} <td> proposed <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||||
|
DIFF="${DIFF}</tr>"
|
||||||
|
|
||||||
|
DIFF="${DIFF}<tr>"
|
||||||
|
DIFF="${DIFF} <td> diff <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.png\"> </td>"
|
||||||
|
DIFF="${DIFF} <td> composite diff <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.gif\"> </td>"
|
||||||
|
DIFF="${DIFF}</tr>"
|
||||||
|
|
||||||
|
DIFF="${DIFF}</table>"
|
||||||
|
DIFF="${DIFF}</details>"
|
||||||
|
else
|
||||||
|
rm -f ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png
|
||||||
|
fi
|
||||||
|
|
||||||
|
INDEX=$(($i % 2))
|
||||||
|
if [[ $INDEX -eq 0 ]]; then
|
||||||
|
TABLE="${TABLE}<tr>"
|
||||||
|
fi
|
||||||
|
TABLE="${TABLE} <td> <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||||
|
if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then
|
||||||
|
TABLE="${TABLE}</tr>"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
TABLE="${TABLE}</table></details>"
|
||||||
|
|
||||||
|
echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Saving proposed ui
|
||||||
|
if: github.event_name == 'pull_request_target'
|
||||||
|
working-directory: ${{ github.workspace }}/master_ui
|
||||||
|
run: |
|
||||||
|
git config user.name "GitHub Actions Bot"
|
||||||
|
git config user.email "<>"
|
||||||
|
git checkout --orphan=${{ env.BRANCH_NAME }}
|
||||||
|
git rm -rf *
|
||||||
|
mv ${{ github.workspace }}/pr_ui/* .
|
||||||
|
git add .
|
||||||
|
git commit -m "screenshots for PR #${{ github.event.number }}"
|
||||||
|
git push origin ${{ env.BRANCH_NAME }} --force
|
||||||
|
|
||||||
|
- name: Comment Screenshots on PR
|
||||||
|
if: github.event_name == 'pull_request_target'
|
||||||
|
uses: thollander/actions-comment-pull-request@v2
|
||||||
|
with:
|
||||||
|
message: |
|
||||||
|
<!-- _(run_id_screenshots **${{ github.run_id }}**)_ -->
|
||||||
|
## UI Preview
|
||||||
|
${{ steps.find_diff.outputs.DIFF }}
|
||||||
|
comment_tag: run_id_screenshots
|
||||||
|
pr_number: ${{ github.event.number }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
+38
-22
@@ -10,16 +10,15 @@ venv/
|
|||||||
.overlay_init
|
.overlay_init
|
||||||
.overlay_consistent
|
.overlay_consistent
|
||||||
.sconsign.dblite
|
.sconsign.dblite
|
||||||
|
model2.png
|
||||||
a.out
|
a.out
|
||||||
.hypothesis
|
.hypothesis
|
||||||
.cache/
|
|
||||||
bin/
|
|
||||||
|
|
||||||
*.mp4
|
/docs_site/
|
||||||
|
|
||||||
*.dylib
|
*.dylib
|
||||||
*.DSYM
|
*.DSYM
|
||||||
*.d
|
*.d
|
||||||
*.pem
|
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
.*.swp
|
.*.swp
|
||||||
@@ -36,56 +35,73 @@ bin/
|
|||||||
*.class
|
*.class
|
||||||
*.pyxbldc
|
*.pyxbldc
|
||||||
*.vcd
|
*.vcd
|
||||||
*.mo
|
*.qm
|
||||||
*_pyx.cpp
|
*_pyx.cpp
|
||||||
*.stats
|
|
||||||
*.pkl
|
|
||||||
*.pkl*
|
|
||||||
config.json
|
config.json
|
||||||
|
clcache
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
compare_runtime*.html
|
compare_runtime*.html
|
||||||
|
|
||||||
# build artifacts
|
persist
|
||||||
selfdrive/pandad/pandad
|
selfdrive/pandad/pandad
|
||||||
cereal/services.h
|
cereal/services.h
|
||||||
cereal/gen
|
cereal/gen
|
||||||
cereal/messaging/bridge
|
cereal/messaging/bridge
|
||||||
|
selfdrive/logcatd/logcatd
|
||||||
|
selfdrive/mapd/default_speeds_by_region.json
|
||||||
|
system/proclogd/proclogd
|
||||||
|
selfdrive/ui/translations/alerts_generated.h
|
||||||
selfdrive/ui/translations/tmp
|
selfdrive/ui/translations/tmp
|
||||||
|
selfdrive/test/longitudinal_maneuvers/out
|
||||||
selfdrive/car/tests/cars_dump
|
selfdrive/car/tests/cars_dump
|
||||||
system/camerad/camerad
|
system/camerad/camerad
|
||||||
system/camerad/test/ae_gray_test
|
system/camerad/test/ae_gray_test
|
||||||
|
|
||||||
|
notebooks
|
||||||
|
hyperthneed
|
||||||
|
provisioning
|
||||||
|
|
||||||
.coverage*
|
.coverage*
|
||||||
coverage.xml
|
coverage.xml
|
||||||
htmlcov
|
htmlcov
|
||||||
|
pandaextra
|
||||||
|
|
||||||
|
.mypy_cache/
|
||||||
|
flycheck_*
|
||||||
|
|
||||||
|
cppcheck_report.txt
|
||||||
|
comma*.sh
|
||||||
|
|
||||||
|
selfdrive/modeld/thneed/compile
|
||||||
|
selfdrive/modeld/models/*.thneed
|
||||||
|
selfdrive/modeld/models/*.pkl
|
||||||
|
|
||||||
# openpilot log files
|
|
||||||
*.bz2
|
*.bz2
|
||||||
*.zst
|
*.zst
|
||||||
*.rlog
|
|
||||||
|
|
||||||
build/
|
build/
|
||||||
|
|
||||||
!**/.gitkeep
|
!**/.gitkeep
|
||||||
|
|
||||||
|
poetry.toml
|
||||||
|
Pipfile
|
||||||
|
|
||||||
### VisualStudioCode ###
|
### VisualStudioCode ###
|
||||||
*.vsix
|
|
||||||
.history
|
|
||||||
.ionide
|
|
||||||
.vscode/*
|
.vscode/*
|
||||||
.history/
|
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
!.vscode/*.code-snippets
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
# agents
|
# Local History for Visual Studio Code
|
||||||
.claude/
|
.history/
|
||||||
.context/
|
|
||||||
PLAN.md
|
|
||||||
TASK.md
|
|
||||||
|
|
||||||
# rick - keep panda_tici standalone
|
# Built Visual Studio Code Extensions
|
||||||
panda_tici/
|
*.vsix
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
.ionide
|
||||||
|
.vs
|
||||||
|
|||||||
Vendored
-3
@@ -4,8 +4,5 @@
|
|||||||
"ms-vscode.cpptools",
|
"ms-vscode.cpptools",
|
||||||
"elagil.pre-commit-helper",
|
"elagil.pre-commit-helper",
|
||||||
"charliermarsh.ruff",
|
"charliermarsh.ruff",
|
||||||
"JamiTech.simply-blame",
|
|
||||||
"k--kato.intellij-idea-keybindings",
|
|
||||||
"trinm1709.dracula-theme-from-intellij"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-40
@@ -23,11 +23,6 @@
|
|||||||
"id": "args",
|
"id": "args",
|
||||||
"description": "Arguments to pass to the process",
|
"description": "Arguments to pass to the process",
|
||||||
"type": "promptString"
|
"type": "promptString"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "replayArg",
|
|
||||||
"type": "promptString",
|
|
||||||
"description": "Enter route or segment to replay."
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configurations": [
|
"configurations": [
|
||||||
@@ -45,41 +40,7 @@
|
|||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/${input:cpp_process}",
|
"program": "${workspaceFolder}/${input:cpp_process}",
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}",
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Attach LLDB to Replay drive",
|
|
||||||
"type": "lldb",
|
|
||||||
"request": "attach",
|
|
||||||
"pid": "${command:pickMyProcess}",
|
|
||||||
"initCommands": [
|
|
||||||
"script import time; time.sleep(3)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Replay drive",
|
|
||||||
"type": "debugpy",
|
|
||||||
"request": "launch",
|
|
||||||
"program": "${workspaceFolder}/opendbc/safety/tests/safety_replay/replay_drive.py",
|
|
||||||
"args": [
|
|
||||||
"${input:replayArg}"
|
|
||||||
],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"justMyCode": false,
|
|
||||||
"env": {
|
|
||||||
"PYTHONPATH": "${workspaceFolder}"
|
|
||||||
},
|
|
||||||
"subProcess": true,
|
|
||||||
"stopOnEntry": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"compounds": [
|
|
||||||
{
|
|
||||||
"name": "Replay drive + Safety LLDB",
|
|
||||||
"configurations": [
|
|
||||||
"Replay drive",
|
|
||||||
"Attach LLDB to Replay drive"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Vendored
+1
-15
@@ -3,24 +3,10 @@
|
|||||||
"editor.insertSpaces": true,
|
"editor.insertSpaces": true,
|
||||||
"editor.renderWhitespace": "trailing",
|
"editor.renderWhitespace": "trailing",
|
||||||
"files.trimTrailingWhitespace": true,
|
"files.trimTrailingWhitespace": true,
|
||||||
"terminal.integrated.defaultProfile.linux": "dragonpilot",
|
|
||||||
"terminal.integrated.profiles.linux": {
|
|
||||||
"dragonpilot": {
|
|
||||||
"path": "bash",
|
|
||||||
"args": ["-c", "distrobox enter dp"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/.git": true,
|
"**/.git": true,
|
||||||
"**/.venv": true,
|
"**/.venv": true,
|
||||||
"**/__pycache__": true,
|
"**/__pycache__": true
|
||||||
"msgq_repo/": true,
|
|
||||||
"rednose/": true,
|
|
||||||
"rednose_repo/": true,
|
|
||||||
"openpilot/": true,
|
|
||||||
"teleoprtc_repo/": true,
|
|
||||||
"tinygrad/": true,
|
|
||||||
"tinygrad_repo/": true
|
|
||||||
},
|
},
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"**/.git": true,
|
"**/.git": true,
|
||||||
|
|||||||
-140
@@ -1,140 +0,0 @@
|
|||||||
# ALKA (Always-on Lane Keeping Assist) Design v3
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
ALKA enables lateral control (steering) when ACC Main is ON, without requiring cruise to be engaged. This allows lane keeping assist to function independently of longitudinal control.
|
|
||||||
|
|
||||||
**Simplified Behavior (v3):**
|
|
||||||
- All brands use direct tracking: `lkas_on = acc_main_on`
|
|
||||||
- No button/toggle tracking (removed TJA, LKAS button, LKAS HUD)
|
|
||||||
- ACC Main ON = ALKA enabled, ACC Main OFF = ALKA disabled
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Per-Brand Summary
|
|
||||||
|
|
||||||
| Brand | Status | ACC Main Source | Notes |
|
|
||||||
|-------|--------|-----------------|-------|
|
|
||||||
| Body | Disabled | - | No steering capability |
|
|
||||||
| Chrysler | Disabled | - | Needs special handling |
|
|
||||||
| Ford | Enabled | EngBrakeData (0x165) CcStat | |
|
|
||||||
| GM | Disabled | - | No ACC Main signal |
|
|
||||||
| Honda Nidec | Enabled | SCM_FEEDBACK (0x326) MAIN_ON | |
|
|
||||||
| Honda Bosch | Enabled | SCM_FEEDBACK (0x326) MAIN_ON | |
|
|
||||||
| Hyundai | Enabled | SCC11 (0x420) bit 0 | |
|
|
||||||
| Hyundai CAN-FD | Enabled | SCC_CONTROL (0x1A0) bit 66 | |
|
|
||||||
| Hyundai Legacy | Enabled | SCC11 (0x420) bit 0 | |
|
|
||||||
| Mazda | Enabled | CRZ_CTRL (0x21C) bit 17 | |
|
|
||||||
| Nissan | Enabled | CRUISE_THROTTLE (0x239) bit 17 | |
|
|
||||||
| PSA | Disabled | - | Not implemented |
|
|
||||||
| Rivian | Disabled | - | Different architecture |
|
|
||||||
| Subaru | Enabled | CruiseControl (0x240) bit 40 | |
|
|
||||||
| Subaru Preglobal | Enabled | CruiseControl (0x144) bit 48 | |
|
|
||||||
| Tesla | Disabled | - | Different architecture |
|
|
||||||
| Toyota | Enabled | PCM_CRUISE_2 (0x1D3) bit 15 | |
|
|
||||||
| Toyota (UNSUPPORTED_DSU) | Enabled | DSU_CRUISE (0x365) bit 0 | |
|
|
||||||
| VW MQB | Enabled | TSK_06 TSK_Status (>=2) | |
|
|
||||||
| VW PQ | Enabled | Motor_5 (0x480) bit 50 (long) | |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Permission Model
|
|
||||||
|
|
||||||
Lateral control requires checks at both layers. Normal path uses `controls_allowed`, ALKA path uses additional checks.
|
|
||||||
|
|
||||||
| Check | Panda | openpilot | Notes |
|
|
||||||
|-------|:-----:|:---------:|-------|
|
|
||||||
| **Normal Path** |
|
|
||||||
| `controls_allowed` (cruise engaged) | ✓ | ✓ | Either this OR ALKA path |
|
|
||||||
| **ALKA Path** |
|
|
||||||
| `alka_allowed` (brand supports) | ✓ | ✓ | Set per brand in safety init |
|
|
||||||
| `ALT_EXP_ALKA` (user enabled) | ✓ | ✓ | alternativeExperience flag |
|
|
||||||
| `lkas_on` (ACC Main ON) | ✓ | ✓ | Tracked via CAN messages |
|
|
||||||
| `vehicle_moving` / `!standstill` | ✓ | ✓ | |
|
|
||||||
| **openpilot Additional** |
|
|
||||||
| `gear_ok` (not P/N/R) | ✗ | ✓ | Python layer only |
|
|
||||||
| `calibrated` | ✗ | ✓ | Python layer only |
|
|
||||||
| `seatbelt latched` | ✗ | ✓ | Python layer only |
|
|
||||||
| `doors closed` | ✗ | ✓ | Python layer only |
|
|
||||||
| `!steerFaultTemporary` | ✗ | ✓ | Python layer only |
|
|
||||||
| `!steerFaultPermanent` | ✗ | ✓ | Python layer only |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────────┐
|
|
||||||
│ CAN Bus │
|
|
||||||
└─────────────────────────────────────────────────────────────────────┘
|
|
||||||
│ │
|
|
||||||
▼ ▼
|
|
||||||
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
|
|
||||||
│ Safety Layer (panda C code) │ │ Python Layer │
|
|
||||||
│ │ │ │
|
|
||||||
│ rx_hook: │ │ carstate.py: │
|
|
||||||
│ - Parse ACC Main signal │ │ - Parse cruiseState.available │
|
|
||||||
│ - Set lkas_on = acc_main_on │ │ - Set self.lkas_on │
|
|
||||||
│ │ │ │
|
|
||||||
│ lat_control_allowed(): │ └─────────────┬───────────────────┘
|
|
||||||
│ - Check lkas_on + other flags │ │
|
|
||||||
│ - Gate steering commands │ ▼
|
|
||||||
└─────────────────────────────────┘ ┌─────────────────────────────────┐
|
|
||||||
│ card.py: │
|
|
||||||
│ - Publish carStateExt.lkasOn │
|
|
||||||
└─────────────┬───────────────────┘
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────┐
|
|
||||||
│ controlsd.py: │
|
|
||||||
│ - Read carStateExt.lkasOn │
|
|
||||||
│ - Check ALKA conditions │
|
|
||||||
│ - Set CC.latActive │
|
|
||||||
└─────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Files
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `custom.capnp` | Defines `CarStateExt` struct with `lkasOn` field |
|
|
||||||
| `log.capnp` | Includes `carStateExt` in event union |
|
|
||||||
| `interfaces.py` | Defines `self.lkas_on = False` default in `CarStateBase` |
|
|
||||||
| `carstate.py` (per brand) | Tracks `lkas_on` based on ACC Main |
|
|
||||||
| `card.py` | Publishes `carStateExt.lkasOn` from `CI.CS.lkas_on` |
|
|
||||||
| `controlsd.py` | Reads `carStateExt.lkasOn` to determine `alka_active` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ACC Main Tracking
|
|
||||||
|
|
||||||
All brands use simple direct tracking:
|
|
||||||
|
|
||||||
```c
|
|
||||||
// Panda (C code)
|
|
||||||
if (alka_allowed && (alternative_experience & ALT_EXP_ALKA)) {
|
|
||||||
lkas_on = acc_main_on; // or GET_BIT(msg, bit_position)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Python carstate.py
|
|
||||||
self.lkas_on = ret.cruiseState.available
|
|
||||||
```
|
|
||||||
|
|
||||||
This guard ensures:
|
|
||||||
1. Brand supports ALKA (`alka_allowed`)
|
|
||||||
2. User enabled ALKA (`ALT_EXP_ALKA`)
|
|
||||||
|
|
||||||
Without both conditions, no ACC Main tracking occurs, and ALKA remains disabled.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
Safety tests verify:
|
|
||||||
- `alka_allowed` flag set correctly per brand
|
|
||||||
- ACC Main tracking updates `lkas_on` directly
|
|
||||||
- `lat_control_allowed()` returns true only when all conditions met
|
|
||||||
- Steering TX blocked when ALKA conditions not met
|
|
||||||
- Bus routing variants (camera_scc, unsupported_dsu)
|
|
||||||
+5
-30
@@ -1,38 +1,13 @@
|
|||||||
FROM ubuntu:24.04
|
FROM ghcr.io/commaai/openpilot-base:latest
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV OPENPILOT_PATH=/home/batman/openpilot
|
||||||
RUN apt-get update && \
|
ENV PYTHONPATH=${OPENPILOT_PATH}:${PYTHONPATH}
|
||||||
apt-get install -y --no-install-recommends sudo tzdata locales && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
|
||||||
ENV LANG=en_US.UTF-8
|
|
||||||
ENV LANGUAGE=en_US:en
|
|
||||||
ENV LC_ALL=en_US.UTF-8
|
|
||||||
|
|
||||||
ENV NVIDIA_VISIBLE_DEVICES=all
|
|
||||||
ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute
|
|
||||||
|
|
||||||
ARG USER=batman
|
|
||||||
ARG USER_UID=1001
|
|
||||||
RUN useradd -m -s /bin/bash -u $USER_UID $USER
|
|
||||||
RUN usermod -aG sudo $USER
|
|
||||||
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
|
||||||
USER $USER
|
|
||||||
|
|
||||||
ENV OPENPILOT_PATH=/home/$USER/openpilot
|
|
||||||
RUN mkdir -p ${OPENPILOT_PATH}
|
RUN mkdir -p ${OPENPILOT_PATH}
|
||||||
WORKDIR ${OPENPILOT_PATH}
|
WORKDIR ${OPENPILOT_PATH}
|
||||||
|
|
||||||
COPY --chown=$USER . ${OPENPILOT_PATH}/
|
COPY . ${OPENPILOT_PATH}/
|
||||||
|
|
||||||
ENV UV_BIN="/home/$USER/.local/bin/"
|
RUN scons --cache-readonly -j$(nproc)
|
||||||
ENV VIRTUAL_ENV=${OPENPILOT_PATH}/.venv
|
|
||||||
ENV PATH="$UV_BIN:$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
RUN tools/setup_dependencies.sh && \
|
|
||||||
sudo rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
USER root
|
|
||||||
RUN git config --global --add safe.directory '*'
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends sudo tzdata locales ssh pulseaudio xvfb x11-xserver-utils gnome-screenshot python3-tk python3-dev && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||||
|
ENV LANG=en_US.UTF-8
|
||||||
|
ENV LANGUAGE=en_US:en
|
||||||
|
ENV LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
|
COPY tools/install_ubuntu_dependencies.sh /tmp/tools/
|
||||||
|
RUN /tmp/tools/install_ubuntu_dependencies.sh && \
|
||||||
|
rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||||
|
cd /usr/lib/gcc/arm-none-eabi/* && \
|
||||||
|
rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp
|
||||||
|
|
||||||
|
# Add OpenCL
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
apt-utils \
|
||||||
|
alien \
|
||||||
|
unzip \
|
||||||
|
tar \
|
||||||
|
curl \
|
||||||
|
xz-utils \
|
||||||
|
dbus \
|
||||||
|
gcc-arm-none-eabi \
|
||||||
|
tmux \
|
||||||
|
vim \
|
||||||
|
libx11-6 \
|
||||||
|
wget \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir -p /tmp/opencl-driver-intel && \
|
||||||
|
cd /tmp/opencl-driver-intel && \
|
||||||
|
wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \
|
||||||
|
wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \
|
||||||
|
mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \
|
||||||
|
cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \
|
||||||
|
tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \
|
||||||
|
mkdir -p /etc/OpenCL/vendors && \
|
||||||
|
echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \
|
||||||
|
cd /opt/intel && \
|
||||||
|
tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \
|
||||||
|
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||||
|
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||||
|
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||||
|
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||||
|
mkdir -p /etc/ld.so.conf.d && \
|
||||||
|
echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \
|
||||||
|
ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \
|
||||||
|
cd / && \
|
||||||
|
rm -rf /tmp/opencl-driver-intel
|
||||||
|
|
||||||
|
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||||
|
ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute
|
||||||
|
ENV QTWEBENGINE_DISABLE_SANDBOX=1
|
||||||
|
|
||||||
|
RUN dbus-uuidgen > /etc/machine-id
|
||||||
|
|
||||||
|
ARG USER=batman
|
||||||
|
ARG USER_UID=1001
|
||||||
|
RUN useradd -m -s /bin/bash -u $USER_UID $USER
|
||||||
|
RUN usermod -aG sudo $USER
|
||||||
|
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||||
|
USER $USER
|
||||||
|
|
||||||
|
COPY --chown=$USER pyproject.toml uv.lock /home/$USER
|
||||||
|
COPY --chown=$USER tools/install_python_dependencies.sh /home/$USER/tools/
|
||||||
|
|
||||||
|
ENV VIRTUAL_ENV=/home/$USER/.venv
|
||||||
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
RUN cd /home/$USER && \
|
||||||
|
tools/install_python_dependencies.sh && \
|
||||||
|
rm -rf tools/ pyproject.toml uv.lock .cache
|
||||||
|
|
||||||
|
USER root
|
||||||
|
RUN sudo git config --global --add safe.directory /tmp/openpilot
|
||||||
Vendored
+38
-17
@@ -22,7 +22,7 @@ shopt -s huponexit # kill all child processes when the shell exits
|
|||||||
|
|
||||||
export CI=1
|
export CI=1
|
||||||
export PYTHONWARNINGS=error
|
export PYTHONWARNINGS=error
|
||||||
#export LOGPRINT=debug # this has gotten too spammy...
|
export LOGPRINT=debug
|
||||||
export TEST_DIR=${env.TEST_DIR}
|
export TEST_DIR=${env.TEST_DIR}
|
||||||
export SOURCE_DIR=${env.SOURCE_DIR}
|
export SOURCE_DIR=${env.SOURCE_DIR}
|
||||||
export GIT_BRANCH=${env.GIT_BRANCH}
|
export GIT_BRANCH=${env.GIT_BRANCH}
|
||||||
@@ -167,7 +167,7 @@ node {
|
|||||||
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
||||||
|
|
||||||
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
|
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
|
||||||
'release-tici', 'release-tizi', 'release-tizi-staging', 'testing-closet*', 'hotfix-*']
|
'testing-closet*', 'hotfix-*']
|
||||||
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
||||||
|
|
||||||
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
||||||
@@ -178,20 +178,20 @@ node {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (env.BRANCH_NAME == 'devel-staging') {
|
if (env.BRANCH_NAME == 'devel-staging') {
|
||||||
deviceStage("build release-tizi-staging", "tizi-needs-can", [], [
|
deviceStage("build release3-staging", "tici-needs-can", [], [
|
||||||
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh"),
|
step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env.BRANCH_NAME == '__nightly') {
|
if (env.BRANCH_NAME == '__nightly') {
|
||||||
parallel (
|
parallel (
|
||||||
'nightly': {
|
'nightly': {
|
||||||
deviceStage("build nightly", "tizi-needs-can", [], [
|
deviceStage("build nightly", "tici-needs-can", [], [
|
||||||
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'nightly-dev': {
|
'nightly-dev': {
|
||||||
deviceStage("build nightly-dev", "tizi-needs-can", [], [
|
deviceStage("build nightly-dev", "tici-needs-can", [], [
|
||||||
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@@ -200,43 +200,63 @@ node {
|
|||||||
|
|
||||||
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
||||||
parallel (
|
parallel (
|
||||||
|
// tici tests
|
||||||
'onroad tests': {
|
'onroad tests': {
|
||||||
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
|
deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [
|
||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
step("check dirty", "release/check-dirty.sh"),
|
step("check dirty", "release/check-dirty.sh"),
|
||||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'HW + Unit Tests': {
|
'HW + Unit Tests': {
|
||||||
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
|
deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
|
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||||
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
||||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
||||||
|
step("test pigeond", "pytest system/ubloxd/tests/test_pigeond.py", [diffPaths: ["system/ubloxd/"]]),
|
||||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
step("test manager", "pytest system/manager/test/test_manager.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'camerad OX03C10': {
|
'loopback': {
|
||||||
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
|
||||||
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
|
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
'camerad AR0231': {
|
||||||
|
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"),
|
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
|
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
'camerad OX03C10': {
|
||||||
|
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
|
||||||
|
step("build", "cd system/manager && ./build.py"),
|
||||||
|
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||||
|
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'camerad OS04C10': {
|
'camerad OS04C10': {
|
||||||
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
|
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"),
|
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
|
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'sensord': {
|
'sensord': {
|
||||||
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
|
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
|
||||||
|
step("build", "cd system/manager && ./build.py"),
|
||||||
|
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||||
|
])
|
||||||
|
deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'replay': {
|
'replay': {
|
||||||
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
|
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||||
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||||
])
|
])
|
||||||
@@ -244,8 +264,9 @@ node {
|
|||||||
'tizi': {
|
'tizi': {
|
||||||
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||||
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||||
|
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||||
step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]),
|
step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]),
|
||||||
])
|
])
|
||||||
|
|||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
Copyright (c) 2019, Rick Lan
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
|
||||||
for non-commercial purposes only, subject to the following conditions:
|
|
||||||
|
|
||||||
- The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
- Commercial use (e.g. use in a product, service, or activity intended to
|
|
||||||
generate revenue) is prohibited without explicit written permission from
|
|
||||||
the copyright holder.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
---
|
|
||||||
Copyright (c) 2018, Comma.ai, Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
@@ -1,74 +1,143 @@
|
|||||||

|
## ⚠️ 법적 안내 / Legal Notice
|
||||||
|
|
||||||
[Read this in English](README_EN.md)
|
🚫 대한민국 자동차관리법 개정안에 따라, 본 소프트웨어를 실제 차량에 장착하거나 주행에 사용하는 것은 법률에 위배될 수 있습니다.
|
||||||
|
이 저장소에 있는 모든 소프트웨어는 **연구, 실험, 시뮬레이션 목적**으로만 제공됩니다.
|
||||||
|
개발자는 본 소프트웨어의 실제 사용으로 인해 발생하는 **모든 법적 책임을 지지 않습니다.**
|
||||||
|
|
||||||
# **🐲 dragonpilot - 賦予您的愛車「龍」之魂**
|
In accordance with the amended **Korean Motor Vehicle Management Act** (effective August 14, 2025),
|
||||||
|
**modifying or installing software that affects the safe operation of a vehicle** is prohibited.
|
||||||
|
|
||||||
**我們與您一同翱翔於更智慧、更貼心的駕駛旅程。**
|
This software is provided **for research and educational use only**.
|
||||||
|
The developer does **not take any responsibility** for real-world installation or usage.
|
||||||
|
|
||||||
## **👋 嘿, 朋友,歡迎您的到來!**
|
**Carrotpilot에서 사용하는 차량(현대,기아)에 따라 Harness가 다릅니다..**
|
||||||
|
- CAN통신차량: Comma 정품 Harness, Camera에 연결
|
||||||
|
- CANFD-일반차량: Comma정품 Harness, Camera에 연결
|
||||||
|
- CANFD-HDA2(ADAS Module 장착)차량: 사제 Harness, ADAS Module에 연결
|
||||||
|
- 모든차량이 지원되는것이 아니니 반드시 확인바랍니다.
|
||||||
|
|
||||||
`dragonpilot` 誕生於 2019 年,由三位早期的 openpilot 華人玩家共同創立。初衷很簡單:為廣大的華人用戶、玩家們提供一個友善的交流環境、更簡便的設定協助,並加入更多適合在地使用的貼心功能。
|
**In CarrotPilot, the harness used varies depending on the vehicle(HKG):**
|
||||||
|
* **CAN vehicles** Use the official Comma harness, connected to the camera.
|
||||||
|
* **CAN FD (standard) vehicles** Use the official Comma harness, connected to the camera.
|
||||||
|
* **CAN FD vehicles with HDA2 (ADAS module equipped)** Use an aftermarket harness, connected to the ADAS module.
|
||||||
|
* Please note that not all vehicles are supported.
|
||||||
|
|
||||||
我們深知在地化的重要性,特別是語言的親切感。因此,我們率先導入了完整的中文介面,讓 `dragonpilot` 迅速在華語地區累積了口碑,也讓華人的使用者數量在全球名列前茅。這份來自在地的支持,是我們持續前進的最大動力。
|
|
||||||
|
|
||||||
我們以功能強大的 [openpilot](https://github.com/commaai/openpilot) 為基礎——這套據美國消費者報告評測優於市售車方案的開源輔助駕駛系統——融入了更多在地化的巧思與客製化的溫度,希望能打造出最符合您需求的駕駛夥伴。(您也可以參考我們 repo 中保留的 [openpilot 原始說明檔案](README_OPENPILOT.md))
|
<div align="center" style="text-align: center;">
|
||||||
|
|
||||||
取名 `dragonpilot`,是因為我們希望它能像神話中的「龍」一樣,既強大又充滿智慧,為您的行車安全保駕護航。龍,在我們華人文化中,更是吉祥與力量的象徵,也代表著我們的根源與驕傲。
|
<h1>carrotpilot</h1>
|
||||||
|
|
||||||
## **✨ dragonpilot 的里程碑**
|
<h3>
|
||||||
|
<a href="https://g4iwnl.gitbook.io/carrotpilot">Manual</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
我們不僅保留了 openpilot 的核心優勢,更達成了許多從社群回饋中誕生的里程碑,這些是我們引以為傲的足跡:
|

|
||||||
|
|
||||||
* **🚘 全時置中車道維持 (ALKA)**
|
|
||||||
|
|
||||||
這不只是一個功能,更是 `dragonpilot` 的哲學。我們最早於 [0.6.2 版本](https://github.com/dragonpilot-community/dragonpilot/blob/2861467183d62151024320447ba04d18fc3fe1e6/selfdrive/car/toyota/carstate.py#L199) 時便實現了這個功能,其開發歷程始於 2017 Lexus IS300h,接著擴展至 Toyota 全車系,並逐步延伸到其他支援的品牌。它能溫柔地輔助您,讓車輛始終穩定地保持在車道中央,提供一份額外的安心與從容。
|
<div align="center" style="text-align: center;">
|
||||||
|
|
||||||
* **🌐 率先導入多國語言介面**
|
<h1>openpilot</h1>
|
||||||
|
|
||||||
在官方 openpilot 還未支援前,我們便已將多國語言介面實現。`dragonpilot` 完整支援繁體中文、簡體中文與英文,讓操作毫無隔閡。
|
<p>
|
||||||
|
<b>openpilot is an operating system for robotics.</b>
|
||||||
|
<br>
|
||||||
|
Currently, it upgrades the driver assistance system in 275+ supported cars.
|
||||||
|
</p>
|
||||||
|
|
||||||
* **💻 唯一同時支援多硬體平台**
|
<h3>
|
||||||
|
<a href="https://docs.comma.ai">Docs</a>
|
||||||
|
<span> · </span>
|
||||||
|
<a href="https://docs.comma.ai/contributing/roadmap/">Roadmap</a>
|
||||||
|
<span> · </span>
|
||||||
|
<a href="https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md">Contribute</a>
|
||||||
|
<span> · </span>
|
||||||
|
<a href="https://discord.comma.ai">Community</a>
|
||||||
|
<span> · </span>
|
||||||
|
<a href="https://comma.ai/shop">Try it on a comma 3X</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
我們是唯一曾致力於讓專案同時兼容 EON、comma two、comma 3 與 Jetson 平台的社群分支,這份努力是為了服務最廣大的玩家社群。
|
Quick start: `bash <(curl -fsSL openpilot.comma.ai)`
|
||||||
此外,在 comma.ai 團隊於 0.10.0 版本宣布停止支持 comma 3 後,我們仍是唯一一個完整同時支援 comma 3、comma 3X 以及 O3、O3L、O3XL(O3 系列為副廠硬體)的社群分支。
|
|
||||||
|
|
||||||
* **📜 曾榮獲官方認證第一大分支**
|

|
||||||
|
[](https://codecov.io/gh/commaai/openpilot)
|
||||||
|
[](LICENSE)
|
||||||
|
[](https://x.com/comma_ai)
|
||||||
|
[](https://discord.comma.ai)
|
||||||
|
|
||||||
基於活躍的社群與功能創新,`dragonpilot` 曾一度成長為 comma ai 官方認證的第一大 openpilot 分支,這份榮耀屬於每一位參與者。
|
</div>
|
||||||
|
|
||||||
## **🧑💻 設計理念 - 少即是多 (Less is More)**
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://youtu.be/NmBfgOanCyk" title="Video By Greer Viau"><img src="https://github.com/commaai/openpilot/assets/8762862/2f7112ae-f748-4f39-b617-fabd689c3772"></a></td>
|
||||||
|
<td><a href="https://youtu.be/VHKyqZ7t8Gw" title="Video By Logan LeGrand"><img src="https://github.com/commaai/openpilot/assets/8762862/92351544-2833-40d7-9e0b-7ef7ae37ec4c"></a></td>
|
||||||
|
<td><a href="https://youtu.be/SUIZYzxtMQs" title="A drive to Taco Bell"><img src="https://github.com/commaai/openpilot/assets/8762862/05ceefc5-2628-439c-a9b2-89ce77dc6f63"></a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
隨著 openpilot 的 AI 模型日益強大,許多過去需要手動微調的功能,現在都已能透過更先進的模型來實現。因此,我們現在的開發重心回歸到 **「最小化修改」(minimal changes)** 的核心原則上。
|
|
||||||
|
|
||||||
我們的目標是為您提供最純粹、最接近官方的 openpilot 駕駛感受,同時保留 `dragonpilot` 那些經過時間考驗、最受社群喜愛的經典功能。我們相信,在強大的 AI 基礎上,簡潔即是力量。
|
Using openpilot in a car
|
||||||
|
------
|
||||||
|
|
||||||
## **🛠️ 硬件的足跡 - 一路走來的夥伴們**
|
To use openpilot in a car, you need four things:
|
||||||
|
1. **Supported Device:** a comma 3/3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x).
|
||||||
|
2. **Software:** The setup procedure for the comma 3/3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version.
|
||||||
|
3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md).
|
||||||
|
4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3/3X to your car.
|
||||||
|
|
||||||
從最早的 **EON**,到官方的 **comma two / three (C2/C3/C3X)**,再到社群中各式各樣充滿智慧的**副廠機 (如 C1.5, O2, O3, O3L, O3XL 等)**,甚至我們也曾探索過在 [**Jetson Xavier NX**](https://github.com/eFiniLan/xnxpilot) 上的可能性。
|
We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play.
|
||||||
|
|
||||||
目前最新版本主要支援: comma3 / 3X 以及 O3 / O3L / O3XL 等社群硬體。
|
### Branches
|
||||||
針對 EON / C1.5 / C2 等舊款硬體,最後支援的版本位於 [d2 分支](https://github.com/dragonpilot-community/dragonpilot/tree/d2)。
|
| branch | URL | description |
|
||||||
無論您手上是哪一款設備,都代表著您對開源駕駛輔助的一份熱情。
|
|------------------|----------------------------------------|-------------------------------------------------------------------------------------|
|
||||||
|
| `release3` | openpilot.comma.ai | This is openpilot's release branch. |
|
||||||
|
| `release3-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. |
|
||||||
|
| `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. |
|
||||||
|
| `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. |
|
||||||
|
|
||||||
## **🫂 加入我們,成為「尋龍者」的一份子**
|
To start developing openpilot
|
||||||
|
------
|
||||||
|
|
||||||
`dragonpilot` 的成長,離不開每一位使用者的貢獻與回饋。我們是一個以**公開、透明**為原則的溫暖社群,希望在這裡能與所有對 openpilot / dragonpilot 有興趣的用戶分享、交流開發與使用上的經驗。
|
openpilot is developed by [comma](https://comma.ai/) and by users like you. We welcome both pull requests and issues on [GitHub](http://github.com/commaai/openpilot).
|
||||||
|
|
||||||
[**歡迎加入我們的 Facebook 社團進行交流!**](https://www.facebook.com/groups/930190251238639)
|
* Join the [community Discord](https://discord.comma.ai)
|
||||||
|
* Check out [the contributing docs](docs/CONTRIBUTING.md)
|
||||||
|
* Check out the [openpilot tools](tools/)
|
||||||
|
* Read about the [development workflow](docs/WORKFLOW.md)
|
||||||
|
* Code documentation lives at https://docs.comma.ai
|
||||||
|
* Information about running openpilot lives on the [community wiki](https://github.com/commaai/openpilot/wiki)
|
||||||
|
|
||||||
## **❤️ 特別感謝**
|
Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs#open-positions) and offers lots of [bounties](https://comma.ai/bounties) for external contributors.
|
||||||
|
|
||||||
`dragonpilot` 從創立至今,從未打算透過 Patreon 等平台進行任何形式的募資。我們的初衷是建立一個讓大家能一起學習、一起成長的社群。It's all about fun, not money.
|
Safety and Testing
|
||||||
|
----
|
||||||
|
|
||||||
然而,我們仍要對那些自發性支持本專案的朋友們,致上最誠摯的感謝。正是因為有您們的鼓勵,我們才有更大的動力持續前進。
|
* openpilot observes [ISO26262](https://en.wikipedia.org/wiki/ISO_26262) guidelines, see [SAFETY.md](docs/SAFETY.md) for more details.
|
||||||
|
* openpilot has software-in-the-loop [tests](.github/workflows/selfdrive_tests.yaml) that run on every commit.
|
||||||
|
* The code enforcing the safety model lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details.
|
||||||
|
* panda has software-in-the-loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety).
|
||||||
|
* Internally, we have a hardware-in-the-loop Jenkins test suite that builds and unit tests the various processes.
|
||||||
|
* panda has additional hardware-in-the-loop [tests](https://github.com/commaai/panda/blob/master/Jenkinsfile).
|
||||||
|
* We run the latest openpilot in a testing closet containing 10 comma devices continuously replaying routes.
|
||||||
|
|
||||||
[**我們的贊助者名單**](SPONSORS.md)
|
Licensing
|
||||||
|
------
|
||||||
|
|
||||||
### **安全聲明**
|
openpilot is released under the MIT license. Some parts of the software are released under other licenses as specified.
|
||||||
|
|
||||||
`dragonpilot` 是一種駕駛**輔助**系統,並非全自動駕駛。它旨在減輕您的駕駛疲勞,提升行車安全,但駕駛人仍需時刻保持專注,並隨時準備接管車輛。請務必遵守您所在地區的交通法規。
|
Any user of this software shall indemnify and hold harmless Comma.ai, Inc. and its directors, officers, employees, agents, stockholders, affiliates, subcontractors and customers from and against all allegations, claims, actions, suits, demands, damages, liabilities, obligations, losses, settlements, judgments, costs and expenses (including without limitation attorneys’ fees and costs) which arise out of, relate to or result from any use of this software by user.
|
||||||
|
|
||||||
**最後,再次感謝您的到來。**
|
**THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH PURPOSES ONLY. THIS IS NOT A PRODUCT.
|
||||||
|
YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS.
|
||||||
|
NO WARRANTY EXPRESSED OR IMPLIED.**
|
||||||
|
|
||||||
**期待與您一同在智慧駕駛的道路上,乘「龍」而行!**
|
User Data and comma Account
|
||||||
|
------
|
||||||
|
|
||||||
|
By default, openpilot uploads the driving data to our servers. You can also access your data through [comma connect](https://connect.comma.ai/). We use your data to train better models and improve openpilot for everyone.
|
||||||
|
|
||||||
|
openpilot is open source software: the user is free to disable data collection if they wish to do so.
|
||||||
|
|
||||||
|
openpilot logs the road-facing cameras, CAN, GPS, IMU, magnetometer, thermal sensors, crashes, and operating system logs.
|
||||||
|
The driver-facing camera is only logged if you explicitly opt-in in settings. The microphone is not recorded.
|
||||||
|
|
||||||
|
By using openpilot, you agree to [our Privacy Policy](https://comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data.
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||

|
|
||||||
|
|
||||||
[Read this in Chinese](README.md)
|
|
||||||
|
|
||||||
# **🐲 dragonpilot - Bringing the Spirit of the Dragon to Your Car**
|
|
||||||
|
|
||||||
**Join us on a smarter, more thoughtful driving journey.**
|
|
||||||
|
|
||||||
## **👋 Welcome, friend!**
|
|
||||||
|
|
||||||
`dragonpilot` was launched in 2019 by three early openpilot enthusiasts from the Chinese community. Our mission was simple: create a friendly space for users to share experiences, provide easier setup help, and add features tailored for local needs.
|
|
||||||
|
|
||||||
Localization has always been at the heart of what we do—starting with a fully Chinese interface. This made `dragonpilot` quickly popular in Chinese-speaking regions and helped our user base grow into one of the largest worldwide. That community support is what keeps us moving forward.
|
|
||||||
|
|
||||||
Built on top of the powerful [openpilot](https://github.com/commaai/openpilot)—an open-source driver assistance system rated by Consumer Reports as outperforming commercial offerings—we add localized refinements and user-focused features to create a driving companion that truly fits your needs. (You can also see the [original openpilot README](README_OPENPILOT.md) preserved in our repo.)
|
|
||||||
|
|
||||||
The name `dragonpilot` reflects our vision: like the dragon of mythology, it is strong and wise, guarding your safety on the road. In Chinese culture, the dragon is also a symbol of luck and strength, representing our roots and pride.
|
|
||||||
|
|
||||||
## **✨ Milestones**
|
|
||||||
|
|
||||||
Beyond carrying forward openpilot's core strengths, we've reached several milestones inspired by community feedback:
|
|
||||||
|
|
||||||
* **🚘 Always Lane Keep Assist (ALKA)**
|
|
||||||
|
|
||||||
More than a feature—it's part of the `dragonpilot` philosophy. Introduced as early as [version 0.6.2](https://github.com/dragonpilot-community/dragonpilot/blob/2861467183d62151024320447ba04d18fc3fe1e6/selfdrive/car/toyota/carstate.py#L199), first tested on a 2017 Lexus IS300h, then expanded to Toyota's lineup and beyond. ALKA helps keep your vehicle steadily centered, giving you extra confidence on the road.
|
|
||||||
|
|
||||||
* **🌐 First to add multilingual support**
|
|
||||||
|
|
||||||
Before openpilot officially supported it, we had already introduced multiple languages. `dragonpilot` fully supports Traditional Chinese, Simplified Chinese, and English.
|
|
||||||
|
|
||||||
* **💻 Only community fork to support multiple hardware platforms at once**
|
|
||||||
|
|
||||||
We uniquely worked to make the project run on EON, comma two, comma 3, and Jetson—serving the widest range of users possible.
|
|
||||||
Additionally, after the comma.ai team deprecated the comma 3 in version 0.10.0, we remain the only community fork to offer full, simultaneous support for the comma 3, comma 3X, and the O3, O3L, and O3XL (the O3 series being third-party hardware).
|
|
||||||
|
|
||||||
* **📜 Once recognized as the #1 openpilot fork**
|
|
||||||
|
|
||||||
Thanks to an active community and continuous innovation, `dragonpilot` was once the largest openpilot fork officially recognized by comma ai. This honor belongs to everyone who contributed.
|
|
||||||
|
|
||||||
## **🧑💻 Design Philosophy - Less is More**
|
|
||||||
|
|
||||||
As openpilot's AI grows stronger, many features that once required manual tuning are now handled by advanced models. That's why our focus has returned to **“minimal changes.”**
|
|
||||||
|
|
||||||
We aim to give you the purest, most official-like openpilot driving experience—while preserving `dragonpilot`'s classic, community-loved features. With a solid AI foundation, simplicity is strength.
|
|
||||||
|
|
||||||
## **🛠️ Hardware Journey**
|
|
||||||
|
|
||||||
From the early **EON**, to official devices like **comma two / three (C2/C3/C3X)**, to creative community builds (**C1.5, O2, O3, O3L, O3XL, etc.**), and even experiments with [**Jetson Xavier NX**](https://github.com/eFiniLan/xnxpilot).
|
|
||||||
|
|
||||||
Currently, the latest versions support: **comma3 / 3X** and community hardware like **O3 / O3L / O3XL**.
|
|
||||||
Older devices such as **EON / C1.5 / C2** are supported in the [d2 branch](https://github.com/dragonpilot-community/dragonpilot/tree/d2).
|
|
||||||
Whatever device you're on, it represents your passion for open-source driver assistance.
|
|
||||||
|
|
||||||
## **🫂 Join Us – Become a “Dragon Seeker”**
|
|
||||||
|
|
||||||
`dragonpilot` thrives thanks to every user's contributions and feedback. We're an open, transparent, and welcoming community where enthusiasts can share experiences with openpilot and `dragonpilot`.
|
|
||||||
|
|
||||||
[**Join our Facebook group here!**](https://www.facebook.com/groups/930190251238639)
|
|
||||||
|
|
||||||
## **❤️ Special Thanks**
|
|
||||||
|
|
||||||
Since day one, `dragonpilot` has never asked for funding through Patreon or similar platforms. Our vision is a community where everyone learns and grows together. It's about fun, not money.
|
|
||||||
|
|
||||||
That said, we're deeply grateful to those who voluntarily supported the project. Your encouragement keeps us motivated to keep building.
|
|
||||||
|
|
||||||
[**See our sponsors**](SPONSORS.md)
|
|
||||||
|
|
||||||
### **Safety Notice**
|
|
||||||
|
|
||||||
`dragonpilot` is a driver **assistance** system, not full self-driving. It reduces fatigue and improves safety, but you must remain alert and ready to take control at all times. Always follow your local traffic laws.
|
|
||||||
|
|
||||||
**Thanks again for being here.**
|
|
||||||
|
|
||||||
**We look forward to riding the “dragon” with you on the road to smarter driving!**
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
<div align="center" style="text-align: center;">
|
|
||||||
|
|
||||||
<h1>openpilot</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<b>openpilot is an operating system for robotics.</b>
|
|
||||||
<br>
|
|
||||||
Currently, it upgrades the driver assistance system in 300+ supported cars.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>
|
|
||||||
<a href="https://docs.comma.ai">Docs</a>
|
|
||||||
<span> · </span>
|
|
||||||
<a href="https://docs.comma.ai/contributing/roadmap/">Roadmap</a>
|
|
||||||
<span> · </span>
|
|
||||||
<a href="https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md">Contribute</a>
|
|
||||||
<span> · </span>
|
|
||||||
<a href="https://discord.comma.ai">Community</a>
|
|
||||||
<span> · </span>
|
|
||||||
<a href="https://comma.ai/shop">Try it on a comma 3X</a>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
Quick start: `bash <(curl -fsSL openpilot.comma.ai)`
|
|
||||||
|
|
||||||
[](https://github.com/commaai/openpilot/actions/workflows/tests.yaml)
|
|
||||||
[](LICENSE)
|
|
||||||
[](https://x.com/comma_ai)
|
|
||||||
[](https://discord.comma.ai)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><a href="https://youtu.be/NmBfgOanCyk" title="Video By Greer Viau"><img src="https://github.com/commaai/openpilot/assets/8762862/2f7112ae-f748-4f39-b617-fabd689c3772"></a></td>
|
|
||||||
<td><a href="https://youtu.be/VHKyqZ7t8Gw" title="Video By Logan LeGrand"><img src="https://github.com/commaai/openpilot/assets/8762862/92351544-2833-40d7-9e0b-7ef7ae37ec4c"></a></td>
|
|
||||||
<td><a href="https://youtu.be/SUIZYzxtMQs" title="A drive to Taco Bell"><img src="https://github.com/commaai/openpilot/assets/8762862/05ceefc5-2628-439c-a9b2-89ce77dc6f63"></a></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
Using openpilot in a car
|
|
||||||
------
|
|
||||||
|
|
||||||
To use openpilot in a car, you need four things:
|
|
||||||
1. **Supported Device:** a comma 3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x).
|
|
||||||
2. **Software:** The setup procedure for the comma 3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version.
|
|
||||||
3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md).
|
|
||||||
4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3X to your car.
|
|
||||||
|
|
||||||
We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play.
|
|
||||||
|
|
||||||
|
|
||||||
### Branches
|
|
||||||
|
|
||||||
Running `master` and other branches directly is supported, but it's recommended to run one of the following prebuilt branches:
|
|
||||||
|
|
||||||
| comma four branch | comma 3X branch | URL | description |
|
|
||||||
|------------------------|------------------------|----------------------------------------|-------------------------------------------------------------------------------------|
|
|
||||||
| `release-mici` | `release-tizi` | openpilot.comma.ai | This is openpilot's release branch. |
|
|
||||||
| `release-mici-staging` | `release-tizi-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. |
|
|
||||||
| `nightly` | `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. |
|
|
||||||
| `nightly-dev` | `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. |
|
|
||||||
|
|
||||||
To start developing openpilot
|
|
||||||
------
|
|
||||||
|
|
||||||
openpilot is developed by [comma](https://comma.ai/) and by users like you. We welcome both pull requests and issues on [GitHub](http://github.com/commaai/openpilot).
|
|
||||||
|
|
||||||
* Join the [community Discord](https://discord.comma.ai)
|
|
||||||
* Check out [the contributing docs](docs/CONTRIBUTING.md)
|
|
||||||
* Check out the [openpilot tools](tools/)
|
|
||||||
* Code documentation lives at https://docs.comma.ai
|
|
||||||
* Information about running openpilot lives on the [community wiki](https://github.com/commaai/openpilot/wiki)
|
|
||||||
|
|
||||||
Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs#open-positions) and offers lots of [bounties](https://comma.ai/bounties) for external contributors.
|
|
||||||
|
|
||||||
Safety and Testing
|
|
||||||
----
|
|
||||||
|
|
||||||
* openpilot observes [ISO26262](https://en.wikipedia.org/wiki/ISO_26262) guidelines, see [SAFETY.md](docs/SAFETY.md) for more details.
|
|
||||||
* openpilot has software-in-the-loop [tests](.github/workflows/tests.yaml) that run on every commit.
|
|
||||||
* The code enforcing the safety model lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details.
|
|
||||||
* panda has software-in-the-loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety).
|
|
||||||
* Internally, we have a hardware-in-the-loop Jenkins test suite that builds and unit tests the various processes.
|
|
||||||
* panda has additional hardware-in-the-loop [tests](https://github.com/commaai/panda/blob/master/Jenkinsfile).
|
|
||||||
* We run the latest openpilot in a testing closet containing 10 comma devices continuously replaying routes.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>MIT Licensed</summary>
|
|
||||||
|
|
||||||
openpilot is released under the MIT license. Some parts of the software are released under other licenses as specified.
|
|
||||||
|
|
||||||
Any user of this software shall indemnify and hold harmless Comma.ai, Inc. and its directors, officers, employees, agents, stockholders, affiliates, subcontractors and customers from and against all allegations, claims, actions, suits, demands, damages, liabilities, obligations, losses, settlements, judgments, costs and expenses (including without limitation attorneys’ fees and costs) which arise out of, relate to or result from any use of this software by user.
|
|
||||||
|
|
||||||
**THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH PURPOSES ONLY. THIS IS NOT A PRODUCT.
|
|
||||||
YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS.
|
|
||||||
NO WARRANTY EXPRESSED OR IMPLIED.**
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>User Data and comma Account</summary>
|
|
||||||
|
|
||||||
By default, openpilot uploads the driving data to our servers. You can also access your data through [comma connect](https://connect.comma.ai/). We use your data to train better models and improve openpilot for everyone.
|
|
||||||
|
|
||||||
openpilot is open source software: the user is free to disable data collection if they wish to do so.
|
|
||||||
|
|
||||||
openpilot logs the road-facing cameras, CAN, GPS, IMU, magnetometer, thermal sensors, crashes, and operating system logs.
|
|
||||||
The driver-facing camera and microphone are only logged if you explicitly opt-in in settings.
|
|
||||||
|
|
||||||
By using openpilot, you agree to [our Privacy Policy](https://comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data.
|
|
||||||
</details>
|
|
||||||
+81
-51
@@ -1,65 +1,95 @@
|
|||||||
Version 0.11.0 (2026-03-17)
|
Carrot2-v9 (2026-02-xx)
|
||||||
========================
|
========================
|
||||||
* New driving model #36798
|
* CD210 model
|
||||||
* Fully trained using a learned simulator
|
* web carrot_man (http://ip:7000)
|
||||||
* Improved longitudinal performance in Experimental mode
|
* fix speed based TF
|
||||||
* Reduce comma four standby power usage by 77% to 52 mW
|
|
||||||
* Kia K7 2017 support thanks to royjr!
|
|
||||||
* Lexus LS 2018 support thanks to Hacheoy!
|
|
||||||
|
|
||||||
Version 0.10.3 (2025-12-17)
|
Carrot2-v9 (2026-01-xx)
|
||||||
========================
|
========================
|
||||||
* New driving model #36249
|
* WMI model
|
||||||
* New temporal policy architecture
|
* Activate corner radar(HDA2)
|
||||||
* New on-policy training physics noise model
|
* fix Angle Steering(HKG car)
|
||||||
* New driver monitoring model #36409
|
* Keep blinker while LaneChange
|
||||||
* Trained on a new dataset, including comma four data
|
* Speed based TF adjustment
|
||||||
* Improved inter-process communication memory efficiency
|
* Sorento HEV 4WD(Niro HEV), Long bug fix.
|
||||||
|
|
||||||
Version 0.10.2 (2025-11-19)
|
Carrot2-v9 (2025-12-06)
|
||||||
========================
|
========================
|
||||||
* comma four support
|
* DarkSouls model
|
||||||
|
* fix Angle Steering(HKG car)
|
||||||
|
* fix LaneChange desire
|
||||||
|
|
||||||
Version 0.10.1 (2025-09-08)
|
Carrot2-v9 (2025-12-06)
|
||||||
========================
|
========================
|
||||||
* New driving model #36276
|
* PP(planplus) model
|
||||||
* World Model: removed global localization inputs
|
* fixDM
|
||||||
* World Model: 2x the number of parameters
|
* fix angle steering torque(HKG car)
|
||||||
* World Model: trained on 4x the number of segments
|
|
||||||
* VAE Compression Model: new architecture and training objective
|
|
||||||
* Driving Vision Model: trained on 4x the number of segments
|
|
||||||
* New Driver Monitoring model #36198
|
|
||||||
* Acura TLX 2021 support thanks to MVL!
|
|
||||||
* Honda City 2023 support thanks to vanillagorillaa and drFritz!
|
|
||||||
* Honda N-Box 2018 support thanks to miettal!
|
|
||||||
* Honda Odyssey 2021-25 support thanks to csouers and MVL!
|
|
||||||
* Honda Passport 2026 support thanks to vanillagorillaa and MVL!
|
|
||||||
|
|
||||||
Version 0.10.0 (2025-08-05)
|
Carrot2-v9 (2025-12-03)
|
||||||
|
========================
|
||||||
|
* ST model
|
||||||
|
* fix CasperEV FCA11
|
||||||
|
* fix DriverMonitoring alert (for USA)
|
||||||
|
* apply livePose
|
||||||
|
* update sensor code
|
||||||
|
|
||||||
|
Carrot2-v9 (2025-10-17)
|
||||||
|
========================
|
||||||
|
* Nuggets In Dijon model
|
||||||
|
* Setting: Adjust traffic stop distance
|
||||||
|
* liveLocationKalman -> livePose
|
||||||
|
* C3x lite
|
||||||
|
|
||||||
|
Carrot2-v9 (2025-09-21)
|
||||||
|
========================
|
||||||
|
* GWM Model
|
||||||
|
* Lead + 1 detect (전전차 감지기능)
|
||||||
|
* Improve radar vision matching
|
||||||
|
* Auto safe-mode on stopped vehicle detection
|
||||||
|
|
||||||
|
Carrot2-v9 (2025-09-xx)
|
||||||
|
========================
|
||||||
|
* TR16 Model
|
||||||
|
* RadarTrack Option:3 (Cutin Detect, vision fail detection)
|
||||||
|
* RdarTrack Option: 2 (always use SCC radar)
|
||||||
|
* Brake light (CANFD)
|
||||||
|
|
||||||
|
|
||||||
|
Carrot2-v9 (2025-08-12)
|
||||||
|
========================
|
||||||
|
* TombRaider16 v2 model.
|
||||||
|
* Remove ShowPathMode CruiseOff
|
||||||
|
* Add CancelButtonMode (0: Long Only, 1: Long + Lat)
|
||||||
|
* fix CASPER cruise button
|
||||||
|
* bugfix. Mapbox ATC with Waze
|
||||||
|
* ScreenRecorder 3 -> 20min
|
||||||
|
* fix RadarTrack processing
|
||||||
|
|
||||||
|
Carrot2-v9 (2025-08-04)
|
||||||
|
========================
|
||||||
|
* CruiseSpeedUnit
|
||||||
|
* fix RadarTrack processing
|
||||||
|
|
||||||
|
Carrot2-v9 (2025-08-03)
|
||||||
|
========================
|
||||||
|
* IONIQ9 support
|
||||||
|
* fix StockSCC bug.
|
||||||
|
* fix CPU usage(card: core 4->6)
|
||||||
|
|
||||||
|
Carrot2-v9 (2025-08-01)
|
||||||
|
========================
|
||||||
|
* SpaceLab V3 model
|
||||||
|
* RadarTracks support(CANFD)
|
||||||
|
* new CanParser
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.9.9 (2025-04-30)
|
||||||
========================
|
========================
|
||||||
* New driving model
|
* New driving model
|
||||||
* New training architecture
|
|
||||||
* Described in our CVPR paper: "Learning to Drive from a World Model"
|
|
||||||
* Longitudinal MPC replaced by E2E planning from World Model in Experimental Mode
|
|
||||||
* Action from lateral MPC as training objective replaced by E2E planning from World Model
|
|
||||||
* Low-speed lead car ground-truth fixes
|
|
||||||
* Enable live-learned steering actuation delay
|
|
||||||
* Opt-in audio recording for dashcam video
|
|
||||||
* Acura MDX 2025 support thanks to vanillagorillaa and MVL!
|
|
||||||
* Honda Accord 2023-25 support thanks to vanillagorillaa and MVL!
|
|
||||||
* Honda CR-V 2023-25 support thanks to vanillagorillaa and MVL!
|
|
||||||
* Honda Pilot 2023-25 support thanks to vanillagorillaa and MVL!
|
|
||||||
|
|
||||||
Version 0.9.9 (2025-05-23)
|
|
||||||
========================
|
|
||||||
* New driving model
|
|
||||||
* New training architecture using parts from MLSIM
|
|
||||||
* Steering actuation delay is now learned online
|
|
||||||
* Ford Escape 2023-24 support thanks to incognitojam!
|
|
||||||
* Ford Kuga 2024 support thanks to incognitojam!
|
|
||||||
* Hyundai Nexo 2021 support thanks to sunnyhaibin!
|
|
||||||
* Tesla Model 3 and Y support thanks to lukasloetkolben!
|
* Tesla Model 3 and Y support thanks to lukasloetkolben!
|
||||||
* Lexus RC 2023 support thanks to nelsonjchen!
|
* Coming soon
|
||||||
|
* New driving model supervised by MLSIM
|
||||||
|
* An online learner for steering actuator delay
|
||||||
|
|
||||||
Version 0.9.8 (2025-02-28)
|
Version 0.9.8 (2025-02-28)
|
||||||
========================
|
========================
|
||||||
|
|||||||
+259
-116
@@ -4,90 +4,213 @@ import sys
|
|||||||
import sysconfig
|
import sysconfig
|
||||||
import platform
|
import platform
|
||||||
import shlex
|
import shlex
|
||||||
import importlib
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import SCons.Errors
|
import SCons.Errors
|
||||||
|
|
||||||
SCons.Warnings.warningAsException(True)
|
SCons.Warnings.warningAsException(True)
|
||||||
|
|
||||||
|
TICI = os.path.isfile('/TICI')
|
||||||
|
AGNOS = TICI
|
||||||
|
|
||||||
Decider('MD5-timestamp')
|
Decider('MD5-timestamp')
|
||||||
|
|
||||||
SetOption('num_jobs', max(1, int(os.cpu_count()/2)))
|
SetOption('num_jobs', int(os.cpu_count()/2))
|
||||||
|
|
||||||
|
AddOption('--kaitai',
|
||||||
|
action='store_true',
|
||||||
|
help='Regenerate kaitai struct parsers')
|
||||||
|
|
||||||
|
AddOption('--asan',
|
||||||
|
action='store_true',
|
||||||
|
help='turn on ASAN')
|
||||||
|
|
||||||
|
AddOption('--ubsan',
|
||||||
|
action='store_true',
|
||||||
|
help='turn on UBSan')
|
||||||
|
|
||||||
|
AddOption('--coverage',
|
||||||
|
action='store_true',
|
||||||
|
help='build with test coverage options')
|
||||||
|
|
||||||
|
AddOption('--clazy',
|
||||||
|
action='store_true',
|
||||||
|
help='build with clazy')
|
||||||
|
|
||||||
|
AddOption('--compile_db',
|
||||||
|
action='store_true',
|
||||||
|
help='build clang compilation database')
|
||||||
|
|
||||||
|
AddOption('--ccflags',
|
||||||
|
action='store',
|
||||||
|
type='string',
|
||||||
|
default='',
|
||||||
|
help='pass arbitrary flags over the command line')
|
||||||
|
|
||||||
|
AddOption('--external-sconscript',
|
||||||
|
action='store',
|
||||||
|
metavar='FILE',
|
||||||
|
dest='external_sconscript',
|
||||||
|
help='add an external SConscript to the build')
|
||||||
|
|
||||||
|
AddOption('--pc-thneed',
|
||||||
|
action='store_true',
|
||||||
|
dest='pc_thneed',
|
||||||
|
help='use thneed on pc')
|
||||||
|
|
||||||
|
AddOption('--mutation',
|
||||||
|
action='store_true',
|
||||||
|
help='generate mutation-ready code')
|
||||||
|
|
||||||
AddOption('--asan', action='store_true', help='turn on ASAN')
|
|
||||||
AddOption('--ubsan', action='store_true', help='turn on UBSan')
|
|
||||||
AddOption('--mutation', action='store_true', help='generate mutation-ready code')
|
|
||||||
AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line')
|
|
||||||
AddOption('--verbose', action='store_true', default=False, help='show full build commands')
|
|
||||||
AddOption('--minimal',
|
AddOption('--minimal',
|
||||||
action='store_false',
|
action='store_false',
|
||||||
dest='extras',
|
dest='extras',
|
||||||
default=os.path.exists(File('#.gitattributes').abspath), # minimal by default on release branch (where there's no LFS)
|
default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS)
|
||||||
help='the minimum build to run openpilot. no tests, tools, etc.')
|
help='the minimum build to run openpilot. no tests, tools, etc.')
|
||||||
|
|
||||||
# Detect platform
|
## Architecture name breakdown (arch)
|
||||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
## - larch64: linux tici aarch64
|
||||||
|
## - aarch64: linux pc aarch64
|
||||||
|
## - x86_64: linux pc x64
|
||||||
|
## - Darwin: mac x64 or arm64
|
||||||
|
real_arch = arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||||
if platform.system() == "Darwin":
|
if platform.system() == "Darwin":
|
||||||
arch = "Darwin"
|
arch = "Darwin"
|
||||||
elif arch == "aarch64" and os.path.isfile('/TICI'):
|
brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip()
|
||||||
|
elif arch == "aarch64" and AGNOS:
|
||||||
arch = "larch64"
|
arch = "larch64"
|
||||||
assert arch in [
|
assert arch in ["larch64", "aarch64", "x86_64", "Darwin"]
|
||||||
"larch64", # linux tici arm64
|
|
||||||
"aarch64", # linux pc arm64
|
|
||||||
"x86_64", # linux pc x64
|
|
||||||
"Darwin", # macOS arm64 (x86 not supported)
|
|
||||||
]
|
|
||||||
|
|
||||||
pkg_names = ['bzip2', 'capnproto', 'eigen', 'ffmpeg', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd']
|
lenv = {
|
||||||
pkgs = [importlib.import_module(name) for name in pkg_names]
|
"PATH": os.environ['PATH'],
|
||||||
py_include = importlib.import_module('python3_dev').INCLUDE_DIR
|
"LD_LIBRARY_PATH": [Dir(f"#third_party/acados/{arch}/lib").abspath],
|
||||||
|
"PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath,
|
||||||
|
|
||||||
|
"ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath,
|
||||||
|
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath,
|
||||||
|
"TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer"
|
||||||
|
}
|
||||||
|
|
||||||
|
rpath = lenv["LD_LIBRARY_PATH"].copy()
|
||||||
|
|
||||||
|
if arch == "larch64":
|
||||||
|
cpppath = [
|
||||||
|
"#third_party/opencl/include",
|
||||||
|
]
|
||||||
|
|
||||||
|
libpath = [
|
||||||
|
"/usr/local/lib",
|
||||||
|
"/system/vendor/lib64",
|
||||||
|
"#third_party/nanovg",
|
||||||
|
f"#third_party/acados/{arch}/lib",
|
||||||
|
]
|
||||||
|
|
||||||
|
libpath += [
|
||||||
|
"#third_party/libyuv/larch64/lib",
|
||||||
|
"/usr/lib/aarch64-linux-gnu"
|
||||||
|
]
|
||||||
|
cflags = ["-DQCOM2", "-mcpu=cortex-a57"]
|
||||||
|
cxxflags = ["-DQCOM2", "-mcpu=cortex-a57"]
|
||||||
|
rpath += ["/usr/local/lib"]
|
||||||
|
else:
|
||||||
|
cflags = []
|
||||||
|
cxxflags = []
|
||||||
|
cpppath = []
|
||||||
|
rpath += []
|
||||||
|
|
||||||
|
# MacOS
|
||||||
|
if arch == "Darwin":
|
||||||
|
libpath = [
|
||||||
|
f"#third_party/libyuv/{arch}/lib",
|
||||||
|
f"#third_party/acados/{arch}/lib",
|
||||||
|
f"{brew_prefix}/lib",
|
||||||
|
f"{brew_prefix}/opt/openssl@3.0/lib",
|
||||||
|
"/System/Library/Frameworks/OpenGL.framework/Libraries",
|
||||||
|
]
|
||||||
|
|
||||||
|
cflags += ["-DGL_SILENCE_DEPRECATION"]
|
||||||
|
cxxflags += ["-DGL_SILENCE_DEPRECATION"]
|
||||||
|
cpppath += [
|
||||||
|
f"{brew_prefix}/include",
|
||||||
|
f"{brew_prefix}/opt/openssl@3.0/include",
|
||||||
|
]
|
||||||
|
lenv["DYLD_LIBRARY_PATH"] = lenv["LD_LIBRARY_PATH"]
|
||||||
|
# Linux
|
||||||
|
else:
|
||||||
|
libpath = [
|
||||||
|
f"#third_party/acados/{arch}/lib",
|
||||||
|
f"#third_party/libyuv/{arch}/lib",
|
||||||
|
f"#third_party/mapbox-gl-native-qt/{arch}",
|
||||||
|
"/usr/lib",
|
||||||
|
"/usr/local/lib",
|
||||||
|
]
|
||||||
|
|
||||||
|
if GetOption('asan'):
|
||||||
|
ccflags = ["-fsanitize=address", "-fno-omit-frame-pointer"]
|
||||||
|
ldflags = ["-fsanitize=address"]
|
||||||
|
elif GetOption('ubsan'):
|
||||||
|
ccflags = ["-fsanitize=undefined"]
|
||||||
|
ldflags = ["-fsanitize=undefined"]
|
||||||
|
else:
|
||||||
|
ccflags = []
|
||||||
|
ldflags = []
|
||||||
|
|
||||||
|
# no --as-needed on mac linker
|
||||||
|
if arch != "Darwin":
|
||||||
|
ldflags += ["-Wl,--as-needed", "-Wl,--no-undefined"]
|
||||||
|
|
||||||
|
ccflags_option = GetOption('ccflags')
|
||||||
|
if ccflags_option:
|
||||||
|
ccflags += ccflags_option.split(' ')
|
||||||
|
|
||||||
env = Environment(
|
env = Environment(
|
||||||
ENV={
|
ENV=lenv,
|
||||||
"PATH": os.environ['PATH'],
|
|
||||||
"PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath,
|
|
||||||
"ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath,
|
|
||||||
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath,
|
|
||||||
"TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer"
|
|
||||||
},
|
|
||||||
CCFLAGS=[
|
CCFLAGS=[
|
||||||
"-g",
|
"-g",
|
||||||
"-fPIC",
|
"-fPIC",
|
||||||
"-O2",
|
"-O2",
|
||||||
"-Wunused",
|
"-Wunused",
|
||||||
"-Werror",
|
"-Werror",
|
||||||
"-Wshadow" if arch in ("Darwin", "larch64") else "-Wshadow=local",
|
"-Wshadow",
|
||||||
"-Wno-unknown-warning-option",
|
"-Wno-unknown-warning-option",
|
||||||
"-Wno-inconsistent-missing-override",
|
"-Wno-inconsistent-missing-override",
|
||||||
"-Wno-c99-designator",
|
"-Wno-c99-designator",
|
||||||
"-Wno-reorder-init-list",
|
"-Wno-reorder-init-list",
|
||||||
"-Wno-vla-cxx-extension",
|
"-Wno-vla-cxx-extension",
|
||||||
],
|
] + cflags + ccflags,
|
||||||
CFLAGS=["-std=gnu11"],
|
|
||||||
CXXFLAGS=["-std=c++1z"],
|
CPPPATH=cpppath + [
|
||||||
CPPPATH=[
|
|
||||||
"#",
|
"#",
|
||||||
"#msgq",
|
|
||||||
"#third_party",
|
|
||||||
"#third_party/json11",
|
|
||||||
"#third_party/linux/include",
|
|
||||||
"#third_party/acados/include",
|
"#third_party/acados/include",
|
||||||
"#third_party/acados/include/blasfeo/include",
|
"#third_party/acados/include/blasfeo/include",
|
||||||
"#third_party/acados/include/hpipm/include",
|
"#third_party/acados/include/hpipm/include",
|
||||||
"#third_party/catch2/include",
|
"#third_party/catch2/include",
|
||||||
[x.INCLUDE_DIR for x in pkgs],
|
"#third_party/libyuv/include",
|
||||||
|
"#third_party/json11",
|
||||||
|
"#third_party/linux/include",
|
||||||
|
"#third_party/snpe/include",
|
||||||
|
"#third_party/nanovg",
|
||||||
|
"#third_party",
|
||||||
|
"#msgq",
|
||||||
|
"#third_party/maplibre-native-qt/include",
|
||||||
|
f"#third_party/maplibre-native-qt/{arch}/include"
|
||||||
],
|
],
|
||||||
LIBPATH=[
|
|
||||||
"#common",
|
CC='clang',
|
||||||
|
CXX='clang++',
|
||||||
|
LINKFLAGS=ldflags,
|
||||||
|
|
||||||
|
RPATH=rpath,
|
||||||
|
|
||||||
|
CFLAGS=["-std=gnu11"] + cflags,
|
||||||
|
CXXFLAGS=["-std=c++1z"] + cxxflags,
|
||||||
|
LIBPATH=libpath + [
|
||||||
"#msgq_repo",
|
"#msgq_repo",
|
||||||
"#third_party",
|
"#third_party",
|
||||||
"#selfdrive/pandad_tici" if "TICI_DOS" in os.environ else "#selfdrive/pandad",
|
"#selfdrive/pandad",
|
||||||
|
"#common",
|
||||||
"#rednose/helpers",
|
"#rednose/helpers",
|
||||||
f"#third_party/acados/{arch}/lib",
|
|
||||||
[x.LIB_DIR for x in pkgs],
|
|
||||||
],
|
],
|
||||||
RPATH=[],
|
|
||||||
CYTHONCFILESUFFIX=".cpp",
|
CYTHONCFILESUFFIX=".cpp",
|
||||||
COMPILATIONDB_USE_ABSPATH=True,
|
COMPILATIONDB_USE_ABSPATH=True,
|
||||||
REDNOSE_ROOT="#",
|
REDNOSE_ROOT="#",
|
||||||
@@ -95,101 +218,113 @@ env = Environment(
|
|||||||
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Arch-specific flags and paths
|
if arch == "Darwin":
|
||||||
if arch == "larch64":
|
# RPATH is not supported on macOS, instead use the linker flags
|
||||||
env["CC"] = "clang"
|
darwin_rpath_link_flags = [f"-Wl,-rpath,{path}" for path in env["RPATH"]]
|
||||||
env["CXX"] = "clang++"
|
env["LINKFLAGS"] += darwin_rpath_link_flags
|
||||||
env.Append(LIBPATH=[
|
|
||||||
"/usr/local/lib",
|
|
||||||
"/system/vendor/lib64",
|
|
||||||
"/usr/lib/aarch64-linux-gnu",
|
|
||||||
])
|
|
||||||
arch_flags = ["-D__TICI__", "-mcpu=cortex-a57"]
|
|
||||||
env.Append(CCFLAGS=arch_flags)
|
|
||||||
env.Append(CXXFLAGS=arch_flags)
|
|
||||||
elif arch == "Darwin":
|
|
||||||
env.Append(LIBPATH=[
|
|
||||||
"/System/Library/Frameworks/OpenGL.framework/Libraries",
|
|
||||||
])
|
|
||||||
env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"])
|
|
||||||
env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"])
|
|
||||||
else:
|
|
||||||
env.Append(LIBPATH=[
|
|
||||||
"/usr/lib",
|
|
||||||
"/usr/local/lib",
|
|
||||||
])
|
|
||||||
|
|
||||||
# Sanitizers and extra CCFLAGS from CLI
|
if GetOption('compile_db'):
|
||||||
if GetOption('asan'):
|
env.CompilationDatabase('compile_commands.json')
|
||||||
env.Append(CCFLAGS=["-fsanitize=address", "-fno-omit-frame-pointer"])
|
|
||||||
env.Append(LINKFLAGS=["-fsanitize=address"])
|
|
||||||
elif GetOption('ubsan'):
|
|
||||||
env.Append(CCFLAGS=["-fsanitize=undefined"])
|
|
||||||
env.Append(LINKFLAGS=["-fsanitize=undefined"])
|
|
||||||
|
|
||||||
_extra_cc = shlex.split(GetOption('ccflags') or '')
|
# Setup cache dir
|
||||||
if _extra_cc:
|
cache_dir = '/data/scons_cache' if AGNOS else '/tmp/scons_cache'
|
||||||
env.Append(CCFLAGS=_extra_cc)
|
CacheDir(cache_dir)
|
||||||
|
Clean(["."], cache_dir)
|
||||||
|
|
||||||
# no --as-needed on mac linker
|
|
||||||
if arch != "Darwin":
|
|
||||||
env.Append(LINKFLAGS=["-Wl,--as-needed", "-Wl,--no-undefined"])
|
|
||||||
|
|
||||||
# Shorter build output: show brief descriptions instead of full commands.
|
|
||||||
# Full command lines are still printed on failure by scons.
|
|
||||||
if not GetOption('verbose'):
|
|
||||||
for action, short in (
|
|
||||||
("CC", "CC"),
|
|
||||||
("CXX", "CXX"),
|
|
||||||
("LINK", "LINK"),
|
|
||||||
("SHCC", "CC"),
|
|
||||||
("SHCXX", "CXX"),
|
|
||||||
("SHLINK", "LINK"),
|
|
||||||
("AR", "AR"),
|
|
||||||
("RANLIB", "RANLIB"),
|
|
||||||
("AS", "AS"),
|
|
||||||
):
|
|
||||||
env[f"{action}COMSTR"] = f" [{short}] $TARGET"
|
|
||||||
|
|
||||||
# progress output
|
|
||||||
node_interval = 5
|
node_interval = 5
|
||||||
node_count = 0
|
node_count = 0
|
||||||
def progress_function(node):
|
def progress_function(node):
|
||||||
global node_count
|
global node_count
|
||||||
node_count += node_interval
|
node_count += node_interval
|
||||||
sys.stderr.write("progress: %d\n" % node_count)
|
sys.stderr.write("progress: %d\n" % node_count)
|
||||||
|
|
||||||
if os.environ.get('SCONS_PROGRESS'):
|
if os.environ.get('SCONS_PROGRESS'):
|
||||||
Progress(progress_function, interval=node_interval)
|
Progress(progress_function, interval=node_interval)
|
||||||
|
|
||||||
# ********** Cython build environment **********
|
# Cython build environment
|
||||||
|
py_include = sysconfig.get_paths()['include']
|
||||||
envCython = env.Clone()
|
envCython = env.Clone()
|
||||||
envCython["CPPPATH"] += [py_include, np.get_include()]
|
envCython["CPPPATH"] += [py_include, np.get_include()]
|
||||||
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"]
|
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-shadow", "-Wno-deprecated-declarations"]
|
||||||
envCython["CCFLAGS"].remove("-Werror")
|
envCython["CCFLAGS"].remove("-Werror")
|
||||||
|
|
||||||
envCython["LIBS"] = []
|
envCython["LIBS"] = []
|
||||||
if arch == "Darwin":
|
if arch == "Darwin":
|
||||||
envCython["LINKFLAGS"] = env["LINKFLAGS"] + ["-bundle", "-undefined", "dynamic_lookup"]
|
envCython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"] + darwin_rpath_link_flags
|
||||||
else:
|
else:
|
||||||
envCython["LINKFLAGS"] = ["-pthread", "-shared"]
|
envCython["LINKFLAGS"] = ["-pthread", "-shared"]
|
||||||
|
|
||||||
np_version = SCons.Script.Value(np.__version__)
|
np_version = SCons.Script.Value(np.__version__)
|
||||||
Export('envCython', 'np_version')
|
Export('envCython', 'np_version')
|
||||||
|
|
||||||
Export('env', 'arch')
|
# Qt build environment
|
||||||
|
qt_env = env.Clone()
|
||||||
|
qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "Qml", "QuickWidgets", "Location", "Positioning", "DBus", "Xml"]
|
||||||
|
|
||||||
# Setup cache dir
|
qt_libs = []
|
||||||
cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
|
if arch == "Darwin":
|
||||||
CacheDir(cache_dir)
|
qt_env['QTDIR'] = f"{brew_prefix}/opt/qt@5"
|
||||||
Clean(["."], cache_dir)
|
qt_dirs = [
|
||||||
|
os.path.join(qt_env['QTDIR'], "include"),
|
||||||
|
]
|
||||||
|
qt_dirs += [f"{qt_env['QTDIR']}/include/Qt{m}" for m in qt_modules]
|
||||||
|
qt_env["LINKFLAGS"] += ["-F" + os.path.join(qt_env['QTDIR'], "lib")]
|
||||||
|
qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"]
|
||||||
|
qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin"))
|
||||||
|
else:
|
||||||
|
qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip()
|
||||||
|
qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip()
|
||||||
|
|
||||||
# dragonpilot settings generation — runs every scons invocation, idempotent.
|
qt_env['QTDIR'] = qt_install_prefix
|
||||||
# Writes common/params_keys.h in place; we don't declare a target so scons
|
qt_dirs = [
|
||||||
# treats it purely as a pre-build side effect.
|
f"{qt_install_headers}",
|
||||||
if env.Execute('./generate_settings.py') != 0:
|
]
|
||||||
Exit('generate_settings.py failed')
|
|
||||||
|
|
||||||
# ********** start building stuff **********
|
qt_gui_path = os.path.join(qt_install_headers, "QtGui")
|
||||||
|
qt_gui_dirs = [d for d in os.listdir(qt_gui_path) if os.path.isdir(os.path.join(qt_gui_path, d))]
|
||||||
|
qt_dirs += [f"{qt_install_headers}/QtGui/{qt_gui_dirs[0]}/QtGui", ] if qt_gui_dirs else []
|
||||||
|
qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules]
|
||||||
|
|
||||||
|
qt_libs = [f"Qt5{m}" for m in qt_modules]
|
||||||
|
if arch == "larch64":
|
||||||
|
qt_libs += ["GLESv2", "wayland-client"]
|
||||||
|
qt_env.PrependENVPath('PATH', Dir("#third_party/qt5/larch64/bin/").abspath)
|
||||||
|
elif arch != "Darwin":
|
||||||
|
qt_libs += ["GL"]
|
||||||
|
qt_env['QT3DIR'] = qt_env['QTDIR']
|
||||||
|
|
||||||
|
# compatibility for older SCons versions
|
||||||
|
try:
|
||||||
|
qt_env.Tool('qt3')
|
||||||
|
except SCons.Errors.UserError:
|
||||||
|
qt_env.Tool('qt')
|
||||||
|
|
||||||
|
qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"]
|
||||||
|
qt_flags = [
|
||||||
|
"-D_REENTRANT",
|
||||||
|
"-DQT_NO_DEBUG",
|
||||||
|
"-DQT_WIDGETS_LIB",
|
||||||
|
"-DQT_GUI_LIB",
|
||||||
|
"-DQT_CORE_LIB",
|
||||||
|
"-DQT_MESSAGELOGCONTEXT",
|
||||||
|
]
|
||||||
|
qt_env['CXXFLAGS'] += qt_flags
|
||||||
|
qt_env['LIBPATH'] += ['#selfdrive/ui', f"#third_party/maplibre-native-qt/{arch}/lib"]
|
||||||
|
qt_env['RPATH'] += [Dir(f"#third_party/maplibre-native-qt/{arch}/lib").srcnode().abspath]
|
||||||
|
qt_env['LIBS'] = qt_libs
|
||||||
|
|
||||||
|
if GetOption("clazy"):
|
||||||
|
checks = [
|
||||||
|
"level0",
|
||||||
|
"level1",
|
||||||
|
"no-range-loop",
|
||||||
|
"no-non-pod-global-static",
|
||||||
|
]
|
||||||
|
qt_env['CXX'] = 'clazy'
|
||||||
|
qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0]
|
||||||
|
qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks)
|
||||||
|
|
||||||
|
Export('env', 'qt_env', 'arch', 'real_arch')
|
||||||
|
|
||||||
# Build common module
|
# Build common module
|
||||||
SConscript(['common/SConscript'])
|
SConscript(['common/SConscript'])
|
||||||
@@ -213,15 +348,20 @@ Export('messaging')
|
|||||||
|
|
||||||
# Build other submodules
|
# Build other submodules
|
||||||
SConscript(['panda/SConscript'])
|
SConscript(['panda/SConscript'])
|
||||||
SConscript(['panda_tici/SConscript'])
|
|
||||||
|
|
||||||
# Build rednose library
|
# Build rednose library
|
||||||
SConscript(['rednose/SConscript'])
|
SConscript(['rednose/SConscript'])
|
||||||
|
|
||||||
# Build system services
|
# Build system services
|
||||||
SConscript([
|
SConscript([
|
||||||
|
'system/proclogd/SConscript',
|
||||||
|
'system/ubloxd/SConscript',
|
||||||
'system/loggerd/SConscript',
|
'system/loggerd/SConscript',
|
||||||
])
|
])
|
||||||
|
if arch != "Darwin":
|
||||||
|
SConscript([
|
||||||
|
'system/logcatd/SConscript',
|
||||||
|
])
|
||||||
|
|
||||||
if arch == "larch64":
|
if arch == "larch64":
|
||||||
SConscript(['system/camerad/SConscript'])
|
SConscript(['system/camerad/SConscript'])
|
||||||
@@ -231,8 +371,11 @@ SConscript(['third_party/SConscript'])
|
|||||||
|
|
||||||
SConscript(['selfdrive/SConscript'])
|
SConscript(['selfdrive/SConscript'])
|
||||||
|
|
||||||
if Dir('#tools/cabana/').exists() and arch != "larch64":
|
if Dir('#tools/cabana/').exists() and GetOption('extras'):
|
||||||
SConscript(['tools/cabana/SConscript'])
|
SConscript(['tools/replay/SConscript'])
|
||||||
|
if arch != "larch64":
|
||||||
|
SConscript(['tools/cabana/SConscript'])
|
||||||
|
|
||||||
|
external_sconscript = GetOption('external_sconscript')
|
||||||
env.CompilationDatabase('compile_commands.json')
|
if external_sconscript:
|
||||||
|
SConscript([external_sconscript])
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
http://shind0.synology.me/carrotman/CarrotMan51.apk
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
비 루팅폰 설정
|
||||||
|
|
||||||
|
pc에 adb를 설치한다.
|
||||||
|
|
||||||
|
command prompt창에서
|
||||||
|
|
||||||
|
adb shell pm grant com.ajouatom.carrotman android.permission.READ_LOGS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
************ 티네비설정...
|
||||||
|
|
||||||
|
권한은 모두 허가하고,
|
||||||
|
|
||||||
|
블랙박스모드를 켠다.
|
||||||
|
|
||||||
|
블랙박스 저장소를 Download 로 가서
|
||||||
|
|
||||||
|
Carrot폴더를 만든다. (여기서 만들어야한다.) 이유는 모름..
|
||||||
|
|
||||||
|
여기서 저장소 권한을 넣는다.
|
||||||
|
|
||||||
|
블백박스를 사용하지 않으면 다시 끈다.
|
||||||
|
|
||||||
|
|
||||||
|
루팅폰 잘됨~
|
||||||
+1
-1
@@ -13,7 +13,7 @@ cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in schema_files])
|
|||||||
|
|
||||||
# Build messaging
|
# Build messaging
|
||||||
services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET')
|
services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET')
|
||||||
env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc', 'messaging/bridge_zmq.cc'], LIBS=[msgq, common, 'pthread'])
|
env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc'], LIBS=[msgq, common, 'pthread'])
|
||||||
|
|
||||||
socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc'])
|
socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc'])
|
||||||
|
|
||||||
|
|||||||
+34
-12
@@ -10,24 +10,46 @@ $Cxx.namespace("cereal");
|
|||||||
# DO rename the structs
|
# DO rename the structs
|
||||||
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
||||||
|
|
||||||
struct ControlsStateExt @0x81c2f05a394cf4af {
|
# you can rename the struct, but don't change the identifier
|
||||||
alkaActive @0 :Bool;
|
struct CarrotMan @0x81c2f05a394cf4af {
|
||||||
|
activeCarrot @0 : Int32;
|
||||||
|
nRoadLimitSpeed @1 : Int32;
|
||||||
|
remote @2 : Text;
|
||||||
|
xSpdType @3 : Int32;
|
||||||
|
xSpdLimit @4 : Int32;
|
||||||
|
xSpdDist @5 : Int32;
|
||||||
|
xSpdCountDown @6 : Int32;
|
||||||
|
xTurnInfo @7 : Int32;
|
||||||
|
xDistToTurn @8 : Int32;
|
||||||
|
xTurnCountDown @9 : Int32;
|
||||||
|
atcType @10 : Text;
|
||||||
|
vTurnSpeed @11 : Int32;
|
||||||
|
szPosRoadName @12 : Text;
|
||||||
|
szTBTMainText @13 : Text;
|
||||||
|
desiredSpeed @14 : Int32;
|
||||||
|
desiredSource @15 : Text;
|
||||||
|
carrotCmdIndex @16 : Int32;
|
||||||
|
carrotCmd @17 : Text;
|
||||||
|
carrotArg @18 : Text;
|
||||||
|
xPosLat @19 : Float32;
|
||||||
|
xPosLon @20 : Float32;
|
||||||
|
xPosAngle @21 : Float32;
|
||||||
|
xPosSpeed @22 : Float32;
|
||||||
|
trafficState @23 : Int32;
|
||||||
|
nGoPosDist @24 : Int32;
|
||||||
|
nGoPosTime @25 : Int32;
|
||||||
|
szSdiDescr @26 : Text;
|
||||||
|
naviPaths @27 : Text;
|
||||||
|
leftSec @28 : Int32;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CarStateExt @0xaedffd8f31e7b55d {
|
struct CustomReserved1 @0xaedffd8f31e7b55d {
|
||||||
# dp - ALKA: lkasOn state from carstate (mirrors panda's lkas_on)
|
|
||||||
lkasOn @0 :Bool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ModelExt @0xf35cc4560bbf6ec2 {
|
struct CustomReserved2 @0xf35cc4560bbf6ec2 {
|
||||||
leftEdgeDetected @0 :Bool;
|
|
||||||
rightEdgeDetected @1 :Bool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DashyState @0xda96579883444c35 {
|
struct CustomReserved3 @0xda96579883444c35 {
|
||||||
# Pre-serialized JSON bytes for dashy UI
|
|
||||||
# Aggregates all topics needed by dashy into single message
|
|
||||||
json @0 :Data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CustomReserved4 @0x80ae746ee2596b11 {
|
struct CustomReserved4 @0x80ae746ee2596b11 {
|
||||||
|
|||||||
+90
-46
@@ -87,7 +87,6 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
|||||||
laneChange @50;
|
laneChange @50;
|
||||||
lowMemory @51;
|
lowMemory @51;
|
||||||
stockAeb @52;
|
stockAeb @52;
|
||||||
stockLkas @98;
|
|
||||||
ldw @53;
|
ldw @53;
|
||||||
carUnrecognized @54;
|
carUnrecognized @54;
|
||||||
invalidLkasSetting @55;
|
invalidLkasSetting @55;
|
||||||
@@ -128,9 +127,32 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
|||||||
espActive @90;
|
espActive @90;
|
||||||
personalityChanged @91;
|
personalityChanged @91;
|
||||||
aeb @92;
|
aeb @92;
|
||||||
userBookmark @95;
|
userFlag @95;
|
||||||
excessiveActuation @96;
|
|
||||||
audioFeedback @97;
|
softHold @115;
|
||||||
|
trafficStopping @116;
|
||||||
|
audioPrompt @117;
|
||||||
|
audioRefuse @96;
|
||||||
|
stopStop @97;
|
||||||
|
audioLaneChange @98;
|
||||||
|
audioTurn @99;
|
||||||
|
trafficSignGreen @100;
|
||||||
|
trafficSignChanged @101;
|
||||||
|
turningLeft @102;
|
||||||
|
turningRight @103;
|
||||||
|
audio1 @104;
|
||||||
|
audio2 @105;
|
||||||
|
audio3 @106;
|
||||||
|
audio4 @107;
|
||||||
|
audio5 @108;
|
||||||
|
audio6 @109;
|
||||||
|
audio7 @110;
|
||||||
|
audio8 @111;
|
||||||
|
audio9 @112;
|
||||||
|
audio10 @113;
|
||||||
|
audio0 @114;
|
||||||
|
|
||||||
|
torqueNNLoad @118;
|
||||||
|
|
||||||
soundsUnavailableDEPRECATED @47;
|
soundsUnavailableDEPRECATED @47;
|
||||||
}
|
}
|
||||||
@@ -140,6 +162,7 @@ enum LongitudinalPersonality {
|
|||||||
aggressive @0;
|
aggressive @0;
|
||||||
standard @1;
|
standard @1;
|
||||||
relaxed @2;
|
relaxed @2;
|
||||||
|
moreRelaxed @3;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InitData {
|
struct InitData {
|
||||||
@@ -495,12 +518,12 @@ struct DeviceState @0xa4d8b5af2aa492eb {
|
|||||||
gpuTempC @27 :List(Float32);
|
gpuTempC @27 :List(Float32);
|
||||||
dspTempC @49 :Float32;
|
dspTempC @49 :Float32;
|
||||||
memoryTempC @28 :Float32;
|
memoryTempC @28 :Float32;
|
||||||
|
nvmeTempC @35 :List(Float32);
|
||||||
modemTempC @36 :List(Float32);
|
modemTempC @36 :List(Float32);
|
||||||
pmicTempC @39 :List(Float32);
|
pmicTempC @39 :List(Float32);
|
||||||
intakeTempC @46 :Float32;
|
intakeTempC @46 :Float32;
|
||||||
exhaustTempC @47 :Float32;
|
exhaustTempC @47 :Float32;
|
||||||
gnssTempC @48 :Float32;
|
caseTempC @48 :Float32;
|
||||||
bottomSocTempC @50 :Float32;
|
|
||||||
maxTempC @44 :Float32; # max of other temps, used to control fan
|
maxTempC @44 :Float32; # max of other temps, used to control fan
|
||||||
thermalZones @38 :List(ThermalZone);
|
thermalZones @38 :List(ThermalZone);
|
||||||
thermalStatus @14 :ThermalStatus;
|
thermalStatus @14 :ThermalStatus;
|
||||||
@@ -571,7 +594,6 @@ struct DeviceState @0xa4d8b5af2aa492eb {
|
|||||||
chargingDisabledDEPRECATED @18 :Bool;
|
chargingDisabledDEPRECATED @18 :Bool;
|
||||||
usbOnlineDEPRECATED @12 :Bool;
|
usbOnlineDEPRECATED @12 :Bool;
|
||||||
ambientTempCDEPRECATED @30 :Float32;
|
ambientTempCDEPRECATED @30 :Float32;
|
||||||
nvmeTempCDEPRECATED @35 :List(Float32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PandaState @0xa7649e2575e4591e {
|
struct PandaState @0xa7649e2575e4591e {
|
||||||
@@ -587,13 +609,13 @@ struct PandaState @0xa7649e2575e4591e {
|
|||||||
heartbeatLost @22 :Bool;
|
heartbeatLost @22 :Bool;
|
||||||
interruptLoad @25 :Float32;
|
interruptLoad @25 :Float32;
|
||||||
fanPower @28 :UInt8;
|
fanPower @28 :UInt8;
|
||||||
|
fanStallCount @34 :UInt8;
|
||||||
|
|
||||||
spiErrorCount @33 :UInt16;
|
spiChecksumErrorCount @33 :UInt16;
|
||||||
|
|
||||||
harnessStatus @21 :HarnessStatus;
|
harnessStatus @21 :HarnessStatus;
|
||||||
sbu1Voltage @35 :Float32;
|
sbu1Voltage @35 :Float32;
|
||||||
sbu2Voltage @36 :Float32;
|
sbu2Voltage @36 :Float32;
|
||||||
soundOutputLevel @37 :UInt16;
|
|
||||||
|
|
||||||
# can health
|
# can health
|
||||||
canState0 @29 :PandaCanState;
|
canState0 @29 :PandaCanState;
|
||||||
@@ -716,7 +738,6 @@ struct PandaState @0xa7649e2575e4591e {
|
|||||||
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
|
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
|
||||||
safetyParamDEPRECATED @20 :Int16;
|
safetyParamDEPRECATED @20 :Int16;
|
||||||
safetyParam2DEPRECATED @26 :UInt32;
|
safetyParam2DEPRECATED @26 :UInt32;
|
||||||
fanStallCountDEPRECATED @34 :UInt8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PeripheralState {
|
struct PeripheralState {
|
||||||
@@ -742,6 +763,15 @@ struct RadarState @0x9a185389d6fdd05f {
|
|||||||
leadOne @3 :LeadData;
|
leadOne @3 :LeadData;
|
||||||
leadTwo @4 :LeadData;
|
leadTwo @4 :LeadData;
|
||||||
|
|
||||||
|
leadLeft @18 :LeadData;
|
||||||
|
leadRight @14 :LeadData;
|
||||||
|
leadsCenter @15 : List(LeadData);
|
||||||
|
leadsLeft @16 : List(LeadData);
|
||||||
|
leadsRight @17 : List(LeadData);
|
||||||
|
leadsLeft2 @19 : List(LeadData);
|
||||||
|
leadsRight2 @20 : List(LeadData);
|
||||||
|
leadsCutIn @21 : List(LeadData);
|
||||||
|
|
||||||
struct LeadData {
|
struct LeadData {
|
||||||
dRel @0 :Float32;
|
dRel @0 :Float32;
|
||||||
yRel @1 :Float32;
|
yRel @1 :Float32;
|
||||||
@@ -759,7 +789,9 @@ struct RadarState @0x9a185389d6fdd05f {
|
|||||||
radar @14 :Bool;
|
radar @14 :Bool;
|
||||||
radarTrackId @15 :Int32 = -1;
|
radarTrackId @15 :Int32 = -1;
|
||||||
|
|
||||||
aLeadDEPRECATED @5 :Float32;
|
aLead @5 :Float32;
|
||||||
|
jLead @16 :Float32;
|
||||||
|
score @17 :Float32;
|
||||||
}
|
}
|
||||||
|
|
||||||
# deprecated
|
# deprecated
|
||||||
@@ -834,6 +866,7 @@ struct SelfdriveState {
|
|||||||
# configurable driving settings
|
# configurable driving settings
|
||||||
experimentalMode @10 :Bool;
|
experimentalMode @10 :Bool;
|
||||||
personality @11 :LongitudinalPersonality;
|
personality @11 :LongitudinalPersonality;
|
||||||
|
distanceTraveled @13 :Float32;
|
||||||
|
|
||||||
enum OpenpilotState @0xdbe58b96d2d1ac61 {
|
enum OpenpilotState @0xdbe58b96d2d1ac61 {
|
||||||
disabled @0;
|
disabled @0;
|
||||||
@@ -869,6 +902,8 @@ struct ControlsState @0x97ff69c53601abf1 {
|
|||||||
desiredCurvature @61 :Float32; # lag adjusted curvatures used by lateral controllers
|
desiredCurvature @61 :Float32; # lag adjusted curvatures used by lateral controllers
|
||||||
forceDecel @51 :Bool;
|
forceDecel @51 :Bool;
|
||||||
|
|
||||||
|
activeLaneLine @67 : Bool;
|
||||||
|
|
||||||
lateralControlState :union {
|
lateralControlState :union {
|
||||||
pidState @53 :LateralPIDState;
|
pidState @53 :LateralPIDState;
|
||||||
angleState @58 :LateralAngleState;
|
angleState @58 :LateralAngleState;
|
||||||
@@ -921,8 +956,7 @@ struct ControlsState @0x97ff69c53601abf1 {
|
|||||||
saturated @7 :Bool;
|
saturated @7 :Bool;
|
||||||
actualLateralAccel @9 :Float32;
|
actualLateralAccel @9 :Float32;
|
||||||
desiredLateralAccel @10 :Float32;
|
desiredLateralAccel @10 :Float32;
|
||||||
desiredLateralJerk @11 :Float32;
|
nnLog @11 :List(Float32);
|
||||||
version @12 :Int32;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LateralLQRState {
|
struct LateralLQRState {
|
||||||
@@ -1090,15 +1124,15 @@ struct ModelDataV2 {
|
|||||||
confidence @23: ConfidenceClass;
|
confidence @23: ConfidenceClass;
|
||||||
|
|
||||||
# Model perceived motion
|
# Model perceived motion
|
||||||
temporalPoseDEPRECATED @21 :Pose;
|
temporalPose @21 :Pose;
|
||||||
|
|
||||||
# e2e lateral planner
|
# e2e lateral planner
|
||||||
action @26: Action;
|
action @26: Action;
|
||||||
|
|
||||||
gpuExecutionTimeDEPRECATED @17 :Float32;
|
gpuExecutionTimeDEPRECATED @17 :Float32;
|
||||||
navEnabledDEPRECATED @22 :Bool;
|
navEnabled @22 :Bool;
|
||||||
locationMonoTimeDEPRECATED @24 :UInt64;
|
locationMonoTimeDEPRECATED @24 :UInt64;
|
||||||
lateralPlannerSolutionDEPRECATED @25: LateralPlannerSolution;
|
lateralPlannerSolution @25: LateralPlannerSolution;
|
||||||
|
|
||||||
struct LeadDataV2 {
|
struct LeadDataV2 {
|
||||||
prob @0 :Float32; # probability that car is your lead at time t
|
prob @0 :Float32; # probability that car is your lead at time t
|
||||||
@@ -1138,7 +1172,16 @@ struct ModelDataV2 {
|
|||||||
hardBrakePredicted @7 :Bool;
|
hardBrakePredicted @7 :Bool;
|
||||||
laneChangeState @8 :LaneChangeState;
|
laneChangeState @8 :LaneChangeState;
|
||||||
laneChangeDirection @9 :LaneChangeDirection;
|
laneChangeDirection @9 :LaneChangeDirection;
|
||||||
|
laneWidthLeft @10 :Float32;
|
||||||
|
laneWidthRight @11 :Float32;
|
||||||
|
distanceToRoadEdgeLeft @12 :Float32;
|
||||||
|
distanceToRoadEdgeRight @13 :Float32;
|
||||||
|
desire @14 :Desire;
|
||||||
|
laneChangeProb @15 :Float32;
|
||||||
|
desireLog @16 : Text;
|
||||||
|
modelTurnSpeed @17 :Float32;
|
||||||
|
laneChangeAvailableLeft @18 :Bool;
|
||||||
|
laneChangeAvailableRight @19 :Bool;
|
||||||
|
|
||||||
# deprecated
|
# deprecated
|
||||||
brakeDisengageProbDEPRECATED @2 :Float32;
|
brakeDisengageProbDEPRECATED @2 :Float32;
|
||||||
@@ -1186,6 +1229,7 @@ struct ModelDataV2 {
|
|||||||
desiredCurvature @0 :Float32;
|
desiredCurvature @0 :Float32;
|
||||||
desiredAcceleration @1 :Float32;
|
desiredAcceleration @1 :Float32;
|
||||||
shouldStop @2 :Bool;
|
shouldStop @2 :Bool;
|
||||||
|
desiredVelocity @3 :Float32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1257,6 +1301,15 @@ struct LongitudinalPlan @0xe00b5b3eba12876c {
|
|||||||
allowThrottle @38: Bool;
|
allowThrottle @38: Bool;
|
||||||
allowBrake @39: Bool;
|
allowBrake @39: Bool;
|
||||||
|
|
||||||
|
xState @40: Int32;
|
||||||
|
trafficState @41: Int32;
|
||||||
|
events @42:List(OnroadEvent);
|
||||||
|
vTargetNow @43: Float32;
|
||||||
|
cruiseTarget @44: Float32;
|
||||||
|
jTargetNow @45: Float32;
|
||||||
|
tFollow @46: Float32;
|
||||||
|
desiredDistance @47: Float32;
|
||||||
|
myDrivingMode @48: Int32;
|
||||||
|
|
||||||
solverExecutionTime @35 :Float32;
|
solverExecutionTime @35 :Float32;
|
||||||
|
|
||||||
@@ -1310,7 +1363,7 @@ struct UiPlan {
|
|||||||
|
|
||||||
struct LateralPlan @0xe1e9318e2ae8b51e {
|
struct LateralPlan @0xe1e9318e2ae8b51e {
|
||||||
modelMonoTime @31 :UInt64;
|
modelMonoTime @31 :UInt64;
|
||||||
laneWidthDEPRECATED @0 :Float32;
|
laneWidth @0 :Float32;
|
||||||
lProbDEPRECATED @5 :Float32;
|
lProbDEPRECATED @5 :Float32;
|
||||||
rProbDEPRECATED @7 :Float32;
|
rProbDEPRECATED @7 :Float32;
|
||||||
dPathPoints @20 :List(Float32);
|
dPathPoints @20 :List(Float32);
|
||||||
@@ -1331,6 +1384,11 @@ struct LateralPlan @0xe1e9318e2ae8b51e {
|
|||||||
solverCost @32 :Float32;
|
solverCost @32 :Float32;
|
||||||
solverState @33 :SolverState;
|
solverState @33 :SolverState;
|
||||||
|
|
||||||
|
latDebugText @34 :Text;
|
||||||
|
|
||||||
|
position @35 :XYZTData;
|
||||||
|
distances @36 :List(Float32);
|
||||||
|
|
||||||
struct SolverState {
|
struct SolverState {
|
||||||
x @0 :List(List(Float32));
|
x @0 :List(List(Float32));
|
||||||
u @1 :List(Float32);
|
u @1 :List(Float32);
|
||||||
@@ -1480,11 +1538,6 @@ struct ProcLog {
|
|||||||
|
|
||||||
cmdline @15 :List(Text);
|
cmdline @15 :List(Text);
|
||||||
exe @16 :Text;
|
exe @16 :Text;
|
||||||
|
|
||||||
# from /proc/<pid>/smaps_rollup (proportional/private memory)
|
|
||||||
memPss @17 :UInt64; # Pss — shared pages split by mapper count
|
|
||||||
memPssAnon @18 :UInt64; # Pss_Anon — private anonymous (heap, stack)
|
|
||||||
memPssShmem @19 :UInt64; # Pss_Shmem — proportional MSGQ/tmpfs share
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CPUTimes {
|
struct CPUTimes {
|
||||||
@@ -2483,7 +2536,7 @@ struct DebugAlert {
|
|||||||
alertText2 @1 :Text;
|
alertText2 @1 :Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserBookmark @0xfe346a9de48d9b50 {
|
struct UserFlag {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SoundPressure @0xdc24138990726023 {
|
struct SoundPressure @0xdc24138990726023 {
|
||||||
@@ -2501,11 +2554,6 @@ struct AudioData {
|
|||||||
sampleRate @1 :UInt32;
|
sampleRate @1 :UInt32;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AudioFeedback {
|
|
||||||
audio @0 :AudioData;
|
|
||||||
blockNum @1 :UInt16;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Touch {
|
struct Touch {
|
||||||
sec @0 :Int64;
|
sec @0 :Int64;
|
||||||
usec @1 :Int64;
|
usec @1 :Int64;
|
||||||
@@ -2532,10 +2580,13 @@ struct Event {
|
|||||||
controlsState @7 :ControlsState;
|
controlsState @7 :ControlsState;
|
||||||
selfdriveState @130 :SelfdriveState;
|
selfdriveState @130 :SelfdriveState;
|
||||||
gyroscope @99 :SensorEventData;
|
gyroscope @99 :SensorEventData;
|
||||||
|
gyroscope2 @100 :SensorEventData;
|
||||||
accelerometer @98 :SensorEventData;
|
accelerometer @98 :SensorEventData;
|
||||||
|
accelerometer2 @101 :SensorEventData;
|
||||||
magnetometer @95 :SensorEventData;
|
magnetometer @95 :SensorEventData;
|
||||||
lightSensor @96 :SensorEventData;
|
lightSensor @96 :SensorEventData;
|
||||||
temperatureSensor @97 :SensorEventData;
|
temperatureSensor @97 :SensorEventData;
|
||||||
|
temperatureSensor2 @123 :SensorEventData;
|
||||||
pandaStates @81 :List(PandaState);
|
pandaStates @81 :List(PandaState);
|
||||||
peripheralState @80 :PeripheralState;
|
peripheralState @80 :PeripheralState;
|
||||||
radarState @13 :RadarState;
|
radarState @13 :RadarState;
|
||||||
@@ -2603,13 +2654,9 @@ struct Event {
|
|||||||
mapRenderState @105: MapRenderState;
|
mapRenderState @105: MapRenderState;
|
||||||
|
|
||||||
# UI services
|
# UI services
|
||||||
|
userFlag @93 :UserFlag;
|
||||||
uiDebug @102 :UIDebug;
|
uiDebug @102 :UIDebug;
|
||||||
|
|
||||||
# driving feedback
|
|
||||||
userBookmark @93 :UserBookmark;
|
|
||||||
bookmarkButton @148 :UserBookmark;
|
|
||||||
audioFeedback @149 :AudioFeedback;
|
|
||||||
|
|
||||||
# *********** debug ***********
|
# *********** debug ***********
|
||||||
testJoystick @52 :Joystick;
|
testJoystick @52 :Joystick;
|
||||||
roadEncodeData @86 :EncodeData;
|
roadEncodeData @86 :EncodeData;
|
||||||
@@ -2627,16 +2674,16 @@ struct Event {
|
|||||||
# DO change the name of the field
|
# DO change the name of the field
|
||||||
# DON'T change anything after the "@"
|
# DON'T change anything after the "@"
|
||||||
customReservedRawData0 @124 :Data;
|
customReservedRawData0 @124 :Data;
|
||||||
customReservedRawData1 @125 :Data;
|
navRouteNavd @125 :NavRoute;
|
||||||
customReservedRawData2 @126 :Data;
|
navInstructionCarrot @126 :NavInstruction;
|
||||||
|
|
||||||
# DO change the name of the field and struct
|
# DO change the name of the field and struct
|
||||||
# DON'T change the ID (e.g. @107)
|
# DON'T change the ID (e.g. @107)
|
||||||
# DON'T change which struct it points to
|
# DON'T change which struct it points to
|
||||||
controlsStateExt @107 :Custom.ControlsStateExt;
|
carrotMan @107 :Custom.CarrotMan;
|
||||||
carStateExt @108 :Custom.CarStateExt;
|
customReserved1 @108 :Custom.CustomReserved1;
|
||||||
modelExt @109 :Custom.ModelExt;
|
customReserved2 @109 :Custom.CustomReserved2;
|
||||||
dashyState @110 :Custom.DashyState;
|
customReserved3 @110 :Custom.CustomReserved3;
|
||||||
customReserved4 @111 :Custom.CustomReserved4;
|
customReserved4 @111 :Custom.CustomReserved4;
|
||||||
customReserved5 @112 :Custom.CustomReserved5;
|
customReserved5 @112 :Custom.CustomReserved5;
|
||||||
customReserved6 @113 :Custom.CustomReserved6;
|
customReserved6 @113 :Custom.CustomReserved6;
|
||||||
@@ -2692,14 +2739,11 @@ struct Event {
|
|||||||
pandaStateDEPRECATED @12 :PandaState;
|
pandaStateDEPRECATED @12 :PandaState;
|
||||||
driverStateDEPRECATED @59 :DriverStateDEPRECATED;
|
driverStateDEPRECATED @59 :DriverStateDEPRECATED;
|
||||||
sensorEventsDEPRECATED @11 :List(SensorEventData);
|
sensorEventsDEPRECATED @11 :List(SensorEventData);
|
||||||
lateralPlanDEPRECATED @64 :LateralPlan;
|
lateralPlan @64 :LateralPlan;
|
||||||
navModelDEPRECATED @104 :NavModelData;
|
navModel @104 :NavModelData;
|
||||||
uiPlanDEPRECATED @106 :UiPlan;
|
uiPlanDEPRECATED @106 :UiPlan;
|
||||||
liveLocationKalmanDEPRECATED @72 :LiveLocationKalman;
|
liveLocationKalman @72 :LiveLocationKalman;
|
||||||
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
|
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
|
||||||
onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED);
|
onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED);
|
||||||
gyroscope2DEPRECATED @100 :SensorEventData;
|
|
||||||
accelerometer2DEPRECATED @101 :SensorEventData;
|
|
||||||
temperatureSensor2DEPRECATED @123 :SensorEventData;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# must be built with scons
|
# must be built with scons
|
||||||
from msgq import fake_event_handle, drain_sock_raw, MultiplePublishersError, IpcError, \
|
from msgq.ipc_pyx import Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \
|
||||||
Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \
|
set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event
|
||||||
set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event
|
from msgq.ipc_pyx import MultiplePublishersError, IpcError
|
||||||
|
from msgq import fake_event_handle, pub_sock, sub_sock, drain_sock_raw
|
||||||
import msgq
|
import msgq
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import capnp
|
import capnp
|
||||||
import time
|
import time
|
||||||
@@ -11,25 +13,11 @@ from typing import Optional, List, Union, Dict
|
|||||||
|
|
||||||
from cereal import log
|
from cereal import log
|
||||||
from cereal.services import SERVICE_LIST
|
from cereal.services import SERVICE_LIST
|
||||||
from openpilot.common.utils import MovingAverage
|
from openpilot.common.util import MovingAverage
|
||||||
|
|
||||||
NO_TRAVERSAL_LIMIT = 2**64-1
|
NO_TRAVERSAL_LIMIT = 2**64-1
|
||||||
|
|
||||||
|
|
||||||
def pub_sock(endpoint: str) -> PubSocket:
|
|
||||||
service = SERVICE_LIST.get(endpoint)
|
|
||||||
segment_size = service.queue_size if service else 0
|
|
||||||
return msgq.pub_sock(endpoint, segment_size)
|
|
||||||
|
|
||||||
|
|
||||||
def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
|
|
||||||
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
|
|
||||||
service = SERVICE_LIST.get(endpoint)
|
|
||||||
segment_size = service.queue_size if service else 0
|
|
||||||
return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate,
|
|
||||||
timeout=timeout, segment_size=segment_size)
|
|
||||||
|
|
||||||
|
|
||||||
def reset_context():
|
def reset_context():
|
||||||
msgq.context = Context()
|
msgq.context = Context()
|
||||||
|
|
||||||
|
|||||||
+48
-18
@@ -1,4 +1,7 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
#include "cereal/messaging/msgq_to_zmq.h"
|
#include "cereal/messaging/msgq_to_zmq.h"
|
||||||
#include "cereal/services.h"
|
#include "cereal/services.h"
|
||||||
@@ -6,35 +9,62 @@
|
|||||||
|
|
||||||
ExitHandler do_exit;
|
ExitHandler do_exit;
|
||||||
|
|
||||||
static std::vector<std::string> get_services(const std::string &whitelist_str, bool zmq_to_msgq) {
|
static std::unordered_set<std::string> parse_whitelist(const std::string& s) {
|
||||||
std::vector<std::string> service_list;
|
std::unordered_set<std::string> out;
|
||||||
for (const auto& it : services) {
|
std::string token;
|
||||||
std::string name = it.second.name;
|
token.reserve(s.size());
|
||||||
bool in_whitelist = whitelist_str.find(name) != std::string::npos;
|
|
||||||
if (zmq_to_msgq && !in_whitelist) {
|
auto flush = [&]() {
|
||||||
continue;
|
if (!token.empty()) {
|
||||||
|
out.insert(token);
|
||||||
|
token.clear();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (char c : s) {
|
||||||
|
// 구분자: 콤마/공백/탭/세미콜론/파이프 등
|
||||||
|
if (std::isspace((unsigned char)c) || c == ',' || c == ';' || c == '|') {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
token.push_back(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> get_services(const std::string& whitelist_str, bool /*zmq_to_msgq*/) {
|
||||||
|
std::vector<std::string> service_list;
|
||||||
|
|
||||||
|
const bool use_filter = !whitelist_str.empty();
|
||||||
|
std::unordered_set<std::string> whitelist = use_filter ? parse_whitelist(whitelist_str)
|
||||||
|
: std::unordered_set<std::string>{};
|
||||||
|
|
||||||
|
for (const auto& it : services) {
|
||||||
|
const std::string& name = it.second.name;
|
||||||
|
if (use_filter && whitelist.find(name) == whitelist.end()) continue;
|
||||||
service_list.push_back(name);
|
service_list.push_back(name);
|
||||||
}
|
}
|
||||||
return service_list;
|
return service_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void msgq_to_zmq(const std::vector<std::string> &endpoints, const std::string &ip) {
|
void msgq_to_zmq(const std::vector<std::string> &endpoints, const std::string &ip) {
|
||||||
MsgqToZmq bridge;
|
MsgqToZmq bridge;
|
||||||
bridge.run(endpoints, ip);
|
bridge.run(endpoints, ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
void zmq_to_msgq(const std::vector<std::string> &endpoints, const std::string &ip) {
|
void zmq_to_msgq(const std::vector<std::string> &endpoints, const std::string &ip) {
|
||||||
auto poller = std::make_unique<BridgeZmqPoller>();
|
auto poller = std::make_unique<ZMQPoller>();
|
||||||
auto pub_context = std::make_unique<Context>();
|
auto pub_context = std::make_unique<MSGQContext>();
|
||||||
auto sub_context = std::make_unique<BridgeZmqContext>();
|
auto sub_context = std::make_unique<ZMQContext>();
|
||||||
std::map<BridgeZmqSubSocket *, PubSocket *> sub2pub;
|
std::map<SubSocket *, PubSocket *> sub2pub;
|
||||||
|
|
||||||
for (auto endpoint : endpoints) {
|
for (auto endpoint : endpoints) {
|
||||||
auto pub_sock = new PubSocket();
|
auto pub_sock = new MSGQPubSocket();
|
||||||
auto sub_sock = new BridgeZmqSubSocket();
|
auto sub_sock = new ZMQSubSocket();
|
||||||
size_t queue_size = services.at(endpoint).queue_size;
|
pub_sock->connect(pub_context.get(), endpoint);
|
||||||
pub_sock->connect(pub_context.get(), endpoint, true, queue_size);
|
|
||||||
sub_sock->connect(sub_context.get(), endpoint, ip, false);
|
sub_sock->connect(sub_context.get(), endpoint, ip, false);
|
||||||
|
|
||||||
poller->registerSocket(sub_sock);
|
poller->registerSocket(sub_sock);
|
||||||
@@ -58,9 +88,9 @@ void zmq_to_msgq(const std::vector<std::string> &endpoints, const std::string &i
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
bool is_zmq_to_msgq = argc > 2;
|
bool is_zmq_to_msgq = argc > 3;
|
||||||
std::string ip = is_zmq_to_msgq ? argv[1] : "127.0.0.1";
|
std::string ip = argc > 2 ? argv[1] : "127.0.0.1";
|
||||||
std::string whitelist_str = is_zmq_to_msgq ? std::string(argv[2]) : "";
|
std::string whitelist_str = argc > 2 ? std::string(argv[2]) : "";
|
||||||
std::vector<std::string> endpoints = get_services(whitelist_str, is_zmq_to_msgq);
|
std::vector<std::string> endpoints = get_services(whitelist_str, is_zmq_to_msgq);
|
||||||
|
|
||||||
if (is_zmq_to_msgq) {
|
if (is_zmq_to_msgq) {
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
#include "cereal/messaging/bridge_zmq.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstring>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
static size_t fnv1a_hash(const std::string &str) {
|
|
||||||
const size_t fnv_prime = 0x100000001b3;
|
|
||||||
size_t hash_value = 0xcbf29ce484222325;
|
|
||||||
for (char c : str) {
|
|
||||||
hash_value ^= (unsigned char)c;
|
|
||||||
hash_value *= fnv_prime;
|
|
||||||
}
|
|
||||||
return hash_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: This is a hack to get the port number from the socket name, might have collisions.
|
|
||||||
static int get_port(std::string endpoint) {
|
|
||||||
size_t hash_value = fnv1a_hash(endpoint);
|
|
||||||
int start_port = 8023;
|
|
||||||
int max_port = 65535;
|
|
||||||
return start_port + (hash_value % (max_port - start_port));
|
|
||||||
}
|
|
||||||
|
|
||||||
BridgeZmqContext::BridgeZmqContext() {
|
|
||||||
context = zmq_ctx_new();
|
|
||||||
}
|
|
||||||
|
|
||||||
BridgeZmqContext::~BridgeZmqContext() {
|
|
||||||
if (context != nullptr) {
|
|
||||||
zmq_ctx_term(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BridgeZmqMessage::init(size_t sz) {
|
|
||||||
size = sz;
|
|
||||||
data = new char[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
void BridgeZmqMessage::init(char *d, size_t sz) {
|
|
||||||
size = sz;
|
|
||||||
data = new char[size];
|
|
||||||
memcpy(data, d, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BridgeZmqMessage::close() {
|
|
||||||
if (size > 0) {
|
|
||||||
delete[] data;
|
|
||||||
}
|
|
||||||
data = nullptr;
|
|
||||||
size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
BridgeZmqMessage::~BridgeZmqMessage() {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
int BridgeZmqSubSocket::connect(BridgeZmqContext *context, std::string endpoint, std::string address, bool conflate, bool check_endpoint) {
|
|
||||||
sock = zmq_socket(context->getRawContext(), ZMQ_SUB);
|
|
||||||
if (sock == nullptr) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
zmq_setsockopt(sock, ZMQ_SUBSCRIBE, "", 0);
|
|
||||||
|
|
||||||
if (conflate) {
|
|
||||||
int arg = 1;
|
|
||||||
zmq_setsockopt(sock, ZMQ_CONFLATE, &arg, sizeof(int));
|
|
||||||
}
|
|
||||||
|
|
||||||
int reconnect_ivl = 500;
|
|
||||||
zmq_setsockopt(sock, ZMQ_RECONNECT_IVL_MAX, &reconnect_ivl, sizeof(reconnect_ivl));
|
|
||||||
|
|
||||||
full_endpoint = "tcp://" + address + ":";
|
|
||||||
if (check_endpoint) {
|
|
||||||
full_endpoint += std::to_string(get_port(endpoint));
|
|
||||||
} else {
|
|
||||||
full_endpoint += endpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
return zmq_connect(sock, full_endpoint.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void BridgeZmqSubSocket::setTimeout(int timeout) {
|
|
||||||
zmq_setsockopt(sock, ZMQ_RCVTIMEO, &timeout, sizeof(int));
|
|
||||||
}
|
|
||||||
|
|
||||||
Message *BridgeZmqSubSocket::receive(bool non_blocking) {
|
|
||||||
zmq_msg_t msg;
|
|
||||||
assert(zmq_msg_init(&msg) == 0);
|
|
||||||
|
|
||||||
int flags = non_blocking ? ZMQ_DONTWAIT : 0;
|
|
||||||
int rc = zmq_msg_recv(&msg, sock, flags);
|
|
||||||
|
|
||||||
Message *ret = nullptr;
|
|
||||||
if (rc >= 0) {
|
|
||||||
ret = new BridgeZmqMessage;
|
|
||||||
ret->init((char *)zmq_msg_data(&msg), zmq_msg_size(&msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
zmq_msg_close(&msg);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
BridgeZmqSubSocket::~BridgeZmqSubSocket() {
|
|
||||||
if (sock != nullptr) {
|
|
||||||
zmq_close(sock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int BridgeZmqPubSocket::connect(BridgeZmqContext *context, std::string endpoint, bool check_endpoint) {
|
|
||||||
sock = zmq_socket(context->getRawContext(), ZMQ_PUB);
|
|
||||||
if (sock == nullptr) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
full_endpoint = "tcp://*:";
|
|
||||||
if (check_endpoint) {
|
|
||||||
full_endpoint += std::to_string(get_port(endpoint));
|
|
||||||
} else {
|
|
||||||
full_endpoint += endpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZMQ pub sockets cannot be shared between processes, so we need to ensure pid stays the same.
|
|
||||||
pid = getpid();
|
|
||||||
|
|
||||||
return zmq_bind(sock, full_endpoint.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
int BridgeZmqPubSocket::sendMessage(Message *message) {
|
|
||||||
assert(pid == getpid());
|
|
||||||
return zmq_send(sock, message->getData(), message->getSize(), ZMQ_DONTWAIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
int BridgeZmqPubSocket::send(char *data, size_t size) {
|
|
||||||
assert(pid == getpid());
|
|
||||||
return zmq_send(sock, data, size, ZMQ_DONTWAIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
BridgeZmqPubSocket::~BridgeZmqPubSocket() {
|
|
||||||
if (sock != nullptr) {
|
|
||||||
zmq_close(sock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BridgeZmqPoller::registerSocket(BridgeZmqSubSocket *socket) {
|
|
||||||
assert(num_polls + 1 < (sizeof(polls) / sizeof(polls[0])));
|
|
||||||
polls[num_polls].socket = socket->getRawSocket();
|
|
||||||
polls[num_polls].events = ZMQ_POLLIN;
|
|
||||||
|
|
||||||
sockets.push_back(socket);
|
|
||||||
num_polls++;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<BridgeZmqSubSocket *> BridgeZmqPoller::poll(int timeout) {
|
|
||||||
std::vector<BridgeZmqSubSocket *> ret;
|
|
||||||
|
|
||||||
int rc = zmq_poll(polls, num_polls, timeout);
|
|
||||||
if (rc < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < num_polls; i++) {
|
|
||||||
if (polls[i].revents) {
|
|
||||||
ret.push_back(sockets[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <zmq.h>
|
|
||||||
|
|
||||||
#include "msgq/ipc.h"
|
|
||||||
|
|
||||||
class BridgeZmqContext {
|
|
||||||
public:
|
|
||||||
BridgeZmqContext();
|
|
||||||
void *getRawContext() { return context; }
|
|
||||||
~BridgeZmqContext();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void *context = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BridgeZmqMessage : public Message {
|
|
||||||
public:
|
|
||||||
void init(size_t size);
|
|
||||||
void init(char *data, size_t size);
|
|
||||||
void close();
|
|
||||||
size_t getSize() { return size; }
|
|
||||||
char *getData() { return data; }
|
|
||||||
~BridgeZmqMessage();
|
|
||||||
|
|
||||||
private:
|
|
||||||
char *data = nullptr;
|
|
||||||
size_t size = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BridgeZmqSubSocket {
|
|
||||||
public:
|
|
||||||
int connect(BridgeZmqContext *context, std::string endpoint, std::string address, bool conflate = false, bool check_endpoint = true);
|
|
||||||
void setTimeout(int timeout);
|
|
||||||
Message *receive(bool non_blocking = false);
|
|
||||||
void *getRawSocket() { return sock; }
|
|
||||||
~BridgeZmqSubSocket();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void *sock = nullptr;
|
|
||||||
std::string full_endpoint;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BridgeZmqPubSocket {
|
|
||||||
public:
|
|
||||||
int connect(BridgeZmqContext *context, std::string endpoint, bool check_endpoint = true);
|
|
||||||
int sendMessage(Message *message);
|
|
||||||
int send(char *data, size_t size);
|
|
||||||
void *getRawSocket() { return sock; }
|
|
||||||
~BridgeZmqPubSocket();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void *sock = nullptr;
|
|
||||||
std::string full_endpoint;
|
|
||||||
int pid = -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BridgeZmqPoller {
|
|
||||||
public:
|
|
||||||
void registerSocket(BridgeZmqSubSocket *socket);
|
|
||||||
std::vector<BridgeZmqSubSocket *> poll(int timeout);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr size_t MAX_BRIDGE_ZMQ_POLLERS = 128;
|
|
||||||
std::vector<BridgeZmqSubSocket *> sockets;
|
|
||||||
zmq_pollitem_t polls[MAX_BRIDGE_ZMQ_POLLERS] = {};
|
|
||||||
size_t num_polls = 0;
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "cereal/services.h"
|
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
|
|
||||||
extern ExitHandler do_exit;
|
extern ExitHandler do_exit;
|
||||||
@@ -22,14 +21,14 @@ static std::string recv_zmq_msg(void *sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MsgqToZmq::run(const std::vector<std::string> &endpoints, const std::string &ip) {
|
void MsgqToZmq::run(const std::vector<std::string> &endpoints, const std::string &ip) {
|
||||||
zmq_context = std::make_unique<BridgeZmqContext>();
|
zmq_context = std::make_unique<ZMQContext>();
|
||||||
msgq_context = std::make_unique<Context>();
|
msgq_context = std::make_unique<MSGQContext>();
|
||||||
|
|
||||||
// Create ZMQPubSockets for each endpoint
|
// Create ZMQPubSockets for each endpoint
|
||||||
for (const auto &endpoint : endpoints) {
|
for (const auto &endpoint : endpoints) {
|
||||||
auto &socket_pair = socket_pairs.emplace_back();
|
auto &socket_pair = socket_pairs.emplace_back();
|
||||||
socket_pair.endpoint = endpoint;
|
socket_pair.endpoint = endpoint;
|
||||||
socket_pair.pub_sock = std::make_unique<BridgeZmqPubSocket>();
|
socket_pair.pub_sock = std::make_unique<ZMQPubSocket>();
|
||||||
int ret = socket_pair.pub_sock->connect(zmq_context.get(), endpoint);
|
int ret = socket_pair.pub_sock->connect(zmq_context.get(), endpoint);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
printf("Failed to create ZMQ publisher for [%s]: %s\n", endpoint.c_str(), zmq_strerror(zmq_errno()));
|
printf("Failed to create ZMQ publisher for [%s]: %s\n", endpoint.c_str(), zmq_strerror(zmq_errno()));
|
||||||
@@ -49,7 +48,7 @@ void MsgqToZmq::run(const std::vector<std::string> &endpoints, const std::string
|
|||||||
|
|
||||||
for (auto sub_sock : msgq_poller->poll(100)) {
|
for (auto sub_sock : msgq_poller->poll(100)) {
|
||||||
// Process messages for each socket
|
// Process messages for each socket
|
||||||
BridgeZmqPubSocket *pub_sock = sub2pub.at(sub_sock);
|
ZMQPubSocket *pub_sock = sub2pub.at(sub_sock);
|
||||||
for (int i = 0; i < MAX_MESSAGES_PER_SOCKET; ++i) {
|
for (int i = 0; i < MAX_MESSAGES_PER_SOCKET; ++i) {
|
||||||
auto msg = std::unique_ptr<Message>(sub_sock->receive(true));
|
auto msg = std::unique_ptr<Message>(sub_sock->receive(true));
|
||||||
if (!msg) break;
|
if (!msg) break;
|
||||||
@@ -72,7 +71,7 @@ void MsgqToZmq::zmqMonitorThread() {
|
|||||||
// Set up ZMQ monitor for each pub socket
|
// Set up ZMQ monitor for each pub socket
|
||||||
for (int i = 0; i < socket_pairs.size(); ++i) {
|
for (int i = 0; i < socket_pairs.size(); ++i) {
|
||||||
std::string addr = "inproc://op-bridge-monitor-" + std::to_string(i);
|
std::string addr = "inproc://op-bridge-monitor-" + std::to_string(i);
|
||||||
zmq_socket_monitor(socket_pairs[i].pub_sock->getRawSocket(), addr.c_str(), ZMQ_EVENT_ACCEPTED | ZMQ_EVENT_DISCONNECTED);
|
zmq_socket_monitor(socket_pairs[i].pub_sock->sock, addr.c_str(), ZMQ_EVENT_ACCEPTED | ZMQ_EVENT_DISCONNECTED);
|
||||||
|
|
||||||
void *monitor_socket = zmq_socket(zmq_context->getRawContext(), ZMQ_PAIR);
|
void *monitor_socket = zmq_socket(zmq_context->getRawContext(), ZMQ_PAIR);
|
||||||
zmq_connect(monitor_socket, addr.c_str());
|
zmq_connect(monitor_socket, addr.c_str());
|
||||||
@@ -109,8 +108,7 @@ void MsgqToZmq::zmqMonitorThread() {
|
|||||||
if (++pair.connected_clients == 1) {
|
if (++pair.connected_clients == 1) {
|
||||||
// Create new MSGQ subscriber socket and map to ZMQ publisher
|
// Create new MSGQ subscriber socket and map to ZMQ publisher
|
||||||
pair.sub_sock = std::make_unique<MSGQSubSocket>();
|
pair.sub_sock = std::make_unique<MSGQSubSocket>();
|
||||||
size_t queue_size = services.at(pair.endpoint).queue_size;
|
pair.sub_sock->connect(msgq_context.get(), pair.endpoint, "127.0.0.1");
|
||||||
pair.sub_sock->connect(msgq_context.get(), pair.endpoint, "127.0.0.1", false, true, queue_size);
|
|
||||||
sub2pub[pair.sub_sock.get()] = pair.pub_sock.get();
|
sub2pub[pair.sub_sock.get()] = pair.pub_sock.get();
|
||||||
registerSockets();
|
registerSockets();
|
||||||
}
|
}
|
||||||
@@ -130,7 +128,7 @@ void MsgqToZmq::zmqMonitorThread() {
|
|||||||
|
|
||||||
// Clean up monitor sockets
|
// Clean up monitor sockets
|
||||||
for (int i = 0; i < pollitems.size(); ++i) {
|
for (int i = 0; i < pollitems.size(); ++i) {
|
||||||
zmq_socket_monitor(socket_pairs[i].pub_sock->getRawSocket(), nullptr, 0);
|
zmq_socket_monitor(socket_pairs[i].pub_sock->sock, nullptr, 0);
|
||||||
zmq_close(pollitems[i].socket);
|
zmq_close(pollitems[i].socket);
|
||||||
}
|
}
|
||||||
cv.notify_one();
|
cv.notify_one();
|
||||||
|
|||||||
@@ -7,8 +7,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#define private public
|
||||||
#include "msgq/impl_msgq.h"
|
#include "msgq/impl_msgq.h"
|
||||||
#include "cereal/messaging/bridge_zmq.h"
|
#include "msgq/impl_zmq.h"
|
||||||
|
|
||||||
class MsgqToZmq {
|
class MsgqToZmq {
|
||||||
public:
|
public:
|
||||||
@@ -21,16 +22,16 @@ protected:
|
|||||||
|
|
||||||
struct SocketPair {
|
struct SocketPair {
|
||||||
std::string endpoint;
|
std::string endpoint;
|
||||||
std::unique_ptr<BridgeZmqPubSocket> pub_sock;
|
std::unique_ptr<ZMQPubSocket> pub_sock;
|
||||||
std::unique_ptr<MSGQSubSocket> sub_sock;
|
std::unique_ptr<MSGQSubSocket> sub_sock;
|
||||||
int connected_clients = 0;
|
int connected_clients = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<Context> msgq_context;
|
std::unique_ptr<MSGQContext> msgq_context;
|
||||||
std::unique_ptr<BridgeZmqContext> zmq_context;
|
std::unique_ptr<ZMQContext> zmq_context;
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
std::condition_variable cv;
|
std::condition_variable cv;
|
||||||
std::unique_ptr<MSGQPoller> msgq_poller;
|
std::unique_ptr<MSGQPoller> msgq_poller;
|
||||||
std::map<SubSocket *, BridgeZmqPubSocket *> sub2pub;
|
std::map<SubSocket *, ZMQPubSocket *> sub2pub;
|
||||||
std::vector<SocketPair> socket_pairs;
|
std::vector<SocketPair> socket_pairs;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ MessageContext message_context;
|
|||||||
struct SubMaster::SubMessage {
|
struct SubMaster::SubMessage {
|
||||||
std::string name;
|
std::string name;
|
||||||
SubSocket *socket = nullptr;
|
SubSocket *socket = nullptr;
|
||||||
float freq = 0.0f;
|
int freq = 0;
|
||||||
bool updated = false, alive = false, valid = false, ignore_alive;
|
bool updated = false, alive = false, valid = false, ignore_alive;
|
||||||
uint64_t rcv_time = 0, rcv_frame = 0;
|
uint64_t rcv_time = 0, rcv_frame = 0;
|
||||||
void *allocated_msg_reader = nullptr;
|
void *allocated_msg_reader = nullptr;
|
||||||
@@ -47,10 +47,13 @@ SubMaster::SubMaster(const std::vector<const char *> &service_list, const std::v
|
|||||||
const char *address, const std::vector<const char *> &ignore_alive) {
|
const char *address, const std::vector<const char *> &ignore_alive) {
|
||||||
poller_ = Poller::create();
|
poller_ = Poller::create();
|
||||||
for (auto name : service_list) {
|
for (auto name : service_list) {
|
||||||
|
if (services.count(std::string(name)) == 0) {
|
||||||
|
printf("Unknown service name: %s\n", name);
|
||||||
|
}
|
||||||
assert(services.count(std::string(name)) > 0);
|
assert(services.count(std::string(name)) > 0);
|
||||||
|
|
||||||
service serv = services.at(std::string(name));
|
service serv = services.at(std::string(name));
|
||||||
SubSocket *socket = SubSocket::create(message_context.context(), name, address ? address : "127.0.0.1", true, true, serv.queue_size);
|
SubSocket *socket = SubSocket::create(message_context.context(), name, address ? address : "127.0.0.1", true);
|
||||||
assert(socket != 0);
|
assert(socket != 0);
|
||||||
bool is_polled = inList(poll, name) || poll.empty();
|
bool is_polled = inList(poll, name) || poll.empty();
|
||||||
if (is_polled) poller_->registerSocket(socket);
|
if (is_polled) poller_->registerSocket(socket);
|
||||||
@@ -186,9 +189,11 @@ SubMaster::~SubMaster() {
|
|||||||
|
|
||||||
PubMaster::PubMaster(const std::vector<const char *> &service_list) {
|
PubMaster::PubMaster(const std::vector<const char *> &service_list) {
|
||||||
for (auto name : service_list) {
|
for (auto name : service_list) {
|
||||||
|
if (services.count(name) == 0) {
|
||||||
|
printf("PubMaster Error: unknown service '%s'\n", name);
|
||||||
|
}
|
||||||
assert(services.count(name) > 0);
|
assert(services.count(name) > 0);
|
||||||
service serv = services.at(std::string(name));
|
PubSocket *socket = PubSocket::create(message_context.context(), name);
|
||||||
PubSocket *socket = PubSocket::create(message_context.context(), name, true, serv.queue_size);
|
|
||||||
assert(socket);
|
assert(socket);
|
||||||
sockets_[name] = socket;
|
sockets_[name] = socket;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import numbers
|
|||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from openpilot.common.parameterized import parameterized
|
from parameterized import parameterized
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from cereal import log, car
|
from cereal import log, car
|
||||||
@@ -30,7 +30,7 @@ def zmq_sleep(t=1):
|
|||||||
|
|
||||||
# TODO: this should take any capnp struct and returrn a msg with random populated data
|
# TODO: this should take any capnp struct and returrn a msg with random populated data
|
||||||
def random_carstate():
|
def random_carstate():
|
||||||
fields = ["vEgo", "aEgo", "brake", "steeringAngleDeg"]
|
fields = ["vEgo", "aEgo", "gas", "steeringAngleDeg"]
|
||||||
msg = messaging.new_message("carState")
|
msg = messaging.new_message("carState")
|
||||||
cs = msg.carState
|
cs = msg.carState
|
||||||
for f in fields:
|
for f in fields:
|
||||||
@@ -177,8 +177,8 @@ class TestMessaging:
|
|||||||
|
|
||||||
# wait 5 socket timeouts before sending
|
# wait 5 socket timeouts before sending
|
||||||
msg = random_carstate()
|
msg = random_carstate()
|
||||||
start_time = time.monotonic()
|
|
||||||
delayed_send(sock_timeout*5, pub_sock, msg.to_bytes())
|
delayed_send(sock_timeout*5, pub_sock, msg.to_bytes())
|
||||||
|
start_time = time.monotonic()
|
||||||
recvd = messaging.recv_one_retry(sub_sock)
|
recvd = messaging.recv_one_retry(sub_sock)
|
||||||
assert (time.monotonic() - start_time) >= sock_timeout*5
|
assert (time.monotonic() - start_time) >= sock_timeout*5
|
||||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import cereal.messaging as messaging
|
|||||||
from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
|
from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
|
||||||
random_bytes, random_carstate, assert_carstate, \
|
random_bytes, random_carstate, assert_carstate, \
|
||||||
zmq_sleep
|
zmq_sleep
|
||||||
from cereal.services import SERVICE_LIST
|
|
||||||
|
|
||||||
|
|
||||||
class TestSubMaster:
|
class TestSubMaster:
|
||||||
@@ -27,9 +26,7 @@ class TestSubMaster:
|
|||||||
sm = messaging.SubMaster(socks)
|
sm = messaging.SubMaster(socks)
|
||||||
assert sm.frame == -1
|
assert sm.frame == -1
|
||||||
assert not any(sm.updated.values())
|
assert not any(sm.updated.values())
|
||||||
assert not any(sm.seen.values())
|
assert not any(sm.alive.values())
|
||||||
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in sm.services}
|
|
||||||
assert all(sm.alive[s] == sm.valid[s] == sm.freq_ok[s] == on_demand[s] for s in sm.services)
|
|
||||||
assert all(t == 0. for t in sm.recv_time.values())
|
assert all(t == 0. for t in sm.recv_time.values())
|
||||||
assert all(f == 0 for f in sm.recv_frame.values())
|
assert all(f == 0 for f in sm.recv_frame.values())
|
||||||
assert all(t == 0 for t in sm.logMonoTime.values())
|
assert all(t == 0 for t in sm.logMonoTime.values())
|
||||||
@@ -86,7 +83,6 @@ class TestSubMaster:
|
|||||||
"cameraOdometry": (20, 10),
|
"cameraOdometry": (20, 10),
|
||||||
"liveCalibration": (4, 4),
|
"liveCalibration": (4, 4),
|
||||||
"carParams": (None, None),
|
"carParams": (None, None),
|
||||||
"userBookmark": (None, None),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for service, (max_freq, min_freq) in checks.items():
|
for service, (max_freq, min_freq) in checks.items():
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from openpilot.common.parameterized import parameterized
|
from parameterized import parameterized
|
||||||
|
|
||||||
import cereal.services as services
|
import cereal.services as services
|
||||||
from cereal.services import SERVICE_LIST
|
from cereal.services import SERVICE_LIST
|
||||||
|
|||||||
Executable → Regular
+28
-33
@@ -1,44 +1,37 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from enum import IntEnum
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
# TODO: this should be automatically determined using the capnp schema
|
|
||||||
class QueueSize(IntEnum):
|
|
||||||
BIG = 10 * 1024 * 1024 # 10MB - video frames, large AI outputs
|
|
||||||
MEDIUM = 2 * 1024 * 1024 # 2MB - high freq (CAN), livestream
|
|
||||||
SMALL = 250 * 1024 # 250KB - most services
|
|
||||||
|
|
||||||
|
|
||||||
class Service:
|
class Service:
|
||||||
def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None,
|
def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None):
|
||||||
queue_size: QueueSize = QueueSize.SMALL):
|
|
||||||
self.should_log = should_log
|
self.should_log = should_log
|
||||||
self.frequency = frequency
|
self.frequency = frequency
|
||||||
self.decimation = decimation
|
self.decimation = decimation
|
||||||
self.queue_size = queue_size
|
|
||||||
|
|
||||||
|
|
||||||
_services: dict[str, tuple] = {
|
_services: dict[str, tuple] = {
|
||||||
# service: (should_log, frequency, qlog decimation (optional))
|
# service: (should_log, frequency, qlog decimation (optional))
|
||||||
# note: the "EncodeIdx" packets will still be in the log
|
# note: the "EncodeIdx" packets will still be in the log
|
||||||
"gyroscope": (True, 104., 104),
|
"gyroscope": (True, 104., 104),
|
||||||
|
"gyroscope2": (True, 100., 100),
|
||||||
"accelerometer": (True, 104., 104),
|
"accelerometer": (True, 104., 104),
|
||||||
|
"accelerometer2": (True, 100., 100),
|
||||||
"magnetometer": (True, 25.),
|
"magnetometer": (True, 25.),
|
||||||
"lightSensor": (True, 100., 100),
|
"lightSensor": (True, 100., 100),
|
||||||
"temperatureSensor": (True, 2., 200),
|
"temperatureSensor": (True, 2., 200),
|
||||||
|
"temperatureSensor2": (True, 2., 200),
|
||||||
"gpsNMEA": (True, 9.),
|
"gpsNMEA": (True, 9.),
|
||||||
"deviceState": (True, 2., 1),
|
"deviceState": (True, 2., 1),
|
||||||
"touch": (True, 20., 1),
|
"touch": (True, 20., 1),
|
||||||
"can": (True, 100., 2053, QueueSize.BIG), # decimation gives ~3 msgs in a full segment
|
"can": (True, 100., 2053), # decimation gives ~3 msgs in a full segment
|
||||||
"controlsState": (True, 100., 10, QueueSize.MEDIUM),
|
"controlsState": (True, 100., 10),
|
||||||
"selfdriveState": (True, 100., 10),
|
"selfdriveState": (True, 100., 10),
|
||||||
"pandaStates": (True, 10., 1),
|
"pandaStates": (True, 10., 1),
|
||||||
"peripheralState": (True, 2., 1),
|
"peripheralState": (True, 2., 1),
|
||||||
"radarState": (True, 20., 5),
|
"radarState": (True, 20., 5),
|
||||||
"roadEncodeIdx": (False, 20., 1),
|
"roadEncodeIdx": (False, 20., 1),
|
||||||
"liveTracks": (True, 20.),
|
"liveTracks": (True, 20.),
|
||||||
"sendcan": (True, 100., 139, QueueSize.MEDIUM),
|
"sendcan": (True, 100., 139),
|
||||||
"logMessage": (True, 0.),
|
"logMessage": (True, 0.),
|
||||||
"errorLogMessage": (True, 0., 1),
|
"errorLogMessage": (True, 0., 1),
|
||||||
"liveCalibration": (True, 4., 4),
|
"liveCalibration": (True, 4., 4),
|
||||||
@@ -50,7 +43,7 @@ _services: dict[str, tuple] = {
|
|||||||
"carOutput": (True, 100., 10),
|
"carOutput": (True, 100., 10),
|
||||||
"longitudinalPlan": (True, 20., 10),
|
"longitudinalPlan": (True, 20., 10),
|
||||||
"driverAssistance": (True, 20., 20),
|
"driverAssistance": (True, 20., 20),
|
||||||
"procLog": (True, 0.5, 15, QueueSize.BIG),
|
"procLog": (True, 0.5, 15),
|
||||||
"gpsLocationExternal": (True, 10., 10),
|
"gpsLocationExternal": (True, 10., 10),
|
||||||
"gpsLocation": (True, 1., 1),
|
"gpsLocation": (True, 1., 1),
|
||||||
"ubloxGnss": (True, 10.),
|
"ubloxGnss": (True, 10.),
|
||||||
@@ -59,9 +52,11 @@ _services: dict[str, tuple] = {
|
|||||||
"clocks": (True, 0.1, 1),
|
"clocks": (True, 0.1, 1),
|
||||||
"ubloxRaw": (True, 20.),
|
"ubloxRaw": (True, 20.),
|
||||||
"livePose": (True, 20., 4),
|
"livePose": (True, 20., 4),
|
||||||
|
#"liveLocationKalman": (True, 20., 5),
|
||||||
"liveParameters": (True, 20., 5),
|
"liveParameters": (True, 20., 5),
|
||||||
"cameraOdometry": (True, 20., 10),
|
"cameraOdometry": (True, 20., 10),
|
||||||
"thumbnail": (True, 1 / 60., 1),
|
"thumbnail": (True, 1 / 60., 1),
|
||||||
|
"lateralPlan": (True, 20., 5),
|
||||||
"onroadEvents": (True, 1., 1),
|
"onroadEvents": (True, 1., 1),
|
||||||
"carParams": (True, 0.02, 1),
|
"carParams": (True, 0.02, 1),
|
||||||
"roadCameraState": (True, 20., 20),
|
"roadCameraState": (True, 20., 20),
|
||||||
@@ -72,40 +67,40 @@ _services: dict[str, tuple] = {
|
|||||||
"wideRoadEncodeIdx": (False, 20., 1),
|
"wideRoadEncodeIdx": (False, 20., 1),
|
||||||
"wideRoadCameraState": (True, 20., 20),
|
"wideRoadCameraState": (True, 20., 20),
|
||||||
"drivingModelData": (True, 20., 10),
|
"drivingModelData": (True, 20., 10),
|
||||||
"modelV2": (True, 20., None, QueueSize.BIG),
|
"modelV2": (True, 20.),
|
||||||
"managerState": (True, 2., 1),
|
"managerState": (True, 2., 1),
|
||||||
"uploaderState": (True, 0., 1),
|
"uploaderState": (True, 0., 1),
|
||||||
"navInstruction": (True, 1., 10),
|
"navInstruction": (True, 1., 10),
|
||||||
"navRoute": (True, 0.),
|
"navRoute": (True, 0.),
|
||||||
|
"navRouteNavd": (True, 0.),
|
||||||
"navThumbnail": (True, 0.),
|
"navThumbnail": (True, 0.),
|
||||||
|
"navModel": (True, 2., 4.),
|
||||||
|
"mapRenderState": (True, 2., 1.),
|
||||||
"qRoadEncodeIdx": (False, 20.),
|
"qRoadEncodeIdx": (False, 20.),
|
||||||
"userBookmark": (True, 0., 1),
|
"userFlag": (True, 0., 1),
|
||||||
"soundPressure": (True, 10., 10),
|
"soundPressure": (True, 10., 10),
|
||||||
"rawAudioData": (False, 20.),
|
"rawAudioData": (False, 20.),
|
||||||
"bookmarkButton": (True, 0., 1),
|
|
||||||
"audioFeedback": (True, 0., 1),
|
"carrotMan": (True, 0.),
|
||||||
"roadEncodeData": (False, 20., None, QueueSize.BIG),
|
"navInstructionCarrot": (True, 1., 10),
|
||||||
"driverEncodeData": (False, 20., None, QueueSize.BIG),
|
|
||||||
"wideRoadEncodeData": (False, 20., None, QueueSize.BIG),
|
|
||||||
"qRoadEncodeData": (False, 20., None, QueueSize.BIG),
|
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
"uiDebug": (True, 0., 1),
|
"uiDebug": (True, 0., 1),
|
||||||
"testJoystick": (True, 0.),
|
"testJoystick": (True, 0.),
|
||||||
"alertDebug": (True, 20., 5),
|
"alertDebug": (True, 20., 5),
|
||||||
|
"roadEncodeData": (False, 20.),
|
||||||
|
"driverEncodeData": (False, 20.),
|
||||||
|
"wideRoadEncodeData": (False, 20.),
|
||||||
|
"qRoadEncodeData": (False, 20.),
|
||||||
"livestreamWideRoadEncodeIdx": (False, 20.),
|
"livestreamWideRoadEncodeIdx": (False, 20.),
|
||||||
"livestreamRoadEncodeIdx": (False, 20.),
|
"livestreamRoadEncodeIdx": (False, 20.),
|
||||||
"livestreamDriverEncodeIdx": (False, 20.),
|
"livestreamDriverEncodeIdx": (False, 20.),
|
||||||
"livestreamWideRoadEncodeData": (False, 20., None, QueueSize.MEDIUM),
|
"livestreamWideRoadEncodeData": (False, 20.),
|
||||||
"livestreamRoadEncodeData": (False, 20., None, QueueSize.MEDIUM),
|
"livestreamRoadEncodeData": (False, 20.),
|
||||||
"livestreamDriverEncodeData": (False, 20., None, QueueSize.MEDIUM),
|
"livestreamDriverEncodeData": (False, 20.),
|
||||||
"customReservedRawData0": (True, 0.),
|
"customReservedRawData0": (True, 0.),
|
||||||
"customReservedRawData1": (True, 0.),
|
"customReservedRawData1": (True, 0.),
|
||||||
"customReservedRawData2": (True, 0.),
|
"customReservedRawData2": (True, 0.),
|
||||||
"controlsStateExt": (True, 100.),
|
|
||||||
"carStateExt": (True, 100.),
|
|
||||||
"modelExt": (True, 20.),
|
|
||||||
"dashyState": (True, 0.), # Aggregated dashy UI state (optional)
|
|
||||||
}
|
}
|
||||||
SERVICE_LIST = {name: Service(*vals) for
|
SERVICE_LIST = {name: Service(*vals) for
|
||||||
idx, (name, vals) in enumerate(_services.items())}
|
idx, (name, vals) in enumerate(_services.items())}
|
||||||
@@ -120,13 +115,13 @@ def build_header():
|
|||||||
h += "#include <map>\n"
|
h += "#include <map>\n"
|
||||||
h += "#include <string>\n"
|
h += "#include <string>\n"
|
||||||
|
|
||||||
h += "struct service { std::string name; bool should_log; float frequency; int decimation; size_t queue_size; };\n"
|
h += "struct service { std::string name; bool should_log; int frequency; int decimation; };\n"
|
||||||
h += "static std::map<std::string, service> services = {\n"
|
h += "static std::map<std::string, service> services = {\n"
|
||||||
for k, v in SERVICE_LIST.items():
|
for k, v in SERVICE_LIST.items():
|
||||||
should_log = "true" if v.should_log else "false"
|
should_log = "true" if v.should_log else "false"
|
||||||
decimation = -1 if v.decimation is None else v.decimation
|
decimation = -1 if v.decimation is None else v.decimation
|
||||||
h += ' { "%s", {"%s", %s, %f, %d, %d}},\n' % \
|
h += ' { "%s", {"%s", %s, %d, %d}},\n' % \
|
||||||
(k, k, should_log, v.frequency, decimation, v.queue_size)
|
(k, k, should_log, v.frequency, decimation)
|
||||||
h += "};\n"
|
h += "};\n"
|
||||||
|
|
||||||
h += "#endif\n"
|
h += "#endif\n"
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
comment: false
|
||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
informational: true
|
||||||
|
patch: off
|
||||||
|
|
||||||
|
ignore:
|
||||||
|
- "**/test_*.py"
|
||||||
|
- "selfdrive/test/**"
|
||||||
|
- "system/version.py" # codecov changes depending on if we are in a branch or not
|
||||||
|
- "tools"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*.cpp
|
||||||
+8
-1
@@ -4,7 +4,9 @@ common_libs = [
|
|||||||
'params.cc',
|
'params.cc',
|
||||||
'swaglog.cc',
|
'swaglog.cc',
|
||||||
'util.cc',
|
'util.cc',
|
||||||
|
'watchdog.cc',
|
||||||
'ratekeeper.cc',
|
'ratekeeper.cc',
|
||||||
|
'clutil.cc',
|
||||||
]
|
]
|
||||||
|
|
||||||
_common = env.Library('common', common_libs, LIBS="json11")
|
_common = env.Library('common', common_libs, LIBS="json11")
|
||||||
@@ -18,6 +20,11 @@ if GetOption('extras'):
|
|||||||
# Cython bindings
|
# Cython bindings
|
||||||
params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11'])
|
params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11'])
|
||||||
|
|
||||||
common_python = [params_python]
|
SConscript([
|
||||||
|
'transformations/SConscript',
|
||||||
|
])
|
||||||
|
|
||||||
|
Import('transformations_python')
|
||||||
|
common_python = [params_python, transformations_python]
|
||||||
|
|
||||||
Export('common_python')
|
Export('common_python')
|
||||||
|
|||||||
+6
-22
@@ -7,15 +7,11 @@ from openpilot.system.version import get_version
|
|||||||
|
|
||||||
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')
|
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')
|
||||||
|
|
||||||
# name: jwt signature algorithm
|
|
||||||
KEYS = {"id_rsa": "RS256",
|
|
||||||
"id_ecdsa": "ES256"}
|
|
||||||
|
|
||||||
|
|
||||||
class Api:
|
class Api:
|
||||||
def __init__(self, dongle_id):
|
def __init__(self, dongle_id):
|
||||||
self.dongle_id = dongle_id
|
self.dongle_id = dongle_id
|
||||||
self.jwt_algorithm, self.private_key, _ = get_key_pair()
|
with open(Paths.persist_root()+'/comma/id_rsa') as f:
|
||||||
|
self.private_key = f.read()
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
return self.request('GET', *args, **kwargs)
|
return self.request('GET', *args, **kwargs)
|
||||||
@@ -26,7 +22,7 @@ class Api:
|
|||||||
def request(self, method, endpoint, timeout=None, access_token=None, **params):
|
def request(self, method, endpoint, timeout=None, access_token=None, **params):
|
||||||
return api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params)
|
return api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params)
|
||||||
|
|
||||||
def get_token(self, payload_extra=None, expiry_hours=1):
|
def get_token(self, expiry_hours=1):
|
||||||
now = datetime.now(UTC).replace(tzinfo=None)
|
now = datetime.now(UTC).replace(tzinfo=None)
|
||||||
payload = {
|
payload = {
|
||||||
'identity': self.dongle_id,
|
'identity': self.dongle_id,
|
||||||
@@ -34,29 +30,17 @@ class Api:
|
|||||||
'iat': now,
|
'iat': now,
|
||||||
'exp': now + timedelta(hours=expiry_hours)
|
'exp': now + timedelta(hours=expiry_hours)
|
||||||
}
|
}
|
||||||
if payload_extra is not None:
|
token = jwt.encode(payload, self.private_key, algorithm='RS256')
|
||||||
payload.update(payload_extra)
|
|
||||||
token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm)
|
|
||||||
if isinstance(token, bytes):
|
if isinstance(token, bytes):
|
||||||
token = token.decode('utf8')
|
token = token.decode('utf8')
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):
|
def api_get(endpoint, method='GET', timeout=None, access_token=None, **params):
|
||||||
headers = {}
|
headers = {}
|
||||||
if access_token is not None:
|
if access_token is not None:
|
||||||
headers['Authorization'] = "JWT " + access_token
|
headers['Authorization'] = "JWT " + access_token
|
||||||
|
|
||||||
headers['User-Agent'] = "openpilot-" + get_version()
|
headers['User-Agent'] = "openpilot-" + get_version()
|
||||||
|
|
||||||
# TODO: add session to Api
|
return requests.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params)
|
||||||
req = requests if session is None else session
|
|
||||||
return req.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params)
|
|
||||||
|
|
||||||
|
|
||||||
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
|
|
||||||
for key in KEYS:
|
|
||||||
if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'):
|
|
||||||
with open(Paths.persist_root() + f'/comma/{key}') as private, open(Paths.persist_root() + f'/comma/{key}.pub') as public:
|
|
||||||
return KEYS[key], private.read(), public.read()
|
|
||||||
return None, None, None
|
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#include "common/clutil.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "common/util.h"
|
||||||
|
#include "common/swaglog.h"
|
||||||
|
|
||||||
|
namespace { // helper functions
|
||||||
|
|
||||||
|
template <typename Func, typename Id, typename Name>
|
||||||
|
std::string get_info(Func get_info_func, Id id, Name param_name) {
|
||||||
|
size_t size = 0;
|
||||||
|
CL_CHECK(get_info_func(id, param_name, 0, NULL, &size));
|
||||||
|
std::string info(size, '\0');
|
||||||
|
CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL));
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); }
|
||||||
|
inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); }
|
||||||
|
|
||||||
|
void cl_print_info(cl_platform_id platform, cl_device_id device) {
|
||||||
|
size_t work_group_size = 0;
|
||||||
|
cl_device_type device_type = 0;
|
||||||
|
clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL);
|
||||||
|
clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL);
|
||||||
|
const char *type_str = "Other...";
|
||||||
|
switch (device_type) {
|
||||||
|
case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break;
|
||||||
|
case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break;
|
||||||
|
case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str());
|
||||||
|
LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str());
|
||||||
|
LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str());
|
||||||
|
LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str());
|
||||||
|
LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str());
|
||||||
|
LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str());
|
||||||
|
LOGD("max work group size: %zu", work_group_size);
|
||||||
|
LOGD("type = %d, %s", (int)device_type, type_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cl_print_build_errors(cl_program program, cl_device_id device) {
|
||||||
|
cl_build_status status;
|
||||||
|
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL);
|
||||||
|
size_t log_size;
|
||||||
|
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size);
|
||||||
|
std::string log(log_size, '\0');
|
||||||
|
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL);
|
||||||
|
|
||||||
|
LOGE("build failed; status=%d, log: %s", status, log.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
cl_device_id cl_get_device_id(cl_device_type device_type) {
|
||||||
|
cl_uint num_platforms = 0;
|
||||||
|
CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms));
|
||||||
|
std::unique_ptr<cl_platform_id[]> platform_ids = std::make_unique<cl_platform_id[]>(num_platforms);
|
||||||
|
CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_platforms; ++i) {
|
||||||
|
LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str());
|
||||||
|
|
||||||
|
// Get first device
|
||||||
|
if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) {
|
||||||
|
cl_print_info(platform_ids[i], device_id);
|
||||||
|
return device_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOGE("No valid openCL platform found");
|
||||||
|
assert(0);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
cl_context cl_create_context(cl_device_id device_id) {
|
||||||
|
return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err));
|
||||||
|
}
|
||||||
|
|
||||||
|
void cl_release_context(cl_context context) {
|
||||||
|
clReleaseContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) {
|
||||||
|
return cl_program_from_source(ctx, device_id, util::read_file(path), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) {
|
||||||
|
const char *csrc = src.c_str();
|
||||||
|
cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err));
|
||||||
|
if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) {
|
||||||
|
cl_print_build_errors(prg, device_id);
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
return prg;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <OpenCL/cl.h>
|
||||||
|
#else
|
||||||
|
#include <CL/cl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#define CL_CHECK(_expr) \
|
||||||
|
do { \
|
||||||
|
assert(CL_SUCCESS == (_expr)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define CL_CHECK_ERR(_expr) \
|
||||||
|
({ \
|
||||||
|
cl_int err = CL_INVALID_VALUE; \
|
||||||
|
__typeof__(_expr) _ret = _expr; \
|
||||||
|
assert(_ret&& err == CL_SUCCESS); \
|
||||||
|
_ret; \
|
||||||
|
})
|
||||||
|
|
||||||
|
cl_device_id cl_get_device_id(cl_device_type device_type);
|
||||||
|
cl_context cl_create_context(cl_device_id device_id);
|
||||||
|
void cl_release_context(cl_context context);
|
||||||
|
cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr);
|
||||||
|
cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import numpy as np
|
||||||
|
|
||||||
|
class Conversions:
|
||||||
|
# Speed
|
||||||
|
MPH_TO_KPH = 1.609344
|
||||||
|
KPH_TO_MPH = 1. / MPH_TO_KPH
|
||||||
|
MS_TO_KPH = 3.6
|
||||||
|
KPH_TO_MS = 1. / MS_TO_KPH
|
||||||
|
MS_TO_MPH = MS_TO_KPH * KPH_TO_MPH
|
||||||
|
MPH_TO_MS = MPH_TO_KPH * KPH_TO_MS
|
||||||
|
MS_TO_KNOTS = 1.9438
|
||||||
|
KNOTS_TO_MS = 1. / MS_TO_KNOTS
|
||||||
|
|
||||||
|
# Angle
|
||||||
|
DEG_TO_RAD = np.pi / 180.
|
||||||
|
RAD_TO_DEG = 1. / DEG_TO_RAD
|
||||||
|
|
||||||
|
# Mass
|
||||||
|
LB_TO_KG = 0.453592
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# remove all keys that end in DEPRECATED
|
||||||
|
def strip_deprecated_keys(d):
|
||||||
|
for k in list(d.keys()):
|
||||||
|
if isinstance(k, str):
|
||||||
|
if k.endswith('DEPRECATED'):
|
||||||
|
d.pop(k)
|
||||||
|
elif isinstance(d[k], dict):
|
||||||
|
strip_deprecated_keys(d[k])
|
||||||
|
return d
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import platform
|
||||||
|
|
||||||
|
|
||||||
|
def suffix():
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
return ".dylib"
|
||||||
|
else:
|
||||||
|
return ".so"
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import math
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
CHUNK_SIZE = 45 * 1024 * 1024 # 45MB, under GitHub's 50MB limit
|
|
||||||
|
|
||||||
def get_chunk_name(name, idx, num_chunks):
|
|
||||||
return f"{name}.chunk{idx+1:02d}of{num_chunks:02d}"
|
|
||||||
|
|
||||||
def get_manifest_path(name):
|
|
||||||
return f"{name}.chunkmanifest"
|
|
||||||
|
|
||||||
def get_chunk_paths(path, file_size):
|
|
||||||
num_chunks = math.ceil(file_size / CHUNK_SIZE)
|
|
||||||
return [get_manifest_path(path)] + [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)]
|
|
||||||
|
|
||||||
def chunk_file(path, targets):
|
|
||||||
manifest_path, *chunk_paths = targets
|
|
||||||
with open(path, 'rb') as f:
|
|
||||||
data = f.read()
|
|
||||||
actual_num_chunks = max(1, math.ceil(len(data) / CHUNK_SIZE))
|
|
||||||
assert len(chunk_paths) >= actual_num_chunks, f"Allowed {len(chunk_paths)} chunks but needs at least {actual_num_chunks}, for path {path}"
|
|
||||||
for i, chunk_path in enumerate(chunk_paths):
|
|
||||||
with open(chunk_path, 'wb') as f:
|
|
||||||
f.write(data[i * CHUNK_SIZE:(i + 1) * CHUNK_SIZE])
|
|
||||||
Path(manifest_path).write_text(str(len(chunk_paths)))
|
|
||||||
os.remove(path)
|
|
||||||
|
|
||||||
|
|
||||||
def read_file_chunked(path):
|
|
||||||
manifest_path = get_manifest_path(path)
|
|
||||||
if os.path.isfile(manifest_path):
|
|
||||||
num_chunks = int(Path(manifest_path).read_text().strip())
|
|
||||||
return b''.join(Path(get_chunk_name(path, i, num_chunks)).read_bytes() for i in range(num_chunks))
|
|
||||||
if os.path.isfile(path):
|
|
||||||
return Path(path).read_bytes()
|
|
||||||
raise FileNotFoundError(path)
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import io
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import contextlib
|
||||||
|
import zstandard as zstd
|
||||||
|
|
||||||
|
LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackReader:
|
||||||
|
"""Wraps a file, but overrides the read method to also
|
||||||
|
call a callback function with the number of bytes read so far."""
|
||||||
|
def __init__(self, f, callback, *args):
|
||||||
|
self.f = f
|
||||||
|
self.callback = callback
|
||||||
|
self.cb_args = args
|
||||||
|
self.total_read = 0
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.f, attr)
|
||||||
|
|
||||||
|
def read(self, *args, **kwargs):
|
||||||
|
chunk = self.f.read(*args, **kwargs)
|
||||||
|
self.total_read += len(chunk)
|
||||||
|
self.callback(*self.cb_args, self.total_read)
|
||||||
|
return chunk
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: str = None, newline: str = None,
|
||||||
|
overwrite: bool = False):
|
||||||
|
"""Write to a file atomically using a temporary file in the same directory as the destination file."""
|
||||||
|
dir_name = os.path.dirname(path)
|
||||||
|
|
||||||
|
if not overwrite and os.path.exists(path):
|
||||||
|
raise FileExistsError(f"File '{path}' already exists. To overwrite it, set 'overwrite' to True.")
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline, dir=dir_name, delete=False) as tmp_file:
|
||||||
|
yield tmp_file
|
||||||
|
tmp_file_name = tmp_file.name
|
||||||
|
os.replace(tmp_file_name, path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_upload_stream(filepath: str, should_compress: bool) -> tuple[io.BufferedIOBase, int]:
|
||||||
|
if not should_compress:
|
||||||
|
file_size = os.path.getsize(filepath)
|
||||||
|
file_stream = open(filepath, "rb")
|
||||||
|
return file_stream, file_size
|
||||||
|
|
||||||
|
# Compress the file on the fly
|
||||||
|
compressed_stream = io.BytesIO()
|
||||||
|
compressor = zstd.ZstdCompressor(level=LOG_COMPRESSION_LEVEL)
|
||||||
|
|
||||||
|
with open(filepath, "rb") as f:
|
||||||
|
compressor.copy_stream(f, compressed_stream)
|
||||||
|
compressed_size = compressed_stream.tell()
|
||||||
|
compressed_stream.seek(0)
|
||||||
|
return compressed_stream, compressed_size
|
||||||
+36
-1
@@ -1,4 +1,8 @@
|
|||||||
|
import numpy as np
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
class FirstOrderFilter:
|
class FirstOrderFilter:
|
||||||
|
# first order filter
|
||||||
def __init__(self, x0, rc, dt, initialized=True):
|
def __init__(self, x0, rc, dt, initialized=True):
|
||||||
self.x = x0
|
self.x = x0
|
||||||
self.dt = dt
|
self.dt = dt
|
||||||
@@ -28,7 +32,38 @@ class BounceFilter(FirstOrderFilter):
|
|||||||
scale = self.dt / (1.0 / 60.0) # tuned at 60 fps
|
scale = self.dt / (1.0 / 60.0) # tuned at 60 fps
|
||||||
self.velocity.x += (x - self.x) * self.bounce * scale * self.dt
|
self.velocity.x += (x - self.x) * self.bounce * scale * self.dt
|
||||||
self.velocity.update(0.0)
|
self.velocity.update(0.0)
|
||||||
if abs(self.velocity.x) < 1e-3:
|
if abs(self.velocity.x) < 1e-5:
|
||||||
self.velocity.x = 0.0
|
self.velocity.x = 0.0
|
||||||
self.x += self.velocity.x
|
self.x += self.velocity.x
|
||||||
return self.x
|
return self.x
|
||||||
|
|
||||||
|
class MyMovingAverage:
|
||||||
|
def __init__(self, window_size, value=None):
|
||||||
|
self.window_size = window_size
|
||||||
|
if (value is not None):
|
||||||
|
self.values = deque([value] * window_size, maxlen=window_size)
|
||||||
|
self.sum = value * window_size
|
||||||
|
self.result = value
|
||||||
|
else:
|
||||||
|
self.values = deque(maxlen=window_size)
|
||||||
|
self.sum = 0
|
||||||
|
self.result = 0
|
||||||
|
|
||||||
|
def set(self, value):
|
||||||
|
self.values.clear()
|
||||||
|
self.values.append(value)
|
||||||
|
self.sum = value
|
||||||
|
self.result = value
|
||||||
|
return value
|
||||||
|
|
||||||
|
def set_all(self, value):
|
||||||
|
self.values = deque([value] * self.window_size, maxlen=self.window_size)
|
||||||
|
self.sum = value * self.window_size
|
||||||
|
self.result = value
|
||||||
|
return value
|
||||||
|
|
||||||
|
def process(self, value, median=False):
|
||||||
|
self.values.append(value)
|
||||||
|
self.sum = sum(self.values)
|
||||||
|
self.result = float(np.median(self.values)) if median else float(self.sum) / len(self.values)
|
||||||
|
return self.result
|
||||||
|
|||||||
+7
-7
@@ -1,30 +1,30 @@
|
|||||||
from functools import cache
|
from functools import cache
|
||||||
import subprocess
|
import subprocess
|
||||||
from openpilot.common.utils import run_cmd, run_cmd_default
|
from openpilot.common.run import run_cmd, run_cmd_default
|
||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def get_commit(cwd: str | None = None, branch: str = "HEAD") -> str:
|
def get_commit(cwd: str = None, branch: str = "HEAD") -> str:
|
||||||
return run_cmd_default(["git", "rev-parse", branch], cwd=cwd)
|
return run_cmd_default(["git", "rev-parse", branch], cwd=cwd)
|
||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def get_commit_date(cwd: str | None = None, commit: str = "HEAD") -> str:
|
def get_commit_date(cwd: str = None, commit: str = "HEAD") -> str:
|
||||||
return run_cmd_default(["git", "show", "--no-patch", "--format='%ct %ci'", commit], cwd=cwd)
|
return run_cmd_default(["git", "show", "--no-patch", "--format='%ct %ci'", commit], cwd=cwd)
|
||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def get_short_branch(cwd: str | None = None) -> str:
|
def get_short_branch(cwd: str = None) -> str:
|
||||||
return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd)
|
return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd)
|
||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def get_branch(cwd: str | None = None) -> str:
|
def get_branch(cwd: str = None) -> str:
|
||||||
return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd=cwd)
|
return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd=cwd)
|
||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def get_origin(cwd: str | None = None) -> str:
|
def get_origin(cwd: str = None) -> str:
|
||||||
try:
|
try:
|
||||||
local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"], cwd=cwd)
|
local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"], cwd=cwd)
|
||||||
tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"], cwd=cwd)
|
tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"], cwd=cwd)
|
||||||
@@ -34,7 +34,7 @@ def get_origin(cwd: str | None = None) -> str:
|
|||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def get_normalized_origin(cwd: str | None = None) -> str:
|
def get_normalized_origin(cwd: str = None) -> str:
|
||||||
return get_origin(cwd) \
|
return get_origin(cwd) \
|
||||||
.replace("git@", "", 1) \
|
.replace("git@", "", 1) \
|
||||||
.replace(".git", "", 1) \
|
.replace(".git", "", 1) \
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
import os
|
|
||||||
import fcntl
|
|
||||||
import ctypes
|
|
||||||
|
|
||||||
# I2C constants from /usr/include/linux/i2c-dev.h
|
|
||||||
I2C_SLAVE = 0x0703
|
|
||||||
I2C_SLAVE_FORCE = 0x0706
|
|
||||||
I2C_SMBUS = 0x0720
|
|
||||||
|
|
||||||
# SMBus transfer types
|
|
||||||
I2C_SMBUS_READ = 1
|
|
||||||
I2C_SMBUS_WRITE = 0
|
|
||||||
I2C_SMBUS_BYTE_DATA = 2
|
|
||||||
I2C_SMBUS_I2C_BLOCK_DATA = 8
|
|
||||||
|
|
||||||
I2C_SMBUS_BLOCK_MAX = 32
|
|
||||||
|
|
||||||
|
|
||||||
class _I2cSmbusData(ctypes.Union):
|
|
||||||
_fields_ = [
|
|
||||||
("byte", ctypes.c_uint8),
|
|
||||||
("word", ctypes.c_uint16),
|
|
||||||
("block", ctypes.c_uint8 * (I2C_SMBUS_BLOCK_MAX + 2)),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class _I2cSmbusIoctlData(ctypes.Structure):
|
|
||||||
_fields_ = [
|
|
||||||
("read_write", ctypes.c_uint8),
|
|
||||||
("command", ctypes.c_uint8),
|
|
||||||
("size", ctypes.c_uint32),
|
|
||||||
("data", ctypes.POINTER(_I2cSmbusData)),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class SMBus:
|
|
||||||
def __init__(self, bus: int):
|
|
||||||
self._fd = os.open(f'/dev/i2c-{bus}', os.O_RDWR)
|
|
||||||
|
|
||||||
def __enter__(self) -> 'SMBus':
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *args) -> None:
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def close(self) -> None:
|
|
||||||
if hasattr(self, '_fd') and self._fd >= 0:
|
|
||||||
os.close(self._fd)
|
|
||||||
self._fd = -1
|
|
||||||
|
|
||||||
def _set_address(self, addr: int, force: bool = False) -> None:
|
|
||||||
ioctl_arg = I2C_SLAVE_FORCE if force else I2C_SLAVE
|
|
||||||
fcntl.ioctl(self._fd, ioctl_arg, addr)
|
|
||||||
|
|
||||||
def _smbus_access(self, read_write: int, command: int, size: int, data: _I2cSmbusData) -> None:
|
|
||||||
ioctl_data = _I2cSmbusIoctlData(read_write, command, size, ctypes.pointer(data))
|
|
||||||
fcntl.ioctl(self._fd, I2C_SMBUS, ioctl_data)
|
|
||||||
|
|
||||||
def read_byte_data(self, addr: int, register: int, force: bool = False) -> int:
|
|
||||||
self._set_address(addr, force)
|
|
||||||
data = _I2cSmbusData()
|
|
||||||
self._smbus_access(I2C_SMBUS_READ, register, I2C_SMBUS_BYTE_DATA, data)
|
|
||||||
return int(data.byte)
|
|
||||||
|
|
||||||
def write_byte_data(self, addr: int, register: int, value: int, force: bool = False) -> None:
|
|
||||||
self._set_address(addr, force)
|
|
||||||
data = _I2cSmbusData()
|
|
||||||
data.byte = value & 0xFF
|
|
||||||
self._smbus_access(I2C_SMBUS_WRITE, register, I2C_SMBUS_BYTE_DATA, data)
|
|
||||||
|
|
||||||
def read_i2c_block_data(self, addr: int, register: int, length: int, force: bool = False) -> list[int]:
|
|
||||||
self._set_address(addr, force)
|
|
||||||
if not (0 <= length <= I2C_SMBUS_BLOCK_MAX):
|
|
||||||
raise ValueError(f"length must be 0..{I2C_SMBUS_BLOCK_MAX}")
|
|
||||||
|
|
||||||
data = _I2cSmbusData()
|
|
||||||
data.block[0] = length
|
|
||||||
self._smbus_access(I2C_SMBUS_READ, register, I2C_SMBUS_I2C_BLOCK_DATA, data)
|
|
||||||
read_len = int(data.block[0]) or length
|
|
||||||
read_len = min(read_len, length)
|
|
||||||
return [int(b) for b in data.block[1 : read_len + 1]]
|
|
||||||
@@ -199,6 +199,7 @@ class SwagLogger(logging.Logger):
|
|||||||
co = f.f_code
|
co = f.f_code
|
||||||
filename = os.path.normcase(co.co_filename)
|
filename = os.path.normcase(co.co_filename)
|
||||||
|
|
||||||
|
# TODO: is this pylint exception correct?
|
||||||
if filename == _srcfile:
|
if filename == _srcfile:
|
||||||
f = f.f_back
|
f = f.f_back
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct vec3 {
|
||||||
|
float v[3];
|
||||||
|
} vec3;
|
||||||
|
|
||||||
|
typedef struct vec4 {
|
||||||
|
float v[4];
|
||||||
|
} vec4;
|
||||||
|
|
||||||
|
typedef struct mat3 {
|
||||||
|
float v[3*3];
|
||||||
|
} mat3;
|
||||||
|
|
||||||
|
typedef struct mat4 {
|
||||||
|
float v[4*4];
|
||||||
|
} mat4;
|
||||||
|
|
||||||
|
static inline mat3 matmul3(const mat3 &a, const mat3 &b) {
|
||||||
|
mat3 ret = {{0.0}};
|
||||||
|
for (int r=0; r<3; r++) {
|
||||||
|
for (int c=0; c<3; c++) {
|
||||||
|
float v = 0.0;
|
||||||
|
for (int k=0; k<3; k++) {
|
||||||
|
v += a.v[r*3+k] * b.v[k*3+c];
|
||||||
|
}
|
||||||
|
ret.v[r*3+c] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) {
|
||||||
|
vec3 ret = {{0.0}};
|
||||||
|
for (int r=0; r<3; r++) {
|
||||||
|
for (int c=0; c<3; c++) {
|
||||||
|
ret.v[r] += a.v[r*3+c] * b.v[c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline mat4 matmul(const mat4 &a, const mat4 &b) {
|
||||||
|
mat4 ret = {{0.0}};
|
||||||
|
for (int r=0; r<4; r++) {
|
||||||
|
for (int c=0; c<4; c++) {
|
||||||
|
float v = 0.0;
|
||||||
|
for (int k=0; k<4; k++) {
|
||||||
|
v += a.v[r*4+k] * b.v[k*4+c];
|
||||||
|
}
|
||||||
|
ret.v[r*4+c] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline vec4 matvecmul(const mat4 &a, const vec4 &b) {
|
||||||
|
vec4 ret = {{0.0}};
|
||||||
|
for (int r=0; r<4; r++) {
|
||||||
|
for (int c=0; c<4; c++) {
|
||||||
|
ret.v[r] += a.v[r*4+c] * b.v[c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scales the input and output space of a transformation matrix
|
||||||
|
// that assumes pixel-center origin.
|
||||||
|
static inline mat3 transform_scale_buffer(const mat3 &in, float s) {
|
||||||
|
// in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s
|
||||||
|
|
||||||
|
mat3 transform_out = {{
|
||||||
|
1.0f/s, 0.0f, 0.5f,
|
||||||
|
0.0f, 1.0f/s, 0.5f,
|
||||||
|
0.0f, 0.0f, 1.0f,
|
||||||
|
}};
|
||||||
|
|
||||||
|
mat3 transform_in = {{
|
||||||
|
s, 0.0f, -0.5f*s,
|
||||||
|
0.0f, s, -0.5f*s,
|
||||||
|
0.0f, 0.0f, 1.0f,
|
||||||
|
}};
|
||||||
|
|
||||||
|
return matmul3(transform_in, matmul3(in, transform_out));
|
||||||
|
}
|
||||||
@@ -8,12 +8,13 @@ import functools
|
|||||||
import threading
|
import threading
|
||||||
from cereal.messaging import PubMaster
|
from cereal.messaging import PubMaster
|
||||||
from cereal.services import SERVICE_LIST
|
from cereal.services import SERVICE_LIST
|
||||||
from openpilot.common.mock.generators import generate_livePose
|
from openpilot.common.mock.generators import generate_livePose, generate_liveLocationKalman
|
||||||
from openpilot.common.realtime import Ratekeeper
|
from openpilot.common.realtime import Ratekeeper
|
||||||
|
|
||||||
|
|
||||||
MOCK_GENERATOR = {
|
MOCK_GENERATOR = {
|
||||||
"livePose": generate_livePose
|
"livePose": generate_livePose,
|
||||||
|
"liveLocationKalman": generate_liveLocationKalman
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
from cereal import messaging
|
from cereal import messaging
|
||||||
|
|
||||||
|
|
||||||
|
LOCATION1 = (32.7174, -117.16277)
|
||||||
|
LOCATION2 = (32.7558, -117.2037)
|
||||||
|
|
||||||
|
LLK_DECIMATION = 10
|
||||||
|
RENDER_FRAMES = 15
|
||||||
|
DEFAULT_ITERATIONS = RENDER_FRAMES * LLK_DECIMATION
|
||||||
|
|
||||||
|
|
||||||
|
def generate_liveLocationKalman(location=LOCATION1):
|
||||||
|
msg = messaging.new_message('liveLocationKalman')
|
||||||
|
msg.liveLocationKalman.positionGeodetic = {'value': [*location, 0], 'std': [0., 0., 0.], 'valid': True}
|
||||||
|
msg.liveLocationKalman.positionECEF = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True}
|
||||||
|
msg.liveLocationKalman.calibratedOrientationNED = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True}
|
||||||
|
msg.liveLocationKalman.velocityCalibrated = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True}
|
||||||
|
msg.liveLocationKalman.status = 'valid'
|
||||||
|
msg.liveLocationKalman.gpsOK = True
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def generate_livePose():
|
def generate_livePose():
|
||||||
msg = messaging.new_message('livePose')
|
msg = messaging.new_message('livePose')
|
||||||
meas = {'x': 0.0, 'y': 0.0, 'z': 0.0, 'xStd': 0.0, 'yStd': 0.0, 'zStd': 0.0, 'valid': True}
|
meas = {'x': 0.0, 'y': 0.0, 'z': 0.0, 'xStd': 0.0, 'yStd': 0.0, 'zStd': 0.0, 'valid': True}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import sys
|
|
||||||
import pytest
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
|
|
||||||
class parameterized:
|
|
||||||
@staticmethod
|
|
||||||
def expand(cases):
|
|
||||||
cases = list(cases)
|
|
||||||
|
|
||||||
if not cases:
|
|
||||||
return lambda func: pytest.mark.skip("no parameterized cases")(func)
|
|
||||||
|
|
||||||
def decorator(func):
|
|
||||||
params = [p for p in inspect.signature(func).parameters if p != 'self']
|
|
||||||
normalized = [c if isinstance(c, tuple) else (c,) for c in cases]
|
|
||||||
# Infer arg count from first case so extra params (e.g. from @given) are left untouched
|
|
||||||
expand_params = params[: len(normalized[0])]
|
|
||||||
if len(expand_params) == 1:
|
|
||||||
return pytest.mark.parametrize(expand_params[0], [c[0] for c in normalized])(func)
|
|
||||||
return pytest.mark.parametrize(', '.join(expand_params), normalized)(func)
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def parameterized_class(attrs, input_list=None):
|
|
||||||
if isinstance(attrs, list) and (not attrs or isinstance(attrs[0], dict)):
|
|
||||||
params_list = attrs
|
|
||||||
else:
|
|
||||||
assert input_list is not None
|
|
||||||
attr_names = (attrs,) if isinstance(attrs, str) else tuple(attrs)
|
|
||||||
params_list = [dict(zip(attr_names, v if isinstance(v, (tuple, list)) else (v,), strict=False)) for v in input_list]
|
|
||||||
|
|
||||||
def decorator(cls):
|
|
||||||
globs = sys._getframe(1).f_globals
|
|
||||||
for i, params in enumerate(params_list):
|
|
||||||
name = f"{cls.__name__}_{i}"
|
|
||||||
new_cls = type(name, (cls,), dict(params))
|
|
||||||
new_cls.__module__ = cls.__module__
|
|
||||||
new_cls.__test__ = True # override inherited False so pytest collects this subclass
|
|
||||||
globs[name] = new_cls
|
|
||||||
# Don't collect the un-parametrised base, but return it so outer decorators
|
|
||||||
# (e.g. @pytest.mark.skip) land on it and propagate to subclasses via MRO.
|
|
||||||
cls.__test__ = False
|
|
||||||
return cls
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
@@ -63,6 +63,14 @@ public:
|
|||||||
inline bool getBool(const std::string &key, bool block = false) {
|
inline bool getBool(const std::string &key, bool block = false) {
|
||||||
return get(key, block) == "1";
|
return get(key, block) == "1";
|
||||||
}
|
}
|
||||||
|
inline int getInt(const std::string &key, bool block = false) {
|
||||||
|
std::string value = get(key, block);
|
||||||
|
return value.empty() ? 0 : std::stoi(value);
|
||||||
|
}
|
||||||
|
inline float getFloat(const std::string &key, bool block = false) {
|
||||||
|
std::string value = get(key, block);
|
||||||
|
return value.empty() ? 0.0 : std::stof(value);
|
||||||
|
}
|
||||||
std::map<std::string, std::string> readAll();
|
std::map<std::string, std::string> readAll();
|
||||||
|
|
||||||
// helpers for writing values
|
// helpers for writing values
|
||||||
@@ -73,10 +81,24 @@ public:
|
|||||||
inline int putBool(const std::string &key, bool val) {
|
inline int putBool(const std::string &key, bool val) {
|
||||||
return put(key.c_str(), val ? "1" : "0", 1);
|
return put(key.c_str(), val ? "1" : "0", 1);
|
||||||
}
|
}
|
||||||
|
inline int putInt(const std::string &key, int val) {
|
||||||
|
return put(key.c_str(), std::to_string(val).c_str(), std::to_string(val).size());
|
||||||
|
}
|
||||||
|
inline int putFloat(const std::string &key, float val) {
|
||||||
|
return put(key.c_str(), std::to_string(val).c_str(), std::to_string(val).size());
|
||||||
|
}
|
||||||
void putNonBlocking(const std::string &key, const std::string &val);
|
void putNonBlocking(const std::string &key, const std::string &val);
|
||||||
inline void putBoolNonBlocking(const std::string &key, bool val) {
|
inline void putBoolNonBlocking(const std::string &key, bool val) {
|
||||||
putNonBlocking(key, val ? "1" : "0");
|
putNonBlocking(key, val ? "1" : "0");
|
||||||
}
|
}
|
||||||
|
void putIntNonBlocking(const std::string &key, const std::string &val);
|
||||||
|
inline void putIntNonBlocking(const std::string &key, int val) {
|
||||||
|
putNonBlocking(key, std::to_string(val));
|
||||||
|
}
|
||||||
|
void putFloatNonBlocking(const std::string &key, const std::string &val);
|
||||||
|
inline void putFloatNonBlocking(const std::string &key, float val) {
|
||||||
|
putNonBlocking(key, std::to_string(val));
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void asyncWriteThread();
|
void asyncWriteThread();
|
||||||
|
|||||||
+218
-14
@@ -28,7 +28,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"ControlsReady", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
{"ControlsReady", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||||
{"CurrentBootlog", {PERSISTENT, STRING}},
|
{"CurrentBootlog", {PERSISTENT, STRING}},
|
||||||
{"CurrentRoute", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
{"CurrentRoute", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||||
{"DisableLogging", {PERSISTENT, BOOL, "0"}},
|
{"DisableLogging", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||||
{"DisablePowerDown", {PERSISTENT, BOOL}},
|
{"DisablePowerDown", {PERSISTENT, BOOL}},
|
||||||
{"DisableUpdates", {PERSISTENT, BOOL}},
|
{"DisableUpdates", {PERSISTENT, BOOL}},
|
||||||
{"DisengageOnAccelerator", {PERSISTENT, BOOL, "0"}},
|
{"DisengageOnAccelerator", {PERSISTENT, BOOL, "0"}},
|
||||||
@@ -71,12 +71,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"LastGPSPosition", {PERSISTENT, STRING}},
|
{"LastGPSPosition", {PERSISTENT, STRING}},
|
||||||
{"LastManagerExitReason", {CLEAR_ON_MANAGER_START, STRING}},
|
{"LastManagerExitReason", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
{"LastOffroadStatusPacket", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON}},
|
{"LastOffroadStatusPacket", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON}},
|
||||||
{"LastAgnosPowerMonitorShutdown", {CLEAR_ON_MANAGER_START, STRING}},
|
|
||||||
{"LastPowerDropDetected", {CLEAR_ON_MANAGER_START, STRING}},
|
{"LastPowerDropDetected", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
{"LastUpdateException", {CLEAR_ON_MANAGER_START, STRING}},
|
{"LastUpdateException", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
{"LastUpdateRouteCount", {PERSISTENT, INT, "0"}},
|
|
||||||
{"LastUpdateTime", {PERSISTENT, TIME}},
|
{"LastUpdateTime", {PERSISTENT, TIME}},
|
||||||
{"LastUpdateUptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
|
|
||||||
{"LiveDelay", {PERSISTENT, BYTES}},
|
{"LiveDelay", {PERSISTENT, BYTES}},
|
||||||
{"LiveParameters", {PERSISTENT, JSON}},
|
{"LiveParameters", {PERSISTENT, JSON}},
|
||||||
{"LiveParametersV2", {PERSISTENT, BYTES}},
|
{"LiveParametersV2", {PERSISTENT, BYTES}},
|
||||||
@@ -87,33 +84,33 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"NetworkMetered", {PERSISTENT, BOOL}},
|
{"NetworkMetered", {PERSISTENT, BOOL}},
|
||||||
{"ObdMultiplexingChanged", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
{"ObdMultiplexingChanged", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||||
{"ObdMultiplexingEnabled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
{"ObdMultiplexingEnabled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||||
|
{"Offroad_BadNvme", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"Offroad_CarUnrecognized", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
{"Offroad_CarUnrecognized", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||||
{"Offroad_ConnectivityNeeded", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_ConnectivityNeeded", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_ExcessiveActuation", {PERSISTENT, JSON}},
|
|
||||||
{"Offroad_IsTakingSnapshot", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_IsTakingSnapshot", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
{"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||||
{"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
{"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||||
|
{"Offroad_StorageMissing", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_UnofficialHardware", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_DriverMonitoringUncertain", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
{"Offroad_DriverMonitoringUncertain", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||||
{"OnroadCycleRequested", {CLEAR_ON_MANAGER_START, BOOL}},
|
|
||||||
{"OpenpilotEnabledToggle", {PERSISTENT, BOOL, "1"}},
|
{"OpenpilotEnabledToggle", {PERSISTENT, BOOL, "1"}},
|
||||||
{"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
{"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
{"PandaSomResetTriggered", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
{"PandaSomResetTriggered", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
{"PandaSignatures", {CLEAR_ON_MANAGER_START, BYTES}},
|
{"PandaSignatures", {CLEAR_ON_MANAGER_START, BYTES}},
|
||||||
{"PrimeType", {PERSISTENT, INT}},
|
{"PrimeType", {PERSISTENT, INT}},
|
||||||
{"RecordAudio", {PERSISTENT, BOOL}},
|
{"RecordAudio", {PERSISTENT, BOOL}},
|
||||||
{"RecordAudioFeedback", {PERSISTENT, BOOL, "0"}},
|
|
||||||
{"RecordFront", {PERSISTENT, BOOL}},
|
{"RecordFront", {PERSISTENT, BOOL}},
|
||||||
{"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet
|
{"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet
|
||||||
{"SecOCKey", {PERSISTENT | DONT_LOG, STRING}},
|
{"SecOCKey", {PERSISTENT | DONT_LOG, STRING}},
|
||||||
{"ShowDebugInfo", {PERSISTENT, BOOL}},
|
|
||||||
{"RouteCount", {PERSISTENT, INT, "0"}},
|
{"RouteCount", {PERSISTENT, INT, "0"}},
|
||||||
{"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
{"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
{"SshEnabled", {PERSISTENT, BOOL}},
|
{"SshEnabled", {PERSISTENT, BOOL}},
|
||||||
|
{"TermsVersion", {PERSISTENT, STRING}},
|
||||||
|
{"TrainingVersion", {PERSISTENT, STRING}},
|
||||||
{"UbloxAvailable", {PERSISTENT, BOOL}},
|
{"UbloxAvailable", {PERSISTENT, BOOL}},
|
||||||
{"UpdateAvailable", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
{"UpdateAvailable", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||||
{"UpdateFailedCount", {CLEAR_ON_MANAGER_START, INT}},
|
{"UpdateFailedCount", {CLEAR_ON_MANAGER_START, INT}},
|
||||||
@@ -126,10 +123,217 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"UpdaterState", {CLEAR_ON_MANAGER_START, STRING}},
|
{"UpdaterState", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
{"UpdaterTargetBranch", {CLEAR_ON_MANAGER_START, STRING}},
|
{"UpdaterTargetBranch", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
{"UpdaterLastFetchTime", {PERSISTENT, TIME}},
|
{"UpdaterLastFetchTime", {PERSISTENT, TIME}},
|
||||||
{"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}},
|
|
||||||
{"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
|
|
||||||
{"Version", {PERSISTENT, STRING}},
|
{"Version", {PERSISTENT, STRING}},
|
||||||
{"dp_dev_last_log", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
|
||||||
{"dp_dev_reset_conf", {CLEAR_ON_MANAGER_START, BOOL, "0"}},
|
// carrot
|
||||||
{"dp_dev_go_off_road", {CLEAR_ON_MANAGER_START, BOOL, "0"}},
|
{"LongitudinalPersonalityMax", {PERSISTENT, INT, "3"}},
|
||||||
|
{"NetworkAddress", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
|
|
||||||
|
{"ApiCache_NavDestinations", {PERSISTENT, STRING}},
|
||||||
|
{"NavDestination", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, STRING}},
|
||||||
|
{"NavDestinationWaypoints", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, STRING}},
|
||||||
|
{"NavPastDestinations", {PERSISTENT, STRING}},
|
||||||
|
{"NavSettingLeftSide", {PERSISTENT, BOOL}},
|
||||||
|
{"NavSettingTime24h", {PERSISTENT, BOOL}},
|
||||||
|
{"MapboxStyle", {PERSISTENT, INT}},
|
||||||
|
{"MapboxPublicKey", {PERSISTENT, STRING}},
|
||||||
|
{"MapboxSecretKey", {PERSISTENT, STRING}},
|
||||||
|
{"GMapKey", {PERSISTENT, STRING}},
|
||||||
|
{"SearchInput", {PERSISTENT, INT}},
|
||||||
|
|
||||||
|
{"CarSelected3", {PERSISTENT, STRING, "MOCK"}},
|
||||||
|
{"SupportedCars", {PERSISTENT, STRING}},
|
||||||
|
{"SupportedCars_gm", {PERSISTENT, STRING}},
|
||||||
|
{"ShowDebugUI", {PERSISTENT, INT, "0"}},
|
||||||
|
{"ShowTpms", {PERSISTENT, INT, "1"}},
|
||||||
|
{"ShowDateTime", {PERSISTENT, INT, "1"}},
|
||||||
|
{"ShowPathEnd", {PERSISTENT, INT, "1"}},
|
||||||
|
{"ShowCustomBrightness", {PERSISTENT, INT, "100"}},
|
||||||
|
{"ShowLaneInfo", {PERSISTENT, INT, "1"}},
|
||||||
|
{"ShowRadarInfo", {PERSISTENT, INT, "1"}},
|
||||||
|
{"ShowDeviceState", {PERSISTENT, INT, "1"}},
|
||||||
|
{"ShowRouteInfo", {PERSISTENT, INT, "1"}},
|
||||||
|
{"ShowPathMode", {PERSISTENT, INT, "9"}},
|
||||||
|
{"ShowPathColor", {PERSISTENT, INT, "13"}},
|
||||||
|
{"ShowPathColorCruiseOff", {PERSISTENT, INT, "19"}},
|
||||||
|
{"ShowPathModeLane", {PERSISTENT, INT, "14"}},
|
||||||
|
{"ShowPathColorLane", {PERSISTENT, INT, "13"}},
|
||||||
|
{"ShowPlotMode", {PERSISTENT, INT, "0"}},
|
||||||
|
{"RecordRoadCam", {PERSISTENT, INT, "0"}},
|
||||||
|
{"HDPuse", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"AutoCruiseControl", {PERSISTENT, INT, "0"}},
|
||||||
|
{"CruiseEcoControl", {PERSISTENT, INT, "2"}},
|
||||||
|
{"CarrotCruiseDecel", {PERSISTENT, INT, "-1"}},
|
||||||
|
{"CarrotCruiseAtcDecel", {PERSISTENT, INT, "-1"}},
|
||||||
|
{"CommaLongAcc", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"AutoGasTokSpeed", {PERSISTENT, INT, "0"}},
|
||||||
|
{"AutoGasSyncSpeed", {PERSISTENT, INT, "1"}},
|
||||||
|
{"AutoEngage", {PERSISTENT, INT, "0"}},
|
||||||
|
{"DisableMinSteerSpeed", {PERSISTENT, INT, "0"}},
|
||||||
|
{"AutoCurveSpeedLowerLimit", {PERSISTENT, INT, "30"}},
|
||||||
|
{"AutoCurveSpeedFactor", {PERSISTENT, INT, "120"}},
|
||||||
|
{"AutoCurveSpeedAggressiveness", {PERSISTENT, INT, "100"}},
|
||||||
|
|
||||||
|
{"AutoTurnControl", {PERSISTENT, INT, "0"}},
|
||||||
|
{"AutoTurnControlSpeedTurn", {PERSISTENT, INT, "20"}},
|
||||||
|
{"AutoTurnControlTurnEnd", {PERSISTENT, INT, "6"}},
|
||||||
|
{"AutoTurnMapChange", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"AutoNaviSpeedCtrlEnd", {PERSISTENT, INT, "7"}},
|
||||||
|
{"AutoNaviSpeedCtrlMode", {PERSISTENT, INT, "2"}},
|
||||||
|
{"AutoRoadSpeedLimitOffset", {PERSISTENT, INT, "-1"}},
|
||||||
|
{"AutoNaviSpeedBumpTime", {PERSISTENT, INT, "1"}},
|
||||||
|
{"AutoNaviSpeedBumpSpeed", {PERSISTENT, INT, "35"}},
|
||||||
|
{"AutoNaviSpeedDecelRate", {PERSISTENT, INT, "120"}},
|
||||||
|
{"AutoNaviSpeedSafetyFactor", {PERSISTENT, INT, "105"}},
|
||||||
|
{"AutoNaviCountDownMode", {PERSISTENT, INT, "2"}},
|
||||||
|
{"TurnSpeedControlMode", {PERSISTENT, INT, "1"}},
|
||||||
|
|
||||||
|
{"CarrotSmartSpeedControl", {PERSISTENT, INT, "0"}},
|
||||||
|
{"MapTurnSpeedFactor", {PERSISTENT, INT, "90"}},
|
||||||
|
{"ModelTurnSpeedFactor", {PERSISTENT, INT, "0"}},
|
||||||
|
{"StoppingAccel", {PERSISTENT, INT, "0"}},
|
||||||
|
{"AutoSpeedUptoRoadSpeedLimit", {PERSISTENT, INT, "0"}},
|
||||||
|
{"AutoRoadSpeedAdjust", {PERSISTENT, INT, "50"}},
|
||||||
|
|
||||||
|
{"StopDistanceCarrot", {PERSISTENT, INT, "550"}},
|
||||||
|
{"JLeadFactor3", {PERSISTENT, INT, "0"}},
|
||||||
|
{"CruiseButtonMode", {PERSISTENT, INT, "0"}},
|
||||||
|
{"CancelButtonMode", {PERSISTENT, INT, "0"}},
|
||||||
|
{"LfaButtonMode", {PERSISTENT, INT, "0"}},
|
||||||
|
{"CruiseButtonTest1", {PERSISTENT, INT, "8"}},
|
||||||
|
{"CruiseButtonTest2", {PERSISTENT, INT, "30"}},
|
||||||
|
{"CruiseButtonTest3", {PERSISTENT, INT, "1"}},
|
||||||
|
|
||||||
|
{"CruiseSpeedUnit", {PERSISTENT, INT, "10"}},
|
||||||
|
{"CruiseSpeedUnitBasic", {PERSISTENT, INT, "1"}},
|
||||||
|
{"CruiseSpeed1", {PERSISTENT, INT, "30"}},
|
||||||
|
{"CruiseSpeed2", {PERSISTENT, INT, "50"}},
|
||||||
|
{"CruiseSpeed3", {PERSISTENT, INT, "80"}},
|
||||||
|
{"CruiseSpeed4", {PERSISTENT, INT, "110"}},
|
||||||
|
{"CruiseSpeed5", {PERSISTENT, INT, "130"}},
|
||||||
|
|
||||||
|
{"PaddleMode", {PERSISTENT, INT, "0"}},
|
||||||
|
{"MyDrivingMode", {PERSISTENT, INT, "3"}},
|
||||||
|
{"MyDrivingModeAuto", {PERSISTENT, INT, "0"}},
|
||||||
|
{"TrafficLightDetectMode", {PERSISTENT, INT, "2"}},
|
||||||
|
|
||||||
|
{"SteerActuatorDelay", {PERSISTENT, INT, "0"}},
|
||||||
|
{"LatSmoothSec", {PERSISTENT, INT, "13"}},
|
||||||
|
{"LatSuspendAngleDeg", {PERSISTENT, INT, "300"}},
|
||||||
|
{"CruiseOnDist", {PERSISTENT, INT, "400"}},
|
||||||
|
|
||||||
|
{"CruiseMaxVals0", {PERSISTENT, INT, "160"}},
|
||||||
|
{"CruiseMaxVals1", {PERSISTENT, INT, "200"}},
|
||||||
|
{"CruiseMaxVals2", {PERSISTENT, INT, "160"}},
|
||||||
|
{"CruiseMaxVals3", {PERSISTENT, INT, "130"}},
|
||||||
|
{"CruiseMaxVals4", {PERSISTENT, INT, "110"}},
|
||||||
|
{"CruiseMaxVals5", {PERSISTENT, INT, "95"}},
|
||||||
|
{"CruiseMaxVals6", {PERSISTENT, INT, "80"}},
|
||||||
|
|
||||||
|
{"LongTuningKpV", {PERSISTENT, INT, "100"}},
|
||||||
|
{"LongTuningKiV", {PERSISTENT, INT, "0"}},
|
||||||
|
{"LongTuningKf", {PERSISTENT, INT, "100"}},
|
||||||
|
{"LongActuatorDelay", {PERSISTENT, INT, "20"}},
|
||||||
|
{"VEgoStopping", {PERSISTENT, INT, "50"}},
|
||||||
|
|
||||||
|
{"RadarReactionFactor", {PERSISTENT, INT, "100"}},
|
||||||
|
{"EnableRadarTracks", {PERSISTENT, INT, "0"}},
|
||||||
|
{"RadarLatFactor", {PERSISTENT, INT, "0"}},
|
||||||
|
{"EnableCornerRadar", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"EnableRadarTracksResult", {PERSISTENT | CLEAR_ON_MANAGER_START, INT}},
|
||||||
|
{"CanParserResult", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, STRING}},
|
||||||
|
|
||||||
|
{"HotspotOnBoot", {PERSISTENT, INT, "0"}},
|
||||||
|
{"SoftwareMenu", {PERSISTENT, INT, "1"}},
|
||||||
|
|
||||||
|
{"HyundaiCameraSCC", {PERSISTENT, INT, "0"}},
|
||||||
|
{"FingerPrints", {PERSISTENT | CLEAR_ON_MANAGER_START, STRING}},
|
||||||
|
{"IsLdwsCar", {PERSISTENT, INT, "0"}},
|
||||||
|
{"CanfdHDA2", {PERSISTENT, INT, "0"}},
|
||||||
|
{"CanfdDebug", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"SoundVolumeAdjust", {PERSISTENT, INT, "100"}},
|
||||||
|
{"SoundVolumeAdjustEngage", {PERSISTENT, INT, "10"}},
|
||||||
|
|
||||||
|
{"TFollowGap1", {PERSISTENT, INT, "110"}},
|
||||||
|
{"TFollowGap2", {PERSISTENT, INT, "120"}},
|
||||||
|
{"TFollowGap3", {PERSISTENT, INT, "140"}},
|
||||||
|
{"TFollowGap4", {PERSISTENT, INT, "160"}},
|
||||||
|
|
||||||
|
{"DynamicTFollow", {PERSISTENT, INT, "0"}},
|
||||||
|
{"DynamicTFollowLC", {PERSISTENT, INT, "100"}},
|
||||||
|
{"EnableSpeedTF", {PERSISTENT, INT, "0"}},
|
||||||
|
{"AChangeCostStarting", {PERSISTENT, INT, "10"}},
|
||||||
|
{"TrafficStopDistanceAdjust", {PERSISTENT, INT, "400"}},
|
||||||
|
|
||||||
|
{"HapticFeedbackWhenSpeedCamera", {PERSISTENT, INT, "0"}},
|
||||||
|
{"UseLaneLineSpeed", {PERSISTENT, INT, "0"}},
|
||||||
|
{"UseLaneLineCurveSpeed", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"AdjustLaneOffset", {PERSISTENT, INT, "0"}},
|
||||||
|
{"LaneChangeNeedTorque", {PERSISTENT, INT, "0"}},
|
||||||
|
{"LaneChangeDelay", {PERSISTENT, INT, "0"}},
|
||||||
|
{"LaneChangeBsd", {PERSISTENT, INT, "0"}},
|
||||||
|
{"MaxAngleFrames", {PERSISTENT, INT, "89"}},
|
||||||
|
|
||||||
|
{"SoftHoldMode", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"LatMpcPathCost", {PERSISTENT, INT, "200"}},
|
||||||
|
{"LatMpcMotionCost", {PERSISTENT, INT, "7"}},
|
||||||
|
{"LatMpcAccelCost", {PERSISTENT, INT, "120"}},
|
||||||
|
{"LatMpcJerkCost", {PERSISTENT, INT, "4"}},
|
||||||
|
{"LatMpcSteeringRateCost", {PERSISTENT, INT, "7"}},
|
||||||
|
{"LatMpcInputOffset", {PERSISTENT, INT, "4"}},
|
||||||
|
|
||||||
|
{"PathOffset", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"LateralTorqueCustom", {PERSISTENT, INT, "0"}},
|
||||||
|
{"LateralTorqueAccelFactor", {PERSISTENT, INT, "2500"}},
|
||||||
|
{"LateralTorqueFriction", {PERSISTENT, INT, "100"}},
|
||||||
|
{"LateralTorqueKpV", {PERSISTENT, INT, "100"}},
|
||||||
|
{"LateralTorqueKiV", {PERSISTENT, INT, "10"}},
|
||||||
|
{"LateralTorqueKf", {PERSISTENT, INT, "100"}},
|
||||||
|
{"LateralTorqueKd", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"CustomSteerMax", {PERSISTENT, INT, "0"}},
|
||||||
|
{"CustomSteerDeltaUp", {PERSISTENT, INT, "0"}},
|
||||||
|
{"CustomSteerDeltaDown", {PERSISTENT, INT, "0"}},
|
||||||
|
{"CustomSteerDeltaUpLC", {PERSISTENT, INT, "0"}},
|
||||||
|
{"CustomSteerDeltaDownLC", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"SpeedFromPCM", {PERSISTENT, INT, "2"}},
|
||||||
|
{"MaxTimeOffroadMin", {PERSISTENT, INT, "60"}},
|
||||||
|
|
||||||
|
{"DisableDM", {PERSISTENT, INT, "0"}},
|
||||||
|
{"EnableConnect", {PERSISTENT, INT, "0"}},
|
||||||
|
{"MuteDoor", {PERSISTENT, INT, "0"}},
|
||||||
|
{"MuteSeatbelt", {PERSISTENT, INT, "0"}},
|
||||||
|
|
||||||
|
{"CarrotException", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
|
|
||||||
|
{"CarrotSpeed", {PERSISTENT, INT} },
|
||||||
|
{"CarrotSpeedViz", {PERSISTENT, JSON} },
|
||||||
|
{"CarrotSpeedTable", {PERSISTENT, BYTES} },
|
||||||
|
{"CarName", {PERSISTENT, STRING}},
|
||||||
|
{"EVTable", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"LongPitch", {PERSISTENT, BOOL, "0"}},
|
||||||
|
|
||||||
|
{"ActivateCruiseAfterBrake", {CLEAR_ON_MANAGER_START, INT, "0"}},
|
||||||
|
|
||||||
|
{"CustomSR", {PERSISTENT, INT, "0"}},
|
||||||
|
{"SteerRatioRate", {PERSISTENT, INT, "100"}},
|
||||||
|
|
||||||
|
{"SoftRestartTriggered", {CLEAR_ON_MANAGER_START, INT}},
|
||||||
|
|
||||||
|
{"DevicePosition", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
|
{"NNFF", {PERSISTENT, INT, "0"}},
|
||||||
|
{"NNFFLite", {PERSISTENT, INT, "0"}},
|
||||||
|
{"NNFFModelName", {CLEAR_ON_OFFROAD_TRANSITION, STRING}},
|
||||||
|
|
||||||
|
{"HardwareC3xLite", {PERSISTENT, INT, "0"}},
|
||||||
|
{"ShareData", {PERSISTENT, INT, "0"}},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,11 +33,17 @@ cdef extern from "common/params.h":
|
|||||||
c_Params(string) except + nogil
|
c_Params(string) except + nogil
|
||||||
string get(string, bool) nogil
|
string get(string, bool) nogil
|
||||||
bool getBool(string, bool) nogil
|
bool getBool(string, bool) nogil
|
||||||
|
int getInt(string, bool) nogil
|
||||||
|
float getFloat(string, bool) nogil
|
||||||
int remove(string) nogil
|
int remove(string) nogil
|
||||||
int put(string, string) nogil
|
int put(string, string) nogil
|
||||||
void putNonBlocking(string, string) nogil
|
void putNonBlocking(string, string) nogil
|
||||||
void putBoolNonBlocking(string, bool) nogil
|
void putBoolNonBlocking(string, bool) nogil
|
||||||
|
void putIntNonBlocking(string, int) nogil
|
||||||
|
void putFloatNonBlocking(string, float) nogil
|
||||||
int putBool(string, bool) nogil
|
int putBool(string, bool) nogil
|
||||||
|
int putInt(string, int) nogil
|
||||||
|
int putFloat(string, float) nogil
|
||||||
bool checkKey(string) nogil
|
bool checkKey(string) nogil
|
||||||
ParamKeyType getKeyType(string) nogil
|
ParamKeyType getKeyType(string) nogil
|
||||||
optional[string] getKeyDefaultValue(string) nogil
|
optional[string] getKeyDefaultValue(string) nogil
|
||||||
@@ -141,6 +147,20 @@ cdef class Params:
|
|||||||
cdef ParamKeyType t = self.p.getKeyType(k)
|
cdef ParamKeyType t = self.p.getKeyType(k)
|
||||||
return ensure_bytes(self.python2cpp(type(dat), t, dat, key))
|
return ensure_bytes(self.python2cpp(type(dat), t, dat, key))
|
||||||
|
|
||||||
|
def get_int(self, key, bool block=False):
|
||||||
|
cdef string k = self.check_key(key)
|
||||||
|
cdef int r
|
||||||
|
with nogil:
|
||||||
|
r = self.p.getInt(k, block)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def get_float(self, key, bool block=False):
|
||||||
|
cdef string k = self.check_key(key)
|
||||||
|
cdef float r
|
||||||
|
with nogil:
|
||||||
|
r = self.p.getFloat(k, block)
|
||||||
|
return r
|
||||||
|
|
||||||
def put(self, key, dat):
|
def put(self, key, dat):
|
||||||
"""
|
"""
|
||||||
Warning: This function blocks until the param is written to disk!
|
Warning: This function blocks until the param is written to disk!
|
||||||
@@ -158,6 +178,16 @@ cdef class Params:
|
|||||||
with nogil:
|
with nogil:
|
||||||
self.p.putBool(k, val)
|
self.p.putBool(k, val)
|
||||||
|
|
||||||
|
def put_int(self, key, int val):
|
||||||
|
cdef string k = self.check_key(key)
|
||||||
|
with nogil:
|
||||||
|
self.p.putInt(k, val)
|
||||||
|
|
||||||
|
def put_float(self, key, float val):
|
||||||
|
cdef string k = self.check_key(key)
|
||||||
|
with nogil:
|
||||||
|
self.p.putFloat(k, val)
|
||||||
|
|
||||||
def put_nonblocking(self, key, dat):
|
def put_nonblocking(self, key, dat):
|
||||||
cdef string k = self.check_key(key)
|
cdef string k = self.check_key(key)
|
||||||
cdef string dat_bytes = self._put_cast(key, dat)
|
cdef string dat_bytes = self._put_cast(key, dat)
|
||||||
@@ -169,6 +199,16 @@ cdef class Params:
|
|||||||
with nogil:
|
with nogil:
|
||||||
self.p.putBoolNonBlocking(k, val)
|
self.p.putBoolNonBlocking(k, val)
|
||||||
|
|
||||||
|
def put_int_nonblocking(self, key, int val):
|
||||||
|
cdef string k = self.check_key(key)
|
||||||
|
with nogil:
|
||||||
|
self.p.putIntNonBlocking(k, val)
|
||||||
|
|
||||||
|
def put_float_nonblocking(self, key, float val):
|
||||||
|
cdef string k = self.check_key(key)
|
||||||
|
with nogil:
|
||||||
|
self.p.putFloatNonBlocking(k, val)
|
||||||
|
|
||||||
def remove(self, key):
|
def remove(self, key):
|
||||||
cdef string k = self.check_key(key)
|
cdef string k = self.check_key(key)
|
||||||
with nogil:
|
with nogil:
|
||||||
|
|||||||
+34
-21
@@ -2,14 +2,23 @@ import numpy as np
|
|||||||
from numbers import Number
|
from numbers import Number
|
||||||
|
|
||||||
class PIDController:
|
class PIDController:
|
||||||
def __init__(self, k_p, k_i, k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
|
def __init__(self, k_p, k_i, k_f=0., k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
|
||||||
self._k_p: list[list[float]] = [[0], [k_p]] if isinstance(k_p, Number) else k_p
|
self._k_p = k_p
|
||||||
self._k_i: list[list[float]] = [[0], [k_i]] if isinstance(k_i, Number) else k_i
|
self._k_i = k_i
|
||||||
self._k_d: list[list[float]] = [[0], [k_d]] if isinstance(k_d, Number) else k_d
|
self._k_d = k_d
|
||||||
|
self.k_f = k_f # feedforward gain
|
||||||
|
if isinstance(self._k_p, Number):
|
||||||
|
self._k_p = [[0], [self._k_p]]
|
||||||
|
if isinstance(self._k_i, Number):
|
||||||
|
self._k_i = [[0], [self._k_i]]
|
||||||
|
if isinstance(self._k_d, Number):
|
||||||
|
self._k_d = [[0], [self._k_d]]
|
||||||
|
|
||||||
self.set_limits(pos_limit, neg_limit)
|
self.pos_limit = pos_limit
|
||||||
|
self.neg_limit = neg_limit
|
||||||
|
|
||||||
self.i_dt = 1.0 / rate
|
self.i_unwind_rate = 0.3 / rate
|
||||||
|
self.i_rate = 1.0 / rate
|
||||||
self.speed = 0.0
|
self.speed = 0.0
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
@@ -26,6 +35,10 @@ class PIDController:
|
|||||||
def k_d(self):
|
def k_d(self):
|
||||||
return np.interp(self.speed, self._k_d[0], self._k_d[1])
|
return np.interp(self.speed, self._k_d[0], self._k_d[1])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def error_integral(self):
|
||||||
|
return self.i/self.k_i
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.p = 0.0
|
self.p = 0.0
|
||||||
self.i = 0.0
|
self.i = 0.0
|
||||||
@@ -33,25 +46,25 @@ class PIDController:
|
|||||||
self.f = 0.0
|
self.f = 0.0
|
||||||
self.control = 0
|
self.control = 0
|
||||||
|
|
||||||
def set_limits(self, pos_limit, neg_limit):
|
def update(self, error, error_rate=0.0, speed=0.0, override=False, feedforward=0., freeze_integrator=False):
|
||||||
self.pos_limit = pos_limit
|
|
||||||
self.neg_limit = neg_limit
|
|
||||||
|
|
||||||
def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False):
|
|
||||||
self.speed = speed
|
self.speed = speed
|
||||||
self.p = self.k_p * float(error)
|
|
||||||
self.d = self.k_d * error_rate
|
|
||||||
self.f = feedforward
|
|
||||||
|
|
||||||
if not freeze_integrator:
|
self.p = float(error) * self.k_p
|
||||||
i = self.i + self.k_i * self.i_dt * error
|
self.f = feedforward * self.k_f
|
||||||
|
self.d = error_rate * self.k_d
|
||||||
|
|
||||||
# Don't allow windup if already clipping
|
if override:
|
||||||
test_control = self.p + i + self.d + self.f
|
self.i -= self.i_unwind_rate * float(np.sign(self.i))
|
||||||
i_upperbound = self.i if test_control > self.pos_limit else self.pos_limit
|
else:
|
||||||
i_lowerbound = self.i if test_control < self.neg_limit else self.neg_limit
|
if not freeze_integrator:
|
||||||
self.i = np.clip(i, i_lowerbound, i_upperbound)
|
self.i = self.i + error * self.k_i * self.i_rate
|
||||||
|
|
||||||
|
# Clip i to prevent exceeding control limits
|
||||||
|
control_no_i = self.p + self.d + self.f
|
||||||
|
control_no_i = np.clip(control_no_i, self.neg_limit, self.pos_limit)
|
||||||
|
self.i = np.clip(self.i, self.neg_limit - control_no_i, self.pos_limit - control_no_i)
|
||||||
|
|
||||||
control = self.p + self.i + self.d + self.f
|
control = self.p + self.i + self.d + self.f
|
||||||
|
|
||||||
self.control = np.clip(control, self.neg_limit, self.pos_limit)
|
self.control = np.clip(control, self.neg_limit, self.pos_limit)
|
||||||
return self.control
|
return self.control
|
||||||
|
|||||||
+5
-9
@@ -13,11 +13,7 @@ public:
|
|||||||
if (prefix.empty()) {
|
if (prefix.empty()) {
|
||||||
prefix = util::random_string(15);
|
prefix = util::random_string(15);
|
||||||
}
|
}
|
||||||
#ifdef __APPLE__
|
msgq_path = Path::shm_path() + "/" + prefix;
|
||||||
msgq_path = "/tmp/msgq_" + prefix;
|
|
||||||
#else
|
|
||||||
msgq_path = "/dev/shm/msgq_" + prefix;
|
|
||||||
#endif
|
|
||||||
bool ret = util::create_directories(msgq_path, 0777);
|
bool ret = util::create_directories(msgq_path, 0777);
|
||||||
assert(ret);
|
assert(ret);
|
||||||
setenv("OPENPILOT_PREFIX", prefix.c_str(), 1);
|
setenv("OPENPILOT_PREFIX", prefix.c_str(), 1);
|
||||||
@@ -27,14 +23,14 @@ public:
|
|||||||
auto param_path = Params().getParamPath();
|
auto param_path = Params().getParamPath();
|
||||||
if (util::file_exists(param_path)) {
|
if (util::file_exists(param_path)) {
|
||||||
std::string real_path = util::readlink(param_path);
|
std::string real_path = util::readlink(param_path);
|
||||||
util::check_system(util::string_format("rm %s -rf", real_path.c_str()));
|
system(util::string_format("rm %s -rf", real_path.c_str()).c_str());
|
||||||
unlink(param_path.c_str());
|
unlink(param_path.c_str());
|
||||||
}
|
}
|
||||||
if (getenv("COMMA_CACHE") == nullptr) {
|
if (getenv("COMMA_CACHE") == nullptr) {
|
||||||
util::check_system(util::string_format("rm %s -rf", Path::download_cache_root().c_str()));
|
system(util::string_format("rm %s -rf", Path::download_cache_root().c_str()).c_str());
|
||||||
}
|
}
|
||||||
util::check_system(util::string_format("rm %s -rf", Path::comma_home().c_str()));
|
system(util::string_format("rm %s -rf", Path::comma_home().c_str()).c_str());
|
||||||
util::check_system(util::string_format("rm %s -rf", msgq_path.c_str()));
|
system(util::string_format("rm %s -rf", msgq_path.c_str()).c_str());
|
||||||
unsetenv("OPENPILOT_PREFIX");
|
unsetenv("OPENPILOT_PREFIX");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-15
@@ -1,5 +1,4 @@
|
|||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import shutil
|
import shutil
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@@ -10,20 +9,20 @@ from openpilot.system.hardware.hw import Paths
|
|||||||
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||||
|
|
||||||
class OpenpilotPrefix:
|
class OpenpilotPrefix:
|
||||||
def __init__(self, prefix: str | None = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
|
def __init__(self, prefix: str = None, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
|
||||||
self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15])
|
self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15])
|
||||||
shm_path = "/tmp" if platform.system() == "Darwin" else "/dev/shm"
|
self.msgq_path = os.path.join(Paths.shm_path(), self.prefix)
|
||||||
self.msgq_path = os.path.join(shm_path, "msgq_" + self.prefix)
|
|
||||||
self.create_dirs_on_enter = create_dirs_on_enter
|
|
||||||
self.clean_dirs_on_exit = clean_dirs_on_exit
|
self.clean_dirs_on_exit = clean_dirs_on_exit
|
||||||
self.shared_download_cache = shared_download_cache
|
self.shared_download_cache = shared_download_cache
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.original_prefix = os.environ.get('OPENPILOT_PREFIX', None)
|
self.original_prefix = os.environ.get('OPENPILOT_PREFIX', None)
|
||||||
os.environ['OPENPILOT_PREFIX'] = self.prefix
|
os.environ['OPENPILOT_PREFIX'] = self.prefix
|
||||||
|
try:
|
||||||
if self.create_dirs_on_enter:
|
os.mkdir(self.msgq_path)
|
||||||
self.create_dirs()
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
os.makedirs(Paths.log_root(), exist_ok=True)
|
||||||
|
|
||||||
if self.shared_download_cache:
|
if self.shared_download_cache:
|
||||||
os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT
|
os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||||
@@ -41,13 +40,6 @@ class OpenpilotPrefix:
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_dirs(self):
|
|
||||||
try:
|
|
||||||
os.mkdir(self.msgq_path)
|
|
||||||
except FileExistsError:
|
|
||||||
pass
|
|
||||||
os.makedirs(Paths.log_root(), exist_ok=True)
|
|
||||||
|
|
||||||
def clean_dirs(self):
|
def clean_dirs(self):
|
||||||
symlink_path = Params().get_param_path()
|
symlink_path = Params().get_param_path()
|
||||||
if os.path.exists(symlink_path):
|
if os.path.exists(symlink_path):
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
#include "common/timing.h"
|
#include "common/timing.h"
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
|
|
||||||
RateKeeper::RateKeeper(const std::string &name_, float rate, float print_delay_threshold_)
|
RateKeeper::RateKeeper(const std::string &name, float rate, float print_delay_threshold)
|
||||||
: name(name_),
|
: name(name),
|
||||||
print_delay_threshold(std::max(0.f, print_delay_threshold_)) {
|
print_delay_threshold(std::max(0.f, print_delay_threshold)) {
|
||||||
interval = 1 / rate;
|
interval = 1 / rate;
|
||||||
last_monitor_time = seconds_since_boot();
|
last_monitor_time = seconds_since_boot();
|
||||||
next_frame_time = last_monitor_time + interval;
|
next_frame_time = last_monitor_time + interval;
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import time
|
|||||||
|
|
||||||
from setproctitle import getproctitle
|
from setproctitle import getproctitle
|
||||||
|
|
||||||
from openpilot.common.utils import MovingAverage
|
from openpilot.common.util import MovingAverage
|
||||||
from openpilot.system.hardware import PC
|
from openpilot.system.hardware import PC
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import time
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from openpilot.common.swaglog import cloudlog
|
||||||
|
|
||||||
|
|
||||||
|
def retry(attempts=3, delay=1.0, ignore_failure=False):
|
||||||
|
def decorator(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
for _ in range(attempts):
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
cloudlog.exception(f"{func.__name__} failed, trying again")
|
||||||
|
time.sleep(delay)
|
||||||
|
|
||||||
|
if ignore_failure:
|
||||||
|
cloudlog.error(f"{func.__name__} failed after retry")
|
||||||
|
else:
|
||||||
|
raise Exception(f"{func.__name__} failed after retry")
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
@retry(attempts=10)
|
||||||
|
def abc():
|
||||||
|
raise ValueError("abc failed :(")
|
||||||
|
abc()
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(cmd: list[str], cwd=None, env=None) -> str:
|
||||||
|
return subprocess.check_output(cmd, encoding='utf8', cwd=cwd, env=env).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd_default(cmd: list[str], default: str = "", cwd=None, env=None) -> str:
|
||||||
|
try:
|
||||||
|
return run_cmd(cmd, cwd=cwd, env=env)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return default
|
||||||
|
|
||||||
Executable → Regular
+2
-2
@@ -6,9 +6,9 @@ from openpilot.common.basedir import BASEDIR
|
|||||||
class Spinner:
|
class Spinner:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
try:
|
try:
|
||||||
self.spinner_proc = subprocess.Popen(["./spinner.py"],
|
self.spinner_proc = subprocess.Popen(["./spinner"],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
cwd=os.path.join(BASEDIR, "system", "ui"),
|
cwd=os.path.join(BASEDIR, "selfdrive", "ui"),
|
||||||
close_fds=True)
|
close_fds=True)
|
||||||
except OSError:
|
except OSError:
|
||||||
self.spinner_proc = None
|
self.spinner_proc = None
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from openpilot.common.utils import atomic_write
|
from openpilot.common.file_helpers import atomic_write_in_dir
|
||||||
|
|
||||||
|
|
||||||
class TestFileHelpers:
|
class TestFileHelpers:
|
||||||
@@ -15,5 +15,5 @@ class TestFileHelpers:
|
|||||||
assert f.read() == "test"
|
assert f.read() == "test"
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
def test_atomic_write(self):
|
def test_atomic_write_in_dir(self):
|
||||||
self.run_atomic_write_func(atomic_write)
|
self.run_atomic_write_func(atomic_write_in_dir)
|
||||||
|
|||||||
+12
-44
@@ -1,11 +1,10 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import datetime
|
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from openpilot.common.params import Params, ParamKeyFlag, UnknownKeyName
|
from openpilot.common.params import Params, ParamKeyType, UnknownKeyName
|
||||||
|
|
||||||
class TestParams:
|
class TestParams:
|
||||||
def setup_method(self):
|
def setup_method(self):
|
||||||
@@ -13,7 +12,7 @@ class TestParams:
|
|||||||
|
|
||||||
def test_params_put_and_get(self):
|
def test_params_put_and_get(self):
|
||||||
self.params.put("DongleId", "cb38263377b873ee")
|
self.params.put("DongleId", "cb38263377b873ee")
|
||||||
assert self.params.get("DongleId") == "cb38263377b873ee"
|
assert self.params.get("DongleId") == b"cb38263377b873ee"
|
||||||
|
|
||||||
def test_params_non_ascii(self):
|
def test_params_non_ascii(self):
|
||||||
st = b"\xe1\x90\xff"
|
st = b"\xe1\x90\xff"
|
||||||
@@ -21,7 +20,7 @@ class TestParams:
|
|||||||
assert self.params.get("CarParams") == st
|
assert self.params.get("CarParams") == st
|
||||||
|
|
||||||
def test_params_get_cleared_manager_start(self):
|
def test_params_get_cleared_manager_start(self):
|
||||||
self.params.put("CarParams", b"test")
|
self.params.put("CarParams", "test")
|
||||||
self.params.put("DongleId", "cb38263377b873ee")
|
self.params.put("DongleId", "cb38263377b873ee")
|
||||||
assert self.params.get("CarParams") == b"test"
|
assert self.params.get("CarParams") == b"test"
|
||||||
|
|
||||||
@@ -30,24 +29,24 @@ class TestParams:
|
|||||||
f.write("test")
|
f.write("test")
|
||||||
assert os.path.isfile(undefined_param)
|
assert os.path.isfile(undefined_param)
|
||||||
|
|
||||||
self.params.clear_all(ParamKeyFlag.CLEAR_ON_MANAGER_START)
|
self.params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START)
|
||||||
assert self.params.get("CarParams") is None
|
assert self.params.get("CarParams") is None
|
||||||
assert self.params.get("DongleId") is not None
|
assert self.params.get("DongleId") is not None
|
||||||
assert not os.path.isfile(undefined_param)
|
assert not os.path.isfile(undefined_param)
|
||||||
|
|
||||||
def test_params_two_things(self):
|
def test_params_two_things(self):
|
||||||
self.params.put("DongleId", "bob")
|
self.params.put("DongleId", "bob")
|
||||||
self.params.put("AthenadPid", 123)
|
self.params.put("AthenadPid", "123")
|
||||||
assert self.params.get("DongleId") == "bob"
|
assert self.params.get("DongleId") == b"bob"
|
||||||
assert self.params.get("AthenadPid") == 123
|
assert self.params.get("AthenadPid") == b"123"
|
||||||
|
|
||||||
def test_params_get_block(self):
|
def test_params_get_block(self):
|
||||||
def _delayed_writer():
|
def _delayed_writer():
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
self.params.put("CarParams", b"test")
|
self.params.put("CarParams", "test")
|
||||||
threading.Thread(target=_delayed_writer).start()
|
threading.Thread(target=_delayed_writer).start()
|
||||||
assert self.params.get("CarParams") is None
|
assert self.params.get("CarParams") is None
|
||||||
assert self.params.get("CarParams", block=True) == b"test"
|
assert self.params.get("CarParams", True) == b"test"
|
||||||
|
|
||||||
def test_params_unknown_key_fails(self):
|
def test_params_unknown_key_fails(self):
|
||||||
with pytest.raises(UnknownKeyName):
|
with pytest.raises(UnknownKeyName):
|
||||||
@@ -77,17 +76,17 @@ class TestParams:
|
|||||||
self.params.put_bool("IsMetric", False)
|
self.params.put_bool("IsMetric", False)
|
||||||
assert not self.params.get_bool("IsMetric")
|
assert not self.params.get_bool("IsMetric")
|
||||||
|
|
||||||
self.params.put("IsMetric", True)
|
self.params.put("IsMetric", "1")
|
||||||
assert self.params.get_bool("IsMetric")
|
assert self.params.get_bool("IsMetric")
|
||||||
|
|
||||||
self.params.put("IsMetric", False)
|
self.params.put("IsMetric", "0")
|
||||||
assert not self.params.get_bool("IsMetric")
|
assert not self.params.get_bool("IsMetric")
|
||||||
|
|
||||||
def test_put_non_blocking_with_get_block(self):
|
def test_put_non_blocking_with_get_block(self):
|
||||||
q = Params()
|
q = Params()
|
||||||
def _delayed_writer():
|
def _delayed_writer():
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
Params().put_nonblocking("CarParams", b"test")
|
Params().put_nonblocking("CarParams", "test")
|
||||||
threading.Thread(target=_delayed_writer).start()
|
threading.Thread(target=_delayed_writer).start()
|
||||||
assert q.get("CarParams") is None
|
assert q.get("CarParams") is None
|
||||||
assert q.get("CarParams", True) == b"test"
|
assert q.get("CarParams", True) == b"test"
|
||||||
@@ -108,34 +107,3 @@ class TestParams:
|
|||||||
assert len(keys) > 20
|
assert len(keys) > 20
|
||||||
assert len(keys) == len(set(keys))
|
assert len(keys) == len(set(keys))
|
||||||
assert b"CarParams" in keys
|
assert b"CarParams" in keys
|
||||||
|
|
||||||
def test_params_default_value(self):
|
|
||||||
self.params.remove("LanguageSetting")
|
|
||||||
self.params.remove("LongitudinalPersonality")
|
|
||||||
self.params.remove("LiveParameters")
|
|
||||||
|
|
||||||
assert self.params.get("LanguageSetting") is None
|
|
||||||
assert self.params.get("LanguageSetting", return_default=False) is None
|
|
||||||
assert isinstance(self.params.get("LanguageSetting", return_default=True), str)
|
|
||||||
assert isinstance(self.params.get("LongitudinalPersonality", return_default=True), int)
|
|
||||||
assert self.params.get("LiveParameters") is None
|
|
||||||
assert self.params.get("LiveParameters", return_default=True) is None
|
|
||||||
|
|
||||||
def test_params_get_type(self):
|
|
||||||
# json
|
|
||||||
self.params.put("ApiCache_FirehoseStats", {"a": 0})
|
|
||||||
assert self.params.get("ApiCache_FirehoseStats") == {"a": 0}
|
|
||||||
|
|
||||||
# int
|
|
||||||
self.params.put("BootCount", 1441)
|
|
||||||
assert self.params.get("BootCount") == 1441
|
|
||||||
|
|
||||||
# bool
|
|
||||||
self.params.put("AdbEnabled", True)
|
|
||||||
assert self.params.get("AdbEnabled")
|
|
||||||
assert isinstance(self.params.get("AdbEnabled"), bool)
|
|
||||||
|
|
||||||
# time
|
|
||||||
now = datetime.datetime.now(datetime.UTC)
|
|
||||||
self.params.put("InstallDate", now)
|
|
||||||
assert self.params.get("InstallDate") == now
|
|
||||||
|
|||||||
@@ -24,6 +24,6 @@ class TestSimpleKalman:
|
|||||||
self.kf.set_x([[1.0], [1.0]])
|
self.kf.set_x([[1.0], [1.0]])
|
||||||
assert self.kf.x == [[1.0], [1.0]]
|
assert self.kf.x == [[1.0], [1.0]]
|
||||||
|
|
||||||
def test_update_returns_state(self):
|
def update_returns_state(self):
|
||||||
x = self.kf.update(100)
|
x = self.kf.update(100)
|
||||||
assert x == [i[0] for i in self.kf.x]
|
assert x == self.kf.x
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ TEST_CASE("util::read_file") {
|
|||||||
REQUIRE(util::read_file(filename).empty());
|
REQUIRE(util::read_file(filename).empty());
|
||||||
|
|
||||||
std::string content = random_bytes(64 * 1024);
|
std::string content = random_bytes(64 * 1024);
|
||||||
REQUIRE(write(fd, content.c_str(), content.size()) == (ssize_t)content.size());
|
write(fd, content.c_str(), content.size());
|
||||||
std::string ret = util::read_file(filename);
|
std::string ret = util::read_file(filename);
|
||||||
bool equal = (ret == content);
|
bool equal = (ret == content);
|
||||||
REQUIRE(equal);
|
REQUIRE(equal);
|
||||||
@@ -114,12 +114,12 @@ TEST_CASE("util::safe_fwrite") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("util::create_directories") {
|
TEST_CASE("util::create_directories") {
|
||||||
REQUIRE(system("rm /tmp/test_create_directories -rf") == 0);
|
system("rm /tmp/test_create_directories -rf");
|
||||||
std::string dir = "/tmp/test_create_directories/a/b/c/d/e/f";
|
std::string dir = "/tmp/test_create_directories/a/b/c/d/e/f";
|
||||||
|
|
||||||
auto check_dir_permissions = [](const std::string &path, mode_t mode) -> bool {
|
auto check_dir_permissions = [](const std::string &dir, mode_t mode) -> bool {
|
||||||
struct stat st = {};
|
struct stat st = {};
|
||||||
return stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode;
|
return stat(dir.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
SECTION("create_directories") {
|
SECTION("create_directories") {
|
||||||
@@ -132,7 +132,7 @@ TEST_CASE("util::create_directories") {
|
|||||||
}
|
}
|
||||||
SECTION("a file exists with the same name") {
|
SECTION("a file exists with the same name") {
|
||||||
REQUIRE(util::create_directories(dir, 0755));
|
REQUIRE(util::create_directories(dir, 0755));
|
||||||
int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT, 0644);
|
int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT);
|
||||||
REQUIRE(f != -1);
|
REQUIRE(f != -1);
|
||||||
close(f);
|
close(f);
|
||||||
REQUIRE(util::create_directories(dir + "/file", 0755) == false);
|
REQUIRE(util::create_directories(dir + "/file", 0755) == false);
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ from openpilot.common.basedir import BASEDIR
|
|||||||
class TextWindow:
|
class TextWindow:
|
||||||
def __init__(self, text):
|
def __init__(self, text):
|
||||||
try:
|
try:
|
||||||
self.text_proc = subprocess.Popen(["./text.py", text],
|
self.text_proc = subprocess.Popen(["./text", text],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
cwd=os.path.join(BASEDIR, "system", "ui"),
|
cwd=os.path.join(BASEDIR, "selfdrive", "ui"),
|
||||||
close_fds=True)
|
close_fds=True)
|
||||||
except OSError:
|
except OSError:
|
||||||
self.text_proc = None
|
self.text_proc = None
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
transformations
|
||||||
|
transformations.cpp
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Import('env', 'envCython')
|
||||||
|
|
||||||
|
transformations = env.Library('transformations', ['orientation.cc', 'coordinates.cc'])
|
||||||
|
transformations_python = envCython.Program('transformations.so', 'transformations.pyx')
|
||||||
|
Export('transformations', 'transformations_python')
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
|
||||||
|
#include "common/transformations/coordinates.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <eigen3/Eigen/Dense>
|
||||||
|
|
||||||
|
double a = 6378137; // lgtm [cpp/short-global-name]
|
||||||
|
double b = 6356752.3142; // lgtm [cpp/short-global-name]
|
||||||
|
double esq = 6.69437999014 * 0.001; // lgtm [cpp/short-global-name]
|
||||||
|
double e1sq = 6.73949674228 * 0.001;
|
||||||
|
|
||||||
|
|
||||||
|
static Geodetic to_degrees(Geodetic geodetic){
|
||||||
|
geodetic.lat = RAD2DEG(geodetic.lat);
|
||||||
|
geodetic.lon = RAD2DEG(geodetic.lon);
|
||||||
|
return geodetic;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Geodetic to_radians(Geodetic geodetic){
|
||||||
|
geodetic.lat = DEG2RAD(geodetic.lat);
|
||||||
|
geodetic.lon = DEG2RAD(geodetic.lon);
|
||||||
|
return geodetic;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ECEF geodetic2ecef(const Geodetic &geodetic) {
|
||||||
|
auto g = to_radians(geodetic);
|
||||||
|
double xi = sqrt(1.0 - esq * pow(sin(g.lat), 2));
|
||||||
|
double x = (a / xi + g.alt) * cos(g.lat) * cos(g.lon);
|
||||||
|
double y = (a / xi + g.alt) * cos(g.lat) * sin(g.lon);
|
||||||
|
double z = (a / xi * (1.0 - esq) + g.alt) * sin(g.lat);
|
||||||
|
return {x, y, z};
|
||||||
|
}
|
||||||
|
|
||||||
|
Geodetic ecef2geodetic(const ECEF &e) {
|
||||||
|
// Convert from ECEF to geodetic using Ferrari's methods
|
||||||
|
// https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#Ferrari.27s_solution
|
||||||
|
double x = e.x;
|
||||||
|
double y = e.y;
|
||||||
|
double z = e.z;
|
||||||
|
|
||||||
|
double r = sqrt(x * x + y * y);
|
||||||
|
double Esq = a * a - b * b;
|
||||||
|
double F = 54 * b * b * z * z;
|
||||||
|
double G = r * r + (1 - esq) * z * z - esq * Esq;
|
||||||
|
double C = (esq * esq * F * r * r) / (pow(G, 3));
|
||||||
|
double S = cbrt(1 + C + sqrt(C * C + 2 * C));
|
||||||
|
double P = F / (3 * pow((S + 1 / S + 1), 2) * G * G);
|
||||||
|
double Q = sqrt(1 + 2 * esq * esq * P);
|
||||||
|
double r_0 = -(P * esq * r) / (1 + Q) + sqrt(0.5 * a * a*(1 + 1.0 / Q) - P * (1 - esq) * z * z / (Q * (1 + Q)) - 0.5 * P * r * r);
|
||||||
|
double U = sqrt(pow((r - esq * r_0), 2) + z * z);
|
||||||
|
double V = sqrt(pow((r - esq * r_0), 2) + (1 - esq) * z * z);
|
||||||
|
double Z_0 = b * b * z / (a * V);
|
||||||
|
double h = U * (1 - b * b / (a * V));
|
||||||
|
|
||||||
|
double lat = atan((z + e1sq * Z_0) / r);
|
||||||
|
double lon = atan2(y, x);
|
||||||
|
|
||||||
|
return to_degrees({lat, lon, h});
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalCoord::LocalCoord(const Geodetic &geodetic, const ECEF &e) {
|
||||||
|
init_ecef << e.x, e.y, e.z;
|
||||||
|
|
||||||
|
auto g = to_radians(geodetic);
|
||||||
|
|
||||||
|
ned2ecef_matrix <<
|
||||||
|
-sin(g.lat)*cos(g.lon), -sin(g.lon), -cos(g.lat)*cos(g.lon),
|
||||||
|
-sin(g.lat)*sin(g.lon), cos(g.lon), -cos(g.lat)*sin(g.lon),
|
||||||
|
cos(g.lat), 0, -sin(g.lat);
|
||||||
|
ecef2ned_matrix = ned2ecef_matrix.transpose();
|
||||||
|
}
|
||||||
|
|
||||||
|
NED LocalCoord::ecef2ned(const ECEF &e) {
|
||||||
|
Eigen::Vector3d ecef;
|
||||||
|
ecef << e.x, e.y, e.z;
|
||||||
|
|
||||||
|
Eigen::Vector3d ned = (ecef2ned_matrix * (ecef - init_ecef));
|
||||||
|
return {ned[0], ned[1], ned[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
ECEF LocalCoord::ned2ecef(const NED &n) {
|
||||||
|
Eigen::Vector3d ned;
|
||||||
|
ned << n.n, n.e, n.d;
|
||||||
|
|
||||||
|
Eigen::Vector3d ecef = (ned2ecef_matrix * ned) + init_ecef;
|
||||||
|
return {ecef[0], ecef[1], ecef[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
NED LocalCoord::geodetic2ned(const Geodetic &g) {
|
||||||
|
ECEF e = ::geodetic2ecef(g);
|
||||||
|
return ecef2ned(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Geodetic LocalCoord::ned2geodetic(const NED &n) {
|
||||||
|
ECEF e = ned2ecef(n);
|
||||||
|
return ::ecef2geodetic(e);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eigen3/Eigen/Dense>
|
||||||
|
|
||||||
|
#define DEG2RAD(x) ((x) * M_PI / 180.0)
|
||||||
|
#define RAD2DEG(x) ((x) * 180.0 / M_PI)
|
||||||
|
|
||||||
|
struct ECEF {
|
||||||
|
double x, y, z;
|
||||||
|
Eigen::Vector3d to_vector() const {
|
||||||
|
return Eigen::Vector3d(x, y, z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NED {
|
||||||
|
double n, e, d;
|
||||||
|
Eigen::Vector3d to_vector() const {
|
||||||
|
return Eigen::Vector3d(n, e, d);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Geodetic {
|
||||||
|
double lat, lon, alt;
|
||||||
|
bool radians=false;
|
||||||
|
};
|
||||||
|
|
||||||
|
ECEF geodetic2ecef(const Geodetic &g);
|
||||||
|
Geodetic ecef2geodetic(const ECEF &e);
|
||||||
|
|
||||||
|
class LocalCoord {
|
||||||
|
public:
|
||||||
|
Eigen::Matrix3d ned2ecef_matrix;
|
||||||
|
Eigen::Matrix3d ecef2ned_matrix;
|
||||||
|
Eigen::Vector3d init_ecef;
|
||||||
|
LocalCoord(const Geodetic &g, const ECEF &e);
|
||||||
|
LocalCoord(const Geodetic &g) : LocalCoord(g, ::geodetic2ecef(g)) {}
|
||||||
|
LocalCoord(const ECEF &e) : LocalCoord(::ecef2geodetic(e), e) {}
|
||||||
|
|
||||||
|
NED ecef2ned(const ECEF &e);
|
||||||
|
ECEF ned2ecef(const NED &n);
|
||||||
|
NED geodetic2ned(const Geodetic &g);
|
||||||
|
Geodetic ned2geodetic(const NED &n);
|
||||||
|
};
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <eigen3/Eigen/Dense>
|
||||||
|
|
||||||
|
#include "common/transformations/orientation.hpp"
|
||||||
|
#include "common/transformations/coordinates.hpp"
|
||||||
|
|
||||||
|
Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat) {
|
||||||
|
if (quat.w() > 0){
|
||||||
|
return quat;
|
||||||
|
} else {
|
||||||
|
return Eigen::Quaterniond(-quat.w(), -quat.x(), -quat.y(), -quat.z());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Quaterniond euler2quat(const Eigen::Vector3d &euler) {
|
||||||
|
Eigen::Quaterniond q;
|
||||||
|
|
||||||
|
q = Eigen::AngleAxisd(euler(2), Eigen::Vector3d::UnitZ())
|
||||||
|
* Eigen::AngleAxisd(euler(1), Eigen::Vector3d::UnitY())
|
||||||
|
* Eigen::AngleAxisd(euler(0), Eigen::Vector3d::UnitX());
|
||||||
|
return ensure_unique(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Eigen::Vector3d quat2euler(const Eigen::Quaterniond &quat) {
|
||||||
|
// TODO: switch to eigen implementation if the range of the Euler angles doesn't matter anymore
|
||||||
|
// Eigen::Vector3d euler = quat.toRotationMatrix().eulerAngles(2, 1, 0);
|
||||||
|
// return {euler(2), euler(1), euler(0)};
|
||||||
|
double gamma = atan2(2 * (quat.w() * quat.x() + quat.y() * quat.z()), 1 - 2 * (quat.x()*quat.x() + quat.y()*quat.y()));
|
||||||
|
double asin_arg_clipped = std::clamp(2 * (quat.w() * quat.y() - quat.z() * quat.x()), -1.0, 1.0);
|
||||||
|
double theta = asin(asin_arg_clipped);
|
||||||
|
double psi = atan2(2 * (quat.w() * quat.z() + quat.x() * quat.y()), 1 - 2 * (quat.y()*quat.y() + quat.z()*quat.z()));
|
||||||
|
return {gamma, theta, psi};
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Matrix3d quat2rot(const Eigen::Quaterniond &quat) {
|
||||||
|
return quat.toRotationMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Quaterniond rot2quat(const Eigen::Matrix3d &rot) {
|
||||||
|
return ensure_unique(Eigen::Quaterniond(rot));
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Matrix3d euler2rot(const Eigen::Vector3d &euler) {
|
||||||
|
return quat2rot(euler2quat(euler));
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Vector3d rot2euler(const Eigen::Matrix3d &rot) {
|
||||||
|
return quat2euler(rot2quat(rot));
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw) {
|
||||||
|
return euler2rot({roll, pitch, yaw});
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Matrix3d rot(const Eigen::Vector3d &axis, double angle) {
|
||||||
|
Eigen::Quaterniond q;
|
||||||
|
q = Eigen::AngleAxisd(angle, axis);
|
||||||
|
return q.toRotationMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Eigen::Vector3d ecef_euler_from_ned(const ECEF &ecef_init, const Eigen::Vector3d &ned_pose) {
|
||||||
|
/*
|
||||||
|
Using Rotations to Build Aerospace Coordinate Systems
|
||||||
|
Don Koks
|
||||||
|
https://apps.dtic.mil/dtic/tr/fulltext/u2/a484864.pdf
|
||||||
|
*/
|
||||||
|
LocalCoord converter = LocalCoord(ecef_init);
|
||||||
|
Eigen::Vector3d zero = ecef_init.to_vector();
|
||||||
|
|
||||||
|
Eigen::Vector3d x0 = converter.ned2ecef({1, 0, 0}).to_vector() - zero;
|
||||||
|
Eigen::Vector3d y0 = converter.ned2ecef({0, 1, 0}).to_vector() - zero;
|
||||||
|
Eigen::Vector3d z0 = converter.ned2ecef({0, 0, 1}).to_vector() - zero;
|
||||||
|
|
||||||
|
Eigen::Vector3d x1 = rot(z0, ned_pose(2)) * x0;
|
||||||
|
Eigen::Vector3d y1 = rot(z0, ned_pose(2)) * y0;
|
||||||
|
Eigen::Vector3d z1 = rot(z0, ned_pose(2)) * z0;
|
||||||
|
|
||||||
|
Eigen::Vector3d x2 = rot(y1, ned_pose(1)) * x1;
|
||||||
|
Eigen::Vector3d y2 = rot(y1, ned_pose(1)) * y1;
|
||||||
|
Eigen::Vector3d z2 = rot(y1, ned_pose(1)) * z1;
|
||||||
|
|
||||||
|
Eigen::Vector3d x3 = rot(x2, ned_pose(0)) * x2;
|
||||||
|
Eigen::Vector3d y3 = rot(x2, ned_pose(0)) * y2;
|
||||||
|
|
||||||
|
|
||||||
|
x0 = Eigen::Vector3d(1, 0, 0);
|
||||||
|
y0 = Eigen::Vector3d(0, 1, 0);
|
||||||
|
z0 = Eigen::Vector3d(0, 0, 1);
|
||||||
|
|
||||||
|
double psi = atan2(x3.dot(y0), x3.dot(x0));
|
||||||
|
double theta = atan2(-x3.dot(z0), sqrt(pow(x3.dot(x0), 2) + pow(x3.dot(y0), 2)));
|
||||||
|
|
||||||
|
y2 = rot(z0, psi) * y0;
|
||||||
|
z2 = rot(y2, theta) * z0;
|
||||||
|
|
||||||
|
double phi = atan2(y3.dot(z2), y3.dot(y2));
|
||||||
|
|
||||||
|
return {phi, theta, psi};
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Vector3d ned_euler_from_ecef(const ECEF &ecef_init, const Eigen::Vector3d &ecef_pose) {
|
||||||
|
/*
|
||||||
|
Using Rotations to Build Aerospace Coordinate Systems
|
||||||
|
Don Koks
|
||||||
|
https://apps.dtic.mil/dtic/tr/fulltext/u2/a484864.pdf
|
||||||
|
*/
|
||||||
|
LocalCoord converter = LocalCoord(ecef_init);
|
||||||
|
|
||||||
|
Eigen::Vector3d x0 = Eigen::Vector3d(1, 0, 0);
|
||||||
|
Eigen::Vector3d y0 = Eigen::Vector3d(0, 1, 0);
|
||||||
|
Eigen::Vector3d z0 = Eigen::Vector3d(0, 0, 1);
|
||||||
|
|
||||||
|
Eigen::Vector3d x1 = rot(z0, ecef_pose(2)) * x0;
|
||||||
|
Eigen::Vector3d y1 = rot(z0, ecef_pose(2)) * y0;
|
||||||
|
Eigen::Vector3d z1 = rot(z0, ecef_pose(2)) * z0;
|
||||||
|
|
||||||
|
Eigen::Vector3d x2 = rot(y1, ecef_pose(1)) * x1;
|
||||||
|
Eigen::Vector3d y2 = rot(y1, ecef_pose(1)) * y1;
|
||||||
|
Eigen::Vector3d z2 = rot(y1, ecef_pose(1)) * z1;
|
||||||
|
|
||||||
|
Eigen::Vector3d x3 = rot(x2, ecef_pose(0)) * x2;
|
||||||
|
Eigen::Vector3d y3 = rot(x2, ecef_pose(0)) * y2;
|
||||||
|
|
||||||
|
Eigen::Vector3d zero = ecef_init.to_vector();
|
||||||
|
x0 = converter.ned2ecef({1, 0, 0}).to_vector() - zero;
|
||||||
|
y0 = converter.ned2ecef({0, 1, 0}).to_vector() - zero;
|
||||||
|
z0 = converter.ned2ecef({0, 0, 1}).to_vector() - zero;
|
||||||
|
|
||||||
|
double psi = atan2(x3.dot(y0), x3.dot(x0));
|
||||||
|
double theta = atan2(-x3.dot(z0), sqrt(pow(x3.dot(x0), 2) + pow(x3.dot(y0), 2)));
|
||||||
|
|
||||||
|
y2 = rot(z0, psi) * y0;
|
||||||
|
z2 = rot(y2, theta) * z0;
|
||||||
|
|
||||||
|
double phi = atan2(y3.dot(z2), y3.dot(y2));
|
||||||
|
|
||||||
|
return {phi, theta, psi};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <eigen3/Eigen/Dense>
|
||||||
|
#include "common/transformations/coordinates.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat);
|
||||||
|
|
||||||
|
Eigen::Quaterniond euler2quat(const Eigen::Vector3d &euler);
|
||||||
|
Eigen::Vector3d quat2euler(const Eigen::Quaterniond &quat);
|
||||||
|
Eigen::Matrix3d quat2rot(const Eigen::Quaterniond &quat);
|
||||||
|
Eigen::Quaterniond rot2quat(const Eigen::Matrix3d &rot);
|
||||||
|
Eigen::Matrix3d euler2rot(const Eigen::Vector3d &euler);
|
||||||
|
Eigen::Vector3d rot2euler(const Eigen::Matrix3d &rot);
|
||||||
|
Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw);
|
||||||
|
Eigen::Matrix3d rot(const Eigen::Vector3d &axis, double angle);
|
||||||
|
Eigen::Vector3d ecef_euler_from_ned(const ECEF &ecef_init, const Eigen::Vector3d &ned_pose);
|
||||||
|
Eigen::Vector3d ned_euler_from_ecef(const ECEF &ecef_init, const Eigen::Vector3d &ecef_pose);
|
||||||
@@ -102,36 +102,3 @@ class TestNED:
|
|||||||
np.testing.assert_allclose(converter.ned2ecef(ned_offsets_batch),
|
np.testing.assert_allclose(converter.ned2ecef(ned_offsets_batch),
|
||||||
ecef_positions_offset_batch,
|
ecef_positions_offset_batch,
|
||||||
rtol=1e-9, atol=1e-7)
|
rtol=1e-9, atol=1e-7)
|
||||||
|
|
||||||
def test_errors(self):
|
|
||||||
# Test wrong shape/type for geodetic2ecef
|
|
||||||
# numpy_wrap raises IndexError for scalar input
|
|
||||||
with np.testing.assert_raises(IndexError):
|
|
||||||
coord.geodetic2ecef(1.0)
|
|
||||||
|
|
||||||
with np.testing.assert_raises_regex(ValueError, "Geodetic must be size 3"):
|
|
||||||
coord.geodetic2ecef([0, 0])
|
|
||||||
|
|
||||||
with np.testing.assert_raises_regex(ValueError, "Geodetic must be size 3"):
|
|
||||||
coord.geodetic2ecef([0, 0, 0, 0])
|
|
||||||
|
|
||||||
with np.testing.assert_raises(TypeError):
|
|
||||||
coord.geodetic2ecef(['a', 'b', 'c'])
|
|
||||||
|
|
||||||
# Test LocalCoord constructor errors
|
|
||||||
with np.testing.assert_raises(ValueError):
|
|
||||||
coord.LocalCoord.from_geodetic([0, 0])
|
|
||||||
|
|
||||||
with np.testing.assert_raises(ValueError):
|
|
||||||
coord.LocalCoord.from_geodetic(1)
|
|
||||||
|
|
||||||
with np.testing.assert_raises(TypeError):
|
|
||||||
coord.LocalCoord.from_geodetic(['a', 'b', 'c'])
|
|
||||||
|
|
||||||
# Test wrong shape/type for ecef2geodetic
|
|
||||||
with np.testing.assert_raises(ValueError):
|
|
||||||
coord.ecef2geodetic([1, 2])
|
|
||||||
with np.testing.assert_raises(ValueError):
|
|
||||||
coord.ecef2geodetic([1, 2, 3, 4])
|
|
||||||
with np.testing.assert_raises(IndexError):
|
|
||||||
coord.ecef2geodetic(1.0)
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pytest
|
|
||||||
|
|
||||||
from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \
|
from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \
|
||||||
rot2quat, quat2rot, \
|
rot2quat, quat2rot, \
|
||||||
@@ -60,32 +59,3 @@ class TestOrientation:
|
|||||||
np.testing.assert_allclose(ned_eulers[i], ned_euler_from_ecef(ecef_positions[i], eulers[i]), rtol=1e-7)
|
np.testing.assert_allclose(ned_eulers[i], ned_euler_from_ecef(ecef_positions[i], eulers[i]), rtol=1e-7)
|
||||||
#np.testing.assert_allclose(eulers[i], ecef_euler_from_ned(ecef_positions[i], ned_eulers[i]), rtol=1e-7)
|
#np.testing.assert_allclose(eulers[i], ecef_euler_from_ned(ecef_positions[i], ned_eulers[i]), rtol=1e-7)
|
||||||
# np.testing.assert_allclose(ned_eulers, ned_euler_from_ecef(ecef_positions, eulers), rtol=1e-7)
|
# np.testing.assert_allclose(ned_eulers, ned_euler_from_ecef(ecef_positions, eulers), rtol=1e-7)
|
||||||
|
|
||||||
def test_inputs(self):
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
euler2quat([1, 2])
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
quat2rot([1, 2, 3])
|
|
||||||
|
|
||||||
with pytest.raises(IndexError):
|
|
||||||
rot2quat(np.zeros((2, 2)))
|
|
||||||
|
|
||||||
def test_euler_rot_consistency(self):
|
|
||||||
rpy = [0.1, 0.2, 0.3]
|
|
||||||
R = euler2rot(rpy)
|
|
||||||
|
|
||||||
# R -> q -> R
|
|
||||||
q = rot2quat(R)
|
|
||||||
R_new = quat2rot(q)
|
|
||||||
np.testing.assert_allclose(R, R_new, atol=1e-15)
|
|
||||||
|
|
||||||
# q -> R -> Euler (quat2euler) -> R
|
|
||||||
rpy_new = quat2euler(q)
|
|
||||||
R_new2 = euler2rot(rpy_new)
|
|
||||||
np.testing.assert_allclose(R, R_new2, atol=1e-15)
|
|
||||||
|
|
||||||
# R -> Euler (rot2euler) -> R
|
|
||||||
rpy_from_rot = rot2euler(R)
|
|
||||||
R_new3 = euler2rot(rpy_from_rot)
|
|
||||||
np.testing.assert_allclose(R, R_new3, atol=1e-15)
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# cython: language_level=3
|
||||||
|
from libcpp cimport bool
|
||||||
|
|
||||||
|
cdef extern from "orientation.cc":
|
||||||
|
pass
|
||||||
|
|
||||||
|
cdef extern from "orientation.hpp":
|
||||||
|
cdef cppclass Quaternion "Eigen::Quaterniond":
|
||||||
|
Quaternion()
|
||||||
|
Quaternion(double, double, double, double)
|
||||||
|
double w()
|
||||||
|
double x()
|
||||||
|
double y()
|
||||||
|
double z()
|
||||||
|
|
||||||
|
cdef cppclass Vector3 "Eigen::Vector3d":
|
||||||
|
Vector3()
|
||||||
|
Vector3(double, double, double)
|
||||||
|
double operator()(int)
|
||||||
|
|
||||||
|
cdef cppclass Matrix3 "Eigen::Matrix3d":
|
||||||
|
Matrix3()
|
||||||
|
Matrix3(double*)
|
||||||
|
|
||||||
|
double operator()(int, int)
|
||||||
|
|
||||||
|
Quaternion euler2quat(const Vector3 &)
|
||||||
|
Vector3 quat2euler(const Quaternion &)
|
||||||
|
Matrix3 quat2rot(const Quaternion &)
|
||||||
|
Quaternion rot2quat(const Matrix3 &)
|
||||||
|
Vector3 rot2euler(const Matrix3 &)
|
||||||
|
Matrix3 euler2rot(const Vector3 &)
|
||||||
|
Matrix3 rot_matrix(double, double, double)
|
||||||
|
Vector3 ecef_euler_from_ned(const ECEF &, const Vector3 &)
|
||||||
|
Vector3 ned_euler_from_ecef(const ECEF &, const Vector3 &)
|
||||||
|
|
||||||
|
|
||||||
|
cdef extern from "coordinates.cc":
|
||||||
|
cdef struct ECEF:
|
||||||
|
double x
|
||||||
|
double y
|
||||||
|
double z
|
||||||
|
|
||||||
|
cdef struct NED:
|
||||||
|
double n
|
||||||
|
double e
|
||||||
|
double d
|
||||||
|
|
||||||
|
cdef struct Geodetic:
|
||||||
|
double lat
|
||||||
|
double lon
|
||||||
|
double alt
|
||||||
|
bool radians
|
||||||
|
|
||||||
|
ECEF geodetic2ecef(const Geodetic &)
|
||||||
|
Geodetic ecef2geodetic(const ECEF &)
|
||||||
|
|
||||||
|
cdef cppclass LocalCoord_c "LocalCoord":
|
||||||
|
Matrix3 ned2ecef_matrix
|
||||||
|
Matrix3 ecef2ned_matrix
|
||||||
|
|
||||||
|
LocalCoord_c(const Geodetic &, const ECEF &)
|
||||||
|
LocalCoord_c(const Geodetic &)
|
||||||
|
LocalCoord_c(const ECEF &)
|
||||||
|
|
||||||
|
NED ecef2ned(const ECEF &)
|
||||||
|
ECEF ned2ecef(const NED &)
|
||||||
|
NED geodetic2ned(const Geodetic &)
|
||||||
|
Geodetic ned2geodetic(const NED &)
|
||||||
|
|
||||||
|
cdef extern from "coordinates.hpp":
|
||||||
|
pass
|
||||||
@@ -1,342 +0,0 @@
|
|||||||
import numpy as np
|
|
||||||
|
|
||||||
|
|
||||||
# Constants
|
|
||||||
a = 6378137.0
|
|
||||||
b = 6356752.3142
|
|
||||||
esq = 6.69437999014e-3
|
|
||||||
e1sq = 6.73949674228e-3
|
|
||||||
|
|
||||||
|
|
||||||
def geodetic2ecef_single(g):
|
|
||||||
"""
|
|
||||||
Convert geodetic coordinates (latitude, longitude, altitude) to ECEF.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if len(g) != 3:
|
|
||||||
raise ValueError("Geodetic must be size 3")
|
|
||||||
except TypeError:
|
|
||||||
raise ValueError("Geodetic must be a sequence of length 3") from None
|
|
||||||
|
|
||||||
lat, lon, alt = g
|
|
||||||
lat = np.radians(lat)
|
|
||||||
lon = np.radians(lon)
|
|
||||||
xi = np.sqrt(1.0 - esq * np.sin(lat)**2)
|
|
||||||
x = (a / xi + alt) * np.cos(lat) * np.cos(lon)
|
|
||||||
y = (a / xi + alt) * np.cos(lat) * np.sin(lon)
|
|
||||||
z = (a / xi * (1.0 - esq) + alt) * np.sin(lat)
|
|
||||||
return np.array([x, y, z])
|
|
||||||
|
|
||||||
|
|
||||||
def ecef2geodetic_single(e):
|
|
||||||
"""
|
|
||||||
Convert ECEF to geodetic coordinates using Ferrari's solution.
|
|
||||||
"""
|
|
||||||
x, y, z = e
|
|
||||||
r = np.sqrt(x**2 + y**2)
|
|
||||||
Esq = a**2 - b**2
|
|
||||||
F = 54 * b**2 * z**2
|
|
||||||
G = r**2 + (1 - esq) * z**2 - esq * Esq
|
|
||||||
C = (esq**2 * F * r**2) / (G**3)
|
|
||||||
S = np.cbrt(1 + C + np.sqrt(C**2 + 2 * C))
|
|
||||||
P = F / (3 * (S + 1 / S + 1)**2 * G**2)
|
|
||||||
Q = np.sqrt(1 + 2 * esq**2 * P)
|
|
||||||
r_0 = -(P * esq * r) / (1 + Q) + np.sqrt(0.5 * a**2 * (1 + 1.0 / Q) - P * (1 - esq) * z**2 / (Q * (1 + Q)) - 0.5 * P * r**2)
|
|
||||||
U = np.sqrt((r - esq * r_0)**2 + z**2)
|
|
||||||
V = np.sqrt((r - esq * r_0)**2 + (1 - esq) * z**2)
|
|
||||||
Z_0 = b**2 * z / (a * V)
|
|
||||||
h = U * (1 - b**2 / (a * V))
|
|
||||||
lat = np.arctan((z + e1sq * Z_0) / r)
|
|
||||||
lon = np.arctan2(y, x)
|
|
||||||
return np.array([np.degrees(lat), np.degrees(lon), h])
|
|
||||||
|
|
||||||
|
|
||||||
def euler2quat_single(euler):
|
|
||||||
"""
|
|
||||||
Convert Euler angles (roll, pitch, yaw) to a quaternion.
|
|
||||||
Rotation order: Z-Y-X (yaw, pitch, roll).
|
|
||||||
"""
|
|
||||||
phi, theta, psi = euler
|
|
||||||
|
|
||||||
c_phi, s_phi = np.cos(phi / 2), np.sin(phi / 2)
|
|
||||||
c_theta, s_theta = np.cos(theta / 2), np.sin(theta / 2)
|
|
||||||
c_psi, s_psi = np.cos(psi / 2), np.sin(psi / 2)
|
|
||||||
|
|
||||||
w = c_phi * c_theta * c_psi + s_phi * s_theta * s_psi
|
|
||||||
x = s_phi * c_theta * c_psi - c_phi * s_theta * s_psi
|
|
||||||
y = c_phi * s_theta * c_psi + s_phi * c_theta * s_psi
|
|
||||||
z = c_phi * c_theta * s_psi - s_phi * s_theta * c_psi
|
|
||||||
|
|
||||||
if w < 0:
|
|
||||||
return np.array([-w, -x, -y, -z])
|
|
||||||
return np.array([w, x, y, z])
|
|
||||||
|
|
||||||
|
|
||||||
def quat2euler_single(q):
|
|
||||||
"""
|
|
||||||
Convert a quaternion to Euler angles (roll, pitch, yaw).
|
|
||||||
"""
|
|
||||||
w, x, y, z = q
|
|
||||||
gamma = np.arctan2(2 * (w * x + y * z), 1 - 2 * (x**2 + y**2))
|
|
||||||
sin_arg = 2 * (w * y - z * x)
|
|
||||||
sin_arg = np.clip(sin_arg, -1.0, 1.0)
|
|
||||||
theta = np.arcsin(sin_arg)
|
|
||||||
psi = np.arctan2(2 * (w * z + x * y), 1 - 2 * (y**2 + z**2))
|
|
||||||
return np.array([gamma, theta, psi])
|
|
||||||
|
|
||||||
|
|
||||||
def quat2rot_single(q):
|
|
||||||
"""
|
|
||||||
Convert a quaternion to a 3x3 rotation matrix.
|
|
||||||
"""
|
|
||||||
w, x, y, z = q
|
|
||||||
xx, yy, zz = x * x, y * y, z * z
|
|
||||||
xy, xz, yz = x * y, x * z, y * z
|
|
||||||
wx, wy, wz = w * x, w * y, w * z
|
|
||||||
|
|
||||||
mat = np.array([
|
|
||||||
[1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy)],
|
|
||||||
[2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx)],
|
|
||||||
[2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy)]
|
|
||||||
])
|
|
||||||
return mat
|
|
||||||
|
|
||||||
|
|
||||||
def rot2quat_single(rot):
|
|
||||||
"""
|
|
||||||
Convert a 3x3 rotation matrix to a quaternion.
|
|
||||||
"""
|
|
||||||
trace = np.trace(rot)
|
|
||||||
if trace > 0:
|
|
||||||
s = 0.5 / np.sqrt(trace + 1.0)
|
|
||||||
w = 0.25 / s
|
|
||||||
x = (rot[2, 1] - rot[1, 2]) * s
|
|
||||||
y = (rot[0, 2] - rot[2, 0]) * s
|
|
||||||
z = (rot[1, 0] - rot[0, 1]) * s
|
|
||||||
else:
|
|
||||||
if rot[0, 0] > rot[1, 1] and rot[0, 0] > rot[2, 2]:
|
|
||||||
s = 2.0 * np.sqrt(1.0 + rot[0, 0] - rot[1, 1] - rot[2, 2])
|
|
||||||
w = (rot[2, 1] - rot[1, 2]) / s
|
|
||||||
x = 0.25 * s
|
|
||||||
y = (rot[0, 1] + rot[1, 0]) / s
|
|
||||||
z = (rot[0, 2] + rot[2, 0]) / s
|
|
||||||
elif rot[1, 1] > rot[2, 2]:
|
|
||||||
s = 2.0 * np.sqrt(1.0 + rot[1, 1] - rot[0, 0] - rot[2, 2])
|
|
||||||
w = (rot[0, 2] - rot[2, 0]) / s
|
|
||||||
x = (rot[0, 1] + rot[1, 0]) / s
|
|
||||||
y = 0.25 * s
|
|
||||||
z = (rot[1, 2] + rot[2, 1]) / s
|
|
||||||
else:
|
|
||||||
s = 2.0 * np.sqrt(1.0 + rot[2, 2] - rot[0, 0] - rot[1, 1])
|
|
||||||
w = (rot[1, 0] - rot[0, 1]) / s
|
|
||||||
x = (rot[0, 2] + rot[2, 0]) / s
|
|
||||||
y = (rot[1, 2] + rot[2, 1]) / s
|
|
||||||
z = 0.25 * s
|
|
||||||
|
|
||||||
if w < 0:
|
|
||||||
return np.array([-w, -x, -y, -z])
|
|
||||||
return np.array([w, x, y, z])
|
|
||||||
|
|
||||||
|
|
||||||
def euler2rot_single(euler):
|
|
||||||
"""
|
|
||||||
Convert Euler angles (roll, pitch, yaw) to a 3x3 rotation matrix.
|
|
||||||
Rotation order: Z-Y-X (yaw, pitch, roll).
|
|
||||||
"""
|
|
||||||
phi, theta, psi = euler
|
|
||||||
|
|
||||||
cx, sx = np.cos(phi), np.sin(phi)
|
|
||||||
cy, sy = np.cos(theta), np.sin(theta)
|
|
||||||
cz, sz = np.cos(psi), np.sin(psi)
|
|
||||||
|
|
||||||
Rx = np.array([[1, 0, 0], [0, cx, -sx], [0, sx, cx]])
|
|
||||||
Ry = np.array([[cy, 0, sy], [0, 1, 0], [-sy, 0, cy]])
|
|
||||||
Rz = np.array([[cz, -sz, 0], [sz, cz, 0], [0, 0, 1]])
|
|
||||||
|
|
||||||
return Rz @ Ry @ Rx
|
|
||||||
|
|
||||||
|
|
||||||
def rot2euler_single(rot):
|
|
||||||
"""
|
|
||||||
Convert a 3x3 rotation matrix to Euler angles (roll, pitch, yaw).
|
|
||||||
"""
|
|
||||||
return quat2euler_single(rot2quat_single(rot))
|
|
||||||
|
|
||||||
|
|
||||||
def rot_matrix(roll, pitch, yaw):
|
|
||||||
"""
|
|
||||||
Create a 3x3 rotation matrix from roll, pitch, and yaw angles.
|
|
||||||
"""
|
|
||||||
return euler2rot_single([roll, pitch, yaw])
|
|
||||||
|
|
||||||
|
|
||||||
def axis_angle_to_rot(axis, angle):
|
|
||||||
"""
|
|
||||||
Convert an axis-angle representation to a 3x3 rotation matrix.
|
|
||||||
"""
|
|
||||||
c = np.cos(angle / 2)
|
|
||||||
s = np.sin(angle / 2)
|
|
||||||
q = np.array([c, s*axis[0], s*axis[1], s*axis[2]])
|
|
||||||
return quat2rot_single(q)
|
|
||||||
|
|
||||||
|
|
||||||
class LocalCoord:
|
|
||||||
"""
|
|
||||||
A class to handle conversions between ECEF and local NED coordinates.
|
|
||||||
"""
|
|
||||||
def __init__(self, geodetic=None, ecef=None):
|
|
||||||
"""
|
|
||||||
Initialize LocalCoord with either geodetic or ECEF coordinates.
|
|
||||||
"""
|
|
||||||
if geodetic is not None:
|
|
||||||
self.init_ecef = geodetic2ecef_single(geodetic)
|
|
||||||
lat, lon, _ = geodetic
|
|
||||||
elif ecef is not None:
|
|
||||||
self.init_ecef = np.array(ecef)
|
|
||||||
lat, lon, _ = ecef2geodetic_single(ecef)
|
|
||||||
else:
|
|
||||||
raise ValueError("Must provide geodetic or ecef")
|
|
||||||
|
|
||||||
lat = np.radians(lat)
|
|
||||||
lon = np.radians(lon)
|
|
||||||
|
|
||||||
self.ned2ecef_matrix = np.array([
|
|
||||||
[-np.sin(lat) * np.cos(lon), -np.sin(lon), -np.cos(lat) * np.cos(lon)],
|
|
||||||
[-np.sin(lat) * np.sin(lon), np.cos(lon), -np.cos(lat) * np.sin(lon)],
|
|
||||||
[np.cos(lat), 0, -np.sin(lat)]
|
|
||||||
])
|
|
||||||
self.ecef2ned_matrix = self.ned2ecef_matrix.T
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_geodetic(cls, geodetic):
|
|
||||||
"""
|
|
||||||
Create a LocalCoord instance from geodetic coordinates.
|
|
||||||
"""
|
|
||||||
return cls(geodetic=geodetic)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_ecef(cls, ecef):
|
|
||||||
"""
|
|
||||||
Create a LocalCoord instance from ECEF coordinates.
|
|
||||||
"""
|
|
||||||
return cls(ecef=ecef)
|
|
||||||
|
|
||||||
def ecef2ned_single(self, ecef):
|
|
||||||
"""
|
|
||||||
Convert a single ECEF point to NED coordinates relative to the origin.
|
|
||||||
"""
|
|
||||||
return self.ecef2ned_matrix @ (ecef - self.init_ecef)
|
|
||||||
|
|
||||||
def ned2ecef_single(self, ned):
|
|
||||||
"""
|
|
||||||
Convert a single NED point to ECEF coordinates.
|
|
||||||
"""
|
|
||||||
return self.ned2ecef_matrix @ ned + self.init_ecef
|
|
||||||
|
|
||||||
def geodetic2ned_single(self, geodetic):
|
|
||||||
"""
|
|
||||||
Convert a single geodetic point to NED coordinates.
|
|
||||||
"""
|
|
||||||
ecef = geodetic2ecef_single(geodetic)
|
|
||||||
return self.ecef2ned_single(ecef)
|
|
||||||
|
|
||||||
def ned2geodetic_single(self, ned):
|
|
||||||
"""
|
|
||||||
Convert a single NED point to geodetic coordinates.
|
|
||||||
"""
|
|
||||||
ecef = self.ned2ecef_single(ned)
|
|
||||||
return ecef2geodetic_single(ecef)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ned_from_ecef_matrix(self):
|
|
||||||
"""
|
|
||||||
Returns the rotation matrix from ECEF to NED coordinates.
|
|
||||||
"""
|
|
||||||
return self.ecef2ned_matrix
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ecef_from_ned_matrix(self):
|
|
||||||
"""
|
|
||||||
Returns the rotation matrix from NED to ECEF coordinates.
|
|
||||||
"""
|
|
||||||
return self.ned2ecef_matrix
|
|
||||||
|
|
||||||
|
|
||||||
def ecef_euler_from_ned_single(ecef_init, ned_pose):
|
|
||||||
"""
|
|
||||||
Convert NED Euler angles (roll, pitch, yaw) at a given ECEF origin
|
|
||||||
to equivalent ECEF Euler angles.
|
|
||||||
"""
|
|
||||||
converter = LocalCoord(ecef=ecef_init)
|
|
||||||
zero = np.array(ecef_init)
|
|
||||||
|
|
||||||
x0 = converter.ned2ecef_single([1, 0, 0]) - zero
|
|
||||||
y0 = converter.ned2ecef_single([0, 1, 0]) - zero
|
|
||||||
z0 = converter.ned2ecef_single([0, 0, 1]) - zero
|
|
||||||
|
|
||||||
phi, theta, psi = ned_pose
|
|
||||||
|
|
||||||
x1 = axis_angle_to_rot(z0, psi) @ x0
|
|
||||||
y1 = axis_angle_to_rot(z0, psi) @ y0
|
|
||||||
z1 = axis_angle_to_rot(z0, psi) @ z0
|
|
||||||
|
|
||||||
x2 = axis_angle_to_rot(y1, theta) @ x1
|
|
||||||
y2 = axis_angle_to_rot(y1, theta) @ y1
|
|
||||||
z2 = axis_angle_to_rot(y1, theta) @ z1
|
|
||||||
|
|
||||||
x3 = axis_angle_to_rot(x2, phi) @ x2
|
|
||||||
y3 = axis_angle_to_rot(x2, phi) @ y2
|
|
||||||
|
|
||||||
x0 = np.array([1.0, 0, 0])
|
|
||||||
y0 = np.array([0, 1.0, 0])
|
|
||||||
z0 = np.array([0, 0, 1.0])
|
|
||||||
|
|
||||||
psi_out = np.arctan2(np.dot(x3, y0), np.dot(x3, x0))
|
|
||||||
theta_out = np.arctan2(-np.dot(x3, z0), np.sqrt(np.dot(x3, x0)**2 + np.dot(x3, y0)**2))
|
|
||||||
|
|
||||||
y2 = axis_angle_to_rot(z0, psi_out) @ y0
|
|
||||||
z2 = axis_angle_to_rot(y2, theta_out) @ z0
|
|
||||||
|
|
||||||
phi_out = np.arctan2(np.dot(y3, z2), np.dot(y3, y2))
|
|
||||||
|
|
||||||
return np.array([phi_out, theta_out, psi_out])
|
|
||||||
|
|
||||||
|
|
||||||
def ned_euler_from_ecef_single(ecef_init, ecef_pose):
|
|
||||||
"""
|
|
||||||
Convert ECEF Euler angles (roll, pitch, yaw) at a given ECEF origin
|
|
||||||
to equivalent NED Euler angles.
|
|
||||||
"""
|
|
||||||
converter = LocalCoord(ecef=ecef_init)
|
|
||||||
|
|
||||||
x0 = np.array([1.0, 0, 0])
|
|
||||||
y0 = np.array([0, 1.0, 0])
|
|
||||||
z0 = np.array([0, 0, 1.0])
|
|
||||||
|
|
||||||
phi, theta, psi = ecef_pose
|
|
||||||
|
|
||||||
x1 = axis_angle_to_rot(z0, psi) @ x0
|
|
||||||
y1 = axis_angle_to_rot(z0, psi) @ y0
|
|
||||||
z1 = axis_angle_to_rot(z0, psi) @ z0
|
|
||||||
|
|
||||||
x2 = axis_angle_to_rot(y1, theta) @ x1
|
|
||||||
y2 = axis_angle_to_rot(y1, theta) @ y1
|
|
||||||
z2 = axis_angle_to_rot(y1, theta) @ z1
|
|
||||||
|
|
||||||
x3 = axis_angle_to_rot(x2, phi) @ x2
|
|
||||||
y3 = axis_angle_to_rot(x2, phi) @ y2
|
|
||||||
|
|
||||||
zero = np.array(ecef_init)
|
|
||||||
x0 = converter.ned2ecef_single([1, 0, 0]) - zero
|
|
||||||
y0 = converter.ned2ecef_single([0, 1, 0]) - zero
|
|
||||||
z0 = converter.ned2ecef_single([0, 0, 1]) - zero
|
|
||||||
|
|
||||||
psi_out = np.arctan2(np.dot(x3, y0), np.dot(x3, x0))
|
|
||||||
theta_out = np.arctan2(-np.dot(x3, z0), np.sqrt(np.dot(x3, x0)**2 + np.dot(x3, y0)**2))
|
|
||||||
|
|
||||||
y2 = axis_angle_to_rot(z0, psi_out) @ y0
|
|
||||||
z2 = axis_angle_to_rot(y2, theta_out) @ z0
|
|
||||||
|
|
||||||
phi_out = np.arctan2(np.dot(y3, z2), np.dot(y3, y2))
|
|
||||||
|
|
||||||
return np.array([phi_out, theta_out, psi_out])
|
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
# distutils: language = c++
|
||||||
|
# cython: language_level = 3
|
||||||
|
from openpilot.common.transformations.transformations cimport Matrix3, Vector3, Quaternion
|
||||||
|
from openpilot.common.transformations.transformations cimport ECEF, NED, Geodetic
|
||||||
|
|
||||||
|
from openpilot.common.transformations.transformations cimport euler2quat as euler2quat_c
|
||||||
|
from openpilot.common.transformations.transformations cimport quat2euler as quat2euler_c
|
||||||
|
from openpilot.common.transformations.transformations cimport quat2rot as quat2rot_c
|
||||||
|
from openpilot.common.transformations.transformations cimport rot2quat as rot2quat_c
|
||||||
|
from openpilot.common.transformations.transformations cimport euler2rot as euler2rot_c
|
||||||
|
from openpilot.common.transformations.transformations cimport rot2euler as rot2euler_c
|
||||||
|
from openpilot.common.transformations.transformations cimport rot_matrix as rot_matrix_c
|
||||||
|
from openpilot.common.transformations.transformations cimport ecef_euler_from_ned as ecef_euler_from_ned_c
|
||||||
|
from openpilot.common.transformations.transformations cimport ned_euler_from_ecef as ned_euler_from_ecef_c
|
||||||
|
from openpilot.common.transformations.transformations cimport geodetic2ecef as geodetic2ecef_c
|
||||||
|
from openpilot.common.transformations.transformations cimport ecef2geodetic as ecef2geodetic_c
|
||||||
|
from openpilot.common.transformations.transformations cimport LocalCoord_c
|
||||||
|
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
cimport numpy as np
|
||||||
|
|
||||||
|
cdef np.ndarray[double, ndim=2] matrix2numpy(Matrix3 m):
|
||||||
|
return np.array([
|
||||||
|
[m(0, 0), m(0, 1), m(0, 2)],
|
||||||
|
[m(1, 0), m(1, 1), m(1, 2)],
|
||||||
|
[m(2, 0), m(2, 1), m(2, 2)],
|
||||||
|
])
|
||||||
|
|
||||||
|
cdef Matrix3 numpy2matrix(np.ndarray[double, ndim=2, mode="fortran"] m):
|
||||||
|
assert m.shape[0] == 3
|
||||||
|
assert m.shape[1] == 3
|
||||||
|
return Matrix3(<double*>m.data)
|
||||||
|
|
||||||
|
cdef ECEF list2ecef(ecef):
|
||||||
|
cdef ECEF e
|
||||||
|
e.x = ecef[0]
|
||||||
|
e.y = ecef[1]
|
||||||
|
e.z = ecef[2]
|
||||||
|
return e
|
||||||
|
|
||||||
|
cdef NED list2ned(ned):
|
||||||
|
cdef NED n
|
||||||
|
n.n = ned[0]
|
||||||
|
n.e = ned[1]
|
||||||
|
n.d = ned[2]
|
||||||
|
return n
|
||||||
|
|
||||||
|
cdef Geodetic list2geodetic(geodetic):
|
||||||
|
cdef Geodetic g
|
||||||
|
g.lat = geodetic[0]
|
||||||
|
g.lon = geodetic[1]
|
||||||
|
g.alt = geodetic[2]
|
||||||
|
return g
|
||||||
|
|
||||||
|
def euler2quat_single(euler):
|
||||||
|
cdef Vector3 e = Vector3(euler[0], euler[1], euler[2])
|
||||||
|
cdef Quaternion q = euler2quat_c(e)
|
||||||
|
return [q.w(), q.x(), q.y(), q.z()]
|
||||||
|
|
||||||
|
def quat2euler_single(quat):
|
||||||
|
cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3])
|
||||||
|
cdef Vector3 e = quat2euler_c(q)
|
||||||
|
return [e(0), e(1), e(2)]
|
||||||
|
|
||||||
|
def quat2rot_single(quat):
|
||||||
|
cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3])
|
||||||
|
cdef Matrix3 r = quat2rot_c(q)
|
||||||
|
return matrix2numpy(r)
|
||||||
|
|
||||||
|
def rot2quat_single(rot):
|
||||||
|
cdef Matrix3 r = numpy2matrix(np.asfortranarray(rot, dtype=np.double))
|
||||||
|
cdef Quaternion q = rot2quat_c(r)
|
||||||
|
return [q.w(), q.x(), q.y(), q.z()]
|
||||||
|
|
||||||
|
def euler2rot_single(euler):
|
||||||
|
cdef Vector3 e = Vector3(euler[0], euler[1], euler[2])
|
||||||
|
cdef Matrix3 r = euler2rot_c(e)
|
||||||
|
return matrix2numpy(r)
|
||||||
|
|
||||||
|
def rot2euler_single(rot):
|
||||||
|
cdef Matrix3 r = numpy2matrix(np.asfortranarray(rot, dtype=np.double))
|
||||||
|
cdef Vector3 e = rot2euler_c(r)
|
||||||
|
return [e(0), e(1), e(2)]
|
||||||
|
|
||||||
|
def rot_matrix(roll, pitch, yaw):
|
||||||
|
return matrix2numpy(rot_matrix_c(roll, pitch, yaw))
|
||||||
|
|
||||||
|
def ecef_euler_from_ned_single(ecef_init, ned_pose):
|
||||||
|
cdef ECEF init = list2ecef(ecef_init)
|
||||||
|
cdef Vector3 pose = Vector3(ned_pose[0], ned_pose[1], ned_pose[2])
|
||||||
|
|
||||||
|
cdef Vector3 e = ecef_euler_from_ned_c(init, pose)
|
||||||
|
return [e(0), e(1), e(2)]
|
||||||
|
|
||||||
|
def ned_euler_from_ecef_single(ecef_init, ecef_pose):
|
||||||
|
cdef ECEF init = list2ecef(ecef_init)
|
||||||
|
cdef Vector3 pose = Vector3(ecef_pose[0], ecef_pose[1], ecef_pose[2])
|
||||||
|
|
||||||
|
cdef Vector3 e = ned_euler_from_ecef_c(init, pose)
|
||||||
|
return [e(0), e(1), e(2)]
|
||||||
|
|
||||||
|
def geodetic2ecef_single(geodetic):
|
||||||
|
cdef Geodetic g = list2geodetic(geodetic)
|
||||||
|
cdef ECEF e = geodetic2ecef_c(g)
|
||||||
|
return [e.x, e.y, e.z]
|
||||||
|
|
||||||
|
def ecef2geodetic_single(ecef):
|
||||||
|
cdef ECEF e = list2ecef(ecef)
|
||||||
|
cdef Geodetic g = ecef2geodetic_c(e)
|
||||||
|
return [g.lat, g.lon, g.alt]
|
||||||
|
|
||||||
|
|
||||||
|
cdef class LocalCoord:
|
||||||
|
cdef LocalCoord_c * lc
|
||||||
|
|
||||||
|
def __init__(self, geodetic=None, ecef=None):
|
||||||
|
assert (geodetic is not None) or (ecef is not None)
|
||||||
|
if geodetic is not None:
|
||||||
|
self.lc = new LocalCoord_c(list2geodetic(geodetic))
|
||||||
|
elif ecef is not None:
|
||||||
|
self.lc = new LocalCoord_c(list2ecef(ecef))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ned2ecef_matrix(self):
|
||||||
|
return matrix2numpy(self.lc.ned2ecef_matrix)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ecef2ned_matrix(self):
|
||||||
|
return matrix2numpy(self.lc.ecef2ned_matrix)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ned_from_ecef_matrix(self):
|
||||||
|
return self.ecef2ned_matrix
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ecef_from_ned_matrix(self):
|
||||||
|
return self.ned2ecef_matrix
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_geodetic(cls, geodetic):
|
||||||
|
return cls(geodetic=geodetic)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_ecef(cls, ecef):
|
||||||
|
return cls(ecef=ecef)
|
||||||
|
|
||||||
|
def ecef2ned_single(self, ecef):
|
||||||
|
assert self.lc
|
||||||
|
cdef ECEF e = list2ecef(ecef)
|
||||||
|
cdef NED n = self.lc.ecef2ned(e)
|
||||||
|
return [n.n, n.e, n.d]
|
||||||
|
|
||||||
|
def ned2ecef_single(self, ned):
|
||||||
|
assert self.lc
|
||||||
|
cdef NED n = list2ned(ned)
|
||||||
|
cdef ECEF e = self.lc.ned2ecef(n)
|
||||||
|
return [e.x, e.y, e.z]
|
||||||
|
|
||||||
|
def geodetic2ned_single(self, geodetic):
|
||||||
|
assert self.lc
|
||||||
|
cdef Geodetic g = list2geodetic(geodetic)
|
||||||
|
cdef NED n = self.lc.geodetic2ned(g)
|
||||||
|
return [n.n, n.e, n.d]
|
||||||
|
|
||||||
|
def ned2geodetic_single(self, ned):
|
||||||
|
assert self.lc
|
||||||
|
cdef NED n = list2ned(ned)
|
||||||
|
cdef Geodetic g = self.lc.ned2geodetic(n)
|
||||||
|
return [g.lat, g.lon, g.alt]
|
||||||
|
|
||||||
|
def __dealloc__(self):
|
||||||
|
del self.lc
|
||||||
+7
-15
@@ -1,5 +1,4 @@
|
|||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
#include "common/swaglog.h"
|
|
||||||
|
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -13,7 +12,6 @@
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
@@ -80,9 +78,8 @@ std::string read_file(const std::string& fn) {
|
|||||||
std::ifstream f(fn, std::ios::binary | std::ios::in);
|
std::ifstream f(fn, std::ios::binary | std::ios::in);
|
||||||
if (f.is_open()) {
|
if (f.is_open()) {
|
||||||
f.seekg(0, std::ios::end);
|
f.seekg(0, std::ios::end);
|
||||||
std::streamsize size = f.tellg();
|
int size = f.tellg();
|
||||||
// seekg and tellg on a directory doesn't return pos_type(-1) but max(streamsize)
|
if (f.good() && size > 0) {
|
||||||
if (f.good() && size > 0 && size < std::numeric_limits<std::streamsize>::max()) {
|
|
||||||
std::string result(size, '\0');
|
std::string result(size, '\0');
|
||||||
f.seekg(0, std::ios::beg);
|
f.seekg(0, std::ios::beg);
|
||||||
f.read(result.data(), size);
|
f.read(result.data(), size);
|
||||||
@@ -152,16 +149,11 @@ int safe_fflush(FILE *stream) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int safe_ioctl(int fd, unsigned long request, void *argp, const char* exception_msg) {
|
int safe_ioctl(int fd, unsigned long request, void *argp) {
|
||||||
int ret;
|
int ret;
|
||||||
do {
|
do {
|
||||||
ret = ioctl(fd, request, argp);
|
ret = ioctl(fd, request, argp);
|
||||||
} while ((ret == -1) && (errno == EINTR));
|
} while ((ret == -1) && (errno == EINTR));
|
||||||
|
|
||||||
if (ret == -1 && exception_msg) {
|
|
||||||
LOGE("safe_ioctl error: %s %s(%d) (fd: %d request: %lx argp: %p)", exception_msg, strerror(errno), errno, fd, request, argp);
|
|
||||||
throw std::runtime_error(exception_msg);
|
|
||||||
}
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,9 +173,9 @@ bool file_exists(const std::string& fn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool createDirectory(std::string dir, mode_t mode) {
|
static bool createDirectory(std::string dir, mode_t mode) {
|
||||||
auto verify_dir = [](const std::string& path) -> bool {
|
auto verify_dir = [](const std::string& dir) -> bool {
|
||||||
struct stat st = {};
|
struct stat st = {};
|
||||||
return (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR);
|
return (stat(dir.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR);
|
||||||
};
|
};
|
||||||
// remove trailing /'s
|
// remove trailing /'s
|
||||||
while (dir.size() > 1 && dir.back() == '/') {
|
while (dir.size() > 1 && dir.back() == '/') {
|
||||||
@@ -288,7 +280,7 @@ std::string strip(const std::string &str) {
|
|||||||
std::string check_output(const std::string& command) {
|
std::string check_output(const std::string& command) {
|
||||||
char buffer[128];
|
char buffer[128];
|
||||||
std::string result;
|
std::string result;
|
||||||
std::unique_ptr<FILE, int(*)(FILE*)> pipe(popen(command.c_str(), "r"), pclose);
|
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(command.c_str(), "r"), pclose);
|
||||||
|
|
||||||
if (!pipe) {
|
if (!pipe) {
|
||||||
return "";
|
return "";
|
||||||
@@ -303,7 +295,7 @@ std::string check_output(const std::string& command) {
|
|||||||
|
|
||||||
bool system_time_valid() {
|
bool system_time_valid() {
|
||||||
// Default to August 26, 2024
|
// Default to August 26, 2024
|
||||||
tm min_tm = {.tm_mday = 26, .tm_mon = 7, .tm_year = 2024 - 1900};
|
tm min_tm = {.tm_year = 2024 - 1900, .tm_mon = 7, .tm_mday = 26};
|
||||||
time_t min_date = mktime(&min_tm);
|
time_t min_date = mktime(&min_tm);
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|||||||
+1
-8
@@ -88,7 +88,7 @@ int write_file(const char* path, const void* data, size_t size, int flags = O_WR
|
|||||||
FILE* safe_fopen(const char* filename, const char* mode);
|
FILE* safe_fopen(const char* filename, const char* mode);
|
||||||
size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream);
|
size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream);
|
||||||
int safe_fflush(FILE *stream);
|
int safe_fflush(FILE *stream);
|
||||||
int safe_ioctl(int fd, unsigned long request, void *argp, const char* exception_msg = nullptr);
|
int safe_ioctl(int fd, unsigned long request, void *argp);
|
||||||
|
|
||||||
std::string readlink(const std::string& path);
|
std::string readlink(const std::string& path);
|
||||||
bool file_exists(const std::string& fn);
|
bool file_exists(const std::string& fn);
|
||||||
@@ -96,13 +96,6 @@ bool create_directories(const std::string &dir, mode_t mode);
|
|||||||
|
|
||||||
std::string check_output(const std::string& command);
|
std::string check_output(const std::string& command);
|
||||||
|
|
||||||
inline void check_system(const std::string& cmd) {
|
|
||||||
int ret = std::system(cmd.c_str());
|
|
||||||
if (ret != 0) {
|
|
||||||
fprintf(stderr, "system command failed (%d): %s\n", ret, cmd.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool system_time_valid();
|
bool system_time_valid();
|
||||||
|
|
||||||
inline void sleep_for(const int milliseconds) {
|
inline void sleep_for(const int milliseconds) {
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def sudo_write(val: str, path: str) -> None:
|
||||||
|
try:
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write(str(val))
|
||||||
|
except PermissionError:
|
||||||
|
os.system(f"sudo chmod a+w {path}")
|
||||||
|
try:
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write(str(val))
|
||||||
|
except PermissionError:
|
||||||
|
# fallback for debugfs files
|
||||||
|
os.system(f"sudo su -c 'echo {val} > {path}'")
|
||||||
|
|
||||||
|
def sudo_read(path: str) -> str:
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
class MovingAverage:
|
||||||
|
def __init__(self, window_size: int):
|
||||||
|
self.window_size: int = window_size
|
||||||
|
self.buffer: list[float] = [0.0] * window_size
|
||||||
|
self.index: int = 0
|
||||||
|
self.count: int = 0
|
||||||
|
self.sum: float = 0.0
|
||||||
|
|
||||||
|
def add_value(self, new_value: float):
|
||||||
|
# Update the sum: subtract the value being replaced and add the new value
|
||||||
|
self.sum -= self.buffer[self.index]
|
||||||
|
self.buffer[self.index] = new_value
|
||||||
|
self.sum += new_value
|
||||||
|
|
||||||
|
# Update the index in a circular manner
|
||||||
|
self.index = (self.index + 1) % self.window_size
|
||||||
|
|
||||||
|
# Track the number of added values (for partial windows)
|
||||||
|
self.count = min(self.count + 1, self.window_size)
|
||||||
|
|
||||||
|
def get_average(self) -> float:
|
||||||
|
if self.count == 0:
|
||||||
|
return float('nan')
|
||||||
|
return self.sum / self.count
|
||||||
+5
-159
@@ -7,82 +7,14 @@ import time
|
|||||||
import functools
|
import functools
|
||||||
from subprocess import Popen, PIPE, TimeoutExpired
|
from subprocess import Popen, PIPE, TimeoutExpired
|
||||||
import zstandard as zstd
|
import zstandard as zstd
|
||||||
|
from openpilot.common.swaglog import cloudlog
|
||||||
|
|
||||||
LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change
|
LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change
|
||||||
|
|
||||||
class Timer:
|
|
||||||
"""Simple lap timer for profiling sequential operations."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._start = self._lap = time.monotonic()
|
|
||||||
self._sections = {}
|
|
||||||
|
|
||||||
def lap(self, name):
|
|
||||||
now = time.monotonic()
|
|
||||||
self._sections[name] = now - self._lap
|
|
||||||
self._lap = now
|
|
||||||
|
|
||||||
@property
|
|
||||||
def total(self):
|
|
||||||
return time.monotonic() - self._start
|
|
||||||
|
|
||||||
def fmt(self, duration):
|
|
||||||
parts = ", ".join(f"{k}={v:.2f}s" + (f" ({duration/v:.0f}x)" if k == 'render' and v > 0 else "") for k, v in self._sections.items())
|
|
||||||
total = self.total
|
|
||||||
realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A"
|
|
||||||
return f"{duration}s in {total:.1f}s ({realtime}) | {parts}"
|
|
||||||
|
|
||||||
def sudo_write(val: str, path: str) -> None:
|
|
||||||
try:
|
|
||||||
with open(path, 'w') as f:
|
|
||||||
f.write(str(val))
|
|
||||||
except PermissionError:
|
|
||||||
os.system(f"sudo chmod a+w {path}")
|
|
||||||
try:
|
|
||||||
with open(path, 'w') as f:
|
|
||||||
f.write(str(val))
|
|
||||||
except PermissionError:
|
|
||||||
# fallback for debugfs files
|
|
||||||
os.system(f"sudo su -c 'echo {val} > {path}'")
|
|
||||||
|
|
||||||
|
|
||||||
def sudo_read(path: str) -> str:
|
|
||||||
try:
|
|
||||||
return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
|
|
||||||
except Exception:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
class MovingAverage:
|
|
||||||
def __init__(self, window_size: int):
|
|
||||||
self.window_size: int = window_size
|
|
||||||
self.buffer: list[float] = [0.0] * window_size
|
|
||||||
self.index: int = 0
|
|
||||||
self.count: int = 0
|
|
||||||
self.sum: float = 0.0
|
|
||||||
|
|
||||||
def add_value(self, new_value: float):
|
|
||||||
# Update the sum: subtract the value being replaced and add the new value
|
|
||||||
self.sum -= self.buffer[self.index]
|
|
||||||
self.buffer[self.index] = new_value
|
|
||||||
self.sum += new_value
|
|
||||||
|
|
||||||
# Update the index in a circular manner
|
|
||||||
self.index = (self.index + 1) % self.window_size
|
|
||||||
|
|
||||||
# Track the number of added values (for partial windows)
|
|
||||||
self.count = min(self.count + 1, self.window_size)
|
|
||||||
|
|
||||||
def get_average(self) -> float:
|
|
||||||
if self.count == 0:
|
|
||||||
return float('nan')
|
|
||||||
return self.sum / self.count
|
|
||||||
|
|
||||||
|
|
||||||
class CallbackReader:
|
class CallbackReader:
|
||||||
"""Wraps a file, but overrides the read method to also
|
"""Wraps a file, but overrides the read method to also
|
||||||
call a callback function with the number of bytes read so far."""
|
call a callback function with the number of bytes read so far."""
|
||||||
|
|
||||||
def __init__(self, f, callback, *args):
|
def __init__(self, f, callback, *args):
|
||||||
self.f = f
|
self.f = f
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
@@ -100,8 +32,8 @@ class CallbackReader:
|
|||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def atomic_write(path: str, mode: str = 'w', buffering: int = -1, encoding: str | None = None, newline: str | None = None,
|
def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: str | None = None, newline: str | None = None,
|
||||||
overwrite: bool = False):
|
overwrite: bool = False):
|
||||||
"""Write to a file atomically using a temporary file in the same directory as the destination file."""
|
"""Write to a file atomically using a temporary file in the same directory as the destination file."""
|
||||||
dir_name = os.path.dirname(path)
|
dir_name = os.path.dirname(path)
|
||||||
|
|
||||||
@@ -167,92 +99,6 @@ def managed_proc(cmd: list[str], env: dict[str, str]):
|
|||||||
proc.kill()
|
proc.kill()
|
||||||
|
|
||||||
|
|
||||||
def tabulate(tabular_data, headers=(), tablefmt="simple", floatfmt="g", stralign="left", numalign=None):
|
|
||||||
rows = [list(row) for row in tabular_data]
|
|
||||||
|
|
||||||
def fmt(val):
|
|
||||||
if isinstance(val, str):
|
|
||||||
return val
|
|
||||||
if isinstance(val, (bool, int)):
|
|
||||||
return str(val)
|
|
||||||
try:
|
|
||||||
return format(val, floatfmt)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return str(val)
|
|
||||||
|
|
||||||
formatted = [[fmt(c) for c in row] for row in rows]
|
|
||||||
hdrs = [str(h) for h in headers] if headers else None
|
|
||||||
|
|
||||||
ncols = max((len(r) for r in formatted), default=0)
|
|
||||||
if hdrs:
|
|
||||||
ncols = max(ncols, len(hdrs))
|
|
||||||
if ncols == 0:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
for r in formatted:
|
|
||||||
r.extend([""] * (ncols - len(r)))
|
|
||||||
if hdrs:
|
|
||||||
hdrs.extend([""] * (ncols - len(hdrs)))
|
|
||||||
|
|
||||||
widths = [0] * ncols
|
|
||||||
if hdrs:
|
|
||||||
for i in range(ncols):
|
|
||||||
widths[i] = len(hdrs[i])
|
|
||||||
for row in formatted:
|
|
||||||
for i in range(ncols):
|
|
||||||
widths[i] = max(widths[i], max(len(ln) for ln in row[i].split('\n')))
|
|
||||||
|
|
||||||
def _align(s, w):
|
|
||||||
if stralign == "center":
|
|
||||||
return s.center(w)
|
|
||||||
return s.ljust(w)
|
|
||||||
|
|
||||||
if tablefmt == "html":
|
|
||||||
parts = ["<table>"]
|
|
||||||
if hdrs:
|
|
||||||
parts.append("<thead>")
|
|
||||||
parts.append("<tr>" + "".join(f"<th>{h}</th>" for h in hdrs) + "</tr>")
|
|
||||||
parts.append("</thead>")
|
|
||||||
parts.append("<tbody>")
|
|
||||||
for row in formatted:
|
|
||||||
parts.append("<tr>" + "".join(f"<td>{c}</td>" for c in row) + "</tr>")
|
|
||||||
parts.append("</tbody>")
|
|
||||||
parts.append("</table>")
|
|
||||||
return "\n".join(parts)
|
|
||||||
|
|
||||||
if tablefmt == "simple_grid":
|
|
||||||
def _sep(left, mid, right):
|
|
||||||
return left + mid.join("\u2500" * (w + 2) for w in widths) + right
|
|
||||||
|
|
||||||
top, mid_sep, bot = _sep("\u250c", "\u252c", "\u2510"), _sep("\u251c", "\u253c", "\u2524"), _sep("\u2514", "\u2534", "\u2518")
|
|
||||||
|
|
||||||
def _fmt_row(cells):
|
|
||||||
split = [c.split('\n') for c in cells]
|
|
||||||
nlines = max(len(s) for s in split)
|
|
||||||
for s in split:
|
|
||||||
s.extend([""] * (nlines - len(s)))
|
|
||||||
return ["\u2502" + "\u2502".join(f" {_align(split[i][li], widths[i])} " for i in range(ncols)) + "\u2502" for li in range(nlines)]
|
|
||||||
|
|
||||||
lines = [top]
|
|
||||||
if hdrs:
|
|
||||||
lines.extend(_fmt_row(hdrs))
|
|
||||||
lines.append(mid_sep)
|
|
||||||
for ri, row in enumerate(formatted):
|
|
||||||
lines.extend(_fmt_row(row))
|
|
||||||
lines.append(mid_sep if ri < len(formatted) - 1 else bot)
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
# simple
|
|
||||||
gap = " "
|
|
||||||
lines = []
|
|
||||||
if hdrs:
|
|
||||||
lines.append(gap.join(h.ljust(w) for h, w in zip(hdrs, widths, strict=True)))
|
|
||||||
lines.append(gap.join("-" * w for w in widths))
|
|
||||||
for row in formatted:
|
|
||||||
lines.append(gap.join(_align(row[i], widths[i]) for i in range(ncols)))
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
def retry(attempts=3, delay=1.0, ignore_failure=False):
|
def retry(attempts=3, delay=1.0, ignore_failure=False):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
@@ -261,11 +107,11 @@ def retry(attempts=3, delay=1.0, ignore_failure=False):
|
|||||||
try:
|
try:
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
print(f"{func.__name__} failed, trying again")
|
cloudlog.exception(f"{func.__name__} failed, trying again")
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
if ignore_failure:
|
if ignore_failure:
|
||||||
print(f"{func.__name__} failed after retry")
|
cloudlog.error(f"{func.__name__} failed after retry")
|
||||||
else:
|
else:
|
||||||
raise Exception(f"{func.__name__} failed after retry")
|
raise Exception(f"{func.__name__} failed after retry")
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user