mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-14 01:34:24 +08:00
Compare commits
5 Commits
docs
...
model-test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e131b4a69 | ||
|
|
f402487f9e | ||
|
|
2c922afa12 | ||
|
|
342abcd45a | ||
|
|
6e8586e566 |
19
.clang-tidy
Normal file
19
.clang-tidy
Normal file
@@ -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
|
||||||
|
|||||||
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -7,12 +7,10 @@
|
|||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
*.gif filter=lfs diff=lfs merge=lfs -text
|
*.gif filter=lfs diff=lfs merge=lfs -text
|
||||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
*.otf filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
|
selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
|
||||||
system/hardware/tici/updater_weston filter=lfs diff=lfs merge=lfs -text
|
system/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text
|
||||||
system/hardware/tici/updater_magic filter=lfs diff=lfs merge=lfs -text
|
|
||||||
third_party/**/*.a filter=lfs diff=lfs merge=lfs -text
|
third_party/**/*.a filter=lfs diff=lfs merge=lfs -text
|
||||||
third_party/**/*.so filter=lfs diff=lfs merge=lfs -text
|
third_party/**/*.so filter=lfs diff=lfs merge=lfs -text
|
||||||
third_party/**/*.so.* filter=lfs diff=lfs merge=lfs -text
|
third_party/**/*.so.* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
2
.github/labeler.yaml
vendored
2
.github/labeler.yaml
vendored
@@ -16,7 +16,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/**,system/ui/**}'
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
|||||||
58
.github/workflows/auto-cache/action.yaml
vendored
Normal file
58
.github/workflows/auto-cache/action.yaml
vendored
Normal file
@@ -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: 'true'
|
||||||
|
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 }}
|
||||||
12
.github/workflows/auto_pr_review.yaml
vendored
12
.github/workflows/auto_pr_review.yaml
vendored
@@ -12,12 +12,12 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: false
|
submodules: false
|
||||||
|
|
||||||
# Label PRs
|
# Label PRs
|
||||||
- uses: actions/labeler@v6
|
- uses: actions/labeler@v5.0.0
|
||||||
with:
|
with:
|
||||||
dot: true
|
dot: true
|
||||||
configuration-path: .github/labeler.yaml
|
configuration-path: .github/labeler.yaml
|
||||||
@@ -55,13 +55,13 @@ jobs:
|
|||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: prNumber
|
issue_number: prNumber
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasDevC3Label = labels.some(label => label.name === process.env.PR_LABEL);
|
const hasDevC3Label = labels.some(label => label.name === process.env.PR_LABEL);
|
||||||
const hasTrustLabel = labels.some(label => label.name === process.env.TRUST_FORK_PR_LABEL);
|
const hasTrustLabel = labels.some(label => label.name === process.env.TRUST_FORK_PR_LABEL);
|
||||||
|
|
||||||
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
|
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
|
||||||
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
|
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
|
||||||
|
|
||||||
core.setOutput('has-dev', hasDevC3Label ? 'true' : 'false');
|
core.setOutput('has-dev', hasDevC3Label ? 'true' : 'false');
|
||||||
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
|
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Removed '${process.env.TRUST_FORK_PR_LABEL}' label from PR #${prNumber} as it received new commits`);
|
console.log(`Removed '${process.env.TRUST_FORK_PR_LABEL}' label from PR #${prNumber} as it received new commits`);
|
||||||
|
|
||||||
// Add a comment to the PR
|
// Add a comment to the PR
|
||||||
await github.rest.issues.createComment({
|
await github.rest.issues.createComment({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
|||||||
10
.github/workflows/badges.yaml
vendored
10
.github/workflows/badges.yaml
vendored
@@ -5,7 +5,9 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHONPATH: ${{ github.workspace }}
|
BASE_IMAGE: sunnypilot-base
|
||||||
|
DOCKER_REGISTRY: ghcr.io/sunnypilot
|
||||||
|
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:
|
jobs:
|
||||||
badges:
|
badges:
|
||||||
@@ -15,13 +17,13 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- run: ./tools/op.sh setup
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
- name: Push badges
|
- name: Push badges
|
||||||
run: |
|
run: |
|
||||||
python3 selfdrive/ui/translations/create_badges.py
|
${{ env.RUN }} "scons -j$(nproc) && python3 selfdrive/ui/translations/create_badges.py"
|
||||||
|
|
||||||
rm .gitattributes
|
rm .gitattributes
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
||||||
run: |
|
run: |
|
||||||
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
|
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
|
||||||
cd gitlab_docs
|
cd gitlab_docs
|
||||||
git checkout main
|
git checkout main
|
||||||
git sparse-checkout set --no-cone models/
|
git sparse-checkout set --no-cone models/
|
||||||
@@ -140,7 +140,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo '${{ needs.setup.outputs.model_matrix }}' > matrix.json
|
echo '${{ needs.setup.outputs.model_matrix }}' > matrix.json
|
||||||
built=(); while IFS= read -r line; do built+=("$line"); done < <(
|
built=(); while IFS= read -r line; do built+=("$line"); done < <(
|
||||||
find output -maxdepth 1 -name 'model-*' -printf "%f\n" | sed -E 's/^model-//' | sed -E 's/-[0-9]+$//' | sed -E 's/ \([^)]*\)//' | awk '{gsub(/^ +| +$/, ""); print}'
|
ls output | sed -E 's/^model-//' | sed -E 's/-[0-9]+$//' | sed -E 's/ \([^)]*\)//' | awk '{gsub(/^ +| +$/, ""); print}'
|
||||||
)
|
)
|
||||||
jq -c --argjson built "$(printf '%s\n' "${built[@]}" | jq -R . | jq -s .)" \
|
jq -c --argjson built "$(printf '%s\n' "${built[@]}" | jq -R . | jq -s .)" \
|
||||||
'map(select(.display_name as $n | ($built | index($n | gsub("^ +| +$"; "")) | not)))' matrix.json > retry_matrix.json
|
'map(select(.display_name as $n | ($built | index($n | gsub("^ +| +$"; "")) | not)))' matrix.json > retry_matrix.json
|
||||||
@@ -168,7 +168,6 @@ jobs:
|
|||||||
if: ${{ !cancelled() && (needs.get_and_build.result != 'failure' || needs.retry_get_and_build.result == 'success' || (needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '')) }}
|
if: ${{ !cancelled() && (needs.get_and_build.result != 'failure' || needs.retry_get_and_build.result == 'success' || (needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '')) }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
|
||||||
max-parallel: 1
|
max-parallel: 1
|
||||||
matrix:
|
matrix:
|
||||||
model: ${{ fromJson(needs.setup.outputs.model_matrix) }}
|
model: ${{ fromJson(needs.setup.outputs.model_matrix) }}
|
||||||
@@ -192,7 +191,7 @@ jobs:
|
|||||||
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
||||||
run: |
|
run: |
|
||||||
echo "Cloning GitLab"
|
echo "Cloning GitLab"
|
||||||
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
|
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
|
||||||
cd gitlab_docs
|
cd gitlab_docs
|
||||||
echo "checkout models/${RECOMPILED_DIR}"
|
echo "checkout models/${RECOMPILED_DIR}"
|
||||||
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
|
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ jobs:
|
|||||||
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
||||||
run: |
|
run: |
|
||||||
echo "Cloning GitLab"
|
echo "Cloning GitLab"
|
||||||
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
|
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
|
||||||
cd gitlab_docs
|
cd gitlab_docs
|
||||||
echo "checkout models/${RECOMPILED_DIR}"
|
echo "checkout models/${RECOMPILED_DIR}"
|
||||||
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
|
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
|
||||||
|
|||||||
34
.github/workflows/cereal_validation.yaml
vendored
34
.github/workflows/cereal_validation.yaml
vendored
@@ -20,23 +20,27 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CI: 1
|
PYTHONWARNINGS: error
|
||||||
|
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:
|
jobs:
|
||||||
generate_cereal_artifact:
|
generate_cereal_artifact:
|
||||||
name: Generate cereal validation artifacts
|
name: Generate cereal validation artifacts
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- run: ./tools/op.sh setup
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons -j$(nproc) cereal
|
run: ${{ env.RUN }} "scons -j$(nproc) cereal"
|
||||||
- name: Generate the log file
|
- name: Generate the log file
|
||||||
run: |
|
run: |
|
||||||
export PYTHONPATH=${{ github.workspace }}
|
${{ env.RUN }} "cereal/messaging/tests/validate_sp_cereal_upstream.py -g -f schema_instances.bin" && \
|
||||||
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py -g -f schema_instances.bin
|
ls -la
|
||||||
|
ls -la cereal/messaging/tests
|
||||||
- name: 'Prepare artifact'
|
- name: 'Prepare artifact'
|
||||||
run: |
|
run: |
|
||||||
mkdir -p "cereal/messaging/tests/cereal_validations"
|
mkdir -p "cereal/messaging/tests/cereal_validations"
|
||||||
@@ -53,26 +57,20 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: generate_cereal_artifact
|
needs: generate_cereal_artifact
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sunnypilot
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Checkout upstream openpilot
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
with:
|
||||||
repository: 'commaai/openpilot'
|
repository: 'commaai/openpilot'
|
||||||
path: openpilot
|
|
||||||
submodules: true
|
submodules: true
|
||||||
ref: "refs/heads/master"
|
ref: "refs/heads/master"
|
||||||
- run: ./tools/op.sh setup
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
working-directory: openpilot
|
run: ${{ env.RUN }} "scons -j$(nproc) cereal"
|
||||||
run: scons -j$(nproc) cereal
|
|
||||||
- name: Download build artifacts
|
- name: Download build artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: cereal_validations
|
name: cereal_validations
|
||||||
path: openpilot/cereal/messaging/tests/cereal_validations
|
path: cereal/messaging/tests/cereal_validations
|
||||||
- name: 'Run the validation'
|
- name: 'Run the validation'
|
||||||
run: |
|
run: |
|
||||||
export PYTHONPATH=${{ github.workspace }}/openpilot
|
chmod +x cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py
|
||||||
chmod +x openpilot/cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py
|
${{ env.RUN }} "cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py -r -f cereal/messaging/tests/cereal_validations/schema_instances.bin"
|
||||||
python3 openpilot/cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py -r -f openpilot/cereal/messaging/tests/cereal_validations/schema_instances.bin
|
|
||||||
|
|||||||
101
.github/workflows/ci_weekly_report.yaml
vendored
Normal file
101
.github/workflows/ci_weekly_report.yaml
vendored
Normal file
@@ -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 == 'sunnypilot/sunnypilot'
|
||||||
|
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: sunnypilot/sunnypilot/.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
|
||||||
17
.github/workflows/ci_weekly_run.yaml
vendored
Normal file
17
.github/workflows/ci_weekly_run.yaml
vendored
Normal file
@@ -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: sunnypilot/sunnypilot/.github/workflows/selfdrive_tests.yaml@master
|
||||||
|
with:
|
||||||
|
run_number: ${{ inputs.run_number }}
|
||||||
21
.github/workflows/compile-openpilot/action.yaml
vendored
Normal file
21
.github/workflows/compile-openpilot/action.yaml
vendored
Normal file
@@ -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 }}
|
||||||
66
.github/workflows/docs-sp.yaml
vendored
66
.github/workflows/docs-sp.yaml
vendored
@@ -1,66 +0,0 @@
|
|||||||
name: sunnypilot docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- docs
|
|
||||||
paths:
|
|
||||||
- 'docs_sp/**'
|
|
||||||
- 'zensical.toml'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'docs_sp/**'
|
|
||||||
- 'zensical.toml'
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: docs-sp-${{ github.event_name == 'push' && github.ref == 'refs/heads/docs' && github.run_id || github.head_ref || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: build sunnypilot docs
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.12'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
pip install uv
|
|
||||||
uv pip install --system zensical
|
|
||||||
|
|
||||||
- name: Build docs
|
|
||||||
run: zensical build
|
|
||||||
|
|
||||||
# Push to docs.sunnypilot.ai
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
if: github.ref == 'refs/heads/docs' && github.repository == 'sunnypilot/sunnypilot'
|
|
||||||
with:
|
|
||||||
path: sunnypilot-docs
|
|
||||||
ssh-key: ${{ secrets.OPENPILOT_DOCS_KEY }}
|
|
||||||
repository: sunnypilot/sunnypilot-docs
|
|
||||||
|
|
||||||
- name: Push to GitHub Pages
|
|
||||||
if: github.ref == 'refs/heads/docs' && github.repository == 'sunnypilot/sunnypilot'
|
|
||||||
run: |
|
|
||||||
set -x
|
|
||||||
|
|
||||||
source release/identity.sh
|
|
||||||
|
|
||||||
cd sunnypilot-docs
|
|
||||||
git checkout --orphan tmp
|
|
||||||
git rm -rf .
|
|
||||||
|
|
||||||
cp -r ../docs_site_sp/ docs/
|
|
||||||
|
|
||||||
touch docs/.nojekyll
|
|
||||||
echo -n docs.sunnypilot.ai > docs/CNAME
|
|
||||||
|
|
||||||
git add -f .
|
|
||||||
git commit -m "build sunnypilot docs"
|
|
||||||
|
|
||||||
git push -f origin tmp:gh-pages
|
|
||||||
4
.github/workflows/docs.yaml
vendored
4
.github/workflows/docs.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: commaai/timeout@v1
|
- uses: commaai/timeout@v1
|
||||||
|
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
mkdocs build
|
mkdocs build
|
||||||
|
|
||||||
# Push to docs.comma.ai
|
# Push to docs.comma.ai
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
|
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
|
||||||
with:
|
with:
|
||||||
path: openpilot-docs
|
path: openpilot-docs
|
||||||
|
|||||||
47
.github/workflows/forum-docs.yaml
vendored
47
.github/workflows/forum-docs.yaml
vendored
@@ -1,47 +0,0 @@
|
|||||||
name: Sync docs to Discourse
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ["sunnypilot docs"]
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
branches:
|
|
||||||
- docs
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: forum-docs-sync
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sync:
|
|
||||||
name: sync docs to Discourse
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
if: >
|
|
||||||
github.event.workflow_run.conclusion == 'success' &&
|
|
||||||
github.repository == 'sunnypilot/sunnypilot'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
ref: docs
|
|
||||||
|
|
||||||
- name: Set up Ruby
|
|
||||||
uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: '3.3'
|
|
||||||
|
|
||||||
- name: Restore sync cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: .discourse_sync_cache
|
|
||||||
key: discourse-sync-${{ hashFiles('docs_sp/**/*.md') }}
|
|
||||||
restore-keys: |
|
|
||||||
discourse-sync-
|
|
||||||
|
|
||||||
- name: Sync to Discourse
|
|
||||||
env:
|
|
||||||
DISCOURSE_URL: ${{ secrets.DISCOURSE_URL }}
|
|
||||||
DISCOURSE_API_KEY: ${{ secrets.DISCOURSE_API_KEY }}
|
|
||||||
DISCOURSE_API_USER: ${{ secrets.DISCOURSE_API_USER }}
|
|
||||||
DISCOURSE_CATEGORY: ${{ vars.DISCOURSE_DOCS_CATEGORY || 'documentation' }}
|
|
||||||
DOCS_BASE_URL: https://docs.sunnypilot.ai
|
|
||||||
run: ruby docs_sp/tools/sync_docs_discourse.rb --verbose
|
|
||||||
45
.github/workflows/jenkins-pr-trigger.yaml
vendored
45
.github/workflows/jenkins-pr-trigger.yaml
vendored
@@ -5,44 +5,7 @@ on:
|
|||||||
types: [created, edited]
|
types: [created, edited]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cleanup-branches:
|
# TODO: gc old branches in a separate job in this workflow
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Delete stale Jenkins branches
|
|
||||||
uses: actions/github-script@v8
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
||||||
const prefixes = ['tmp-jenkins', '__jenkins'];
|
|
||||||
|
|
||||||
for await (const response of github.paginate.iterator(github.rest.repos.listBranches, {
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
per_page: 100,
|
|
||||||
})) {
|
|
||||||
for (const branch of response.data) {
|
|
||||||
if (!prefixes.some(p => branch.name.startsWith(p))) continue;
|
|
||||||
|
|
||||||
const { data: commit } = await github.rest.repos.getCommit({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
ref: branch.commit.sha,
|
|
||||||
});
|
|
||||||
|
|
||||||
const commitDate = new Date(commit.commit.committer.date).getTime();
|
|
||||||
if (commitDate < cutoff) {
|
|
||||||
console.log(`Deleting branch: ${branch.name} (last commit: ${commit.commit.committer.date})`);
|
|
||||||
await github.rest.git.deleteRef({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
ref: `heads/${branch.name}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scan-comments:
|
scan-comments:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event.issue.pull_request }}
|
if: ${{ github.event.issue.pull_request }}
|
||||||
@@ -52,7 +15,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check for trigger phrase
|
- name: Check for trigger phrase
|
||||||
id: check_comment
|
id: check_comment
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const triggerPhrase = "trigger-jenkins";
|
const triggerPhrase = "trigger-jenkins";
|
||||||
@@ -72,7 +35,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
if: steps.check_comment.outputs.result == 'true'
|
if: steps.check_comment.outputs.result == 'true'
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: refs/pull/${{ github.event.issue.number }}/head
|
ref: refs/pull/${{ github.event.issue.number }}/head
|
||||||
|
|
||||||
@@ -86,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Delete trigger comment
|
- name: Delete trigger comment
|
||||||
if: steps.check_comment.outputs.result == 'true' && always()
|
if: steps.check_comment.outputs.result == 'true' && always()
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
await github.rest.issues.deleteComment({
|
await github.rest.issues.deleteComment({
|
||||||
|
|||||||
10
.github/workflows/model_review.yaml
vendored
10
.github/workflows/model_review.yaml
vendored
@@ -16,23 +16,23 @@ jobs:
|
|||||||
if: github.repository == 'commaai/openpilot'
|
if: github.repository == 'commaai/openpilot'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Checkout master
|
- name: Checkout master
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
path: base
|
path: base
|
||||||
- run: git lfs pull
|
- run: git lfs pull
|
||||||
- run: cd base && git lfs pull
|
- run: cd base && git lfs pull
|
||||||
|
|
||||||
|
- run: pip install onnx
|
||||||
|
|
||||||
- name: scripts/reporter.py
|
- name: scripts/reporter.py
|
||||||
id: report
|
id: report
|
||||||
run: |
|
run: |
|
||||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||||
echo "## Model Review" >> $GITHUB_OUTPUT
|
echo "## Model Review" >> $GITHUB_OUTPUT
|
||||||
PYTHONPATH=${{ github.workspace }} MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT
|
MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT
|
||||||
echo "EOF" >> $GITHUB_OUTPUT
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Post model report comment
|
- name: Post model report comment
|
||||||
|
|||||||
105
.github/workflows/post-to-discourse/action.yml
vendored
Normal file
105
.github/workflows/post-to-discourse/action.yml
vendored
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
name: 'Post to Discourse'
|
||||||
|
description: 'Posts a message to a Discourse topic (existing or new)'
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
discourse-url:
|
||||||
|
description: 'Discourse instance URL (e.g., https://discourse.example.com)'
|
||||||
|
required: true
|
||||||
|
api-key:
|
||||||
|
description: 'Discourse API key'
|
||||||
|
required: true
|
||||||
|
api-username:
|
||||||
|
description: 'Discourse API username'
|
||||||
|
required: true
|
||||||
|
topic-id:
|
||||||
|
description: 'Discourse topic ID to post to (use this OR category-id + title)'
|
||||||
|
required: false
|
||||||
|
category-id:
|
||||||
|
description: 'Category ID for new topic (required if topic-id not provided)'
|
||||||
|
required: false
|
||||||
|
title:
|
||||||
|
description: 'Title for new topic (required if topic-id not provided)'
|
||||||
|
required: false
|
||||||
|
message:
|
||||||
|
description: 'Message content (markdown supported)'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
post-number:
|
||||||
|
description: 'The post number in the topic'
|
||||||
|
value: ${{ steps.post.outputs.post_number }}
|
||||||
|
post-url:
|
||||||
|
description: 'Direct URL to the post'
|
||||||
|
value: ${{ steps.post.outputs.post_url }}
|
||||||
|
topic-id:
|
||||||
|
description: 'The topic ID (useful when creating a new topic)'
|
||||||
|
value: ${{ steps.post.outputs.topic_id }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Post to Discourse
|
||||||
|
id: post
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Validate inputs
|
||||||
|
if [ -z "${{ inputs.topic-id }}" ] && ([ -z "${{ inputs.category-id }}" ] || [ -z "${{ inputs.title }}" ]); then
|
||||||
|
echo "❌ Error: Must provide either topic-id OR both category-id and title"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${{ inputs.topic-id }}" ] && ([ -n "${{ inputs.category-id }}" ] || [ -n "${{ inputs.title }}" ]); then
|
||||||
|
echo "⚠️ Warning: Both topic-id and category-id/title provided. Will post to existing topic."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine if creating new topic or posting to existing
|
||||||
|
if [ -n "${{ inputs.topic-id }}" ]; then
|
||||||
|
echo "📝 Posting to existing topic ID: ${{ inputs.topic-id }}"
|
||||||
|
|
||||||
|
# Create JSON payload for posting to existing topic
|
||||||
|
PAYLOAD=$(jq -n \
|
||||||
|
--arg content '${{ inputs.message }}' \
|
||||||
|
--arg topic_id "${{ inputs.topic-id }}" \
|
||||||
|
'{topic_id: $topic_id, raw: $content}')
|
||||||
|
else
|
||||||
|
echo "✨ Creating new topic: ${{ inputs.title }}"
|
||||||
|
|
||||||
|
# Create JSON payload for new topic
|
||||||
|
PAYLOAD=$(jq -n \
|
||||||
|
--arg content '${{ inputs.message }}' \
|
||||||
|
--arg title "${{ inputs.title }}" \
|
||||||
|
--arg category "${{ inputs.category-id }}" \
|
||||||
|
'{title: $title, category: ($category | tonumber), raw: $content}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Post to Discourse
|
||||||
|
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
||||||
|
-X POST "${{ inputs.discourse-url }}/posts.json" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Api-Key: ${{ inputs.api-key }}" \
|
||||||
|
-H "Api-Username: ${{ inputs.api-username }}" \
|
||||||
|
-d "$PAYLOAD")
|
||||||
|
|
||||||
|
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||||
|
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||||
|
echo "✅ Successfully posted to Discourse!"
|
||||||
|
|
||||||
|
POST_NUMBER=$(echo "$BODY" | jq -r '.post_number // "unknown"')
|
||||||
|
TOPIC_ID=$(echo "$BODY" | jq -r '.topic_id // "${{ inputs.topic-id }}"')
|
||||||
|
POST_URL="${{ inputs.discourse-url }}/t/${TOPIC_ID}/${POST_NUMBER}"
|
||||||
|
|
||||||
|
echo "post_number=${POST_NUMBER}" >> $GITHUB_OUTPUT
|
||||||
|
echo "post_url=${POST_URL}" >> $GITHUB_OUTPUT
|
||||||
|
echo "topic_id=${TOPIC_ID}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
echo "Topic ID: ${TOPIC_ID}"
|
||||||
|
echo "Post number: ${POST_NUMBER}"
|
||||||
|
echo "URL: ${POST_URL}"
|
||||||
|
else
|
||||||
|
echo "❌ Failed to post to Discourse"
|
||||||
|
echo "HTTP Code: ${HTTP_CODE}"
|
||||||
|
echo "Response: ${BODY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
6
.github/workflows/prebuilt.yaml
vendored
6
.github/workflows/prebuilt.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||||
BUILD: release/ci/docker_build_sp.sh
|
BUILD: release/ci/docker_build_sp.sh prebuilt
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_prebuilt:
|
build_prebuilt:
|
||||||
@@ -28,8 +28,8 @@ jobs:
|
|||||||
wait-interval: 30
|
wait-interval: 30
|
||||||
running-workflow-name: 'build prebuilt'
|
running-workflow-name: 'build prebuilt'
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
check-regexp: ^((?!.*(build master-ci|create badges).*).)*$
|
check-regexp: ^((?!.*(build master-ci).*).)*$
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- run: git lfs pull
|
- run: git lfs pull
|
||||||
|
|||||||
15
.github/workflows/release.yaml
vendored
15
.github/workflows/release.yaml
vendored
@@ -7,12 +7,20 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build___nightly:
|
build___nightly:
|
||||||
name: build __nightly
|
name: build __nightly
|
||||||
|
env:
|
||||||
|
ImageOS: ubuntu24
|
||||||
|
container:
|
||||||
|
image: ghcr.io/sunnypilot/sunnypilot-base:latest
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
permissions:
|
permissions:
|
||||||
checks: read
|
checks: read
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
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
|
- name: Wait for green check mark
|
||||||
if: ${{ github.event_name == 'schedule' }}
|
if: ${{ github.event_name == 'schedule' }}
|
||||||
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
|
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
|
||||||
@@ -21,11 +29,14 @@ jobs:
|
|||||||
wait-interval: 30
|
wait-interval: 30
|
||||||
running-workflow-name: 'build __nightly'
|
running-workflow-name: 'build __nightly'
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$
|
check-regexp: ^((?!.*(build prebuilt).*).)*$
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: ./tools/op.sh setup
|
- name: Pull LFS
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
git lfs pull
|
||||||
- name: Push __nightly
|
- name: Push __nightly
|
||||||
run: BRANCH=__nightly release/build_stripped.sh
|
run: BRANCH=__nightly release/build_stripped.sh
|
||||||
|
|||||||
64
.github/workflows/repo-maintenance.yaml
vendored
64
.github/workflows/repo-maintenance.yaml
vendored
@@ -6,21 +6,22 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHONPATH: ${{ github.workspace }}
|
BASE_IMAGE: sunnypilot-base
|
||||||
|
BUILD: release/ci/docker_build_sp.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:
|
jobs:
|
||||||
update_translations:
|
update_translations:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Update translations
|
- name: Update translations
|
||||||
run: python3 selfdrive/ui/update_translations.py --vanish
|
run: |
|
||||||
|
${{ env.RUN }} "python3 selfdrive/ui/update_translations.py --vanish"
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0
|
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
|
||||||
with:
|
with:
|
||||||
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||||
commit-message: "Update translations"
|
commit-message: "Update translations"
|
||||||
@@ -34,49 +35,33 @@ jobs:
|
|||||||
package_updates:
|
package_updates:
|
||||||
name: package_updates
|
name: package_updates
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ghcr.io/sunnypilot/sunnypilot-base:latest
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: uv lock
|
- name: uv lock
|
||||||
run: uv lock --upgrade
|
if: github.repository == 'commaai/openpilot'
|
||||||
- name: uv pip tree
|
|
||||||
id: pip_tree
|
|
||||||
run: |
|
run: |
|
||||||
echo 'PIP_TREE<<EOF' >> $GITHUB_OUTPUT
|
python3 -m ensurepip --upgrade
|
||||||
uv pip tree >> $GITHUB_OUTPUT
|
pip3 install uv
|
||||||
echo 'EOF' >> $GITHUB_OUTPUT
|
uv lock --upgrade
|
||||||
- name: venv size
|
|
||||||
id: venv_size
|
|
||||||
run: |
|
|
||||||
echo 'VENV_SIZE<<EOF' >> $GITHUB_OUTPUT
|
|
||||||
echo "Total: $(du -sh .venv | cut -f1)" >> $GITHUB_OUTPUT
|
|
||||||
echo "" >> $GITHUB_OUTPUT
|
|
||||||
echo "Top 10 by size:" >> $GITHUB_OUTPUT
|
|
||||||
du -sh .venv/lib/python*/site-packages/* 2>/dev/null \
|
|
||||||
| grep -v '\.dist-info' \
|
|
||||||
| grep -v '__pycache__' \
|
|
||||||
| sort -rh \
|
|
||||||
| head -10 \
|
|
||||||
| while IFS=$'\t' read size path; do echo "$size ${path##*/}"; done >> $GITHUB_OUTPUT
|
|
||||||
echo 'EOF' >> $GITHUB_OUTPUT
|
|
||||||
- name: bump submodules
|
- name: bump submodules
|
||||||
run: |
|
run: |
|
||||||
git config submodule.msgq.update none
|
git config --global --add safe.directory '*'
|
||||||
git config submodule.rednose_repo.update none
|
|
||||||
git config submodule.teleoprtc_repo.update none
|
|
||||||
git config submodule.tinygrad.update none
|
git config submodule.tinygrad.update none
|
||||||
git submodule update --remote
|
git submodule update --remote
|
||||||
git add .
|
git add .
|
||||||
- name: update car docs
|
- name: update car docs
|
||||||
run: |
|
run: |
|
||||||
|
export PYTHONPATH="$PWD"
|
||||||
scons -j$(nproc) --minimal opendbc_repo
|
scons -j$(nproc) --minimal opendbc_repo
|
||||||
python selfdrive/car/docs.py
|
python selfdrive/car/docs.py
|
||||||
git add docs/CARS.md
|
git add docs/CARS.md
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0
|
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
|
||||||
with:
|
with:
|
||||||
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||||
token: ${{ github.repository == 'commaai/openpilot' && secrets.ACTIONS_CREATE_PR_PAT || secrets.GITHUB_TOKEN }}
|
token: ${{ github.repository == 'commaai/openpilot' && secrets.ACTIONS_CREATE_PR_PAT || secrets.GITHUB_TOKEN }}
|
||||||
@@ -85,16 +70,5 @@ jobs:
|
|||||||
branch: auto-package-updates
|
branch: auto-package-updates
|
||||||
base: master
|
base: master
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
body: |
|
body: 'Automatic PR from repo-maintenance -> package_updates'
|
||||||
Automatic PR from repo-maintenance -> package_updates
|
|
||||||
|
|
||||||
```
|
|
||||||
$ du -sh .venv && du -sh .venv/lib/python*/site-packages/* | sort -rh | head -10
|
|
||||||
${{ steps.venv_size.outputs.VENV_SIZE }}
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
$ uv pip tree
|
|
||||||
${{ steps.pip_tree.outputs.PIP_TREE }}
|
|
||||||
```
|
|
||||||
labels: bot
|
labels: bot
|
||||||
|
|||||||
311
.github/workflows/selfdrive_tests.yaml
vendored
Normal file
311
.github/workflows/selfdrive_tests.yaml
vendored
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
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: sunnypilot-base
|
||||||
|
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
|
||||||
|
|
||||||
|
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
BUILD: release/ci/docker_build_sp.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 --durations=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'))
|
||||||
|
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||||
|
|| fromJSON('["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_stripped.sh
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
- 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"
|
||||||
|
- name: Check submodules
|
||||||
|
if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
|
timeout-minutes: 3
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.ref }}" != "refs/heads/master" ]; then
|
||||||
|
git fetch origin master:refs/remotes/origin/master
|
||||||
|
|
||||||
|
SUBMODULE_PATHS=$(git diff origin/master HEAD --name-only | grep -E '^[^/]+$' | while read path; do
|
||||||
|
if git ls-files --stage "$path" | grep -q "^160000"; then
|
||||||
|
echo "$path"
|
||||||
|
fi
|
||||||
|
done | tr '\n' ' ')
|
||||||
|
|
||||||
|
if [ -n "$SUBMODULE_PATHS" ]; then
|
||||||
|
echo "Changed submodule paths: $SUBMODULE_PATHS"
|
||||||
|
export SUBMODULE_PATHS="$SUBMODULE_PATHS"
|
||||||
|
export CHECK_PR_REFS=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
release/check-submodules.sh
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ${{
|
||||||
|
(github.repository == 'commaai/openpilot') &&
|
||||||
|
((github.event_name != 'pull_request') ||
|
||||||
|
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||||
|
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||||
|
|| fromJSON('["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 == 'sunnypilot/sunnypilot'
|
||||||
|
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') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
||||||
|
- name: Homebrew cache
|
||||||
|
uses: ./.github/workflows/auto-cache
|
||||||
|
if: false # disabling the cache for now because it is breaking macos builds...
|
||||||
|
with:
|
||||||
|
save: false # No need save here if we manually save it later conditionally
|
||||||
|
path: ~/Library/Caches/Homebrew
|
||||||
|
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
brew-macos-${{ env.CACHE_COMMIT_DATE }}
|
||||||
|
brew-macos
|
||||||
|
- name: Install dependencies
|
||||||
|
run: ./tools/mac_setup.sh
|
||||||
|
env:
|
||||||
|
PYTHONWARNINGS: default # package install has DeprecationWarnings
|
||||||
|
HOMEBREW_DISPLAY_INSTALL_TIMES: 1
|
||||||
|
- name: Save Homebrew cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
with:
|
||||||
|
path: ~/Library/Caches/Homebrew
|
||||||
|
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||||
|
- run: git lfs pull
|
||||||
|
- name: Getting scons cache
|
||||||
|
uses: ./.github/workflows/auto-cache
|
||||||
|
with:
|
||||||
|
save: false # No need save here if we manually save it later conditionally
|
||||||
|
path: /tmp/scons_cache
|
||||||
|
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}
|
||||||
|
scons-${{ runner.arch }}-macos
|
||||||
|
- name: Building openpilot
|
||||||
|
run: . .venv/bin/activate && scons -j$(nproc)
|
||||||
|
- name: Save scons cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
with:
|
||||||
|
path: /tmp/scons_cache
|
||||||
|
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||||
|
|
||||||
|
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'))
|
||||||
|
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||||
|
|| fromJSON('["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'))
|
||||||
|
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||||
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
id: setup-step
|
||||||
|
- name: Build openpilot
|
||||||
|
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||||
|
- name: Run unit tests
|
||||||
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 999 }}
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
|
||||||
|
# Pre-compile Python bytecode so each pytest worker doesn't need to
|
||||||
|
$PYTEST --collect-only -m 'not slow' -qq && \
|
||||||
|
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"
|
||||||
|
|
||||||
|
process_replay:
|
||||||
|
name: process replay
|
||||||
|
if: false # disable process_replay for forks
|
||||||
|
runs-on: ${{
|
||||||
|
(github.repository == 'commaai/openpilot') &&
|
||||||
|
((github.event_name != 'pull_request') ||
|
||||||
|
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||||
|
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||||
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
id: setup-step
|
||||||
|
- 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') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }}
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
|
||||||
|
chmod -R 777 /tmp/comma_download_cache"
|
||||||
|
- 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: false # TODO: move this to github instead of azure
|
||||||
|
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"
|
||||||
|
|
||||||
|
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'))
|
||||||
|
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||||
|
|| fromJSON('["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
|
||||||
|
id: setup-step
|
||||||
|
- name: Build openpilot
|
||||||
|
run: |
|
||||||
|
${{ env.RUN }} "scons -j$(nproc)"
|
||||||
|
- name: Driving test
|
||||||
|
timeout-minutes: ${{ (steps.setup-step.outputs.duration < 18) && 1 || 2 }}
|
||||||
|
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'))
|
||||||
|
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||||
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
|
if: false # FIXME: FrameReader is broken on CI runners
|
||||||
|
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') && 2 || 4) }}
|
||||||
|
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
|
||||||
52
.github/workflows/setup-with-retry/action.yaml
vendored
Normal file
52
.github/workflows/setup-with-retry/action.yaml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
duration:
|
||||||
|
description: 'Duration of the setup process in seconds'
|
||||||
|
value: ${{ steps.get_duration.outputs.duration }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- id: start_time
|
||||||
|
shell: bash
|
||||||
|
run: echo "START_TIME=$(date +%s)" >> $GITHUB_ENV
|
||||||
|
- 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
|
||||||
|
- id: get_duration
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
END_TIME=$(date +%s)
|
||||||
|
DURATION=$((END_TIME - START_TIME))
|
||||||
|
echo "Total duration: $DURATION seconds"
|
||||||
|
echo "duration=$DURATION" >> $GITHUB_OUTPUT
|
||||||
56
.github/workflows/setup/action.yaml
vendored
Normal file
56
.github/workflows/setup/action.yaml
vendored
Normal file
@@ -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 ${{ github.event.pull_request.head.repo.fork && github.event.pull_request.author_association == 'NONE' && 2 || 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 }}
|
||||||
8
.github/workflows/stale.yaml
vendored
8
.github/workflows/stale.yaml
vendored
@@ -5,15 +5,15 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DAYS_BEFORE_PR_CLOSE: 7
|
DAYS_BEFORE_PR_CLOSE: 2
|
||||||
DAYS_BEFORE_PR_STALE: 24
|
DAYS_BEFORE_PR_STALE: 9
|
||||||
DAYS_BEFORE_PR_STALE_DRAFT: 30
|
DAYS_BEFORE_PR_STALE_DRAFT: 30
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v10
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
exempt-all-milestones: true
|
exempt-all-milestones: true
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
stale_drafts:
|
stale_drafts:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v10
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
exempt-all-milestones: true
|
exempt-all-milestones: true
|
||||||
|
|
||||||
|
|||||||
14
.github/workflows/sunnypilot-build-model.yaml
vendored
14
.github/workflows/sunnypilot-build-model.yaml
vendored
@@ -156,8 +156,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
||||||
path: ${{ github.workspace }}/selfdrive/modeld/models
|
path: ${{ github.workspace }}/selfdrive/modeld/models
|
||||||
- run: |
|
|
||||||
rm -f ${{ github.workspace }}/selfdrive/modeld/models/{dmonitoring_model,big_driving_policy,big_driving_vision}.onnx
|
|
||||||
|
|
||||||
- name: Build Model
|
- name: Build Model
|
||||||
run: |
|
run: |
|
||||||
@@ -173,18 +171,9 @@ jobs:
|
|||||||
|
|
||||||
echo "Compiling: $onnx_file -> $output_file"
|
echo "Compiling: $onnx_file -> $output_file"
|
||||||
QCOM=1 python3 "${{ env.TINYGRAD_PATH }}/examples/openpilot/compile3.py" "$onnx_file" "$output_file"
|
QCOM=1 python3 "${{ env.TINYGRAD_PATH }}/examples/openpilot/compile3.py" "$onnx_file" "$output_file"
|
||||||
DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
QCOM=1 python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Validate Model Outputs
|
|
||||||
run: |
|
|
||||||
source /etc/profile
|
|
||||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
|
||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
|
||||||
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
|
|
||||||
--validate-only \
|
|
||||||
--model-dir "${{ env.MODELS_DIR }}"
|
|
||||||
|
|
||||||
- name: Prepare Output
|
- name: Prepare Output
|
||||||
run: |
|
run: |
|
||||||
sudo rm -rf ${{ env.OUTPUT_DIR }}
|
sudo rm -rf ${{ env.OUTPUT_DIR }}
|
||||||
@@ -193,6 +182,7 @@ jobs:
|
|||||||
# Copy the model files
|
# Copy the model files
|
||||||
rsync -avm \
|
rsync -avm \
|
||||||
--include='*.dlc' \
|
--include='*.dlc' \
|
||||||
|
--include='*.thneed' \
|
||||||
--include='*.pkl' \
|
--include='*.pkl' \
|
||||||
--include='*.onnx' \
|
--include='*.onnx' \
|
||||||
--exclude='*' \
|
--exclude='*' \
|
||||||
|
|||||||
55
.github/workflows/sunnypilot-build-prebuilt.yaml
vendored
55
.github/workflows/sunnypilot-build-prebuilt.yaml
vendored
@@ -6,10 +6,10 @@ env:
|
|||||||
CI_DIR: ${{ github.workspace }}/release/ci
|
CI_DIR: ${{ github.workspace }}/release/ci
|
||||||
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
|
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
|
||||||
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
||||||
|
|
||||||
# Branch configurations
|
# Branch configurations
|
||||||
STAGING_SOURCE_BRANCH: 'master'
|
STAGING_SOURCE_BRANCH: 'master'
|
||||||
|
|
||||||
# Runtime configuration
|
# Runtime configuration
|
||||||
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
wait_for_tests:
|
wait_for_tests:
|
||||||
description: 'Wait for tests to finish'
|
description: 'Wait for selfdrive_tests to finish'
|
||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
cancel="$(echo "$CONFIG" | jq -r '.cancel_publish_in_progress')";
|
cancel="$(echo "$CONFIG" | jq -r '.cancel_publish_in_progress')";
|
||||||
echo "cancel_publish_in_progress=$( [ "$cancel" = "null" ] && echo "true" || echo $cancel)" >> $GITHUB_OUTPUT
|
echo "cancel_publish_in_progress=$( [ "$cancel" = "null" ] && echo "true" || echo $cancel)" >> $GITHUB_OUTPUT
|
||||||
echo "publish_concurrency_group=publish-${BRANCH}$( [ "$cancel" = "null" ] || [ "$cancel" = "true" ] || echo "${{ github.sha }}" )" >> $GITHUB_OUTPUT
|
echo "publish_concurrency_group=publish-${BRANCH}$( [ "$cancel" = "null" ] || [ "$cancel" = "true" ] || echo "${{ github.sha }}" )" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')";
|
is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')";
|
||||||
echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT
|
echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "build=$BUILD" >> $GITHUB_OUTPUT
|
echo "build=$BUILD" >> $GITHUB_OUTPUT
|
||||||
cat $GITHUB_OUTPUT
|
cat $GITHUB_OUTPUT
|
||||||
|
|
||||||
validate_tests:
|
validate_tests:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: [ prepare_strategy ]
|
needs: [ prepare_strategy ]
|
||||||
@@ -99,7 +99,7 @@ jobs:
|
|||||||
- name: Wait for Tests
|
- name: Wait for Tests
|
||||||
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
||||||
with:
|
with:
|
||||||
workflow: tests.yaml # The workflow file to monitor
|
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
should-wait-for-start: ${{ github.event_name == 'push' && 'true' || 'false' }}
|
should-wait-for-start: ${{ github.event_name == 'push' && 'true' || 'false' }}
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ jobs:
|
|||||||
needs.prepare_strategy.result == 'success' &&
|
needs.prepare_strategy.result == 'success' &&
|
||||||
(needs.validate_tests.result == 'success' || needs.validate_tests.result == 'skipped') &&
|
(needs.validate_tests.result == 'success' || needs.validate_tests.result == 'skipped') &&
|
||||||
(!contains(github.event_name, 'pull_request') ||
|
(!contains(github.event_name, 'pull_request') ||
|
||||||
(github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
|
(github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
|
||||||
}}
|
}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ${{env.SCONS_CACHE_DIR}}
|
path: ${{env.SCONS_CACHE_DIR}}
|
||||||
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}-${{ github.sha }}
|
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}-${{ github.sha }}
|
||||||
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
|
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
|
||||||
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
|
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
|
||||||
@@ -148,7 +148,7 @@ jobs:
|
|||||||
echo "version=${{ needs.prepare_strategy.outputs.version }}" >> $GITHUB_OUTPUT
|
echo "version=${{ needs.prepare_strategy.outputs.version }}" >> $GITHUB_OUTPUT
|
||||||
echo "extra_version_identifier=${{ needs.prepare_strategy.outputs.extra_version_identifier }}" >> $GITHUB_OUTPUT
|
echo "extra_version_identifier=${{ needs.prepare_strategy.outputs.extra_version_identifier }}" >> $GITHUB_OUTPUT
|
||||||
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
|
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# Set up common environment
|
# Set up common environment
|
||||||
source /etc/profile;
|
source /etc/profile;
|
||||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||||
@@ -180,13 +180,6 @@ jobs:
|
|||||||
./release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/
|
./release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/
|
||||||
cd $BUILD_DIR
|
cd $BUILD_DIR
|
||||||
sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py
|
sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py
|
||||||
echo "Building sunnypilot's modeld_v2..."
|
|
||||||
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld_v2
|
|
||||||
echo "Building sunnypilot's locationd..."
|
|
||||||
scons -j2 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/selfdrive/locationd
|
|
||||||
echo "Building openpilot's locationd..."
|
|
||||||
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal selfdrive/locationd
|
|
||||||
echo "Building rest of sunnypilot"
|
|
||||||
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal
|
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal
|
||||||
touch ${BUILD_DIR}/prebuilt
|
touch ${BUILD_DIR}/prebuilt
|
||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||||
@@ -198,27 +191,37 @@ jobs:
|
|||||||
sudo rm -rf ${OUTPUT_DIR}
|
sudo rm -rf ${OUTPUT_DIR}
|
||||||
mkdir -p ${OUTPUT_DIR}
|
mkdir -p ${OUTPUT_DIR}
|
||||||
rsync -am${RUNNER_DEBUG:+v} \
|
rsync -am${RUNNER_DEBUG:+v} \
|
||||||
|
--include='**/panda/board/' \
|
||||||
|
--include='**/panda/board/obj' \
|
||||||
|
--include='**/panda/board/obj/panda.bin.signed' \
|
||||||
|
--include='**/panda/board/obj/panda_h7.bin.signed' \
|
||||||
|
--include='**/panda/board/obj/bootstub.panda.bin' \
|
||||||
|
--include='**/panda/board/obj/bootstub.panda_h7.bin' \
|
||||||
--exclude='.sconsign.dblite' \
|
--exclude='.sconsign.dblite' \
|
||||||
--exclude='*.a' \
|
--exclude='*.a' \
|
||||||
--exclude='*.o' \
|
--exclude='*.o' \
|
||||||
--exclude='*.os' \
|
--exclude='*.os' \
|
||||||
--exclude='*.pyc' \
|
--exclude='*.pyc' \
|
||||||
--exclude='moc_*' \
|
--exclude='moc_*' \
|
||||||
--exclude='__pycache__' \
|
--exclude='*.cc' \
|
||||||
--exclude='Jenkinsfile' \
|
--exclude='Jenkinsfile' \
|
||||||
|
--exclude='supercombo.onnx' \
|
||||||
|
--exclude='**/panda/board/*' \
|
||||||
|
--exclude='**/panda/board/obj/**' \
|
||||||
|
--exclude='**/panda/certs/' \
|
||||||
|
--exclude='**/panda/crypto/' \
|
||||||
--exclude='**/release/' \
|
--exclude='**/release/' \
|
||||||
--exclude='**/.github/' \
|
--exclude='**/.github/' \
|
||||||
--exclude='**/selfdrive/ui/replay/' \
|
--exclude='**/selfdrive/ui/replay/' \
|
||||||
--exclude='**/__pycache__/' \
|
--exclude='**/__pycache__/' \
|
||||||
|
--exclude='**/selfdrive/ui/*.h' \
|
||||||
|
--exclude='**/selfdrive/ui/**/*.h' \
|
||||||
|
--exclude='**/selfdrive/ui/qt/offroad/sunnypilot/' \
|
||||||
--exclude='${{env.SCONS_CACHE_DIR}}' \
|
--exclude='${{env.SCONS_CACHE_DIR}}' \
|
||||||
--exclude='**/.git/' \
|
--exclude='**/.git/' \
|
||||||
--exclude='**/SConstruct' \
|
--exclude='**/SConstruct' \
|
||||||
--exclude='**/SConscript' \
|
--exclude='**/SConscript' \
|
||||||
--exclude='**/.venv/' \
|
--exclude='**/.venv/' \
|
||||||
--exclude='selfdrive/modeld/models/driving_vision.onnx' \
|
|
||||||
--exclude='selfdrive/modeld/models/driving_policy.onnx' \
|
|
||||||
--exclude='third_party/*x86*' \
|
|
||||||
--exclude='third_party/*Darwin*' \
|
|
||||||
--delete-excluded \
|
--delete-excluded \
|
||||||
--chown=comma:comma \
|
--chown=comma:comma \
|
||||||
${BUILD_DIR}/ ${OUTPUT_DIR}/
|
${BUILD_DIR}/ ${OUTPUT_DIR}/
|
||||||
@@ -238,8 +241,8 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||||
|
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
concurrency:
|
concurrency:
|
||||||
# We do a bit of a hack here to avoid canceling the publishing job if a new commit comes in while we're publishing by adding the sha to the group name.
|
# We do a bit of a hack here to avoid canceling the publishing job if a new commit comes in while we're publishing by adding the sha to the group name.
|
||||||
@@ -290,7 +293,7 @@ jobs:
|
|||||||
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES"
|
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES"
|
||||||
echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}"
|
echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}"
|
||||||
echo "3. Update as needed (JSON array with no spaces)"
|
echo "3. Update as needed (JSON array with no spaces)"
|
||||||
|
|
||||||
- name: Tag ${{ needs.prepare_strategy.outputs.environment }}
|
- name: Tag ${{ needs.prepare_strategy.outputs.environment }}
|
||||||
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
|
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
|
||||||
run: |
|
run: |
|
||||||
@@ -299,7 +302,7 @@ jobs:
|
|||||||
git push -f origin ${TAG}
|
git push -f origin ${TAG}
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
needs:
|
needs:
|
||||||
- prepare_strategy
|
- prepare_strategy
|
||||||
- build
|
- build
|
||||||
- publish
|
- publish
|
||||||
@@ -328,7 +331,7 @@ jobs:
|
|||||||
${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}
|
${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
{
|
{
|
||||||
echo 'content<<EOFMARKER'
|
echo 'content<<EOFMARKER'
|
||||||
echo "$MESSAGE"
|
echo "$MESSAGE"
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Fetch all history for all branches
|
fetch-depth: 0 # Fetch all history for all branches
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Wait for Tests
|
- name: Wait for Tests
|
||||||
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
||||||
@@ -58,7 +57,7 @@ jobs:
|
|||||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL))))
|
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL))))
|
||||||
)
|
)
|
||||||
with:
|
with:
|
||||||
workflow: tests.yaml # The workflow file to monitor
|
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Configure Git
|
- name: Configure Git
|
||||||
@@ -174,38 +173,11 @@ jobs:
|
|||||||
echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig
|
echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig
|
||||||
echo ' locksverify = false' >> .lfsconfig
|
echo ' locksverify = false' >> .lfsconfig
|
||||||
|
|
||||||
- name: Restore workflows from source
|
|
||||||
run: |
|
|
||||||
TARGET_BRANCH="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"
|
|
||||||
SOURCE_BRANCH="${{ inputs.source_branch || env.DEFAULT_SOURCE_BRANCH }}"
|
|
||||||
|
|
||||||
# Ensure we are on the target branch
|
|
||||||
git checkout $TARGET_BRANCH
|
|
||||||
|
|
||||||
echo "Restoring .github/workflows from $SOURCE_BRANCH"
|
|
||||||
git checkout origin/$SOURCE_BRANCH -- .github/workflows
|
|
||||||
|
|
||||||
if ! git diff --cached --quiet; then
|
|
||||||
echo "Workflows differ. Committing restoration."
|
|
||||||
git commit -m "chore: restore .github/workflows from $SOURCE_BRANCH"
|
|
||||||
else
|
|
||||||
echo "Workflows match $SOURCE_BRANCH."
|
|
||||||
fi
|
|
||||||
|
|
||||||
- uses: actions/create-github-app-token@v2
|
|
||||||
id: ci-token
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.CI_GITHUB_ACTIONS_TOKEN_APP_ID }}
|
|
||||||
private-key: ${{ secrets.CI_GITHUB_ACTIONS_TOKEN_APP_PRIVATE_KEY }}
|
|
||||||
|
|
||||||
- name: Push changes if there are diffs
|
- name: Push changes if there are diffs
|
||||||
id: push-changes
|
id: push-changes # Add an id so we can reference this step
|
||||||
run: |
|
run: |
|
||||||
TARGET_BRANCH="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"
|
TARGET_BRANCH="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"
|
||||||
|
|
||||||
# Use the App Token to set the remote URL with authentication
|
|
||||||
git remote set-url origin "https://x-access-token:${{ steps.ci-token.outputs.token }}@github.com/${{ github.repository }}.git"
|
|
||||||
|
|
||||||
# Fetch the latest from remote
|
# Fetch the latest from remote
|
||||||
git fetch origin $TARGET_BRANCH
|
git fetch origin $TARGET_BRANCH
|
||||||
|
|
||||||
@@ -216,7 +188,7 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Push with the authenticated origin
|
# If we get here, there are diffs, so push
|
||||||
if ! git push origin $TARGET_BRANCH --force; then
|
if ! git push origin $TARGET_BRANCH --force; then
|
||||||
echo "Failed to push changes to $TARGET_BRANCH"
|
echo "Failed to push changes to $TARGET_BRANCH"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -229,15 +201,22 @@ jobs:
|
|||||||
if: steps.push-changes.outputs.has_changes == 'true'
|
if: steps.push-changes.outputs.has_changes == 'true'
|
||||||
run: |
|
run: |
|
||||||
echo "Triggering selfdrive tests..."
|
echo "Triggering selfdrive tests..."
|
||||||
gh workflow run tests.yaml --ref "${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"
|
gh workflow run selfdrive_tests.yaml --ref "${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"
|
||||||
|
|
||||||
echo "Sleeping for 120s to give plenty of time for the action to start and then we wait"
|
echo "Sleeping for 120s to give plenty of time for the action to start and then we wait"
|
||||||
sleep 120
|
sleep 120
|
||||||
|
|
||||||
echo "Getting latest run ID..."
|
echo "Getting latest run ID..."
|
||||||
RUN_ID=$(gh run list --workflow=tests.yaml --branch="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}" --limit=1 --json databaseId --jq '.[0].databaseId')
|
RUN_ID=$(gh run list --workflow=selfdrive_tests.yaml --branch="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}" --limit=1 --json databaseId --jq '.[0].databaseId')
|
||||||
|
|
||||||
echo "Watching run ID: $RUN_ID"
|
echo "Watching run ID: $RUN_ID"
|
||||||
gh run watch "$RUN_ID"
|
gh run watch "$RUN_ID"
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Trigger prebuilt workflow
|
||||||
|
if: success() && steps.push-changes.outputs.has_changes == 'true'
|
||||||
|
run: |
|
||||||
|
gh workflow run sunnypilot-build-prebuilt.yaml --ref "${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
93
.github/workflows/sync-docs-discourse.yml
vendored
93
.github/workflows/sync-docs-discourse.yml
vendored
@@ -1,93 +0,0 @@
|
|||||||
# Discourse Docs Sync — one-way push from docs_sp/ Markdown to Discourse API.
|
|
||||||
#
|
|
||||||
# WARNING: This workflow is strictly for Discourse API syncing.
|
|
||||||
# Do NOT add Zensical build steps, GitHub Pages deployment, or any
|
|
||||||
# static-site generation to this file. Those belong in docs.yaml.
|
|
||||||
|
|
||||||
name: Sync Docs to Discourse
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
# paths:
|
|
||||||
# - "docs_sp/**"
|
|
||||||
# - "zensical.toml"
|
|
||||||
pull_request:
|
|
||||||
# paths:
|
|
||||||
# - "docs_sp/**"
|
|
||||||
# - "zensical.toml"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
smoke-test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup environment
|
|
||||||
run: ./tools/op.sh setup
|
|
||||||
|
|
||||||
- name: Smoke test - post one doc to Discourse
|
|
||||||
env:
|
|
||||||
DISCOURSE_URL: ${{ secrets.DISCOURSE_URL }}
|
|
||||||
DISCOURSE_API_KEY: ${{ secrets.DISCOURSE_API_KEY }}
|
|
||||||
DISCOURSE_API_USER: ${{ secrets.DISCOURSE_API_USER }}
|
|
||||||
DISCOURSE_CATEGORY_MAP: '{"getting-started": 133}'
|
|
||||||
run: |
|
|
||||||
uv run --python 3.12 python -c "
|
|
||||||
import os, sys
|
|
||||||
sys.path.insert(0, 'docs_sp/tools')
|
|
||||||
from pathlib import Path
|
|
||||||
from converter import convert
|
|
||||||
from discourse_client import DiscourseClient, DiscourseConfig
|
|
||||||
|
|
||||||
DOC_PATH = 'getting-started/what-is-sunnypilot.md'
|
|
||||||
GITHUB_BRANCH = os.environ.get('GITHUB_REF_NAME', 'master')
|
|
||||||
|
|
||||||
raw = (Path('docs_sp') / DOC_PATH).read_text()
|
|
||||||
body = convert(raw, file_path=DOC_PATH)
|
|
||||||
|
|
||||||
# Append sync footer
|
|
||||||
gh_url = f'https://github.com/sunnypilot/sunnypilot/blob/{GITHUB_BRANCH}/docs_sp/{DOC_PATH}'
|
|
||||||
body = body.rstrip('\n') + f'\n\n---\n<small>This document is version-controlled. Suggest changes [on GitHub]({gh_url}).</small>\n\n[^docs-sync]: docs-sync-id: {DOC_PATH}\n'
|
|
||||||
|
|
||||||
# Extract title from front matter or first heading
|
|
||||||
title = None
|
|
||||||
for line in raw.splitlines():
|
|
||||||
s = line.strip()
|
|
||||||
if s.startswith('title:'):
|
|
||||||
title = s[len('title:'):].strip().strip('\"').strip(\"'\")
|
|
||||||
break
|
|
||||||
if s.startswith('# '):
|
|
||||||
title = s[2:].strip()
|
|
||||||
break
|
|
||||||
title = f'{title or \"What is sunnypilot?\"} - sunnypilot Docs'
|
|
||||||
|
|
||||||
print(f'Title: {title}')
|
|
||||||
print(f'Body: {len(body)} chars')
|
|
||||||
|
|
||||||
config = DiscourseConfig.from_env()
|
|
||||||
client = DiscourseClient(config)
|
|
||||||
category_id = config.category_id_for(DOC_PATH)
|
|
||||||
print(f'Category ID: {category_id}')
|
|
||||||
|
|
||||||
existing = client.find_topic_by_sync_id(DOC_PATH)
|
|
||||||
if existing is not None:
|
|
||||||
topic_id = existing['id']
|
|
||||||
post_id = client.first_post_id(topic_id)
|
|
||||||
if post_id is None:
|
|
||||||
print(f'ERROR: No first post for topic {topic_id}')
|
|
||||||
sys.exit(1)
|
|
||||||
result = client.update_post(post_id, body, edit_reason='CI smoke test')
|
|
||||||
if result is None:
|
|
||||||
print('ERROR: Failed to update post')
|
|
||||||
sys.exit(1)
|
|
||||||
print(f'Updated: {config.base_url}/t/{topic_id}')
|
|
||||||
else:
|
|
||||||
result = client.create_topic(title=title, raw=body, category_id=category_id, tags=['docs-auto-sync'])
|
|
||||||
if result is None:
|
|
||||||
print('ERROR: Failed to create topic')
|
|
||||||
sys.exit(1)
|
|
||||||
print(f'Created: {config.base_url}/t/{result.get(\"topic_id\", \"?\")}')
|
|
||||||
|
|
||||||
print('Smoke test passed!')
|
|
||||||
"
|
|
||||||
78
.github/workflows/test-discourse.yaml.yml
vendored
Normal file
78
.github/workflows/test-discourse.yaml.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
name: Debug Discourse Posting
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-discourse-post:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Post test message to Discourse
|
||||||
|
uses: ./.github/workflows/post-to-discourse
|
||||||
|
with:
|
||||||
|
discourse-url: ${{ vars.DISCOURSE_URL }}
|
||||||
|
api-key: ${{ secrets.DISCOURSE_API_KEY }}
|
||||||
|
api-username: ${{ secrets.DISCOURSE_API_USERNAME }}
|
||||||
|
topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }}
|
||||||
|
message: |
|
||||||
|
## 🧪 Test Post from GitHub Actions
|
||||||
|
|
||||||
|
**This is a test post to verify Discourse integration**
|
||||||
|
|
||||||
|
- **Workflow**: ${{ github.workflow }}
|
||||||
|
- **Run Number**: #${{ github.run_number }}
|
||||||
|
- **Branch**: `${{ github.ref_name }}`
|
||||||
|
- **Commit**: ${{ github.sha }}
|
||||||
|
- **Actor**: @${{ github.actor }}
|
||||||
|
- **Timestamp**: ${{ github.event.head_commit.timestamp }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Fake Build Info (for testing)
|
||||||
|
- **Version**: 0.9.8-test
|
||||||
|
- **Build**: #42
|
||||||
|
- **Branch**: release-test
|
||||||
|
|
||||||
|
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||||
|
|
||||||
|
*This is an automated test message. Drive safe! 🚗💨*
|
||||||
|
|
||||||
|
|
||||||
|
- name: Create topic on Discourse
|
||||||
|
uses: ./.github/workflows/post-to-discourse
|
||||||
|
with:
|
||||||
|
discourse-url: ${{ vars.DISCOURSE_URL }}
|
||||||
|
api-key: ${{ secrets.DISCOURSE_API_KEY }}
|
||||||
|
api-username: ${{ secrets.DISCOURSE_API_USERNAME }}
|
||||||
|
#topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }}
|
||||||
|
category-id: 4
|
||||||
|
title: "This is a test of a new topic instead of a reply"
|
||||||
|
message: |
|
||||||
|
## 🧪 Test Post from GitHub Actions
|
||||||
|
|
||||||
|
**This is a test post to verify Discourse integration**
|
||||||
|
|
||||||
|
- **Workflow**: ${{ github.workflow }}
|
||||||
|
- **Run Number**: #${{ github.run_number }}
|
||||||
|
- **Branch**: `${{ github.ref_name }}`
|
||||||
|
- **Commit**: ${{ github.sha }}
|
||||||
|
- **Actor**: @${{ github.actor }}
|
||||||
|
- **Timestamp**: ${{ github.event.head_commit.timestamp }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Fake Build Info (for testing)
|
||||||
|
- **Version**: 0.9.8-test
|
||||||
|
- **Build**: #42
|
||||||
|
- **Branch**: release-test
|
||||||
|
|
||||||
|
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||||
|
|
||||||
|
*This is an automated test message. Drive safe! 🚗💨*
|
||||||
|
- name: Display results
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "::notice::Discourse post test completed"
|
||||||
|
echo "Check your Discourse topic to verify the post appeared correctly"
|
||||||
238
.github/workflows/tests.yaml
vendored
238
.github/workflows/tests.yaml
vendored
@@ -1,238 +0,0 @@
|
|||||||
name: tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request:
|
|
||||||
workflow_dispatch:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
run_number:
|
|
||||||
default: '1'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: 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:
|
|
||||||
CI: 1
|
|
||||||
PYTHONPATH: ${{ github.workspace }}
|
|
||||||
PYTEST: pytest --continue-on-collection-errors --durations=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'))
|
|
||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|
||||||
env:
|
|
||||||
STRIPPED_DIR: /tmp/releasepilot
|
|
||||||
PYTHONPATH: /tmp/releasepilot
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
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_stripped.sh
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Build openpilot and run checks
|
|
||||||
timeout-minutes: 30
|
|
||||||
working-directory: ${{ env.STRIPPED_DIR }}
|
|
||||||
run: python3 system/manager/build.py
|
|
||||||
- name: Run tests
|
|
||||||
timeout-minutes: 1
|
|
||||||
working-directory: ${{ env.STRIPPED_DIR }}
|
|
||||||
run: release/check-dirty.sh
|
|
||||||
- name: Check submodules
|
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
|
||||||
timeout-minutes: 3
|
|
||||||
run: |
|
|
||||||
if [ "${{ github.ref }}" != "refs/heads/master" ]; then
|
|
||||||
git fetch origin master:refs/remotes/origin/master
|
|
||||||
|
|
||||||
SUBMODULE_PATHS=$(git diff origin/master HEAD --name-only | grep -E '^[^/]+$' | while read path; do
|
|
||||||
if git ls-files --stage "$path" | grep -q "^160000"; then
|
|
||||||
echo "$path"
|
|
||||||
fi
|
|
||||||
done | tr '\n' ' ')
|
|
||||||
|
|
||||||
if [ -n "$SUBMODULE_PATHS" ]; then
|
|
||||||
echo "Changed submodule paths: $SUBMODULE_PATHS"
|
|
||||||
export SUBMODULE_PATHS="$SUBMODULE_PATHS"
|
|
||||||
export CHECK_PR_REFS=true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
release/check-submodules.sh
|
|
||||||
|
|
||||||
build_mac:
|
|
||||||
name: build macOS
|
|
||||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Remove Homebrew from environment
|
|
||||||
run: |
|
|
||||||
FILTERED=$(echo "$PATH" | tr ':' '\n' | grep -v '/opt/homebrew' | tr '\n' ':')
|
|
||||||
echo "PATH=${FILTERED}/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" >> $GITHUB_ENV
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Building openpilot
|
|
||||||
run: scons
|
|
||||||
|
|
||||||
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'))
|
|
||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Static analysis
|
|
||||||
timeout-minutes: 1
|
|
||||||
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'))
|
|
||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Build openpilot
|
|
||||||
run: scons -j$(nproc)
|
|
||||||
- name: Run unit tests
|
|
||||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 999 }}
|
|
||||||
run: |
|
|
||||||
source selfdrive/test/setup_xvfb.sh
|
|
||||||
# Pre-compile Python bytecode so each pytest worker doesn't need to
|
|
||||||
$PYTEST --collect-only -m 'not slow' -qq
|
|
||||||
MAX_EXAMPLES=1 $PYTEST -m 'not slow'
|
|
||||||
|
|
||||||
process_replay:
|
|
||||||
name: process replay
|
|
||||||
if: false # disable process_replay for forks
|
|
||||||
runs-on: ${{
|
|
||||||
(github.repository == 'commaai/openpilot') &&
|
|
||||||
((github.event_name != 'pull_request') ||
|
|
||||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
|
||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Build openpilot
|
|
||||||
run: scons -j$(nproc)
|
|
||||||
- name: Run replay
|
|
||||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }}
|
|
||||||
continue-on-error: ${{ github.ref == 'refs/heads/master' }}
|
|
||||||
run: selfdrive/test/process_replay/test_processes.py -j$(nproc)
|
|
||||||
- name: Print diff
|
|
||||||
id: print-diff
|
|
||||||
if: always()
|
|
||||||
run: cat selfdrive/test/process_replay/diff.txt
|
|
||||||
- uses: actions/upload-artifact@v6
|
|
||||||
if: always()
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
name: process_replay_diff.txt
|
|
||||||
path: selfdrive/test/process_replay/diff.txt
|
|
||||||
- name: Checkout ci-artifacts
|
|
||||||
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: commaai/ci-artifacts
|
|
||||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
|
||||||
path: ${{ github.workspace }}/ci-artifacts
|
|
||||||
- name: Push refs
|
|
||||||
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
|
||||||
working-directory: ${{ github.workspace }}/ci-artifacts
|
|
||||||
run: |
|
|
||||||
git config user.name "GitHub Actions Bot"
|
|
||||||
git config user.email "<>"
|
|
||||||
git fetch origin process-replay || true
|
|
||||||
git checkout process-replay 2>/dev/null || git checkout --orphan process-replay
|
|
||||||
cp ${{ github.workspace }}/selfdrive/test/process_replay/fakedata/*.zst .
|
|
||||||
echo "${{ github.sha }}" > ref_commit
|
|
||||||
git add .
|
|
||||||
git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit"
|
|
||||||
git push origin process-replay
|
|
||||||
- name: Run regen
|
|
||||||
if: false
|
|
||||||
timeout-minutes: 4
|
|
||||||
env:
|
|
||||||
ONNXCPU: 1
|
|
||||||
run: $PYTEST selfdrive/test/process_replay/test_regen.py
|
|
||||||
|
|
||||||
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'))
|
|
||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|
||||||
if: false # FIXME: Started to timeout recently
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Build openpilot
|
|
||||||
run: scons -j$(nproc)
|
|
||||||
- name: Driving test
|
|
||||||
timeout-minutes: 2
|
|
||||||
run: |
|
|
||||||
source selfdrive/test/setup_xvfb.sh
|
|
||||||
pytest -s tools/sim/tests/test_metadrive_bridge.py
|
|
||||||
|
|
||||||
create_ui_report:
|
|
||||||
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'))
|
|
||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Build openpilot
|
|
||||||
run: scons -j$(nproc)
|
|
||||||
- name: Create UI Report
|
|
||||||
run: |
|
|
||||||
source selfdrive/test/setup_xvfb.sh
|
|
||||||
python3 selfdrive/ui/tests/diff/replay.py
|
|
||||||
python3 selfdrive/ui/tests/diff/replay.py --big
|
|
||||||
- name: Upload UI Report
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
|
||||||
name: ui-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
|
||||||
path: selfdrive/ui/tests/diff/report
|
|
||||||
168
.github/workflows/ui_preview.yaml
vendored
168
.github/workflows/ui_preview.yaml
vendored
@@ -8,20 +8,14 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
paths:
|
paths:
|
||||||
- 'selfdrive/assets/**'
|
|
||||||
- 'selfdrive/ui/**'
|
- 'selfdrive/ui/**'
|
||||||
- 'system/ui/**'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
UI_JOB_NAME: "Create UI Report"
|
UI_JOB_NAME: "Create UI Report"
|
||||||
REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
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 }}
|
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 }}-ui-preview"
|
BRANCH_NAME: "openpilot/pr-${{ github.event.number }}"
|
||||||
REPORT_FILES_BRANCH_NAME: "mici-raylib-ui-reports"
|
|
||||||
|
|
||||||
# variant:video_prefix:master_branch
|
|
||||||
VARIANTS: "mici:mici_ui_replay:openpilot_master_ui_mici_raylib big:tizi_ui_replay:openpilot_master_ui_big_raylib"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
preview:
|
preview:
|
||||||
@@ -34,9 +28,8 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
actions: read
|
actions: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- name: Waiting for ui generation to start
|
||||||
with:
|
run: sleep 30
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Waiting for ui generation to end
|
- name: Waiting for ui generation to end
|
||||||
uses: lewagon/wait-on-check-action@v1.3.4
|
uses: lewagon/wait-on-check-action@v1.3.4
|
||||||
@@ -53,93 +46,110 @@ jobs:
|
|||||||
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
|
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
|
- name: Getting proposed ui
|
||||||
|
id: download-artifact
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@v6
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run_id: ${{ steps.get_run_id.outputs.run_id }}
|
run_id: ${{ steps.get_run_id.outputs.run_id }}
|
||||||
search_artifacts: true
|
search_artifacts: true
|
||||||
name: ui-report-1-${{ env.REPORT_NAME }}
|
name: report-1-${{ env.REPORT_NAME }}
|
||||||
path: ${{ github.workspace }}/pr_ui
|
path: ${{ github.workspace }}/pr_ui
|
||||||
|
|
||||||
- name: Getting mici master ui
|
- name: Getting master ui
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: sunnypilot/ci-artifacts
|
repository: sunnypilot/ci-artifacts
|
||||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||||
path: ${{ github.workspace }}/master_mici
|
path: ${{ github.workspace }}/master_ui
|
||||||
ref: openpilot_master_ui_mici_raylib
|
ref: openpilot_master_ui
|
||||||
|
|
||||||
- name: Getting big master ui
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
repository: sunnypilot/ci-artifacts
|
|
||||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
|
||||||
path: ${{ github.workspace }}/master_big
|
|
||||||
ref: openpilot_master_ui_big_raylib
|
|
||||||
|
|
||||||
- name: Saving new master ui
|
- name: Saving new master ui
|
||||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||||
|
working-directory: ${{ github.workspace }}/master_ui
|
||||||
run: |
|
run: |
|
||||||
for variant in $VARIANTS; do
|
git checkout --orphan=new_master_ui
|
||||||
IFS=':' read -r name video branch <<< "$variant"
|
git rm -rf *
|
||||||
master_dir="${{ github.workspace }}/master_${name}"
|
git branch -D openpilot_master_ui
|
||||||
cd "$master_dir"
|
git branch -m openpilot_master_ui
|
||||||
git checkout --orphan=new_branch
|
git config user.name "GitHub Actions Bot"
|
||||||
git rm -rf *
|
git config user.email "<>"
|
||||||
git branch -D "$branch"
|
mv ${{ github.workspace }}/pr_ui/*.png .
|
||||||
git branch -m "$branch"
|
git add .
|
||||||
git config user.name "GitHub Actions Bot"
|
git commit -m "screenshots for commit ${{ env.SHA }}"
|
||||||
git config user.email "<>"
|
git push origin openpilot_master_ui --force
|
||||||
cp "${{ github.workspace }}/pr_ui/${video}.mp4" .
|
|
||||||
git add .
|
|
||||||
git commit -m "${name} video for commit ${{ env.SHA }}"
|
|
||||||
git push origin "$branch" --force
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Setup FFmpeg
|
- name: Finding diff
|
||||||
uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae
|
|
||||||
|
|
||||||
- name: Finding diffs
|
|
||||||
if: github.event_name == 'pull_request_target'
|
if: github.event_name == 'pull_request_target'
|
||||||
id: find_diff
|
id: find_diff
|
||||||
run: |
|
run: >-
|
||||||
export PYTHONPATH=${{ github.workspace }}
|
sudo apt-get update && sudo apt-get install -y imagemagick
|
||||||
baseurl="https://github.com/sunnypilot/ci-artifacts/raw/refs/heads/${{ env.BRANCH_NAME }}"
|
|
||||||
|
|
||||||
COMMENT=""
|
scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device')
|
||||||
for variant in $VARIANTS; do
|
A=($scenes)
|
||||||
IFS=':' read -r name video _ <<< "$variant"
|
|
||||||
diff_name="${name}_diff"
|
|
||||||
|
|
||||||
mv "${{ github.workspace }}/pr_ui/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_proposed.mp4"
|
DIFF=""
|
||||||
cp "${{ github.workspace }}/master_${name}/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_master.mp4"
|
TABLE="<details><summary>All Screenshots</summary>"
|
||||||
|
TABLE="${TABLE}<table>"
|
||||||
|
|
||||||
diff_exit_code=0
|
for ((i=0; i<${#A[*]}; i=i+1));
|
||||||
python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py \
|
do
|
||||||
"${{ github.workspace }}/pr_ui/${video}_master.mp4" \
|
# Check if the master file exists
|
||||||
"${{ github.workspace }}/pr_ui/${video}_proposed.mp4" \
|
if [ ! -f "${{ github.workspace }}/master_ui/${A[$i]}.png" ]; then
|
||||||
"${diff_name}.html" --basedir "$baseurl" --no-open || diff_exit_code=$?
|
# This is a new file in PR UI that doesn't exist in master
|
||||||
|
DIFF="${DIFF}<details open>"
|
||||||
|
DIFF="${DIFF}<summary>${A[$i]} : \$\${\\color{cyan}\\text{NEW}}\$\$</summary>"
|
||||||
|
DIFF="${DIFF}<table>"
|
||||||
|
|
||||||
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${{ github.workspace }}/pr_ui/"
|
DIFF="${DIFF}<tr>"
|
||||||
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.mp4" "${{ github.workspace }}/pr_ui/"
|
DIFF="${DIFF} <td> <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||||
|
DIFF="${DIFF}</tr>"
|
||||||
|
|
||||||
REPORT_URL="https://sunnypilot.github.io/ci-artifacts/${diff_name}_pr_${{ github.event.number }}.html"
|
DIFF="${DIFF}</table>"
|
||||||
if [ $diff_exit_code -eq 0 ]; then
|
DIFF="${DIFF}</details>"
|
||||||
COMMENT+="**${name}**: Videos are identical! [View Diff Report]($REPORT_URL)"$'\n'
|
elif ! 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/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_master_ref.png\"> </td>"
|
||||||
|
DIFF="${DIFF} <td> proposed <img src=\"https://raw.githubusercontent.com/sunnypilot/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/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.png\"> </td>"
|
||||||
|
DIFF="${DIFF} <td> composite diff <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.gif\"> </td>"
|
||||||
|
DIFF="${DIFF}</tr>"
|
||||||
|
|
||||||
|
DIFF="${DIFF}</table>"
|
||||||
|
DIFF="${DIFF}</details>"
|
||||||
else
|
else
|
||||||
COMMENT+="**${name}**: ⚠️ <strong>Videos differ!</strong> [View Diff Report]($REPORT_URL)"$'\n'
|
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/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||||
|
if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then
|
||||||
|
TABLE="${TABLE}</tr>"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
{
|
TABLE="${TABLE}</table></details>"
|
||||||
echo "COMMENT<<EOF"
|
|
||||||
echo "$COMMENT"
|
echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT"
|
||||||
echo "EOF"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Saving proposed ui
|
- name: Saving proposed ui
|
||||||
if: github.event_name == 'pull_request_target'
|
if: github.event_name == 'pull_request_target'
|
||||||
working-directory: ${{ github.workspace }}/master_mici
|
working-directory: ${{ github.workspace }}/master_ui
|
||||||
run: |
|
run: |
|
||||||
git config user.name "GitHub Actions Bot"
|
git config user.name "GitHub Actions Bot"
|
||||||
git config user.email "<>"
|
git config user.email "<>"
|
||||||
@@ -147,29 +157,17 @@ jobs:
|
|||||||
git rm -rf *
|
git rm -rf *
|
||||||
mv ${{ github.workspace }}/pr_ui/* .
|
mv ${{ github.workspace }}/pr_ui/* .
|
||||||
git add .
|
git add .
|
||||||
git commit -m "ui videos for PR #${{ github.event.number }}"
|
git commit -m "screenshots for PR #${{ github.event.number }}"
|
||||||
git push origin ${{ env.BRANCH_NAME }} --force
|
git push origin ${{ env.BRANCH_NAME }} --force
|
||||||
|
|
||||||
# Append diff reports to report files branch
|
- name: Comment Screenshots on PR
|
||||||
git fetch origin ${{ env.REPORT_FILES_BRANCH_NAME }}
|
|
||||||
git checkout ${{ env.REPORT_FILES_BRANCH_NAME }}
|
|
||||||
for variant in $VARIANTS; do
|
|
||||||
IFS=':' read -r name _ _ <<< "$variant"
|
|
||||||
diff_name="${name}_diff"
|
|
||||||
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${diff_name}_pr_${{ github.event.number }}.html"
|
|
||||||
git add "${diff_name}_pr_${{ github.event.number }}.html"
|
|
||||||
done
|
|
||||||
git commit -m "ui diff reports for PR #${{ github.event.number }}" || echo "No changes to commit"
|
|
||||||
git push origin ${{ env.REPORT_FILES_BRANCH_NAME }}
|
|
||||||
|
|
||||||
- name: Comment on PR
|
|
||||||
if: github.event_name == 'pull_request_target'
|
if: github.event_name == 'pull_request_target'
|
||||||
uses: thollander/actions-comment-pull-request@v2
|
uses: thollander/actions-comment-pull-request@v2
|
||||||
with:
|
with:
|
||||||
message: |
|
message: |
|
||||||
<!-- _(run_id_ui_preview **${{ github.run_id }}**)_ -->
|
<!-- _(run_id_screenshots **${{ github.run_id }}**)_ -->
|
||||||
## UI Preview
|
## UI Preview
|
||||||
${{ steps.find_diff.outputs.COMMENT }}
|
${{ steps.find_diff.outputs.DIFF }}
|
||||||
comment_tag: run_id_ui_preview
|
comment_tag: run_id_screenshots
|
||||||
pr_number: ${{ github.event.number }}
|
pr_number: ${{ github.event.number }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ inputs:
|
|||||||
workflow:
|
workflow:
|
||||||
description: 'The workflow file name to monitor'
|
description: 'The workflow file name to monitor'
|
||||||
required: true
|
required: true
|
||||||
default: 'tests.yaml'
|
default: 'selfdrive_tests.yaml'
|
||||||
branch:
|
branch:
|
||||||
description: 'The branch to monitor (defaults to current branch)'
|
description: 'The branch to monitor (defaults to current branch)'
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
23
.gitignore
vendored
23
.gitignore
vendored
@@ -10,13 +10,12 @@ venv/
|
|||||||
.overlay_init
|
.overlay_init
|
||||||
.overlay_consistent
|
.overlay_consistent
|
||||||
.sconsign.dblite
|
.sconsign.dblite
|
||||||
|
model2.png
|
||||||
a.out
|
a.out
|
||||||
.hypothesis
|
.hypothesis
|
||||||
.cache/
|
.cache/
|
||||||
|
|
||||||
/docs_site/
|
/docs_site/
|
||||||
/docs_site_sp/
|
|
||||||
/.discourse_sync_cache/
|
|
||||||
|
|
||||||
*.mp4
|
*.mp4
|
||||||
*.dylib
|
*.dylib
|
||||||
@@ -38,23 +37,29 @@ a.out
|
|||||||
*.class
|
*.class
|
||||||
*.pyxbldc
|
*.pyxbldc
|
||||||
*.vcd
|
*.vcd
|
||||||
*.mo
|
*.qm
|
||||||
*_pyx.cpp
|
*_pyx.cpp
|
||||||
*.stats
|
|
||||||
config.json
|
config.json
|
||||||
clcache
|
clcache
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
compare_runtime*.html
|
compare_runtime*.html
|
||||||
|
|
||||||
|
persist
|
||||||
selfdrive/pandad/pandad
|
selfdrive/pandad/pandad
|
||||||
cereal/services.h
|
cereal/services.h
|
||||||
cereal/gen
|
cereal/gen
|
||||||
cereal/messaging/bridge
|
cereal/messaging/bridge
|
||||||
|
selfdrive/mapd/default_speeds_by_region.json
|
||||||
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
|
||||||
@@ -66,10 +71,11 @@ flycheck_*
|
|||||||
cppcheck_report.txt
|
cppcheck_report.txt
|
||||||
comma*.sh
|
comma*.sh
|
||||||
|
|
||||||
selfdrive/modeld/models/*.pkl*
|
selfdrive/modeld/models/*.pkl
|
||||||
|
sunnypilot/modeld*/thneed/compile
|
||||||
|
sunnypilot/modeld*/models/*.thneed
|
||||||
sunnypilot/modeld*/models/*.pkl
|
sunnypilot/modeld*/models/*.pkl
|
||||||
|
|
||||||
# openpilot log files
|
|
||||||
*.bz2
|
*.bz2
|
||||||
*.zst
|
*.zst
|
||||||
|
|
||||||
@@ -99,11 +105,6 @@ Pipfile
|
|||||||
.history
|
.history
|
||||||
.ionide
|
.ionide
|
||||||
|
|
||||||
.claude/
|
|
||||||
.context/
|
|
||||||
PLAN.md
|
|
||||||
TASK.md
|
|
||||||
|
|
||||||
### JetBrains ###
|
### JetBrains ###
|
||||||
!.idea/customTargets.xml
|
!.idea/customTargets.xml
|
||||||
!.idea/tools/*
|
!.idea/tools/*
|
||||||
|
|||||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -6,7 +6,7 @@
|
|||||||
url = https://github.com/sunnypilot/opendbc.git
|
url = https://github.com/sunnypilot/opendbc.git
|
||||||
[submodule "msgq"]
|
[submodule "msgq"]
|
||||||
path = msgq_repo
|
path = msgq_repo
|
||||||
url = https://github.com/commaai/msgq.git
|
url = https://github.com/sunnypilot/msgq.git
|
||||||
[submodule "rednose_repo"]
|
[submodule "rednose_repo"]
|
||||||
path = rednose_repo
|
path = rednose_repo
|
||||||
url = https://github.com/commaai/rednose.git
|
url = https://github.com/commaai/rednose.git
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
url = https://github.com/commaai/teleoprtc
|
url = https://github.com/commaai/teleoprtc
|
||||||
[submodule "tinygrad"]
|
[submodule "tinygrad"]
|
||||||
path = tinygrad_repo
|
path = tinygrad_repo
|
||||||
url = https://github.com/sunnypilot/tinygrad.git
|
url = https://github.com/tinygrad/tinygrad.git
|
||||||
[submodule "sunnypilot/neural_network_data"]
|
[submodule "sunnypilot/neural_network_data"]
|
||||||
path = sunnypilot/neural_network_data
|
path = sunnypilot/neural_network_data
|
||||||
url = https://github.com/sunnypilot/neural-network-data.git
|
url = https://github.com/sunnypilot/neural-network-data.git
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Build_BIG_UI" type="PythonConfigurationType" factoryName="Python">
|
|
||||||
<module name="sunnypilot" />
|
|
||||||
<option name="ENV_FILES" value="" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<envs>
|
|
||||||
<env name="BIG" value="1" />
|
|
||||||
</envs>
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$/" />
|
|
||||||
<option name="IS_MODULE_SDK" value="true" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<option name="SCRIPT_NAME" value="$ProjectFileDir$/selfdrive/ui/ui.py" />
|
|
||||||
<option name="PARAMETERS" value="" />
|
|
||||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
|
||||||
<option name="EMULATE_TERMINAL" value="false" />
|
|
||||||
<option name="MODULE_MODE" value="false" />
|
|
||||||
<option name="REDIRECT_INPUT" value="false" />
|
|
||||||
<option name="INPUT_FILE" value="" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="ToolBeforeRunTask" enabled="true" actionId="Tool_External Tools_uv Scons Build Debug" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Build_SMALL_UI" type="PythonConfigurationType" factoryName="Python">
|
|
||||||
<module name="sunnypilot" />
|
|
||||||
<option name="ENV_FILES" value="" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$/" />
|
|
||||||
<option name="IS_MODULE_SDK" value="true" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<option name="SCRIPT_NAME" value="$ProjectFileDir$/selfdrive/ui/ui.py" />
|
|
||||||
<option name="PARAMETERS" value="" />
|
|
||||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
|
||||||
<option name="EMULATE_TERMINAL" value="false" />
|
|
||||||
<option name="MODULE_MODE" value="false" />
|
|
||||||
<option name="REDIRECT_INPUT" value="false" />
|
|
||||||
<option name="INPUT_FILE" value="" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="ToolBeforeRunTask" enabled="true" actionId="Tool_External Tools_uv Scons Build Debug" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
125
CHANGELOG.md
125
CHANGELOG.md
@@ -1,128 +1,5 @@
|
|||||||
sunnypilot Version 2026.001.000 (2026-03-xx)
|
sunnypilot Version 2025.002.000 (2025-xx-xx)
|
||||||
========================
|
========================
|
||||||
* What's Changed (sunnypilot/sunnypilot)
|
|
||||||
* Complete rewrite of the user interface from Qt C++ to Raylib Python
|
|
||||||
* comma four support
|
|
||||||
* ui: sunnypilot toggle style by @nayan8teen
|
|
||||||
* ui: fix scroll panel mouse wheel behavior by @nayan8teen
|
|
||||||
* ui: sunnypilot panels by @nayan8teen
|
|
||||||
* sunnylink: centralize key pair handling in sunnylink registration by @devtekve
|
|
||||||
* ui: reimplement sunnypilot branding with Raylib by @sunnyhaibin
|
|
||||||
* ui: Platform Selector by @Discountchubbs
|
|
||||||
* ui: vehicle brand settings by @Discountchubbs
|
|
||||||
* ui: sunnylink client-side implementation by @nayan8teen
|
|
||||||
* ui: `NetworkUISP` by @Discountchubbs
|
|
||||||
* ui: add sunnypilot font by @nayan8teen
|
|
||||||
* ui: sunnypilot sponsor tier color mapping by @sunnyhaibin
|
|
||||||
* ui: sunnylink panel by @nayan8teen
|
|
||||||
* ui: Models panel by @Discountchubbs
|
|
||||||
* ui: software panel by @Discountchubbs
|
|
||||||
* modeld_v2: support planplus outputs by @Discountchubbs
|
|
||||||
* ui: OSM panel by @Discountchubbs
|
|
||||||
* ui: Developer panel extension by @Discountchubbs
|
|
||||||
* sunnylink: Vehicle Selector support by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: Developer Metrics by @rav4kumar
|
|
||||||
* [comma 4] ui: sunnylink panel by @nayan8teen
|
|
||||||
* ui: lateral-only and longitudinal-only UI statuses support by @royjr
|
|
||||||
* sunnylink: elliptic curve keys support and improve key path handling by @nayan8teen
|
|
||||||
* sunnylink: block remote modification of SSH key parameters by @zikeji
|
|
||||||
* [TIZI/TICI] ui: rainbow path by @rav4kumar
|
|
||||||
* [TIZI/TICI] ui: chevron metrics by @rav4kumar
|
|
||||||
* ui: include MADS enabled state to `engaged` check by @sunnyhaibin
|
|
||||||
* Toyota: Enforce Factory Longitudinal Control by @sunnyhaibin
|
|
||||||
* ui: fix malformed dongle ID display on the PC if dongleID is not set by @dzid26
|
|
||||||
* SL: Re enable and validate ingestion of swaglogs by @devtekve
|
|
||||||
* modeld_v2: planplus model tuning by @Discountchubbs
|
|
||||||
* ui: fix Always Offroad button visibility by @nayan8teen
|
|
||||||
* Reimplement sunnypilot Terms of Service & sunnylink Consent Screens by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: update dmoji position and Developer UI adjustments by @rav4kumar
|
|
||||||
* modeld: configurable camera offset by @Discountchubbs
|
|
||||||
* [TIZI/TICI] ui: sunnylink status on sidebar by @Copilot
|
|
||||||
* ui: Global Brightness Override by @nayan8teen
|
|
||||||
* ui: Customizable Interactive Timeout by @sunnyhaibin
|
|
||||||
* sunnylink: add units to param metadata by @nayan8teen
|
|
||||||
* ui: Customizable Onroad Brightness by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: Steering panel by @nayan8teen
|
|
||||||
* [TIZI/TICI] ui: Rocket Fuel by @rav4kumar
|
|
||||||
* [TIZI/TICI] ui: MICI style turn signals by @rav4kumar
|
|
||||||
* [TIZI/TICI] ui: MICI style blindspot indicators by @sunnyhaibin
|
|
||||||
* [MICI] ui: display blindspot indicators when available by @rav4kumar
|
|
||||||
* [TIZI/TICI] ui: Road Name by @rav4kumar
|
|
||||||
* [TIZI/TICI] ui: Blue "Exit Always Offroad" button by @dzid26
|
|
||||||
* [TIZI/TICI] ui: Speed Limit by @rav4kumar
|
|
||||||
* Reapply "latcontrol_torque: lower kp and lower friction threshold (commaai/openpilot#36619)" by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: steering arc by @royjr
|
|
||||||
* [TIZI/TICI] ui: Smart Cruise Control elements by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: Green Light and Lead Departure elements by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: standstill timer by @sunnyhaibin
|
|
||||||
* [MICI] ui: driving models selector by @Discountchubbs
|
|
||||||
* [TIZI/TICI] ui: Hide vEgo and True vEgo by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: Visuals panel by @nayan8teen
|
|
||||||
* Device: Retain QuickBoot state after op switch by @nayan8teen
|
|
||||||
* [TIZI/TICI] ui: Trips panel by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: dynamic ICBM status by @sunnyhaibin
|
|
||||||
* [TIZI/TICI] ui: Cruise panel by @sunnyhaibin
|
|
||||||
* ui: better wake mode support by @nayan8teen
|
|
||||||
* Pause Lateral Control with Blinker: Post-Blinker Delay by @CHaucke89
|
|
||||||
* SCC-V: Use p97 for predicted lateral accel by @yasu-oh
|
|
||||||
* Controls: Support for Torque Lateral Control v0 Tune by @sunnyhaibin
|
|
||||||
* What's Changed (sunnypilot/opendbc)
|
|
||||||
* Honda: DBC for Accord 9th Generation by @mvl-boston
|
|
||||||
* FCA: update tire stiffness values for `RAM_HD` by @dparring
|
|
||||||
* Honda: Nidec hybrid baseline brake support by @mvl-boston
|
|
||||||
* Subaru Global Gen2: bump steering limits and update tuning by @sunnyhaibin
|
|
||||||
* Toyota: Enforce Stock Longitudinal Control by @rav4kumar
|
|
||||||
* Nissan: use MADS enabled status for LKAS HUD logic by @downquark7
|
|
||||||
* Reapply "Lateral: lower friction threshold (#2915)" (#378) by @sunnyhaibin
|
|
||||||
* HKG: add KIA_FORTE_2019_NON_SCC fingerprint by @royjr
|
|
||||||
* Nissan: Parse cruise control buttons by @downquark7
|
|
||||||
* Rivian: Add stalk down ACC behavior to match stock Rivian by @lukasloetkolben
|
|
||||||
* Tesla: remove `TESLA_MODEL_X` from `dashcamOnly` by @ssysm
|
|
||||||
* Hyundai Longitudinal: refactor tuning by @Discountchubbs
|
|
||||||
* Tesla: add fingerprint for Model 3 Performance HW4 by @sunnyhaibin
|
|
||||||
* Toyota: do not disable radar when smartDSU or CAN Filter detected by @sunnyhaibin
|
|
||||||
* Honda: add missing `GasInterceptor` messages to Taiwan Odyssey DBC by @mvl-boston
|
|
||||||
* GM: remove `CHEVROLET_EQUINOX_NON_ACC_3RD_GEN` from `dashcamOnly` by @sunnyhaibin
|
|
||||||
* GM: remove `CHEVROLET_BOLT_NON_ACC_2ND_GEN` from `dashcamOnly` by @sunnyhaibin
|
|
||||||
* New Contributors (sunnypilot/sunnypilot)
|
|
||||||
* @TheSecurityDev made their first contribution in "ui: fix sidebar scroll in UI screenshots"
|
|
||||||
* @zikeji made their first contribution in "sunnylink: block remote modification of SSH key parameters"
|
|
||||||
* @Candy0707 made their first contribution in "[TIZI/TICI] ui: Fix misaligned turn signals and blindspot indicators with sidebar"
|
|
||||||
* @CHaucke89 made their first contribution in "Pause Lateral Control with Blinker: Post-Blinker Delay"
|
|
||||||
* @yasu-oh made their first contribution in "SCC-V: Use p97 for predicted lateral accel"
|
|
||||||
* New Contributors (sunnypilot/opendbc)
|
|
||||||
* @AmyJeanes made their first contribution in "Tesla: Fix stock LKAS being blocked when MADS is enabled"
|
|
||||||
* @mvl-boston made their first contribution in "Honda: Update Clarity brake to renamed DBC message name"
|
|
||||||
* @dzid26 made their first contribution in "Tesla: Parse speed limit from CAN"
|
|
||||||
* @firestar5683 made their first contribution in "GM: Non-ACC platforms with steering only support"
|
|
||||||
* @downquark7 made their first contribution in "Nissan: use MADS enabled status for LKAS HUD logic"
|
|
||||||
* @royjr made their first contribution in "HKG: add KIA_FORTE_2019_NON_SCC fingerprint"
|
|
||||||
* @ssysm made their first contribution in "Tesla: remove `TESLA_MODEL_X` from `dashcamOnly`"
|
|
||||||
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2025.002.000...v2026.001.000
|
|
||||||
|
|
||||||
sunnypilot Version 2025.002.000 (2025-11-06)
|
|
||||||
========================
|
|
||||||
* What's Changed (sunnypilot/sunnypilot)
|
|
||||||
* models: bump model json to v8 by @Discountchubbs
|
|
||||||
* Bug: Model UI Crash Fix by @nayan8teen
|
|
||||||
* controlsd: add `CP_SP` to `get_pid_accel_limits` by @THERoenPR
|
|
||||||
* sunnylink: update uploader button logic to support novice tier and above by @devtekve
|
|
||||||
* Tesla: Coop Steering by @AmyJeanes
|
|
||||||
* ui: update discord references and add forum widget by @devtekve
|
|
||||||
* ui: Fix spacing in sunnylink panel by @devtekve
|
|
||||||
* docs: Update README installation branches and discord links by @mpurnell1 in
|
|
||||||
* stats: sunnylink integration by @devtekve
|
|
||||||
* bug: Fix initial registration for sunnylink by @devtekve
|
|
||||||
* What's Changed (sunnypilot/opendbc)
|
|
||||||
* Honda: add brake hold messages for Clarity by @mvl-boston
|
|
||||||
* interface: add `CP_SP` to `get_pid_accel_limits` method signature by @roenthomas
|
|
||||||
* Honda: use fixed accel min/max constants for Gas Interceptor by @roenthomas
|
|
||||||
* Tesla: Coop Steering by @AmyJeanes
|
|
||||||
* New Contributors (sunnypilot/sunnypilot)
|
|
||||||
* @THERoenPR made their first contribution in "controlsd: add `CP_SP` to `get_pid_accel_limits`"
|
|
||||||
* @AmyJeanes made their first contribution in "Tesla: Coop Steering"
|
|
||||||
* @mpurnell1 made their first contribution in "docs: Update README installation branches and discord links"
|
|
||||||
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2025.001.000...v2025.002.000
|
|
||||||
|
|
||||||
sunnypilot Version 2025.001.000 (2025-10-25)
|
sunnypilot Version 2025.001.000 (2025-10-25)
|
||||||
========================
|
========================
|
||||||
|
|||||||
@@ -1,38 +1,12 @@
|
|||||||
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 && \
|
|
||||||
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 '*'
|
|
||||||
|
|||||||
82
Dockerfile.openpilot_base
Normal file
82
Dockerfile.openpilot_base
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
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
|
||||||
|
RUN apt-get update && apt-get install -y fonts-noto-cjk fonts-noto-color-emoji
|
||||||
|
|
||||||
|
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
|
||||||
12
Dockerfile.sunnypilot
Normal file
12
Dockerfile.sunnypilot
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM ghcr.io/sunnypilot/sunnypilot-base:latest
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
ENV OPENPILOT_PATH=/home/batman/openpilot
|
||||||
|
|
||||||
|
RUN mkdir -p ${OPENPILOT_PATH}
|
||||||
|
WORKDIR ${OPENPILOT_PATH}
|
||||||
|
|
||||||
|
COPY . ${OPENPILOT_PATH}/
|
||||||
|
|
||||||
|
RUN scons --cache-readonly -j$(nproc)
|
||||||
83
Dockerfile.sunnypilot_base
Normal file
83
Dockerfile.sunnypilot_base
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
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 \
|
||||||
|
rsync \
|
||||||
|
&& 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
|
||||||
|
RUN apt-get update && apt-get install -y fonts-noto-cjk fonts-noto-color-emoji
|
||||||
|
|
||||||
|
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
|
||||||
27
Jenkinsfile
vendored
27
Jenkinsfile
vendored
@@ -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-*']
|
'release-tici', '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,8 +178,8 @@ 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", "tizi-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"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,23 +210,30 @@ node {
|
|||||||
'HW + Unit Tests': {
|
'HW + Unit Tests': {
|
||||||
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
|
deviceStage("tizi-hardware", "tizi-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 manager", "pytest system/manager/test/test_manager.py"),
|
step("test manager", "pytest system/manager/test/test_manager.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
'loopback': {
|
||||||
|
deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [
|
||||||
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
|
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||||
|
])
|
||||||
|
},
|
||||||
'camerad OX03C10': {
|
'camerad OX03C10': {
|
||||||
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
deviceStage("OX03C10", "tizi-ox03c10", ["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 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 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", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
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': {
|
||||||
@@ -244,9 +251,11 @@ 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 amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||||
|
// TODO: enable once new AGNOS is available
|
||||||
|
// step("test esim", "pytest system/hardware/tici/tests/test_esim.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/"]]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
|||||||
60
README.md
60
README.md
@@ -11,10 +11,66 @@ Join the official sunnypilot community forum to stay up to date with all the lat
|
|||||||
https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot
|
https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot
|
||||||
|
|
||||||
## 🚘 Running on a dedicated device in a car
|
## 🚘 Running on a dedicated device in a car
|
||||||
First, check out this list of items you'll need to [get started](https://community.sunnypilot.ai/t/getting-started-using-sunnypilot-in-your-supported-car/251).
|
* A supported device to run this software
|
||||||
|
* a [comma three](https://comma.ai/shop/products/three) or a [C3X](https://comma.ai/shop/comma-3x)
|
||||||
|
* This software
|
||||||
|
* One of [the 325+ supported cars](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot.
|
||||||
|
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car
|
||||||
|
|
||||||
|
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Next, refer to the sunnypilot community forum for [installation instructions](https://community.sunnypilot.ai/t/read-before-installing-sunnypilot/254), as well as a complete list of [Recommended Branch Installations](https://community.sunnypilot.ai/t/recommended-branch-installations/235).
|
Please refer to [Recommended Branches](#recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging` branch.
|
||||||
|
|
||||||
|
### If you want to use our newest branches (our rewrite)
|
||||||
|
> [!TIP]
|
||||||
|
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
|
||||||
|
|
||||||
|
* sunnypilot not installed or you installed a version before 0.8.17?
|
||||||
|
1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed.
|
||||||
|
2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option.
|
||||||
|
3. Input the installation URL per [Recommended Branches](#recommended-branches). Example: ```https://staging.sunnypilot.ai```.
|
||||||
|
4. Complete the rest of the installation following the onscreen instructions.
|
||||||
|
|
||||||
|
* sunnypilot already installed and you installed a version after 0.8.17?
|
||||||
|
1. On the comma three/3X, go to `Settings` ▶️ `Software`.
|
||||||
|
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
|
||||||
|
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
|
||||||
|
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging`
|
||||||
|
|
||||||
|
### Recommended Branches
|
||||||
|
| Branch | Installation URL |
|
||||||
|
|:---------------:|:---------------------------------------------:|
|
||||||
|
| `release` | `https://release.sunnypilot.ai` |
|
||||||
|
| `staging` | `https://staging.sunnypilot.ai` |
|
||||||
|
| `dev` | `https://dev.sunnypilot.ai` |
|
||||||
|
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging'.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Do you require further assistance with software installation? Join the [sunnypilot community forum](https://community.sunnypilot.ai/new-topic?category=general/qa) and create a topic in the General/Q&A Category channel.
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Older legacy branches</summary>
|
||||||
|
|
||||||
|
### If you want to use our older legacy branches (*not recommended*)
|
||||||
|
|
||||||
|
> [**IMPORTANT**]
|
||||||
|
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
|
||||||
|
> You can still restore the latest sunnylink backup made on the old branches.
|
||||||
|
|
||||||
|
| Branch | Installation URL |
|
||||||
|
|:------------:|:--------------------------------:|
|
||||||
|
| `release-c3` | https://release-c3.sunnypilot.ai |
|
||||||
|
| `staging-c3` | https://staging-c3.sunnypilot.ai |
|
||||||
|
| `dev-c3` | https://dev-c3.sunnypilot.ai |
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## 🎆 Pull Requests
|
## 🎆 Pull Requests
|
||||||
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
|
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
|
||||||
|
|||||||
25
RELEASES.md
25
RELEASES.md
@@ -1,36 +1,13 @@
|
|||||||
Version 0.10.4 (2026-02-17)
|
|
||||||
========================
|
|
||||||
* Kia K7 2017 support thanks to royjr!
|
|
||||||
* Lexus LS 2018 support thanks to Hacheoy!
|
|
||||||
* Reduce comma four standby power usage by 77% to 52 mW
|
|
||||||
|
|
||||||
Version 0.10.3 (2025-12-17)
|
|
||||||
========================
|
|
||||||
* New driving model #36249
|
|
||||||
* New temporal policy architecture
|
|
||||||
* New on-policy training physics noise model
|
|
||||||
* New driver monitoring model #36409
|
|
||||||
* Trained on a new dataset, including comma four data
|
|
||||||
* Improved inter-process communication memory efficiency
|
|
||||||
|
|
||||||
Version 0.10.2 (2025-11-19)
|
|
||||||
========================
|
|
||||||
* comma four support
|
|
||||||
|
|
||||||
Version 0.10.1 (2025-09-08)
|
Version 0.10.1 (2025-09-08)
|
||||||
========================
|
========================
|
||||||
* New driving model #36276
|
* New driving model
|
||||||
* World Model: removed global localization inputs
|
* World Model: removed global localization inputs
|
||||||
* World Model: 2x the number of parameters
|
* World Model: 2x the number of parameters
|
||||||
* World Model: trained on 4x the number of segments
|
* 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
|
* 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 City 2023 support thanks to vanillagorillaa and drFritz!
|
||||||
* Honda N-Box 2018 support thanks to miettal!
|
* Honda N-Box 2018 support thanks to miettal!
|
||||||
* Honda Odyssey 2021-25 support thanks to csouers and MVL!
|
* 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)
|
Version 0.10.0 (2025-08-05)
|
||||||
========================
|
========================
|
||||||
|
|||||||
381
SConstruct
381
SConstruct
@@ -3,104 +3,219 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import platform
|
import platform
|
||||||
import shlex
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import SCons.Errors
|
import SCons.Errors
|
||||||
|
|
||||||
SCons.Warnings.warningAsException(True)
|
SCons.Warnings.warningAsException(True)
|
||||||
|
|
||||||
|
# pending upstream fix - https://github.com/SCons/scons/issues/4461
|
||||||
|
#SetOption('warn', 'all')
|
||||||
|
|
||||||
|
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', max(1, int(os.cpu_count()/2)))
|
||||||
|
|
||||||
AddOption('--asan', action='store_true', help='turn on ASAN')
|
AddOption('--kaitai',
|
||||||
AddOption('--ubsan', action='store_true', help='turn on UBSan')
|
action='store_true',
|
||||||
AddOption('--mutation', action='store_true', help='generate mutation-ready code')
|
help='Regenerate kaitai struct parsers')
|
||||||
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('--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('--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('--mutation',
|
||||||
|
action='store_true',
|
||||||
|
help='generate mutation-ready code')
|
||||||
|
|
||||||
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
|
AddOption('--stock-ui',
|
||||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
action='store_true',
|
||||||
|
dest='stock_ui',
|
||||||
|
default=False,
|
||||||
|
help='Build stock openpilot UI instead of sunnypilot UI')
|
||||||
|
|
||||||
|
## Architecture name breakdown (arch)
|
||||||
|
## - 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)
|
|
||||||
]
|
|
||||||
|
|
||||||
if arch != "larch64":
|
lenv = {
|
||||||
import bzip2
|
"PATH": os.environ['PATH'],
|
||||||
import capnproto
|
"PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath,
|
||||||
import eigen
|
|
||||||
import ffmpeg as ffmpeg_pkg
|
"ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath,
|
||||||
import libjpeg
|
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath,
|
||||||
import libyuv
|
"TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer"
|
||||||
import ncurses
|
}
|
||||||
import python3_dev
|
|
||||||
import zeromq
|
rpath = []
|
||||||
import zstd
|
|
||||||
pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, zeromq, zstd]
|
if arch == "larch64":
|
||||||
py_include = python3_dev.INCLUDE_DIR
|
cpppath = [
|
||||||
|
"#third_party/opencl/include",
|
||||||
|
]
|
||||||
|
|
||||||
|
libpath = [
|
||||||
|
"/usr/local/lib",
|
||||||
|
"/system/vendor/lib64",
|
||||||
|
f"#third_party/acados/{arch}/lib",
|
||||||
|
]
|
||||||
|
|
||||||
|
libpath += [
|
||||||
|
"#third_party/snpe/larch64",
|
||||||
|
"#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:
|
else:
|
||||||
# TODO: remove when AGNOS has our new vendor pkgs
|
cflags = []
|
||||||
pkgs = []
|
cxxflags = []
|
||||||
py_include = sysconfig.get_paths()['include']
|
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",
|
||||||
|
]
|
||||||
|
# Linux
|
||||||
|
else:
|
||||||
|
libpath = [
|
||||||
|
f"#third_party/acados/{arch}/lib",
|
||||||
|
f"#third_party/libyuv/{arch}/lib",
|
||||||
|
"/usr/lib",
|
||||||
|
"/usr/local/lib",
|
||||||
|
]
|
||||||
|
|
||||||
|
if arch == "x86_64":
|
||||||
|
libpath += [
|
||||||
|
f"#third_party/snpe/{arch}"
|
||||||
|
]
|
||||||
|
rpath += [
|
||||||
|
Dir(f"#third_party/snpe/{arch}").abspath,
|
||||||
|
]
|
||||||
|
|
||||||
|
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"]
|
||||||
|
|
||||||
|
if not GetOption('stock_ui'):
|
||||||
|
cflags += ["-DSUNNYPILOT"]
|
||||||
|
cxxflags += ["-DSUNNYPILOT"]
|
||||||
|
|
||||||
|
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",
|
||||||
|
"#msgq",
|
||||||
],
|
],
|
||||||
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",
|
"#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="#",
|
||||||
@@ -108,102 +223,116 @@ 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", "-DQCOM2"]
|
|
||||||
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
|
env.CompilationDatabase('compile_commands.json')
|
||||||
if GetOption('asan'):
|
|
||||||
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:
|
default_cache_dir = '/data/scons_cache' if AGNOS else '/tmp/scons_cache'
|
||||||
env.Append(CCFLAGS=_extra_cc)
|
cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir)
|
||||||
|
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", "DBus", "Xml"]
|
||||||
|
|
||||||
# Setup cache dir
|
qt_libs = []
|
||||||
default_cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
|
if arch == "Darwin":
|
||||||
cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir)
|
qt_env['QTDIR'] = f"{brew_prefix}/opt/qt@5"
|
||||||
CacheDir(cache_dir)
|
qt_dirs = [
|
||||||
Clean(["."], cache_dir)
|
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()
|
||||||
|
|
||||||
# ********** start building stuff **********
|
qt_env['QTDIR'] = qt_install_prefix
|
||||||
|
qt_dirs = [
|
||||||
|
f"{qt_install_headers}",
|
||||||
|
]
|
||||||
|
|
||||||
|
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']
|
||||||
|
qt_env.Tool('qt3')
|
||||||
|
|
||||||
|
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', ]
|
||||||
|
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'])
|
||||||
Import('_common')
|
Import('_common', '_gpucommon')
|
||||||
|
|
||||||
common = [_common, 'json11', 'zmq']
|
common = [_common, 'json11', 'zmq']
|
||||||
Export('common')
|
gpucommon = [_gpucommon]
|
||||||
|
|
||||||
|
Export('common', 'gpucommon')
|
||||||
|
|
||||||
# Build messaging (cereal + msgq + socketmaster + their dependencies)
|
# Build messaging (cereal + msgq + socketmaster + their dependencies)
|
||||||
# Enable swaglog include in submodules
|
# Enable swaglog include in submodules
|
||||||
@@ -227,6 +356,7 @@ SConscript(['rednose/SConscript'])
|
|||||||
|
|
||||||
# Build system services
|
# Build system services
|
||||||
SConscript([
|
SConscript([
|
||||||
|
'system/ubloxd/SConscript',
|
||||||
'system/loggerd/SConscript',
|
'system/loggerd/SConscript',
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -240,8 +370,11 @@ SConscript(['selfdrive/SConscript'])
|
|||||||
|
|
||||||
SConscript(['sunnypilot/SConscript'])
|
SConscript(['sunnypilot/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])
|
||||||
|
|||||||
@@ -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'])
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
|||||||
navigation @1;
|
navigation @1;
|
||||||
vision @2;
|
vision @2;
|
||||||
policy @3;
|
policy @3;
|
||||||
offPolicy @4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -499,8 +498,7 @@ struct DeviceState @0xa4d8b5af2aa492eb {
|
|||||||
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;
|
||||||
@@ -593,7 +591,6 @@ struct PandaState @0xa7649e2575e4591e {
|
|||||||
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;
|
||||||
@@ -921,8 +918,6 @@ struct ControlsState @0x97ff69c53601abf1 {
|
|||||||
saturated @7 :Bool;
|
saturated @7 :Bool;
|
||||||
actualLateralAccel @9 :Float32;
|
actualLateralAccel @9 :Float32;
|
||||||
desiredLateralAccel @10 :Float32;
|
desiredLateralAccel @10 :Float32;
|
||||||
desiredLateralJerk @11 :Float32;
|
|
||||||
version @12 :Int32;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LateralLQRState {
|
struct LateralLQRState {
|
||||||
@@ -1480,11 +1475,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 {
|
||||||
@@ -2156,10 +2146,13 @@ struct Joystick {
|
|||||||
struct DriverStateV2 {
|
struct DriverStateV2 {
|
||||||
frameId @0 :UInt32;
|
frameId @0 :UInt32;
|
||||||
modelExecutionTime @1 :Float32;
|
modelExecutionTime @1 :Float32;
|
||||||
|
dspExecutionTimeDEPRECATED @2 :Float32;
|
||||||
gpuExecutionTime @8 :Float32;
|
gpuExecutionTime @8 :Float32;
|
||||||
rawPredictions @3 :Data;
|
rawPredictions @3 :Data;
|
||||||
|
|
||||||
|
poorVisionProb @4 :Float32;
|
||||||
wheelOnRightProb @5 :Float32;
|
wheelOnRightProb @5 :Float32;
|
||||||
|
|
||||||
leftDriverData @6 :DriverData;
|
leftDriverData @6 :DriverData;
|
||||||
rightDriverData @7 :DriverData;
|
rightDriverData @7 :DriverData;
|
||||||
|
|
||||||
@@ -2174,14 +2167,10 @@ struct DriverStateV2 {
|
|||||||
leftBlinkProb @7 :Float32;
|
leftBlinkProb @7 :Float32;
|
||||||
rightBlinkProb @8 :Float32;
|
rightBlinkProb @8 :Float32;
|
||||||
sunglassesProb @9 :Float32;
|
sunglassesProb @9 :Float32;
|
||||||
phoneProb @13 :Float32;
|
occludedProb @10 :Float32;
|
||||||
notReadyProbDEPRECATED @12 :List(Float32);
|
readyProb @11 :List(Float32);
|
||||||
occludedProbDEPRECATED @10 :Float32;
|
notReadyProb @12 :List(Float32);
|
||||||
readyProbDEPRECATED @11 :List(Float32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dspExecutionTimeDEPRECATED @2 :Float32;
|
|
||||||
poorVisionProbDEPRECATED @4 :Float32;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 {
|
struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 {
|
||||||
@@ -2233,10 +2222,7 @@ struct DriverMonitoringState @0xb83cda094a1da284 {
|
|||||||
hiStdCount @14 :UInt32;
|
hiStdCount @14 :UInt32;
|
||||||
isActiveMode @16 :Bool;
|
isActiveMode @16 :Bool;
|
||||||
isRHD @4 :Bool;
|
isRHD @4 :Bool;
|
||||||
uncertainCount @19 :UInt32;
|
|
||||||
|
|
||||||
phoneProbOffsetDEPRECATED @20 :Float32;
|
|
||||||
phoneProbValidCountDEPRECATED @21 :UInt32;
|
|
||||||
isPreviewDEPRECATED @15 :Bool;
|
isPreviewDEPRECATED @15 :Bool;
|
||||||
rhdCheckedDEPRECATED @5 :Bool;
|
rhdCheckedDEPRECATED @5 :Bool;
|
||||||
eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED);
|
eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED);
|
||||||
@@ -2532,10 +2518,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;
|
||||||
@@ -2698,8 +2687,5 @@ struct Event {
|
|||||||
liveLocationKalman @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()
|
||||||
|
|
||||||
|
|||||||
@@ -25,16 +25,15 @@ void msgq_to_zmq(const std::vector<std::string> &endpoints, const std::string &i
|
|||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ SubMaster::SubMaster(const std::vector<const char *> &service_list, const std::v
|
|||||||
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);
|
||||||
@@ -187,8 +187,7 @@ 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) {
|
||||||
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.),
|
||||||
@@ -72,7 +65,7 @@ _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),
|
||||||
@@ -84,14 +77,10 @@ _services: dict[str, tuple] = {
|
|||||||
"rawAudioData": (False, 20.),
|
"rawAudioData": (False, 20.),
|
||||||
"bookmarkButton": (True, 0., 1),
|
"bookmarkButton": (True, 0., 1),
|
||||||
"audioFeedback": (True, 0., 1),
|
"audioFeedback": (True, 0., 1),
|
||||||
"roadEncodeData": (False, 20., None, QueueSize.BIG),
|
|
||||||
"driverEncodeData": (False, 20., None, QueueSize.BIG),
|
|
||||||
"wideRoadEncodeData": (False, 20., None, QueueSize.BIG),
|
|
||||||
"qRoadEncodeData": (False, 20., None, QueueSize.BIG),
|
|
||||||
|
|
||||||
# sunnypilot
|
# sunnypilot
|
||||||
"modelManagerSP": (False, 1., 1, QueueSize.BIG),
|
"modelManagerSP": (False, 1., 1),
|
||||||
"backupManagerSP": (False, 1., 1, QueueSize.BIG),
|
"backupManagerSP": (False, 1., 1),
|
||||||
"selfdriveStateSP": (True, 100., 10),
|
"selfdriveStateSP": (True, 100., 10),
|
||||||
"longitudinalPlanSP": (True, 20., 10),
|
"longitudinalPlanSP": (True, 20., 10),
|
||||||
"onroadEventsSP": (True, 1., 1),
|
"onroadEventsSP": (True, 1., 1),
|
||||||
@@ -99,19 +88,23 @@ _services: dict[str, tuple] = {
|
|||||||
"carControlSP": (True, 100., 10),
|
"carControlSP": (True, 100., 10),
|
||||||
"carStateSP": (True, 100., 10),
|
"carStateSP": (True, 100., 10),
|
||||||
"liveMapDataSP": (True, 1., 1),
|
"liveMapDataSP": (True, 1., 1),
|
||||||
"modelDataV2SP": (True, 20., None, QueueSize.BIG),
|
"modelDataV2SP": (True, 20.),
|
||||||
"liveLocationKalman": (True, 20.),
|
"liveLocationKalman": (True, 20.),
|
||||||
|
|
||||||
# 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.),
|
||||||
@@ -129,13 +122,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; float 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, %f, %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"
|
||||||
|
|||||||
@@ -4,11 +4,18 @@ common_libs = [
|
|||||||
'params.cc',
|
'params.cc',
|
||||||
'swaglog.cc',
|
'swaglog.cc',
|
||||||
'util.cc',
|
'util.cc',
|
||||||
'ratekeeper.cc',
|
'watchdog.cc',
|
||||||
|
'ratekeeper.cc'
|
||||||
]
|
]
|
||||||
|
|
||||||
_common = env.Library('common', common_libs, LIBS="json11")
|
_common = env.Library('common', common_libs, LIBS="json11")
|
||||||
Export('_common')
|
|
||||||
|
files = [
|
||||||
|
'clutil.cc',
|
||||||
|
]
|
||||||
|
|
||||||
|
_gpucommon = env.Library('gpucommon', files)
|
||||||
|
Export('_common', '_gpucommon')
|
||||||
|
|
||||||
if GetOption('extras'):
|
if GetOption('extras'):
|
||||||
env.Program('tests/test_common',
|
env.Program('tests/test_common',
|
||||||
@@ -18,6 +25,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')
|
||||||
|
|||||||
@@ -14,13 +14,9 @@ class Api:
|
|||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
return self.service.post(*args, **kwargs)
|
return self.service.post(*args, **kwargs)
|
||||||
|
|
||||||
def get_token(self, payload_extra=None, expiry_hours=1):
|
def get_token(self, expiry_hours=1):
|
||||||
return self.service.get_token(payload_extra, expiry_hours)
|
return self.service.get_token(expiry_hours)
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
return CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
|
return CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, **params)
|
||||||
|
|
||||||
|
|
||||||
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
|
|
||||||
return CommaConnectApi(None).get_key_pair()
|
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
import jwt
|
import jwt
|
||||||
import os
|
|
||||||
import requests
|
import requests
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from datetime import datetime, timedelta, UTC
|
from datetime import datetime, timedelta, UTC
|
||||||
from openpilot.system.hardware.hw import Paths
|
from openpilot.system.hardware.hw import Paths
|
||||||
from openpilot.system.version import get_version
|
from openpilot.system.version import get_version
|
||||||
|
|
||||||
# name: jwt signature algorithm
|
|
||||||
KEYS = {"id_rsa": "RS256",
|
|
||||||
"id_ecdsa": "ES256"}
|
|
||||||
|
|
||||||
|
|
||||||
class BaseApi:
|
class BaseApi:
|
||||||
def __init__(self, dongle_id, api_host, user_agent="openpilot-"):
|
def __init__(self, dongle_id, api_host, user_agent="openpilot-"):
|
||||||
self.dongle_id = dongle_id
|
self.dongle_id = dongle_id
|
||||||
self.api_host = api_host
|
self.api_host = api_host
|
||||||
self.user_agent = user_agent
|
self.user_agent = user_agent
|
||||||
self.jwt_algorithm, self.private_key, _ = self.get_key_pair()
|
with open(f'{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)
|
||||||
@@ -27,7 +23,7 @@ class BaseApi:
|
|||||||
def request(self, method, endpoint, timeout=None, access_token=None, **params):
|
def request(self, method, endpoint, timeout=None, access_token=None, **params):
|
||||||
return self.api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params)
|
return self.api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params)
|
||||||
|
|
||||||
def _get_token(self, payload_extra=None, expiry_hours=1, **extra_payload):
|
def _get_token(self, expiry_hours=1, **extra_payload):
|
||||||
now = datetime.now(UTC).replace(tzinfo=None)
|
now = datetime.now(UTC).replace(tzinfo=None)
|
||||||
payload = {
|
payload = {
|
||||||
'identity': self.dongle_id,
|
'identity': self.dongle_id,
|
||||||
@@ -36,22 +32,20 @@ class BaseApi:
|
|||||||
'exp': now + timedelta(hours=expiry_hours),
|
'exp': now + timedelta(hours=expiry_hours),
|
||||||
**extra_payload
|
**extra_payload
|
||||||
}
|
}
|
||||||
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 get_token(self, payload_extra=None, expiry_hours=1):
|
def get_token(self, expiry_hours=1):
|
||||||
return self._get_token(payload_extra, expiry_hours)
|
return self._get_token(expiry_hours)
|
||||||
|
|
||||||
def remove_non_ascii_chars(self, text):
|
def remove_non_ascii_chars(self, text):
|
||||||
normalized_text = unicodedata.normalize('NFD', text)
|
normalized_text = unicodedata.normalize('NFD', text)
|
||||||
ascii_encoded_text = normalized_text.encode('ascii', 'ignore')
|
ascii_encoded_text = normalized_text.encode('ascii', 'ignore')
|
||||||
return ascii_encoded_text.decode()
|
return ascii_encoded_text.decode()
|
||||||
|
|
||||||
def api_get(self, endpoint, method='GET', timeout=None, access_token=None, session=None, json=None, **params):
|
def api_get(self, endpoint, method='GET', timeout=None, access_token=None, json=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
|
||||||
@@ -59,14 +53,4 @@ class BaseApi:
|
|||||||
version = self.remove_non_ascii_chars(get_version())
|
version = self.remove_non_ascii_chars(get_version())
|
||||||
headers['User-Agent'] = self.user_agent + version
|
headers['User-Agent'] = self.user_agent + version
|
||||||
|
|
||||||
# TODO: add session to Api
|
return requests.request(method, f"{self.api_host}/{endpoint}", timeout=timeout, headers=headers, json=json, params=params)
|
||||||
req = requests if session is None else session
|
|
||||||
return req.request(method, f"{self.api_host}/{endpoint}", timeout=timeout, headers=headers, json=json, params=params)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
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
|
|
||||||
|
|||||||
98
common/clutil.cc
Normal file
98
common/clutil.cc
Normal file
@@ -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;
|
||||||
|
}
|
||||||
28
common/clutil.h
Normal file
28
common/clutil.h
Normal file
@@ -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);
|
||||||
9
common/dict_helpers.py
Normal file
9
common/dict_helpers.py
Normal file
@@ -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
|
||||||
@@ -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)
|
|
||||||
58
common/file_helpers.py
Normal file
58
common/file_helpers.py
Normal file
@@ -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
|
||||||
@@ -15,20 +15,3 @@ class FirstOrderFilter:
|
|||||||
self.initialized = True
|
self.initialized = True
|
||||||
self.x = x
|
self.x = x
|
||||||
return self.x
|
return self.x
|
||||||
|
|
||||||
|
|
||||||
class BounceFilter(FirstOrderFilter):
|
|
||||||
def __init__(self, x0, rc, dt, initialized=True, bounce=2):
|
|
||||||
self.velocity = FirstOrderFilter(0.0, 0.15, dt)
|
|
||||||
self.bounce = bounce
|
|
||||||
super().__init__(x0, rc, dt, initialized)
|
|
||||||
|
|
||||||
def update(self, x):
|
|
||||||
super().update(x)
|
|
||||||
scale = self.dt / (1.0 / 60.0) # tuned at 60 fps
|
|
||||||
self.velocity.x += (x - self.x) * self.bounce * scale * self.dt
|
|
||||||
self.velocity.update(0.0)
|
|
||||||
if abs(self.velocity.x) < 1e-5:
|
|
||||||
self.velocity.x = 0.0
|
|
||||||
self.x += self.velocity.x
|
|
||||||
return self.x
|
|
||||||
|
|||||||
@@ -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]]
|
|
||||||
85
common/mat.h
Normal file
85
common/mat.h
Normal file
@@ -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));
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
#define DEFAULT_MODEL "CD210 (Default)"
|
#define DEFAULT_MODEL "TCPv3 + gWMv9 (Default)"
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -66,12 +66,11 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"JoystickDebugMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
{"JoystickDebugMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
{"LanguageSetting", {PERSISTENT | BACKUP, STRING, "en"}},
|
{"LanguageSetting", {PERSISTENT | BACKUP, STRING, "main_en"}},
|
||||||
{"LastAthenaPingTime", {CLEAR_ON_MANAGER_START, INT}},
|
{"LastAthenaPingTime", {CLEAR_ON_MANAGER_START, INT}},
|
||||||
{"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"}},
|
{"LastUpdateRouteCount", {PERSISTENT, INT, "0"}},
|
||||||
@@ -98,7 +97,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"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}},
|
|
||||||
{"OnroadCycleRequested", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"OnroadCycleRequested", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"OpenpilotEnabledToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"OpenpilotEnabledToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
{"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
{"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
@@ -110,12 +108,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"RecordFront", {PERSISTENT | BACKUP, BOOL}},
|
{"RecordFront", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet
|
{"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet
|
||||||
{"SecOCKey", {PERSISTENT | DONT_LOG | BACKUP, STRING}},
|
{"SecOCKey", {PERSISTENT | DONT_LOG | BACKUP, 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 | BACKUP, BOOL}},
|
{"SshEnabled", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"TermsVersion", {PERSISTENT, STRING}},
|
{"TermsVersion", {PERSISTENT, STRING}},
|
||||||
{"TorqueBar", {PERSISTENT | BACKUP, BOOL, "0"}},
|
|
||||||
{"TrainingVersion", {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}},
|
||||||
@@ -137,17 +133,14 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"ApiCache_DriveStats", {PERSISTENT, JSON}},
|
{"ApiCache_DriveStats", {PERSISTENT, JSON}},
|
||||||
{"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}},
|
{"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
{"BlinkerLateralReengageDelay", {PERSISTENT | BACKUP, INT, "0"}}, // seconds
|
|
||||||
{"BlinkerMinLateralControlSpeed", {PERSISTENT | BACKUP, INT, "20"}}, // MPH or km/h
|
{"BlinkerMinLateralControlSpeed", {PERSISTENT | BACKUP, INT, "20"}}, // MPH or km/h
|
||||||
{"BlinkerPauseLateralControl", {PERSISTENT | BACKUP, INT, "0"}},
|
{"BlinkerPauseLateralControl", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
{"Brightness", {PERSISTENT | BACKUP, INT, "0"}},
|
{"Brightness", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
{"CarList", {PERSISTENT, JSON}},
|
|
||||||
{"CarParamsSP", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES}},
|
{"CarParamsSP", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES}},
|
||||||
{"CarParamsSPCache", {CLEAR_ON_MANAGER_START, BYTES}},
|
{"CarParamsSPCache", {CLEAR_ON_MANAGER_START, BYTES}},
|
||||||
{"CarParamsSPPersistent", {PERSISTENT, BYTES}},
|
{"CarParamsSPPersistent", {PERSISTENT, BYTES}},
|
||||||
{"CarPlatformBundle", {PERSISTENT | BACKUP, JSON}},
|
{"CarPlatformBundle", {PERSISTENT | BACKUP, JSON}},
|
||||||
{"ChevronInfo", {PERSISTENT | BACKUP, INT, "4"}},
|
{"ChevronInfo", {PERSISTENT | BACKUP, INT, "4"}},
|
||||||
{"CompletedSunnylinkConsentVersion", {PERSISTENT, STRING, "0"}},
|
|
||||||
{"CustomAccIncrementsEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"CustomAccIncrementsEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
|
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
|
||||||
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
|
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
|
||||||
@@ -157,7 +150,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
|
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"GreenLightAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"GreenLightAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
|
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
|
||||||
{"HasAcceptedTermsSP", {PERSISTENT, STRING, "0"}},
|
|
||||||
{"HideVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"HideVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}},
|
{"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}},
|
||||||
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
|
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
@@ -170,14 +162,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}},
|
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
{"OnroadScreenOffBrightnessMigrated", {PERSISTENT | BACKUP, STRING, "0.0"}},
|
{"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}},
|
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}},
|
||||||
{"OnroadScreenOffTimerMigrated", {PERSISTENT | BACKUP, STRING, "0.0"}},
|
|
||||||
{"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"RocketFuel", {PERSISTENT | BACKUP, BOOL, "0"}},
|
|
||||||
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
@@ -192,7 +182,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
// Model Manager params
|
// Model Manager params
|
||||||
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
|
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
|
||||||
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT}},
|
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT, "0"}},
|
||||||
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
||||||
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||||
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||||
@@ -219,20 +209,16 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"ToyotaEnforceStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}},
|
|
||||||
{"ToyotaStopAndGoHack", {PERSISTENT | BACKUP, BOOL, "0"}},
|
|
||||||
|
|
||||||
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|
||||||
// sunnypilot model params
|
// sunnypilot model params
|
||||||
{"CameraOffset", {PERSISTENT | BACKUP, FLOAT, "0.0"}},
|
|
||||||
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
|
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
|
||||||
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
|
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
|
||||||
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
|
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
|
||||||
{"PlanplusControl", {PERSISTENT | BACKUP, FLOAT, "1.0"}},
|
|
||||||
|
|
||||||
// mapd
|
// mapd
|
||||||
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
|
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
|
||||||
@@ -253,7 +239,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"OsmStateTitle", {PERSISTENT, STRING}},
|
{"OsmStateTitle", {PERSISTENT, STRING}},
|
||||||
{"OsmWayTest", {PERSISTENT, STRING}},
|
{"OsmWayTest", {PERSISTENT, STRING}},
|
||||||
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||||
{"RoadNameToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"RoadNameToggle", {PERSISTENT, STRING}},
|
||||||
|
|
||||||
// Speed Limit
|
// Speed Limit
|
||||||
{"SpeedLimitMode", {PERSISTENT | BACKUP, INT, "1"}},
|
{"SpeedLimitMode", {PERSISTENT | BACKUP, INT, "1"}},
|
||||||
@@ -271,7 +257,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}},
|
{"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}},
|
{"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}},
|
||||||
{"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}},
|
{"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}},
|
||||||
{"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT}},
|
|
||||||
{"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}},
|
{"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}},
|
||||||
{"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}},
|
{"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}},
|
||||||
|
|||||||
@@ -2,14 +2,21 @@ 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.set_limits(pos_limit, neg_limit)
|
||||||
|
|
||||||
self.i_dt = 1.0 / rate
|
self.i_rate = 1.0 / rate
|
||||||
self.speed = 0.0
|
self.speed = 0.0
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
@@ -39,12 +46,12 @@ class PIDController:
|
|||||||
|
|
||||||
def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False):
|
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.p = float(error) * self.k_p
|
||||||
self.d = self.k_d * error_rate
|
self.f = feedforward * self.k_f
|
||||||
self.f = feedforward
|
self.d = error_rate * self.k_d
|
||||||
|
|
||||||
if not freeze_integrator:
|
if not freeze_integrator:
|
||||||
i = self.i + self.k_i * self.i_dt * error
|
i = self.i + error * self.k_i * self.i_rate
|
||||||
|
|
||||||
# Don't allow windup if already clipping
|
# Don't allow windup if already clipping
|
||||||
test_control = self.p + i + self.d + self.f
|
test_control = self.p + i + self.d + self.f
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import shutil
|
import shutil
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@@ -10,10 +9,9 @@ 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, create_dirs_on_enter: bool = True, 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.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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
30
common/retry.py
Normal file
30
common/retry.py
Normal file
@@ -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()
|
||||||
28
common/run.py
Normal file
28
common/run.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import subprocess
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from subprocess import Popen, PIPE, TimeoutExpired
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def managed_proc(cmd: list[str], env: dict[str, str]):
|
||||||
|
proc = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE)
|
||||||
|
try:
|
||||||
|
yield proc
|
||||||
|
finally:
|
||||||
|
if proc.poll() is None:
|
||||||
|
proc.terminate()
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=5)
|
||||||
|
except TimeoutExpired:
|
||||||
|
proc.kill()
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
5
common/transformations/SConscript
Normal file
5
common/transformations/SConscript
Normal file
@@ -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')
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#define _USE_MATH_DEFINES
|
#define _USE_MATH_DEFINES
|
||||||
|
|
||||||
#include "sunnypilot/common/transformations/coordinates.hpp"
|
#include "common/transformations/coordinates.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <eigen3/Eigen/Dense>
|
#include <eigen3/Eigen/Dense>
|
||||||
|
|
||||||
#include "sunnypilot/common/transformations/orientation.hpp"
|
#include "common/transformations/orientation.hpp"
|
||||||
#include "sunnypilot/common/transformations/coordinates.hpp"
|
#include "common/transformations/coordinates.hpp"
|
||||||
|
|
||||||
Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat) {
|
Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat) {
|
||||||
if (quat.w() > 0){
|
if (quat.w() > 0){
|
||||||
@@ -141,3 +141,4 @@ Eigen::Vector3d ned_euler_from_ecef(const ECEF &ecef_init, const Eigen::Vector3d
|
|||||||
|
|
||||||
return {phi, theta, psi};
|
return {phi, theta, psi};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <eigen3/Eigen/Dense>
|
#include <eigen3/Eigen/Dense>
|
||||||
#include "sunnypilot/common/transformations/coordinates.hpp"
|
#include "common/transformations/coordinates.hpp"
|
||||||
|
|
||||||
|
|
||||||
Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat);
|
Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat);
|
||||||
@@ -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)
|
|
||||||
|
|||||||
72
common/transformations/transformations.pxd
Normal file
72
common/transformations/transformations.pxd
Normal file
@@ -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])
|
|
||||||
173
common/transformations/transformations.pyx
Normal file
173
common/transformations/transformations.pyx
Normal file
@@ -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
|
||||||
@@ -181,9 +181,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 +288,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 +303,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;
|
||||||
|
|||||||
@@ -97,13 +97,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) {
|
||||||
|
|||||||
46
common/util.py
Normal file
46
common/util.py
Normal file
@@ -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
|
||||||
272
common/utils.py
272
common/utils.py
@@ -1,272 +0,0 @@
|
|||||||
import io
|
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
import contextlib
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
import functools
|
|
||||||
from subprocess import Popen, PIPE, TimeoutExpired
|
|
||||||
import zstandard as zstd
|
|
||||||
|
|
||||||
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:
|
|
||||||
"""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(path: str, mode: str = 'w', buffering: int = -1, encoding: str | None = None, newline: str | None = 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
|
|
||||||
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def managed_proc(cmd: list[str], env: dict[str, str]):
|
|
||||||
proc = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE)
|
|
||||||
try:
|
|
||||||
yield proc
|
|
||||||
finally:
|
|
||||||
if proc.poll() is None:
|
|
||||||
proc.terminate()
|
|
||||||
try:
|
|
||||||
proc.wait(timeout=5)
|
|
||||||
except TimeoutExpired:
|
|
||||||
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 decorator(func):
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
for _ in range(attempts):
|
|
||||||
try:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
except Exception:
|
|
||||||
print(f"{func.__name__} failed, trying again")
|
|
||||||
time.sleep(delay)
|
|
||||||
|
|
||||||
if ignore_failure:
|
|
||||||
print(f"{func.__name__} failed after retry")
|
|
||||||
else:
|
|
||||||
raise Exception(f"{func.__name__} failed after retry")
|
|
||||||
return wrapper
|
|
||||||
return decorator
|
|
||||||
@@ -1 +1 @@
|
|||||||
#define COMMA_VERSION "0.10.4"
|
#define COMMA_VERSION "0.10.1"
|
||||||
|
|||||||
12
common/watchdog.cc
Normal file
12
common/watchdog.cc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/watchdog.h"
|
||||||
|
#include "common/util.h"
|
||||||
|
#include "system/hardware/hw.h"
|
||||||
|
|
||||||
|
const std::string watchdog_fn_prefix = Path::shm_path() + "/wd_"; // + <pid>
|
||||||
|
|
||||||
|
bool watchdog_kick(uint64_t ts) {
|
||||||
|
static std::string fn = watchdog_fn_prefix + std::to_string(getpid());
|
||||||
|
return util::write_file(fn.c_str(), &ts, sizeof(ts), O_WRONLY | O_CREAT) > 0;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user