mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-08 04:15:51 +08:00
Compare commits
207 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e0d89968d | |||
| fec5a97a9e | |||
| 099143ad9d | |||
| 6909aa95ff | |||
| 5bdc0c23a9 | |||
| 1a07e47228 | |||
| 50b860c928 | |||
| 978ec800fe | |||
| 3a05c03079 | |||
| fd22de1c9a | |||
| ee3583df33 | |||
| 7801bdf0cc | |||
| 0265ae5f76 | |||
| 9163d1cb74 | |||
| a0cc313fdc | |||
| 93bb596a2e | |||
| 41b14fa595 | |||
| fdd1df79fb | |||
| 031b1ad0a3 | |||
| b1f2e638de | |||
| c20263d985 | |||
| 5472e69e35 | |||
| 2c334ede44 | |||
| 78909dac73 | |||
| 24a9b6dae5 | |||
| bf1b93f757 | |||
| bae7e49626 | |||
| 560cde61ae | |||
| d9596fa998 | |||
| e124d6df9b | |||
| 3dbf02f803 | |||
| fef29ad225 | |||
| 157c7080ce | |||
| caa9e770cc | |||
| 24893ebadb | |||
| 39e12c8bb1 | |||
| f0d93eb32d | |||
| 19ecc37de8 | |||
| ecac2d386b | |||
| e475d10adc | |||
| 7d74c3c99b | |||
| cefcf10ec3 | |||
| 1c07e00759 | |||
| f0841b827a | |||
| 3f49e2d33c | |||
| 3f93b00120 | |||
| c21b0821da | |||
| b9f25f8a43 | |||
| c8786d930d | |||
| 3a55f31dc5 | |||
| a04c045cd7 | |||
| 9aa5c3d2c6 | |||
| 5d23a78c77 | |||
| 06a73f538e | |||
| 60716edc37 | |||
| 14318d2f09 | |||
| 61608db786 | |||
| 70f2c3ac79 | |||
| a43fc920b5 | |||
| 91b067cca0 | |||
| 41886a90da | |||
| 680dd7cf3b | |||
| db99c8e5a6 | |||
| 48a5bdc8e4 | |||
| 79b79edd2f | |||
| 2b1fd89064 | |||
| ec72ee096d | |||
| eee13472ce | |||
| d275c65f75 | |||
| a9ffebe96e | |||
| 45661ee8c0 | |||
| c16039e0bc | |||
| d1e143ac98 | |||
| 45b53cf66a | |||
| cf8371c2c5 | |||
| f42dbbb01c | |||
| 4fd4ddb438 | |||
| 503531ab38 | |||
| a52da0d3df | |||
| a26254304f | |||
| c91731b13a | |||
| 60924556f6 | |||
| ee11c24cf4 | |||
| 6392096de5 | |||
| 39117a5879 | |||
| 1478d0c476 | |||
| f283f6703c | |||
| b827c0f55e | |||
| bf5540c364 | |||
| 3b3f5967ec | |||
| 1e49eac4d1 | |||
| 9877f6ac0e | |||
| 1ab1ed7453 | |||
| cb82722dd5 | |||
| 960c988355 | |||
| 05cf8023a9 | |||
| 1b6e127e68 | |||
| c02cf706a6 | |||
| 0d93f0e820 | |||
| 6de5d2c23a | |||
| 92f3d24c8a | |||
| d606014ce2 | |||
| 2aa934e7c2 | |||
| 70a6efb8fc | |||
| 07ec389f4c | |||
| 9d03e737dd | |||
| 4532320fb3 | |||
| 4c5bef5840 | |||
| 8e0ba7a91e | |||
| fa0c6876d3 | |||
| a2c554805b | |||
| e1e9efb965 | |||
| 8196b743af | |||
| 724971afc3 | |||
| 4fc5e308f3 | |||
| 8a3bbcdd4e | |||
| 8c927d0fc2 | |||
| 97215b17d7 | |||
| 31dc4d8e52 | |||
| 61b6d52b01 | |||
| da6313dbe9 | |||
| e9e5548edc | |||
| f9c555a3fe | |||
| 069506aa36 | |||
| 5629b5a83d | |||
| addca46f68 | |||
| 7d325d6650 | |||
| 288aa60bf4 | |||
| cddb6103eb | |||
| 1474d5474d | |||
| 2e9fa514bb | |||
| 8a9ca15464 | |||
| 5edc0bd89d | |||
| 20e0f21b58 | |||
| 37eda06c95 | |||
| df1663c58d | |||
| bc168bb33a | |||
| d79267fa22 | |||
| cea5273f1f | |||
| d8bd6c784e | |||
| ad5151b38b | |||
| fddb9fb31c | |||
| b871364e1f | |||
| f0831e549b | |||
| 7cee534b72 | |||
| e0e9f1a4d3 | |||
| 160942dfde | |||
| 52e25ebe4e | |||
| 3b4077d31b | |||
| f0f7b877c3 | |||
| cb6e422c91 | |||
| f48e99b335 | |||
| b2a7084906 | |||
| 62b97fabf7 | |||
| f257544f1b | |||
| 45d8bcd7f3 | |||
| 0038d84e13 | |||
| cd2c590d50 | |||
| ddc8d37136 | |||
| d4db600d4b | |||
| 5ab7e48479 | |||
| e2e3703ae4 | |||
| 3939f1e92c | |||
| 82a945d145 | |||
| 93224b3c05 | |||
| 8a80bd70e7 | |||
| 29e7f362ed | |||
| 3c4790c089 | |||
| c0456590f6 | |||
| f3b1f97afa | |||
| 4cefe7239d | |||
| 586dd4c611 | |||
| 291315ab25 | |||
| 37390743c1 | |||
| 15fb1a809d | |||
| 0240a62fd9 | |||
| fafcee04f4 | |||
| 2feca929ab | |||
| a40fa3a0b0 | |||
| f9cc67896c | |||
| cd052f124e | |||
| 14cff000e9 | |||
| 9ef3cfdf9a | |||
| 4024d13dda | |||
| b2bb71b4b7 | |||
| 98e5f547ea | |||
| 5a7b710d90 | |||
| 7661e03d1e | |||
| bdac9efa1e | |||
| 433c52f623 | |||
| 24d373062d | |||
| 41390a95b8 | |||
| 57b5eb3113 | |||
| 7abe0205fd | |||
| ef3ec30fe2 | |||
| b0600a4225 | |||
| cc110d1eb1 | |||
| bfd8d4868d | |||
| 14a00e0cc8 | |||
| 268126f376 | |||
| 900a896c63 | |||
| c01b8be968 | |||
| 094591793a | |||
| f02d134f40 | |||
| 5b3d5f74ed | |||
| 866cd01f32 | |||
| d09a411cb4 |
@@ -1,18 +0,0 @@
|
|||||||
**/.git
|
|
||||||
.DS_Store
|
|
||||||
*.dylib
|
|
||||||
*.DSYM
|
|
||||||
*.d
|
|
||||||
*.pyc
|
|
||||||
*.pyo
|
|
||||||
.*.swp
|
|
||||||
.*.swo
|
|
||||||
.*.un~
|
|
||||||
*.tmp
|
|
||||||
*.o
|
|
||||||
*.o-*
|
|
||||||
*.os
|
|
||||||
*.os-*
|
|
||||||
|
|
||||||
venv/
|
|
||||||
.venv/
|
|
||||||
+2
-2
@@ -10,5 +10,5 @@
|
|||||||
*.otf 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
|
openpilot/selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
|
||||||
system/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text
|
openpilot/common/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
@@ -8,24 +8,24 @@ chore:
|
|||||||
|
|
||||||
car:
|
car:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: '{selfdrive/car/**,opendbc_repo}'
|
- any-glob-to-all-files: '{openpilot/selfdrive/car/**,opendbc_repo}'
|
||||||
|
|
||||||
simulation:
|
simulation:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: 'tools/sim/**'
|
- any-glob-to-all-files: 'openpilot/tools/sim/**'
|
||||||
|
|
||||||
ui:
|
ui:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: '{selfdrive/assets/**,selfdrive/ui/**,system/ui/**}'
|
- any-glob-to-all-files: '{openpilot/selfdrive/assets/**,openpilot/selfdrive/ui/**,openpilot/system/ui/**}'
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: 'tools/**'
|
- any-glob-to-all-files: 'openpilot/tools/**'
|
||||||
|
|
||||||
multilanguage:
|
multilanguage:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: 'selfdrive/ui/translations/**'
|
- any-glob-to-all-files: 'openpilot/selfdrive/ui/translations/**'
|
||||||
|
|
||||||
autonomy:
|
autonomy:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: "{selfdrive/modeld/models/**,selfdrive/test/process_replay/model_replay_ref_commit,sunnypilot/modeld*/models/**}"
|
- any-glob-to-all-files: "{openpilot/selfdrive/modeld/models/**,openpilot/selfdrive/test/process_replay/model_replay_ref_commit,openpilot/sunnypilot/modeld*/models/**}"
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ Explain how you tested this bug fix.
|
|||||||
|
|
||||||
**Checklist**
|
**Checklist**
|
||||||
|
|
||||||
- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs
|
- [ ] added entry to CAR in openpilot/selfdrive/car/*/values.py and ran `openpilot/selfdrive/car/docs.py` to generate new docs
|
||||||
- [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/selfdrive/car/tests/routes.py)
|
- [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/openpilot/selfdrive/car/tests/routes.py)
|
||||||
- [ ] route with openpilot:
|
- [ ] route with openpilot:
|
||||||
- [ ] route with stock system:
|
- [ ] route with stock system:
|
||||||
- [ ] car harness used (if comma doesn't sell it, put N/A):
|
- [ ] car harness used (if comma doesn't sell it, put N/A):
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: false
|
submodules: false
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
|
|
||||||
# Check PR target branch
|
# Check PR target branch
|
||||||
- name: check branch
|
- name: check branch
|
||||||
uses: Vankka/pr-target-branch-action@def32ec9d93514138d6ac0132ee62e120a72aed5
|
uses: Vankka/pr-target-branch-action@5da68a42bcb7b43d39104295a876a6f3f8d7908b
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
name: badges
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 * * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
PYTHONPATH: ${{ github.workspace }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
badges:
|
|
||||||
name: create badges
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
|
||||||
- name: Push badges
|
|
||||||
run: |
|
|
||||||
python3 selfdrive/ui/translations/create_badges.py
|
|
||||||
|
|
||||||
rm .gitattributes
|
|
||||||
|
|
||||||
git checkout --orphan badges
|
|
||||||
git rm -rf --cached .
|
|
||||||
git config user.email "badge-researcher@sunnypilot.ai"
|
|
||||||
git config user.name "Badge Researcher"
|
|
||||||
|
|
||||||
git add translation_badge.svg
|
|
||||||
git commit -m "Add/Update badges"
|
|
||||||
git push -f origin HEAD
|
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd sunnypilot
|
cd sunnypilot
|
||||||
export PYTHONPATH=$(pwd)
|
export PYTHONPATH=$(pwd)
|
||||||
ref=$(python3 sunnypilot/models/tinygrad_ref.py)
|
ref=$(python3 openpilot/sunnypilot/models/tinygrad_ref.py)
|
||||||
echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT
|
echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT
|
||||||
echo "tinygrad_ref is $ref"
|
echo "tinygrad_ref is $ref"
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
recompiled_dir:
|
recompiled_dir:
|
||||||
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
|
description: 'Existing recompiled directory number (e.g. 1 for recompiled1)'
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
json_version:
|
json_version:
|
||||||
description: 'driving_models version number to update (e.g. 5 for driving_models_v5.json)'
|
description: 'driving_models version number to update (e.g. 18 for driving_models_v18.json)'
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
artifact_suffix:
|
artifact_suffix:
|
||||||
@@ -63,12 +63,11 @@ on:
|
|||||||
default: 'None'
|
default: 'None'
|
||||||
options:
|
options:
|
||||||
- None
|
- None
|
||||||
- Simple Plan Models
|
- Master Models
|
||||||
- Space Lab Models
|
- Release Models
|
||||||
- TR Models
|
- 2025 World Models
|
||||||
- DTR Models
|
- 2026 World Models
|
||||||
- Custom Merge Models
|
- Custom Merge Models
|
||||||
- FOF series models
|
|
||||||
- Other
|
- Other
|
||||||
custom_model_folder:
|
custom_model_folder:
|
||||||
description: 'Custom model folder name (if "Other" selected)'
|
description: 'Custom model folder name (if "Other" selected)'
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'cereal/**'
|
- 'openpilot/cereal/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
@@ -30,36 +30,68 @@ jobs:
|
|||||||
- name: Checkout sunnypilot cereal
|
- name: Checkout sunnypilot cereal
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
sparse-checkout: cereal
|
sparse-checkout: |
|
||||||
|
openpilot/cereal
|
||||||
|
uv.lock
|
||||||
|
submodules: false
|
||||||
|
|
||||||
- name: Init sunnypilot opendbc submodule
|
- name: Init sunnypilot opendbc submodule
|
||||||
run: git submodule update --init --depth 1 opendbc_repo
|
run: git submodule update --init --depth 1 opendbc_repo
|
||||||
|
|
||||||
- name: Checkout upstream openpilot cereal
|
- name: Locate sunnypilot capnp import paths
|
||||||
|
id: locate-sp-capnp
|
||||||
|
run: |
|
||||||
|
SP_IMPORT_ARGS=""
|
||||||
|
SP_CAR_CAPNP=$(find opendbc_repo -maxdepth 4 -name car.capnp -path '*/opendbc/car/car.capnp' -printf '%h\n' -quit)
|
||||||
|
if [ -n "$SP_CAR_CAPNP" ]; then
|
||||||
|
SP_IMPORT_ARGS="-I $SP_CAR_CAPNP"
|
||||||
|
echo "Found sunnypilot car.capnp at: $SP_CAR_CAPNP"
|
||||||
|
fi
|
||||||
|
echo "import_args=$SP_IMPORT_ARGS" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Checkout upstream openpilot
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
repository: 'commaai/openpilot'
|
repository: 'commaai/openpilot'
|
||||||
path: upstream_openpilot
|
path: upstream_openpilot
|
||||||
sparse-checkout: cereal
|
|
||||||
ref: "refs/heads/master"
|
ref: "refs/heads/master"
|
||||||
|
|
||||||
- name: Init upstream opendbc submodule
|
- name: Init upstream opendbc submodule
|
||||||
working-directory: upstream_openpilot
|
working-directory: upstream_openpilot
|
||||||
run: git submodule update --init --depth 1 opendbc_repo
|
run: git submodule update --init --depth 1 opendbc_repo
|
||||||
|
|
||||||
- name: Install uv
|
- name: Locate upstream capnp paths
|
||||||
run: pip install uv
|
id: locate-capnp
|
||||||
|
run: |
|
||||||
|
CEREAL_DIR=$(find upstream_openpilot -maxdepth 4 -name log.capnp -path '*/cereal/log.capnp' -printf '%h\n' -quit)
|
||||||
|
if [ -z "$CEREAL_DIR" ]; then
|
||||||
|
echo "::error::Could not locate cereal/log.capnp in upstream openpilot"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "cereal_dir=$CEREAL_DIR" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Found upstream cereal at: $CEREAL_DIR"
|
||||||
|
|
||||||
|
IMPORT_ARGS=""
|
||||||
|
CAR_CAPNP=$(find upstream_openpilot -maxdepth 5 -name car.capnp -path '*/opendbc/car/car.capnp' -printf '%h\n' -quit)
|
||||||
|
if [ -n "$CAR_CAPNP" ]; then
|
||||||
|
IMPORT_ARGS="-I $CAR_CAPNP"
|
||||||
|
echo "Found car.capnp at: $CAR_CAPNP"
|
||||||
|
fi
|
||||||
|
echo "import_args=$IMPORT_ARGS" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Install pycapnp
|
||||||
|
run: |
|
||||||
|
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
|
||||||
|
pip install "pycapnp==${PYCAPNP_VER}"
|
||||||
|
|
||||||
- name: Generate sunnypilot schema
|
- name: Generate sunnypilot schema
|
||||||
run: |
|
run: |
|
||||||
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
|
python3 openpilot/cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
||||||
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
|
-g -f /tmp/sp_schema.json --cereal-dir openpilot/cereal \
|
||||||
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
${{ steps.locate-sp-capnp.outputs.import_args }}
|
||||||
-g -f /tmp/sp_schema.json --cereal-dir cereal
|
|
||||||
|
|
||||||
- name: Validate against upstream
|
- name: Validate against upstream
|
||||||
run: |
|
run: |
|
||||||
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
|
python3 openpilot/cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
||||||
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
|
-r -f /tmp/sp_schema.json --cereal-dir ${{ steps.locate-capnp.outputs.cereal_dir }} \
|
||||||
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
|
${{ steps.locate-capnp.outputs.import_args }}
|
||||||
-r -f /tmp/sp_schema.json --cereal-dir upstream_openpilot/cereal
|
|
||||||
|
|||||||
@@ -17,29 +17,41 @@ jobs:
|
|||||||
- name: Wait for process replay
|
- name: Wait for process replay
|
||||||
id: wait
|
id: wait
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: lewagon/wait-on-check-action@v1.3.4
|
uses: lewagon/wait-on-check-action@1d57e2c51a58d812d2765e036a028b6bdb5a6154
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
check-name: process replay
|
check-name: process replay
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
allowed-conclusions: success,failure
|
allowed-conclusions: success,failure
|
||||||
wait-interval: 20
|
wait-interval: 20
|
||||||
|
- name: Get tests run ID
|
||||||
|
if: steps.wait.outcome == 'success'
|
||||||
|
id: tests-run
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
run_id=$(gh api \
|
||||||
|
"repos/${{ github.repository }}/actions/workflows/tests.yaml/runs?event=pull_request&head_sha=${{ github.event.pull_request.head.sha }}&per_page=1" \
|
||||||
|
--jq '.workflow_runs[0].id // empty')
|
||||||
|
if [ -z "$run_id" ]; then
|
||||||
|
echo "No tests.yaml run found for ${{ github.event.pull_request.head.sha }}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "run-id=$run_id" >> "$GITHUB_OUTPUT"
|
||||||
- name: Download diff
|
- name: Download diff
|
||||||
if: steps.wait.outcome == 'success'
|
if: steps.wait.outcome == 'success'
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
workflow: tests.yaml
|
repository: ${{ github.repository }}
|
||||||
workflow_conclusion: ''
|
run-id: ${{ steps.tests-run.outputs.run-id }}
|
||||||
pr: ${{ github.event.number }}
|
|
||||||
name: diff_report_${{ github.event.number }}
|
name: diff_report_${{ github.event.number }}
|
||||||
path: .
|
path: .
|
||||||
allow_forks: true
|
|
||||||
- name: Comment on PR
|
- name: Comment on PR
|
||||||
if: steps.wait.outcome == 'success'
|
if: steps.wait.outcome == 'success'
|
||||||
uses: thollander/actions-comment-pull-request@v2
|
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b
|
||||||
with:
|
with:
|
||||||
filePath: diff_report.txt
|
file-path: diff_report.txt
|
||||||
comment_tag: diff_report
|
comment-tag: diff_report
|
||||||
pr_number: ${{ github.event.number }}
|
pr-number: ${{ github.event.number }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: commaai/timeout@v1
|
- uses: commaai/timeout@v1
|
||||||
|
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
python scripts/docs.py build
|
python scripts/docs.py build
|
||||||
|
|
||||||
# Push to docs.comma.ai
|
# Push to docs.comma.ai
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
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
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
source release/identity.sh
|
source tools/release/identity.sh
|
||||||
|
|
||||||
cd openpilot-docs
|
cd openpilot-docs
|
||||||
git checkout --orphan tmp
|
git checkout --orphan tmp
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Delete stale Jenkins branches
|
- name: Delete stale Jenkins branches
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
||||||
@@ -52,7 +52,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@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const triggerPhrase = "trigger-jenkins";
|
const triggerPhrase = "trigger-jenkins";
|
||||||
@@ -72,7 +72,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@v7
|
||||||
with:
|
with:
|
||||||
ref: refs/pull/${{ github.event.issue.number }}/head
|
ref: refs/pull/${{ github.event.issue.number }}/head
|
||||||
|
|
||||||
@@ -86,7 +86,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@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
await github.rest.issues.deleteComment({
|
await github.rest.issues.deleteComment({
|
||||||
|
|||||||
@@ -65,8 +65,21 @@ jobs:
|
|||||||
echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig
|
echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig
|
||||||
echo ' locksverify = false' >> .lfsconfig
|
echo ' locksverify = false' >> .lfsconfig
|
||||||
|
|
||||||
|
- name: Configure LFS transfer settings
|
||||||
|
run: |
|
||||||
|
git config lfs.activitytimeout 300
|
||||||
|
git config lfs.transfer.maxretries 5
|
||||||
|
git config lfs.concurrenttransfers 4
|
||||||
|
|
||||||
- name: Push LFS
|
- name: Push LFS
|
||||||
id: sync-and-commit
|
id: sync-and-commit
|
||||||
run: |
|
run: |
|
||||||
git lfs ls-files -l
|
git lfs ls-files -l
|
||||||
git lfs push --all origin
|
for attempt in 1 2 3; do
|
||||||
|
echo "Push attempt $attempt..."
|
||||||
|
git lfs push --all origin && exit 0
|
||||||
|
echo "Attempt $attempt failed, retrying in 30s..."
|
||||||
|
sleep 30
|
||||||
|
done
|
||||||
|
echo "All push attempts failed"
|
||||||
|
exit 1
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened, synchronize]
|
types: [opened, reopened, synchronize]
|
||||||
paths:
|
paths:
|
||||||
- 'selfdrive/modeld/models/*.onnx'
|
- 'openpilot/selfdrive/modeld/models/*.onnx'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -16,11 +16,11 @@ jobs:
|
|||||||
if: github.repository == 'commaai/openpilot'
|
if: github.repository == 'commaai/openpilot'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Checkout master
|
- name: Checkout master
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
path: base
|
path: base
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
echo "EOF" >> $GITHUB_OUTPUT
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Post model report comment
|
- name: Post model report comment
|
||||||
uses: marocchino/sticky-pull-request-comment@baa7203ed60924babbe5dcd0ac8eae3b66ec5e16
|
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0
|
||||||
with:
|
with:
|
||||||
header: model-review
|
header: model-review
|
||||||
message: ${{ steps.report.outputs.content }}
|
message: ${{ steps.report.outputs.content }}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
name: prebuilt
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 * * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
BUILD: release/ci/docker_build_sp.sh
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_prebuilt:
|
|
||||||
name: build prebuilt
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
|
||||||
env:
|
|
||||||
PUSH_IMAGE: true
|
|
||||||
permissions:
|
|
||||||
checks: read
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- name: Wait for green check mark
|
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
|
||||||
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
|
|
||||||
with:
|
|
||||||
ref: master
|
|
||||||
wait-interval: 30
|
|
||||||
running-workflow-name: 'build prebuilt'
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
check-regexp: ^((?!.*(build master-ci|create badges).*).)*$
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: git lfs pull
|
|
||||||
- name: Build and Push docker image
|
|
||||||
run: |
|
|
||||||
$DOCKER_LOGIN
|
|
||||||
eval "$BUILD"
|
|
||||||
@@ -15,17 +15,17 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- 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@1d57e2c51a58d812d2765e036a028b6bdb5a6154
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
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|create badges).*).)*$
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4 # checkout version > v4 breaks nightly release script
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Push __nightly
|
- name: Push __nightly
|
||||||
run: BRANCH=__nightly release/build_stripped.sh
|
run: BRANCH=__nightly tools/release/build_stripped.sh
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ name: repo maintenance
|
|||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 14 * * 1" # every Monday at 2am UTC (6am PST)
|
- cron: "0 14 * * 1" # every Monday at 2am UTC (6am PST)
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -12,9 +14,11 @@ jobs:
|
|||||||
package_updates:
|
package_updates:
|
||||||
name: package_updates
|
name: package_updates
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: >-
|
||||||
|
github.repository == 'sunnypilot/sunnypilot' &&
|
||||||
|
(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
@@ -50,10 +54,10 @@ jobs:
|
|||||||
git add .
|
git add .
|
||||||
- name: update car docs
|
- name: update car docs
|
||||||
run: |
|
run: |
|
||||||
python selfdrive/car/docs.py
|
python openpilot/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@5f6978faf089d4d20b00c7766989d076bb2fc7f1
|
||||||
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 }}
|
||||||
@@ -75,3 +79,51 @@ jobs:
|
|||||||
${{ steps.pip_tree.outputs.PIP_TREE }}
|
${{ steps.pip_tree.outputs.PIP_TREE }}
|
||||||
```
|
```
|
||||||
labels: bot
|
labels: bot
|
||||||
|
|
||||||
|
cleanup_closed_branches:
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v9
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const upstream = `${owner}/${repo}`;
|
||||||
|
|
||||||
|
const closed = context.payload.pull_request;
|
||||||
|
if (closed) {
|
||||||
|
if (closed.head.repo?.full_name === upstream) {
|
||||||
|
await github.rest.git.deleteRef({ owner, repo, ref: `heads/${closed.head.ref}` }).catch(console.log);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const response of github.paginate.iterator(github.rest.pulls.list, {
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
state: 'closed',
|
||||||
|
per_page: 100,
|
||||||
|
})) {
|
||||||
|
for (const pr of response.data) {
|
||||||
|
if (pr.head.repo?.full_name !== upstream) continue;
|
||||||
|
|
||||||
|
const branch = pr.head.ref;
|
||||||
|
try {
|
||||||
|
await github.rest.git.deleteRef({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
ref: `heads/${branch}`,
|
||||||
|
});
|
||||||
|
console.log(`Deleted branch ${branch} (PR #${pr.number})`);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status === 422 || error.status === 403) {
|
||||||
|
console.log(`Skipping branch ${branch} (PR #${pr.number}): ${error.message}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ env:
|
|||||||
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
|
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
|
||||||
UPSTREAM_REPO: "commaai/openpilot"
|
UPSTREAM_REPO: "commaai/openpilot"
|
||||||
TINYGRAD_PATH: ${{ github.workspace }}/tinygrad_repo
|
TINYGRAD_PATH: ${{ github.workspace }}/tinygrad_repo
|
||||||
MODELS_DIR: ${{ github.workspace }}/selfdrive/modeld/models
|
MODELS_DIR: ${{ github.workspace }}/openpilot/selfdrive/modeld/models
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
@@ -30,6 +30,11 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: ''
|
default: ''
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for (qcom or usbgpu)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'qcom'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
upstream_branch:
|
upstream_branch:
|
||||||
@@ -46,6 +51,14 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- qcom
|
||||||
|
- usbgpu
|
||||||
|
default: 'qcom'
|
||||||
|
|
||||||
|
|
||||||
run-name: Build model [${{ inputs.custom_name || inputs.upstream_branch }}] from ref [${{ inputs.upstream_branch }}]
|
run-name: Build model [${{ inputs.custom_name || inputs.upstream_branch }}] from ref [${{ inputs.upstream_branch }}]
|
||||||
@@ -92,7 +105,7 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
||||||
path: ${{ github.workspace }}/openpilot/selfdrive/modeld/models/*.onnx
|
path: ${{ github.workspace }}/openpilot/openpilot/selfdrive/modeld/models/*.onnx
|
||||||
|
|
||||||
build_model:
|
build_model:
|
||||||
runs-on: [self-hosted, tici]
|
runs-on: [self-hosted, tici]
|
||||||
@@ -128,7 +141,10 @@ jobs:
|
|||||||
# 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
|
||||||
|
export UV_PYTHON_PREFERENCE=managed
|
||||||
|
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
|
||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
|
uv sync
|
||||||
printenv >> $GITHUB_ENV
|
printenv >> $GITHUB_ENV
|
||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||||
cat $GITHUB_OUTPUT
|
cat $GITHUB_OUTPUT
|
||||||
@@ -148,6 +164,7 @@ jobs:
|
|||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||||
printenv
|
printenv
|
||||||
fi
|
fi
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||||
rm -rf ${{ env.MODELS_DIR }}/*.onnx
|
rm -rf ${{ env.MODELS_DIR }}/*.onnx
|
||||||
|
|
||||||
@@ -155,21 +172,32 @@ jobs:
|
|||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
||||||
path: ${{ github.workspace }}/selfdrive/modeld/models
|
path: ${{ env.MODELS_DIR }}
|
||||||
- run: |
|
- run: |
|
||||||
rm -f ${{ github.workspace }}/selfdrive/modeld/models/{dmonitoring_model,big_driving_policy,big_driving_vision}.onnx
|
rm -f ${{ env.MODELS_DIR }}/{dmonitoring_model,big_driving_policy,big_driving_vision,big_driving_supercombo}.onnx
|
||||||
|
|
||||||
- name: Build Model
|
- name: Build Model
|
||||||
run: |
|
run: |
|
||||||
source /etc/profile
|
source /etc/profile
|
||||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}:${{ github.workspace }}"
|
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}:${{ github.workspace }}"
|
||||||
|
|
||||||
COMPILE_MODELD="${{ github.workspace }}/sunnypilot/modeld_v2/compile_modeld.py"
|
COMPILE_MODELD="${{ github.workspace }}/openpilot/sunnypilot/modeld_v2/compile_modeld.py"
|
||||||
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
||||||
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
||||||
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
|
||||||
|
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
||||||
|
echo "USBGPU build"
|
||||||
|
export USBGPU=1
|
||||||
|
TG_FLAGS="DEV=AMD USBGPU=1 IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
||||||
|
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
||||||
|
else
|
||||||
|
echo "QCOM build"
|
||||||
|
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
||||||
|
OUTPUT_PKL="${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
|
||||||
|
fi
|
||||||
|
|
||||||
# Generate metadata for all ONNX files
|
# Generate metadata for all ONNX files
|
||||||
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
||||||
@@ -182,7 +210,13 @@ jobs:
|
|||||||
POLICY_ONNX="${{ env.MODELS_DIR }}/driving_policy.onnx"
|
POLICY_ONNX="${{ env.MODELS_DIR }}/driving_policy.onnx"
|
||||||
OFF_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_off_policy.onnx"
|
OFF_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_off_policy.onnx"
|
||||||
ON_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_on_policy.onnx"
|
ON_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_on_policy.onnx"
|
||||||
SUPERCOMBO_ONNX="${{ env.MODELS_DIR }}/supercombo.onnx"
|
SUPERCOMBO_ONNX=""
|
||||||
|
for f in "${{ env.MODELS_DIR }}/supercombo.onnx" "${{ env.MODELS_DIR }}/driving_supercombo.onnx"; do
|
||||||
|
if [ -f "$f" ]; then
|
||||||
|
SUPERCOMBO_ONNX="$f"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
|
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
|
||||||
if [ -f "$VISION_ONNX" ]; then
|
if [ -f "$VISION_ONNX" ]; then
|
||||||
@@ -203,24 +237,15 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$MODEL_TYPE" ]; then
|
if [ -n "$MODEL_TYPE" ]; then
|
||||||
echo "Detected: $MODEL_TYPE -> driving_tinygrad.pkl"
|
echo "Detected: $MODEL_TYPE -> $OUTPUT_PKL"
|
||||||
env ${TG_FLAGS} python3 "$COMPILE_MODELD" \
|
env ${TG_FLAGS} python3 "$COMPILE_MODELD" \
|
||||||
--model-type $MODEL_TYPE \
|
--model-type $MODEL_TYPE \
|
||||||
--model-size $MODEL_SIZE \
|
--model-size $MODEL_SIZE \
|
||||||
--camera-resolutions $CAMERA_RES \
|
--camera-resolutions $CAMERA_RES \
|
||||||
$ONNX_ARGS \
|
$ONNX_ARGS \
|
||||||
--output "${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
|
--output "$OUTPUT_PKL"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- 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 }}
|
||||||
@@ -264,4 +289,5 @@ jobs:
|
|||||||
- name: Re-enable powersave
|
- name: Re-enable powersave
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
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
|
||||||
|
|
||||||
stable_version=$(cat sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g');
|
stable_version=$(cat openpilot/sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g');
|
||||||
echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT
|
echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT
|
||||||
echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT
|
echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
@@ -152,7 +152,10 @@ jobs:
|
|||||||
# 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
|
||||||
|
export UV_PYTHON_PREFERENCE=managed
|
||||||
|
export UV_PYTHON_INSTALL_DIR=${HOME}/uv/python
|
||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
|
uv sync
|
||||||
printenv >> $GITHUB_ENV
|
printenv >> $GITHUB_ENV
|
||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||||
cat $GITHUB_OUTPUT
|
cat $GITHUB_OUTPUT
|
||||||
@@ -172,20 +175,26 @@ jobs:
|
|||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||||
printenv
|
printenv
|
||||||
fi
|
fi
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||||
|
|
||||||
- name: Build Main Project
|
- name: Build Main Project
|
||||||
run: |
|
run: |
|
||||||
export PYTHONPATH="$BUILD_DIR"
|
export PYTHONPATH="$BUILD_DIR"
|
||||||
./release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/
|
./tools/release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/
|
||||||
cd $BUILD_DIR
|
cd $BUILD_DIR
|
||||||
|
ln -sfn msgq_repo/msgq msgq
|
||||||
|
ln -sfn opendbc_repo/opendbc opendbc
|
||||||
|
ln -sfn rednose_repo/rednose rednose
|
||||||
|
ln -sfn teleoprtc_repo/teleoprtc teleoprtc
|
||||||
|
ln -sfn tinygrad_repo/tinygrad tinygrad
|
||||||
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..."
|
echo "Building sunnypilot's modeld_v2..."
|
||||||
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld_v2
|
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal openpilot/sunnypilot/modeld_v2
|
||||||
echo "Building sunnypilot's locationd..."
|
echo "Building sunnypilot's locationd..."
|
||||||
scons -j2 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/selfdrive/locationd
|
scons -j2 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal openpilot/sunnypilot/selfdrive/locationd
|
||||||
echo "Building openpilot's locationd..."
|
echo "Building openpilot's locationd..."
|
||||||
scons -j1 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal selfdrive/locationd
|
scons -j1 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal openpilot/selfdrive/locationd
|
||||||
echo "Building rest of sunnypilot"
|
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
|
||||||
@@ -208,17 +217,17 @@ jobs:
|
|||||||
--exclude='Jenkinsfile' \
|
--exclude='Jenkinsfile' \
|
||||||
--exclude='**/release/' \
|
--exclude='**/release/' \
|
||||||
--exclude='**/.github/' \
|
--exclude='**/.github/' \
|
||||||
--exclude='**/selfdrive/ui/replay/' \
|
--exclude='**/openpilot/selfdrive/ui/replay/' \
|
||||||
--exclude='**/__pycache__/' \
|
--exclude='**/__pycache__/' \
|
||||||
--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/*.onnx*' \
|
--exclude='openpilot/selfdrive/modeld/models/*.onnx*' \
|
||||||
--exclude='sunnypilot/modeld*/models/*.onnx*' \
|
--exclude='openpilot/sunnypilot/modeld*/models/*.onnx*' \
|
||||||
--exclude='third_party/*x86*' \
|
--exclude='openpilot/third_party/*x86*' \
|
||||||
--exclude='third_party/*Darwin*' \
|
--exclude='openpilot/third_party/*Darwin*' \
|
||||||
--delete-excluded \
|
--delete-excluded \
|
||||||
--chown=comma:comma \
|
--chown=comma:comma \
|
||||||
${BUILD_DIR}/ ${OUTPUT_DIR}/
|
${BUILD_DIR}/ ${OUTPUT_DIR}/
|
||||||
@@ -237,6 +246,7 @@ jobs:
|
|||||||
- name: Re-enable powersave
|
- name: Re-enable powersave
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||||
|
|
||||||
|
|
||||||
@@ -363,7 +373,7 @@ jobs:
|
|||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: prNumber,
|
issue_number: prNumber,
|
||||||
name: process.env.LABEL
|
name: process.env.LABELf
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Removed '${process.env.LABEL}' label from PR #${prNumber}`);
|
console.log(`Removed '${process.env.LABEL}' label from PR #${prNumber}`);
|
||||||
|
|||||||
@@ -35,27 +35,27 @@ jobs:
|
|||||||
STRIPPED_DIR: /tmp/releasepilot
|
STRIPPED_DIR: /tmp/releasepilot
|
||||||
PYTHONPATH: /tmp/releasepilot
|
PYTHONPATH: /tmp/releasepilot
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Getting LFS files
|
- name: Getting LFS files
|
||||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e
|
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 2
|
timeout_minutes: 2
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
command: git lfs pull
|
command: git lfs pull
|
||||||
- name: Build devel
|
- name: Build devel
|
||||||
timeout-minutes: 1
|
timeout-minutes: 3
|
||||||
run: TARGET_DIR=$STRIPPED_DIR release/build_stripped.sh
|
run: TARGET_DIR=$STRIPPED_DIR tools/release/build_stripped.sh
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Build openpilot and run checks
|
- name: Build openpilot and run checks
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
working-directory: ${{ env.STRIPPED_DIR }}
|
working-directory: ${{ env.STRIPPED_DIR }}
|
||||||
run: python3 system/manager/build.py
|
run: python3 openpilot/system/manager/build.py
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
timeout-minutes: 1
|
timeout-minutes: 1
|
||||||
working-directory: ${{ env.STRIPPED_DIR }}
|
working-directory: ${{ env.STRIPPED_DIR }}
|
||||||
run: release/check-dirty.sh
|
run: tools/release/check-dirty.sh
|
||||||
- name: Check submodules
|
- name: Check submodules
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
@@ -75,15 +75,13 @@ jobs:
|
|||||||
export CHECK_PR_REFS=true
|
export CHECK_PR_REFS=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
release/check-submodules.sh
|
tools/release/check-submodules.sh
|
||||||
|
|
||||||
build_mac:
|
build_mac:
|
||||||
name: build macOS
|
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' }}
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Remove Homebrew from environment
|
- name: Remove Homebrew from environment
|
||||||
run: |
|
run: |
|
||||||
FILTERED=$(echo "$PATH" | tr ':' '\n' | grep -v '/opt/homebrew' | tr '\n' ':')
|
FILTERED=$(echo "$PATH" | tr ':' '\n' | grep -v '/opt/homebrew' | tr '\n' ':')
|
||||||
@@ -101,9 +99,7 @@ jobs:
|
|||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Static analysis
|
- name: Static analysis
|
||||||
timeout-minutes: 1
|
timeout-minutes: 1
|
||||||
@@ -118,16 +114,18 @@ jobs:
|
|||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
|
- name: Install raylib headless dependencies
|
||||||
|
if: ${{ !contains(runner.name, 'nsc') }}
|
||||||
|
run: sudo apt-get install -y --no-install-recommends libgles2 libegl1
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons
|
run: scons
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 999 }}
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 999 }}
|
||||||
|
env:
|
||||||
|
RAYLIB_BACKEND: headless
|
||||||
run: |
|
run: |
|
||||||
source selfdrive/test/setup_xvfb.sh
|
|
||||||
# Pre-compile Python bytecode so each pytest worker doesn't need to
|
# Pre-compile Python bytecode so each pytest worker doesn't need to
|
||||||
$PYTEST --collect-only -m 'not slow' -qq
|
$PYTEST --collect-only -m 'not slow' -qq
|
||||||
MAX_EXAMPLES=1 $PYTEST -m 'not slow'
|
MAX_EXAMPLES=1 $PYTEST -m 'not slow'
|
||||||
@@ -142,39 +140,37 @@ jobs:
|
|||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons
|
run: scons
|
||||||
- name: Run replay
|
- name: Run replay
|
||||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }}
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }}
|
||||||
continue-on-error: ${{ github.ref == 'refs/heads/master' }}
|
continue-on-error: ${{ github.ref == 'refs/heads/master' }}
|
||||||
run: selfdrive/test/process_replay/test_processes.py -j$(nproc)
|
run: openpilot/selfdrive/test/process_replay/test_processes.py -j$(nproc)
|
||||||
- name: Print diff
|
- name: Print diff
|
||||||
id: print-diff
|
id: print-diff
|
||||||
if: always()
|
if: always()
|
||||||
run: cat selfdrive/test/process_replay/diff.txt
|
run: cat openpilot/selfdrive/test/process_replay/diff.txt
|
||||||
- name: Print diff report
|
- name: Print diff report
|
||||||
if: always()
|
if: always()
|
||||||
run: cat selfdrive/test/process_replay/diff_report.txt
|
run: cat openpilot/selfdrive/test/process_replay/diff_report.txt
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
if: always()
|
if: always()
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
name: process_replay_diff.txt
|
name: process_replay_diff.txt
|
||||||
path: selfdrive/test/process_replay/diff.txt
|
path: openpilot/selfdrive/test/process_replay/diff.txt
|
||||||
- name: Upload diff report
|
- name: Upload diff report
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
if: always() && github.event_name == 'pull_request'
|
if: always() && github.event_name == 'pull_request'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
name: diff_report_${{ github.event.number }}
|
name: diff_report_${{ github.event.number }}
|
||||||
path: selfdrive/test/process_replay/diff_report.txt
|
path: openpilot/selfdrive/test/process_replay/diff_report.txt
|
||||||
- name: Checkout ci-artifacts
|
- name: Checkout ci-artifacts
|
||||||
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: commaai/ci-artifacts
|
repository: commaai/ci-artifacts
|
||||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||||
@@ -187,13 +183,13 @@ jobs:
|
|||||||
git config user.email "<>"
|
git config user.email "<>"
|
||||||
git fetch origin process-replay || true
|
git fetch origin process-replay || true
|
||||||
git checkout process-replay 2>/dev/null || git checkout --orphan process-replay
|
git checkout process-replay 2>/dev/null || git checkout --orphan process-replay
|
||||||
cp ${{ github.workspace }}/selfdrive/test/process_replay/fakedata/*.zst .
|
cp ${{ github.workspace }}/openpilot/selfdrive/test/process_replay/fakedata/*.zst .
|
||||||
echo "${{ github.sha }}" > ref_commit
|
echo "${{ github.sha }}" > ref_commit
|
||||||
git add .
|
git add .
|
||||||
git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit"
|
git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit"
|
||||||
- name: Push refs
|
- name: Push refs
|
||||||
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master'
|
||||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e
|
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 2
|
timeout_minutes: 2
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -203,7 +199,7 @@ jobs:
|
|||||||
timeout-minutes: 4
|
timeout-minutes: 4
|
||||||
env:
|
env:
|
||||||
ONNXCPU: 1
|
ONNXCPU: 1
|
||||||
run: $PYTEST selfdrive/test/process_replay/test_regen.py
|
run: $PYTEST openpilot/selfdrive/test/process_replay/test_regen.py
|
||||||
|
|
||||||
simulator_driving:
|
simulator_driving:
|
||||||
name: simulator driving
|
name: simulator driving
|
||||||
@@ -215,17 +211,16 @@ jobs:
|
|||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
if: false # FIXME: Started to timeout recently
|
if: false # FIXME: Started to timeout recently
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons
|
run: scons
|
||||||
- name: Driving test
|
- name: Driving test
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
run: |
|
env:
|
||||||
source selfdrive/test/setup_xvfb.sh
|
# MetaDrive renders offscreen through panda3d's EGL pipe on llvmpipe
|
||||||
pytest -s tools/sim/tests/test_metadrive_bridge.py
|
EGL_PLATFORM: surfaceless
|
||||||
|
run: pytest -s openpilot/tools/sim/tests/test_metadrive_bridge.py
|
||||||
|
|
||||||
create_ui_report:
|
create_ui_report:
|
||||||
name: Create UI Report
|
name: Create UI Report
|
||||||
@@ -236,19 +231,21 @@ jobs:
|
|||||||
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
&& fromJSON('["namespace-profile-amd64-8x16"]')
|
||||||
|| fromJSON('["ubuntu-24.04"]') }}
|
|| fromJSON('["ubuntu-24.04"]') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: ./tools/op.sh setup
|
- run: ./tools/op.sh setup
|
||||||
|
- name: Install raylib headless dependencies
|
||||||
|
if: ${{ !contains(runner.name, 'nsc') }}
|
||||||
|
run: sudo apt-get install -y --no-install-recommends libgles2 libegl1
|
||||||
- name: Build openpilot
|
- name: Build openpilot
|
||||||
run: scons
|
run: scons
|
||||||
- name: Create UI Report
|
- name: Create UI Report
|
||||||
|
env:
|
||||||
|
RAYLIB_BACKEND: headless
|
||||||
run: |
|
run: |
|
||||||
source selfdrive/test/setup_xvfb.sh
|
python3 openpilot/selfdrive/ui/tests/diff/replay.py
|
||||||
python3 selfdrive/ui/tests/diff/replay.py
|
python3 openpilot/selfdrive/ui/tests/diff/replay.py --big
|
||||||
python3 selfdrive/ui/tests/diff/replay.py --big
|
|
||||||
- name: Upload UI Report
|
- name: Upload UI Report
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ui-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
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
|
path: openpilot/selfdrive/ui/tests/diff/report
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
paths:
|
paths:
|
||||||
- 'selfdrive/assets/**'
|
- 'openpilot/selfdrive/assets/**'
|
||||||
- 'selfdrive/ui/**'
|
- 'openpilot/selfdrive/ui/**'
|
||||||
- 'system/ui/**'
|
- 'openpilot/system/ui/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -34,12 +34,12 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
actions: read
|
actions: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
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@1d57e2c51a58d812d2765e036a028b6bdb5a6154
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.SHA }}
|
ref: ${{ env.SHA }}
|
||||||
check-name: ${{ env.UI_JOB_NAME }}
|
check-name: ${{ env.UI_JOB_NAME }}
|
||||||
@@ -53,7 +53,7 @@ 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
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151
|
||||||
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 }}
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
path: ${{ github.workspace }}/pr_ui
|
path: ${{ github.workspace }}/pr_ui
|
||||||
|
|
||||||
- name: Getting mici master ui
|
- name: Getting mici master ui
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: sunnypilot/ci-artifacts
|
repository: sunnypilot/ci-artifacts
|
||||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
ref: openpilot_master_ui_mici_raylib
|
ref: openpilot_master_ui_mici_raylib
|
||||||
|
|
||||||
- name: Getting big master ui
|
- name: Getting big master ui
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: sunnypilot/ci-artifacts
|
repository: sunnypilot/ci-artifacts
|
||||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||||
@@ -97,7 +97,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Setup FFmpeg
|
- name: Setup FFmpeg
|
||||||
uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae
|
uses: AnimMouse/setup-ffmpeg@178e0b4a408f06ce40d896c3cd79f50fa6f8b0c3
|
||||||
|
|
||||||
- name: Finding diffs
|
- name: Finding diffs
|
||||||
if: github.event_name == 'pull_request_target'
|
if: github.event_name == 'pull_request_target'
|
||||||
@@ -115,13 +115,13 @@ jobs:
|
|||||||
cp "${{ github.workspace }}/master_${name}/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_master.mp4"
|
cp "${{ github.workspace }}/master_${name}/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_master.mp4"
|
||||||
|
|
||||||
diff_exit_code=0
|
diff_exit_code=0
|
||||||
python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py \
|
python3 ${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/diff.py \
|
||||||
"${{ github.workspace }}/pr_ui/${video}_master.mp4" \
|
"${{ github.workspace }}/pr_ui/${video}_master.mp4" \
|
||||||
"${{ github.workspace }}/pr_ui/${video}_proposed.mp4" \
|
"${{ github.workspace }}/pr_ui/${video}_proposed.mp4" \
|
||||||
"${diff_name}.html" --basedir "$baseurl" --no-open || diff_exit_code=$?
|
"${diff_name}.html" --basedir "$baseurl" --no-open || diff_exit_code=$?
|
||||||
|
|
||||||
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${{ github.workspace }}/pr_ui/"
|
cp "${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/report/${diff_name}.html" "${{ github.workspace }}/pr_ui/"
|
||||||
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.mp4" "${{ github.workspace }}/pr_ui/"
|
cp "${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/report/${diff_name}.mp4" "${{ github.workspace }}/pr_ui/"
|
||||||
|
|
||||||
REPORT_URL="https://sunnypilot.github.io/ci-artifacts/${diff_name}_pr_${{ github.event.number }}.html"
|
REPORT_URL="https://sunnypilot.github.io/ci-artifacts/${diff_name}_pr_${{ github.event.number }}.html"
|
||||||
if [ $diff_exit_code -eq 0 ]; then
|
if [ $diff_exit_code -eq 0 ]; then
|
||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
for variant in $VARIANTS; do
|
for variant in $VARIANTS; do
|
||||||
IFS=':' read -r name _ _ <<< "$variant"
|
IFS=':' read -r name _ _ <<< "$variant"
|
||||||
diff_name="${name}_diff"
|
diff_name="${name}_diff"
|
||||||
cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${diff_name}_pr_${{ github.event.number }}.html"
|
cp "${{ github.workspace }}/openpilot/selfdrive/ui/tests/diff/report/${diff_name}.html" "${diff_name}_pr_${{ github.event.number }}.html"
|
||||||
git add "${diff_name}_pr_${{ github.event.number }}.html"
|
git add "${diff_name}_pr_${{ github.event.number }}.html"
|
||||||
done
|
done
|
||||||
git commit -m "ui diff reports for PR #${{ github.event.number }}" || echo "No changes to commit"
|
git commit -m "ui diff reports for PR #${{ github.event.number }}" || echo "No changes to commit"
|
||||||
@@ -164,12 +164,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Comment on PR
|
- 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@24bffb9b452ba05a4f3f77933840a6a841d1b32b
|
||||||
with:
|
with:
|
||||||
message: |
|
message: |
|
||||||
<!-- _(run_id_ui_preview **${{ github.run_id }}**)_ -->
|
<!-- _(run_id_ui_preview **${{ github.run_id }}**)_ -->
|
||||||
## UI Preview
|
## UI Preview
|
||||||
${{ steps.find_diff.outputs.COMMENT }}
|
${{ steps.find_diff.outputs.COMMENT }}
|
||||||
comment_tag: run_id_ui_preview
|
comment-tag: run_id_ui_preview
|
||||||
pr_number: ${{ github.event.number }}
|
pr-number: ${{ github.event.number }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
+16
-9
@@ -15,6 +15,13 @@ a.out
|
|||||||
.cache/
|
.cache/
|
||||||
bin/
|
bin/
|
||||||
|
|
||||||
|
# created at launch for TICI PYTHONPATH (PC uses editable installs via pyproject.toml)
|
||||||
|
/msgq
|
||||||
|
/opendbc
|
||||||
|
/rednose
|
||||||
|
/teleoprtc
|
||||||
|
/tinygrad
|
||||||
|
|
||||||
*.mp4
|
*.mp4
|
||||||
*.dylib
|
*.dylib
|
||||||
*.DSYM
|
*.DSYM
|
||||||
@@ -44,18 +51,18 @@ bin/
|
|||||||
config.json
|
config.json
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
compare_runtime*.html
|
compare_runtime*.html
|
||||||
selfdrive/modeld/models/tg_input_devices.json
|
openpilot/selfdrive/modeld/models/tg_input_devices.json
|
||||||
|
|
||||||
# build artifacts
|
# build artifacts
|
||||||
docs_site/
|
docs_site/
|
||||||
selfdrive/pandad/pandad
|
openpilot/selfdrive/pandad/pandad
|
||||||
cereal/services.h
|
openpilot/cereal/services.h
|
||||||
cereal/gen
|
openpilot/cereal/gen
|
||||||
cereal/messaging/bridge
|
openpilot/cereal/messaging/bridge
|
||||||
selfdrive/ui/translations/tmp
|
openpilot/selfdrive/ui/translations/tmp
|
||||||
selfdrive/car/tests/cars_dump
|
openpilot/selfdrive/car/tests/cars_dump
|
||||||
system/camerad/camerad
|
openpilot/system/camerad/camerad
|
||||||
system/camerad/test/ae_gray_test
|
openpilot/system/camerad/test/ae_gray_test
|
||||||
|
|
||||||
.coverage*
|
.coverage*
|
||||||
coverage.xml
|
coverage.xml
|
||||||
|
|||||||
+2
-2
@@ -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
|
||||||
@@ -17,5 +17,5 @@
|
|||||||
path = tinygrad_repo
|
path = tinygrad_repo
|
||||||
url = https://github.com/sunnypilot/tinygrad.git
|
url = https://github.com/sunnypilot/tinygrad.git
|
||||||
[submodule "sunnypilot/neural_network_data"]
|
[submodule "sunnypilot/neural_network_data"]
|
||||||
path = sunnypilot/neural_network_data
|
path = openpilot/sunnypilot/neural_network_data
|
||||||
url = https://github.com/sunnypilot/neural-network-data.git
|
url = https://github.com/sunnypilot/neural-network-data.git
|
||||||
|
|||||||
Vendored
+4
-4
@@ -6,9 +6,9 @@
|
|||||||
"type": "pickString",
|
"type": "pickString",
|
||||||
"description": "Select the process to debug",
|
"description": "Select the process to debug",
|
||||||
"options": [
|
"options": [
|
||||||
"selfdrive/controls/controlsd.py",
|
"openpilot/selfdrive/controls/controlsd.py",
|
||||||
"system/timed/timed.py",
|
"openpilot/system/timed/timed.py",
|
||||||
"tools/sim/run_bridge.py"
|
"openpilot/tools/sim/run_bridge.py"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"type": "pickString",
|
"type": "pickString",
|
||||||
"description": "Select the process to debug",
|
"description": "Select the process to debug",
|
||||||
"options": [
|
"options": [
|
||||||
"selfdrive/ui/ui"
|
"openpilot/selfdrive/ui/ui"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Vendored
-5
@@ -17,10 +17,5 @@
|
|||||||
"**/.git",
|
"**/.git",
|
||||||
"**/.venv",
|
"**/.venv",
|
||||||
"**/__pycache__",
|
"**/__pycache__",
|
||||||
// exclude directories that should be using the symlinked version
|
|
||||||
"common/**",
|
|
||||||
"selfdrive/**",
|
|
||||||
"system/**",
|
|
||||||
"tools/**",
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-1
@@ -1,5 +1,34 @@
|
|||||||
sunnypilot Version 2026.002.000 (2026-xx-xx)
|
sunnypilot Version 2026.002.000 (2026-06-28)
|
||||||
========================
|
========================
|
||||||
|
* What's Changed (sunnypilot/sunnypilot)
|
||||||
|
* ui: update gates for certain toggles by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1830
|
||||||
|
* release: ignore upstream IsReleaseBranch by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1831
|
||||||
|
* manager: disable DEVELOPMENT_ONLY reset by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1833
|
||||||
|
* sunnylink: fix max time offroad values by @nayan8teen in https://github.com/sunnypilot/sunnypilot/pull/1835
|
||||||
|
* ui: show default model name by @nayan8teen in https://github.com/sunnypilot/sunnypilot/pull/1837
|
||||||
|
* sunnylink: add CarParams fallback for brand-specific capabilities by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1839
|
||||||
|
* sunnylink SDUI: tweak DisableUpdate param for clarity by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1842
|
||||||
|
* Revert "DM: Lancia Delta HF Integrale model" by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1849
|
||||||
|
* modeld_v2: safe model validation by @Discountchubbs in https://github.com/sunnypilot/sunnypilot/pull/1855
|
||||||
|
* Revert "deprecate `carState.brake`" for Honda Gas Interceptor by @mvl-boston in https://github.com/sunnypilot/sunnypilot/pull/1860
|
||||||
|
* sunnylink: deprecate legacy params metadata by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1862
|
||||||
|
* ui: reset Enforce Torque Control and NNLC if both are enabled by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1863
|
||||||
|
* What's Changed (sunnypilot/opendbc)
|
||||||
|
* Rivian: suppress ACM hold-the-wheel warning during MADS-only lateral by @lukasloetkolben in https://github.com/sunnypilot/opendbc/pull/465
|
||||||
|
* Sync: `commaai/opendbc:master` → `sunnypilot/opendbc:master` by @sunnyhaibin in https://github.com/sunnypilot/opendbc/pull/479
|
||||||
|
* safety: add option to ignore frequency check for RX checks by @sunnyhaibin in https://github.com/sunnypilot/opendbc/pull/480
|
||||||
|
* Revert "deprecate carState.brake" for Honda Gas Interceptor by @mvl-boston in https://github.com/sunnypilot/opendbc/pull/481
|
||||||
|
* New Contributors (sunnypilot/sunnypilot)
|
||||||
|
* @mvl-boston made their first contribution in https://github.com/sunnypilot/sunnypilot/pull/1860
|
||||||
|
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2026.001.007...v2026.002.000
|
||||||
|
************************
|
||||||
|
* Synced with commaai's openpilot (v0.11.1)
|
||||||
|
* master commit 69e2c321e49760e52f7983eaa0a5f77cb95de637 (June 02, 2026)
|
||||||
|
* New driver monitoring model
|
||||||
|
* Improved image processing pipeline for driver camera
|
||||||
|
* Improved thermal policy for comma four
|
||||||
|
* Acura MDX 2022-24 support thanks to mvl-boston!
|
||||||
|
* Rivian R1S and R1T 2025 support thanks to lukasloetkolben!
|
||||||
|
|
||||||
sunnypilot Version 2026.001.000 (2026-05-06)
|
sunnypilot Version 2026.001.000 (2026-05-06)
|
||||||
========================
|
========================
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
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 && \
|
|
||||||
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}
|
|
||||||
WORKDIR ${OPENPILOT_PATH}
|
|
||||||
|
|
||||||
COPY --chown=$USER . ${OPENPILOT_PATH}/
|
|
||||||
|
|
||||||
ENV UV_BIN="/home/$USER/.local/bin/"
|
|
||||||
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 '*'
|
|
||||||
Vendored
+28
-26
@@ -12,7 +12,7 @@ def retryWithDelay(int maxRetries, int delay, Closure body) {
|
|||||||
def device(String ip, String step_label, String cmd) {
|
def device(String ip, String step_label, String cmd) {
|
||||||
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
||||||
def ssh_cmd = """
|
def ssh_cmd = """
|
||||||
ssh -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec /usr/bin/bash <<'END'
|
ssh -o ControlMaster=auto -o ControlPath=/tmp/ssh_control_%C -o ControlPersist=yes -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec /usr/bin/bash <<'END'
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -63,6 +63,8 @@ if [ -f /data/openpilot/launch_env.sh ]; then
|
|||||||
source /data/openpilot/launch_env.sh
|
source /data/openpilot/launch_env.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH="\$(python -c 'import ffmpeg; print(ffmpeg.LIB_DIR)'):/usr/local/lib:\${LD_LIBRARY_PATH:-}"
|
||||||
|
|
||||||
ln -snf ${env.TEST_DIR} /data/pythonpath
|
ln -snf ${env.TEST_DIR} /data/pythonpath
|
||||||
|
|
||||||
cd ${env.TEST_DIR} || true
|
cd ${env.TEST_DIR} || true
|
||||||
@@ -93,7 +95,7 @@ def deviceStage(String stageName, String deviceType, List extra_env, def steps)
|
|||||||
retry (3) {
|
retry (3) {
|
||||||
def date = sh(script: 'date', returnStdout: true).trim();
|
def date = sh(script: 'date', returnStdout: true).trim();
|
||||||
device(device_ip, "set time", "date -s '" + date + "'")
|
device(device_ip, "set time", "date -s '" + date + "'")
|
||||||
device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
|
device(device_ip, "git checkout", extra + "\n" + readFile("openpilot/selfdrive/test/setup_device_ci.sh"))
|
||||||
}
|
}
|
||||||
steps.each { item ->
|
steps.each { item ->
|
||||||
def name = item[0]
|
def name = item[0]
|
||||||
@@ -179,7 +181,7 @@ 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 release-tizi-staging", "tizi-needs-can", [], [
|
||||||
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging,release-mici-staging $SOURCE_DIR/release/build_release.sh"),
|
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging,release-mici-staging $SOURCE_DIR/tools/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,12 +189,12 @@ node {
|
|||||||
parallel (
|
parallel (
|
||||||
'nightly': {
|
'nightly': {
|
||||||
deviceStage("build nightly", "tizi-needs-can", [], [
|
deviceStage("build nightly", "tizi-needs-can", [], [
|
||||||
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/tools/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'nightly-dev': {
|
'nightly-dev': {
|
||||||
deviceStage("build nightly-dev", "tizi-needs-can", [], [
|
deviceStage("build nightly-dev", "tizi-needs-can", [], [
|
||||||
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/tools/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -202,51 +204,51 @@ node {
|
|||||||
parallel (
|
parallel (
|
||||||
'onroad tests': {
|
'onroad tests': {
|
||||||
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
|
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
|
||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd openpilot/system/manager && ./build.py"),
|
||||||
step("check dirty", "release/check-dirty.sh"),
|
step("check dirty", "tools/release/check-dirty.sh"),
|
||||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
step("onroad tests", "pytest openpilot/selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'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 openpilot/system/manager && ./build.py"),
|
||||||
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
step("test power draw", "pytest -s openpilot/selfdrive/test//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", "pytest openpilot/system/loggerd/tests/test_encoder.py", [diffPaths: ["openpilot/system/loggerd/"]]),
|
||||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
step("test manager", "pytest openpilot/system/manager/test/test_manager.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 openpilot/system/manager && ./build.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"),
|
step("test pandad", "pytest openpilot/selfdrive/pandad/tests/test_pandad.py"),
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
|
step("test camerad", "pytest openpilot/system/camerad/test/test_camerad.py", [timeout: 90]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'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 openpilot/system/manager && ./build.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"),
|
step("test pandad", "pytest openpilot/selfdrive/pandad/tests/test_pandad.py"),
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]),
|
step("test camerad", "pytest openpilot/system/camerad/test/test_camerad.py", [timeout: 90]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'sensord': {
|
'sensord': {
|
||||||
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
|
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd openpilot/system/manager && ./build.py"),
|
||||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
step("test sensord", "pytest openpilot/system/sensord/tests/test_sensord.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'replay': {
|
'replay': {
|
||||||
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
|
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
step("build", "cd openpilot/system/manager && ./build.py", [diffPaths: ["openpilot/selfdrive/modeld/", "tinygrad_repo", "openpilot/selfdrive/test/process_replay/model_replay.py"]]),
|
||||||
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
step("model replay", "openpilot/selfdrive/test/process_replay/model_replay.py", [diffPaths: ["openpilot/selfdrive/modeld/", "tinygrad_repo", "openpilot/selfdrive/test/process_replay/model_replay.py"]]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'tizi': {
|
'tizi': {
|
||||||
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd openpilot/system/manager && ./build.py"),
|
||||||
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
step("test pandad loopback", "pytest openpilot/selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||||
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
step("test pandad spi", "pytest openpilot/selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
step("test amp", "pytest openpilot/common/hardware/tici/tests/test_amplifier.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1104,7 +1104,7 @@ Version 0.2.1 (2016-12-14)
|
|||||||
|
|
||||||
Version 0.2 (2016-12-12)
|
Version 0.2 (2016-12-12)
|
||||||
=========================
|
=========================
|
||||||
* Car/Radar abstraction layers have shipped, see cereal/car.capnp
|
* Car/Radar abstraction layers have shipped, see openpilot/cereal/car.capnp
|
||||||
* controlsd has been refactored
|
* controlsd has been refactored
|
||||||
* Shipped plant model and testing maneuvers
|
* Shipped plant model and testing maneuvers
|
||||||
* visiond exits more gracefully now
|
* visiond exits more gracefully now
|
||||||
|
|||||||
+75
-25
@@ -27,6 +27,21 @@ AddOption('--minimal',
|
|||||||
default=(not TICI and not release),
|
default=(not TICI and not release),
|
||||||
help='the minimum build to run openpilot. no tests, tools, etc.')
|
help='the minimum build to run openpilot. no tests, tools, etc.')
|
||||||
|
|
||||||
|
submodule_python_paths = [
|
||||||
|
Dir("#").abspath,
|
||||||
|
Dir("#msgq_repo").abspath,
|
||||||
|
Dir("#opendbc_repo").abspath,
|
||||||
|
Dir("#rednose_repo").abspath,
|
||||||
|
Dir("#teleoprtc_repo").abspath,
|
||||||
|
Dir("#tinygrad_repo").abspath,
|
||||||
|
]
|
||||||
|
for p in reversed(submodule_python_paths):
|
||||||
|
if p not in sys.path:
|
||||||
|
sys.path.insert(0, p)
|
||||||
|
|
||||||
|
if external_pythonpath := os.environ.get("PYTHONPATH"):
|
||||||
|
submodule_python_paths += [p for p in external_pythonpath.split(os.pathsep) if p and p not in submodule_python_paths]
|
||||||
|
|
||||||
# Detect platform
|
# Detect platform
|
||||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||||
if platform.system() == "Darwin":
|
if platform.system() == "Darwin":
|
||||||
@@ -40,9 +55,24 @@ assert arch in [
|
|||||||
"Darwin", # macOS arm64 (x86 not supported)
|
"Darwin", # macOS arm64 (x86 not supported)
|
||||||
]
|
]
|
||||||
|
|
||||||
pkg_names = ['acados', 'bzip2', 'capnproto', 'catch2', 'eigen', 'ffmpeg', 'json11', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd']
|
pkg_names = ['acados', 'bzip2', 'capnproto', 'catch2', 'eigen', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd']
|
||||||
pkgs = [importlib.import_module(name) for name in pkg_names]
|
pkgs = [importlib.import_module(name) for name in pkg_names]
|
||||||
acados = pkgs[pkg_names.index('acados')]
|
acados = pkgs[pkg_names.index('acados')]
|
||||||
|
ffmpeg = pkgs[pkg_names.index('ffmpeg')]
|
||||||
|
# Shared package ships .so/.dylib; older device venvs still have static .a only.
|
||||||
|
# Keep static link deps (x264/z/va/drm) when the installed package is static so
|
||||||
|
# TICI CI works without upgrading the device venv yet.
|
||||||
|
# TODO: drop the static fallback once device venvs have comma-deps-ffmpeg>=7.1.0.post94
|
||||||
|
_ffmpeg_lib_names = os.listdir(ffmpeg.LIB_DIR) if os.path.isdir(ffmpeg.LIB_DIR) else []
|
||||||
|
ffmpeg_shared = any(
|
||||||
|
n.startswith('libavcodec.so') or (n.startswith('libavcodec') and n.endswith('.dylib'))
|
||||||
|
for n in _ffmpeg_lib_names
|
||||||
|
)
|
||||||
|
ffmpeg_libs = ['avformat', 'avcodec', 'swresample', 'avutil']
|
||||||
|
if not ffmpeg_shared:
|
||||||
|
ffmpeg_libs += ['x264', 'z']
|
||||||
|
if arch != "Darwin":
|
||||||
|
ffmpeg_libs += ['va', 'va-drm', 'drm']
|
||||||
acados_include_dirs = [
|
acados_include_dirs = [
|
||||||
acados.INCLUDE_DIR,
|
acados.INCLUDE_DIR,
|
||||||
os.path.join(acados.INCLUDE_DIR, "blasfeo", "include"),
|
os.path.join(acados.INCLUDE_DIR, "blasfeo", "include"),
|
||||||
@@ -91,7 +121,7 @@ def _libflags(target, source, env, for_signature):
|
|||||||
env = Environment(
|
env = Environment(
|
||||||
ENV={
|
ENV={
|
||||||
"PATH": os.environ['PATH'],
|
"PATH": os.environ['PATH'],
|
||||||
"PYTHONPATH": Dir("#").abspath,
|
"PYTHONPATH": os.pathsep.join(submodule_python_paths),
|
||||||
"ACADOS_SOURCE_DIR": acados.DIR,
|
"ACADOS_SOURCE_DIR": acados.DIR,
|
||||||
"ACADOS_PYTHON_INTERFACE_PATH": acados.TEMPLATE_DIR,
|
"ACADOS_PYTHON_INTERFACE_PATH": acados.TEMPLATE_DIR,
|
||||||
"TERA_PATH": acados.TERA_PATH
|
"TERA_PATH": acados.TERA_PATH
|
||||||
@@ -112,25 +142,35 @@ env = Environment(
|
|||||||
CFLAGS=["-std=gnu11"],
|
CFLAGS=["-std=gnu11"],
|
||||||
CXXFLAGS=["-std=c++1z"],
|
CXXFLAGS=["-std=c++1z"],
|
||||||
CPPPATH=[
|
CPPPATH=[
|
||||||
"#",
|
"#openpilot",
|
||||||
"#msgq",
|
"#msgq_repo", # #include "msgq/..."
|
||||||
|
"#opendbc_repo", # #include "opendbc/..."
|
||||||
|
"#rednose_repo", # #include "rednose/..."
|
||||||
|
"#rednose_repo/rednose", # #include "logger/..." (rednose package root)
|
||||||
|
"#openpilot/cereal/gen/cpp",
|
||||||
acados_include_dirs,
|
acados_include_dirs,
|
||||||
[x.INCLUDE_DIR for x in pkgs],
|
[x.INCLUDE_DIR for x in pkgs],
|
||||||
|
"#",
|
||||||
],
|
],
|
||||||
LIBPATH=[
|
LIBPATH=[
|
||||||
"#common",
|
"#openpilot/common",
|
||||||
"#msgq_repo",
|
"#msgq_repo",
|
||||||
"#selfdrive/pandad",
|
"#openpilot/selfdrive/pandad",
|
||||||
"#rednose/helpers",
|
"#rednose_repo/rednose/helpers",
|
||||||
[x.LIB_DIR for x in pkgs],
|
[x.LIB_DIR for x in pkgs],
|
||||||
],
|
],
|
||||||
RPATH=[],
|
RPATH=[ffmpeg.LIB_DIR] if ffmpeg_shared else [],
|
||||||
CYTHONCFILESUFFIX=".cpp",
|
CYTHONCFILESUFFIX=".cpp",
|
||||||
COMPILATIONDB_USE_ABSPATH=True,
|
COMPILATIONDB_USE_ABSPATH=True,
|
||||||
REDNOSE_ROOT="#",
|
REDNOSE_ROOT="#rednose_repo",
|
||||||
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
||||||
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||||
)
|
)
|
||||||
|
# SCons' Darwin linker tool doesn't define the variables used to expand RPATH.
|
||||||
|
if arch == "Darwin":
|
||||||
|
env["RPATHPREFIX"] = "-Wl,-rpath,"
|
||||||
|
env["RPATHSUFFIX"] = ""
|
||||||
|
env["_RPATH"] = "${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}"
|
||||||
if arch != "larch64":
|
if arch != "larch64":
|
||||||
env['_LIBFLAGS'] = _libflags
|
env['_LIBFLAGS'] = _libflags
|
||||||
|
|
||||||
@@ -190,7 +230,7 @@ else:
|
|||||||
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', 'acados', 'release')
|
Export('env', 'arch', 'acados', 'release', 'ffmpeg_libs')
|
||||||
|
|
||||||
# Setup cache dir
|
# Setup cache dir
|
||||||
default_cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
|
default_cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
|
||||||
@@ -211,7 +251,7 @@ def prune_cache_dir(target=None, source=None, env=None):
|
|||||||
# ********** start building stuff **********
|
# ********** start building stuff **********
|
||||||
|
|
||||||
# Build common module
|
# Build common module
|
||||||
SConscript(['common/SConscript'])
|
SConscript(['openpilot/common/SConscript'])
|
||||||
Import('_common')
|
Import('_common')
|
||||||
common = [_common, 'json11', 'zmq']
|
common = [_common, 'json11', 'zmq']
|
||||||
Export('common')
|
Export('common')
|
||||||
@@ -222,7 +262,7 @@ env_swaglog = env.Clone()
|
|||||||
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
|
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
|
||||||
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
|
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
|
||||||
|
|
||||||
SConscript(['cereal/SConscript'])
|
SConscript(['openpilot/cereal/SConscript'])
|
||||||
|
|
||||||
Import('socketmaster', 'msgq')
|
Import('socketmaster', 'msgq')
|
||||||
messaging = [socketmaster, msgq, 'capnp', 'kj',]
|
messaging = [socketmaster, msgq, 'capnp', 'kj',]
|
||||||
@@ -233,34 +273,33 @@ Export('messaging')
|
|||||||
SConscript(['panda/SConscript'])
|
SConscript(['panda/SConscript'])
|
||||||
|
|
||||||
# Build rednose library
|
# Build rednose library
|
||||||
SConscript(['rednose/SConscript'])
|
SConscript(['rednose_repo/rednose/SConscript'])
|
||||||
|
|
||||||
# Build system services
|
# Build system services
|
||||||
SConscript([
|
SConscript([
|
||||||
'system/loggerd/SConscript',
|
'openpilot/system/loggerd/SConscript',
|
||||||
])
|
])
|
||||||
|
|
||||||
if arch == "larch64":
|
if arch == "larch64":
|
||||||
SConscript(['system/camerad/SConscript'])
|
SConscript(['openpilot/system/camerad/SConscript'])
|
||||||
|
|
||||||
# Build selfdrive
|
# Build selfdrive
|
||||||
SConscript([
|
SConscript([
|
||||||
'selfdrive/pandad/SConscript',
|
'openpilot/selfdrive/pandad/SConscript',
|
||||||
'selfdrive/controls/lib/lateral_mpc_lib/SConscript',
|
'openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript',
|
||||||
'selfdrive/controls/lib/longitudinal_mpc_lib/SConscript',
|
'openpilot/selfdrive/locationd/SConscript',
|
||||||
'selfdrive/locationd/SConscript',
|
'openpilot/selfdrive/modeld/SConscript',
|
||||||
'selfdrive/modeld/SConscript',
|
'openpilot/selfdrive/ui/SConscript',
|
||||||
'selfdrive/ui/SConscript',
|
|
||||||
])
|
])
|
||||||
|
|
||||||
SConscript(['sunnypilot/SConscript'])
|
SConscript(['openpilot/sunnypilot/SConscript'])
|
||||||
|
|
||||||
# Build desktop-only tools
|
# Build desktop-only tools
|
||||||
if GetOption('extras') and arch != "larch64":
|
if GetOption('extras') and arch != "larch64":
|
||||||
SConscript([
|
SConscript([
|
||||||
'tools/replay/SConscript',
|
'openpilot/tools/replay/SConscript',
|
||||||
'tools/cabana/SConscript',
|
'openpilot/tools/cabana/SConscript',
|
||||||
'tools/jotpluggler/SConscript',
|
'openpilot/tools/jotpluggler/SConscript',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@@ -276,6 +315,8 @@ def count_scons_nodes(nodes):
|
|||||||
if node in seen:
|
if node in seen:
|
||||||
continue
|
continue
|
||||||
seen.add(node)
|
seen.add(node)
|
||||||
|
if hasattr(node, 'has_builder') and node.has_builder():
|
||||||
|
build_product_nodes.add(node)
|
||||||
executor = node.get_executor()
|
executor = node.get_executor()
|
||||||
if executor is not None:
|
if executor is not None:
|
||||||
stack += executor.get_all_prerequisites() + executor.get_all_children()
|
stack += executor.get_all_prerequisites() + executor.get_all_children()
|
||||||
@@ -284,6 +325,7 @@ def count_scons_nodes(nodes):
|
|||||||
|
|
||||||
progress_interval = 5
|
progress_interval = 5
|
||||||
progress_count = 0
|
progress_count = 0
|
||||||
|
build_product_nodes = set()
|
||||||
progress_total = max(1, count_scons_nodes(env.arg2nodes(BUILD_TARGETS or [Dir('.')], env.fs.Entry)))
|
progress_total = max(1, count_scons_nodes(env.arg2nodes(BUILD_TARGETS or [Dir('.')], env.fs.Entry)))
|
||||||
|
|
||||||
def progress_function(node):
|
def progress_function(node):
|
||||||
@@ -299,3 +341,11 @@ def progress_function(node):
|
|||||||
|
|
||||||
Progress(progress_function, interval=progress_interval)
|
Progress(progress_function, interval=progress_interval)
|
||||||
AddPostAction(BUILD_TARGETS or [Dir('.')], prune_cache_dir)
|
AddPostAction(BUILD_TARGETS or [Dir('.')], prune_cache_dir)
|
||||||
|
|
||||||
|
def check_build_product_size(target, source, env):
|
||||||
|
limit = 50 * 1024 * 1024 # GitHub max size
|
||||||
|
for t in target:
|
||||||
|
if hasattr(t, 'isfile') and t.isfile() and (size := os.path.getsize(t.abspath)) > limit:
|
||||||
|
raise SCons.Errors.UserError(f"{t} is {size / (1024 * 1024):.1f} MiB, exceeding the {limit / (1024 * 1024):.1f} MiB limit")
|
||||||
|
if not GetOption('extras'):
|
||||||
|
AddPostAction(list(build_product_nodes), Action(check_build_product_size, None))
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
Import('env', 'common', 'msgq')
|
|
||||||
|
|
||||||
cereal_dir = Dir('.')
|
|
||||||
gen_dir = Dir('gen')
|
|
||||||
|
|
||||||
# Build cereal
|
|
||||||
schema_files = ['log.capnp', 'car.capnp', 'deprecated.capnp', 'custom.capnp']
|
|
||||||
env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files],
|
|
||||||
schema_files,
|
|
||||||
f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/")
|
|
||||||
|
|
||||||
cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in schema_files])
|
|
||||||
|
|
||||||
# Build messaging
|
|
||||||
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'])
|
|
||||||
|
|
||||||
socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc'])
|
|
||||||
|
|
||||||
Export('cereal', 'socketmaster')
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import os
|
|
||||||
import capnp
|
|
||||||
from importlib.resources import as_file, files
|
|
||||||
|
|
||||||
capnp.remove_import_hook()
|
|
||||||
|
|
||||||
with as_file(files("cereal")) as fspath:
|
|
||||||
CEREAL_PATH = fspath.as_posix()
|
|
||||||
log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"))
|
|
||||||
car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp"))
|
|
||||||
custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp"))
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../opendbc_repo/opendbc/car/car.capnp
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Schema-level cereal compat check between sunnypilot and upstream openpilot.
|
|
||||||
|
|
||||||
Rules (per struct matched across sides by typeId):
|
|
||||||
R1 shared ordinal must reference the same type.
|
|
||||||
R2 sunnypilot-only ordinal in a union -> FAIL (unknown discriminant upstream).
|
|
||||||
R3 sunnypilot-only ordinal on a regular field -> OK (additive struct evolution).
|
|
||||||
R4 upstream-only ordinal -> OK.
|
|
||||||
R5 sunnypilot-only struct referenced via an upstream-shared field -> FAIL.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
NO_DISCRIMINANT = 0xFFFF
|
|
||||||
|
|
||||||
|
|
||||||
def hex_id(value: int) -> str:
|
|
||||||
return f"0x{value:016x}"
|
|
||||||
|
|
||||||
|
|
||||||
def encode_type(type_node: Any) -> dict:
|
|
||||||
which = type_node.which()
|
|
||||||
if which == "struct":
|
|
||||||
return {"kind": "struct", "typeId": hex_id(type_node.struct.typeId)}
|
|
||||||
if which == "enum":
|
|
||||||
return {"kind": "enum", "typeId": hex_id(type_node.enum.typeId)}
|
|
||||||
if which == "interface":
|
|
||||||
return {"kind": "interface", "typeId": hex_id(type_node.interface.typeId)}
|
|
||||||
if which == "list":
|
|
||||||
return {"kind": "list", "element": encode_type(type_node.list.elementType)}
|
|
||||||
if which == "anyPointer":
|
|
||||||
return {"kind": "anyPointer"}
|
|
||||||
return {"kind": which}
|
|
||||||
|
|
||||||
|
|
||||||
def encode_field(name: str, field: Any) -> dict:
|
|
||||||
proto = field.proto
|
|
||||||
ordinal = proto.ordinal.explicit if proto.ordinal.which() == "explicit" else None
|
|
||||||
discriminant = proto.discriminantValue if proto.discriminantValue != NO_DISCRIMINANT else None
|
|
||||||
|
|
||||||
if proto.which() == "group":
|
|
||||||
type_desc = {"kind": "group", "typeId": hex_id(proto.group.typeId)}
|
|
||||||
else:
|
|
||||||
type_desc = encode_type(proto.slot.type)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"name": name,
|
|
||||||
"ordinal": ordinal,
|
|
||||||
"discriminant": discriminant,
|
|
||||||
"type": type_desc,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def encode_struct(schema: Any) -> dict:
|
|
||||||
node = schema.node
|
|
||||||
return {
|
|
||||||
"typeId": hex_id(node.id),
|
|
||||||
"displayName": node.displayName,
|
|
||||||
"hasUnion": node.struct.discriminantCount > 0,
|
|
||||||
"fields": [encode_field(name, field) for name, field in schema.fields.items()],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _child_struct_schema(field: Any) -> Any:
|
|
||||||
proto = field.proto
|
|
||||||
if proto.which() == "group":
|
|
||||||
return field.schema
|
|
||||||
type_node = proto.slot.type
|
|
||||||
which = type_node.which()
|
|
||||||
if which == "struct":
|
|
||||||
return field.schema
|
|
||||||
if which == "list":
|
|
||||||
container = field.schema
|
|
||||||
element_type = type_node.list.elementType
|
|
||||||
while element_type.which() == "list":
|
|
||||||
container = container.elementType
|
|
||||||
element_type = element_type.list.elementType
|
|
||||||
if element_type.which() == "struct":
|
|
||||||
return container.elementType
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def collect_schema(root: Any) -> dict[str, dict]:
|
|
||||||
structs: dict[str, dict] = {}
|
|
||||||
stack = [root]
|
|
||||||
while stack:
|
|
||||||
schema = stack.pop()
|
|
||||||
type_id = hex_id(schema.node.id)
|
|
||||||
if type_id in structs:
|
|
||||||
continue
|
|
||||||
structs[type_id] = encode_struct(schema)
|
|
||||||
for _name, field in schema.fields.items():
|
|
||||||
try:
|
|
||||||
child = _child_struct_schema(field)
|
|
||||||
except Exception:
|
|
||||||
child = None
|
|
||||||
if child is not None:
|
|
||||||
stack.append(child)
|
|
||||||
return structs
|
|
||||||
|
|
||||||
|
|
||||||
def load_log(cereal_dir: str) -> Any:
|
|
||||||
import capnp
|
|
||||||
cereal_dir = os.path.abspath(cereal_dir)
|
|
||||||
capnp.remove_import_hook()
|
|
||||||
return capnp.load(os.path.join(cereal_dir, "log.capnp"), imports=[cereal_dir])
|
|
||||||
|
|
||||||
|
|
||||||
def dump_schema(cereal_dir: str, path: str) -> None:
|
|
||||||
log = load_log(cereal_dir)
|
|
||||||
payload = {
|
|
||||||
"root": hex_id(log.Event.schema.node.id),
|
|
||||||
"structs": collect_schema(log.Event.schema),
|
|
||||||
}
|
|
||||||
with open(path, "w", encoding="utf-8") as handle:
|
|
||||||
json.dump(payload, handle, indent=2, sort_keys=True)
|
|
||||||
print(f"wrote schema dump with {len(payload['structs'])} structs to {path}")
|
|
||||||
|
|
||||||
|
|
||||||
def types_equal(a: dict, b: dict) -> bool:
|
|
||||||
if a.get("kind") != b.get("kind"):
|
|
||||||
return False
|
|
||||||
kind = a["kind"]
|
|
||||||
if kind in ("struct", "enum", "interface", "group"):
|
|
||||||
return a.get("typeId") == b.get("typeId")
|
|
||||||
if kind == "list":
|
|
||||||
return types_equal(a["element"], b["element"])
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def type_repr(t: dict) -> str:
|
|
||||||
kind = t.get("kind", "?")
|
|
||||||
if kind in ("struct", "enum", "interface", "group"):
|
|
||||||
return f"{kind}({t.get('typeId')})"
|
|
||||||
if kind == "list":
|
|
||||||
return f"list<{type_repr(t['element'])}>"
|
|
||||||
return kind
|
|
||||||
|
|
||||||
|
|
||||||
def field_is_union_variant(field: dict) -> bool:
|
|
||||||
return field.get("discriminant") is not None
|
|
||||||
|
|
||||||
|
|
||||||
def index_fields_by_ordinal(struct: dict) -> dict[int, dict]:
|
|
||||||
indexed: dict[int, dict] = {}
|
|
||||||
for field in struct["fields"]:
|
|
||||||
ordinal = field.get("ordinal")
|
|
||||||
if ordinal is None:
|
|
||||||
continue
|
|
||||||
indexed[ordinal] = field
|
|
||||||
return indexed
|
|
||||||
|
|
||||||
|
|
||||||
def compare(sunnypilot_dump: dict, upstream_dump: dict) -> list[str]:
|
|
||||||
violations: list[str] = []
|
|
||||||
sunnypilot_structs: dict[str, dict] = sunnypilot_dump["structs"]
|
|
||||||
upstream_structs: dict[str, dict] = upstream_dump["structs"]
|
|
||||||
|
|
||||||
sunnypilot_struct_referenced_from_shared: set[str] = set()
|
|
||||||
|
|
||||||
for type_id, sunnypilot_struct in sunnypilot_structs.items():
|
|
||||||
upstream_struct = upstream_structs.get(type_id)
|
|
||||||
if upstream_struct is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
sunnypilot_fields = index_fields_by_ordinal(sunnypilot_struct)
|
|
||||||
upstream_fields = index_fields_by_ordinal(upstream_struct)
|
|
||||||
display = sunnypilot_struct["displayName"]
|
|
||||||
|
|
||||||
for ordinal, sunnypilot_field in sunnypilot_fields.items():
|
|
||||||
upstream_field = upstream_fields.get(ordinal)
|
|
||||||
if upstream_field is None:
|
|
||||||
if field_is_union_variant(sunnypilot_field):
|
|
||||||
violations.append(
|
|
||||||
f"[R2] {display} @{ordinal} ('{sunnypilot_field['name']}', {type_repr(sunnypilot_field['type'])}): "
|
|
||||||
f"union variant not present upstream. upstream cannot parse this discriminant."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not types_equal(sunnypilot_field["type"], upstream_field["type"]):
|
|
||||||
violations.append(
|
|
||||||
f"[R1] {display} @{ordinal}: type mismatch. "
|
|
||||||
f"sunnypilot='{sunnypilot_field['name']}' {type_repr(sunnypilot_field['type'])} vs "
|
|
||||||
f"upstream='{upstream_field['name']}' {type_repr(upstream_field['type'])}."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
cursor = sunnypilot_field["type"]
|
|
||||||
while cursor.get("kind") == "list":
|
|
||||||
cursor = cursor["element"]
|
|
||||||
if cursor.get("kind") in ("struct", "group", "interface") and cursor.get("typeId"):
|
|
||||||
sunnypilot_struct_referenced_from_shared.add(cursor["typeId"])
|
|
||||||
|
|
||||||
for type_id, sunnypilot_struct in sunnypilot_structs.items():
|
|
||||||
if type_id in upstream_structs:
|
|
||||||
continue
|
|
||||||
if type_id in sunnypilot_struct_referenced_from_shared:
|
|
||||||
violations.append(
|
|
||||||
f"[R5] struct {sunnypilot_struct['displayName']} ({type_id}) exists only on sunnypilot "
|
|
||||||
f"but is referenced from an upstream-shared field. upstream cannot resolve this type."
|
|
||||||
)
|
|
||||||
|
|
||||||
return violations
|
|
||||||
|
|
||||||
|
|
||||||
def load_peer(path: str) -> dict:
|
|
||||||
with open(path, "r", encoding="utf-8") as handle:
|
|
||||||
return json.load(handle)
|
|
||||||
|
|
||||||
|
|
||||||
def run_read(cereal_dir: str, peer_path: str) -> int:
|
|
||||||
log = load_log(cereal_dir)
|
|
||||||
peer_dump = load_peer(peer_path)
|
|
||||||
local_dump = {
|
|
||||||
"root": hex_id(log.Event.schema.node.id),
|
|
||||||
"structs": collect_schema(log.Event.schema),
|
|
||||||
}
|
|
||||||
violations = compare(sunnypilot_dump=peer_dump, upstream_dump=local_dump)
|
|
||||||
|
|
||||||
if not violations:
|
|
||||||
print("cereal compat OK: upstream openpilot can parse sunnypilot routes "
|
|
||||||
"(no leaked structs, no ordinal collisions).")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
print(f"cereal compat FAIL: upstream openpilot would misparse sunnypilot routes "
|
|
||||||
f"({len(violations)} violation(s)):")
|
|
||||||
for v in violations:
|
|
||||||
print(f" {v}")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="sunnypilot <-> upstream cereal compatibility validator (schema-level)."
|
|
||||||
)
|
|
||||||
mode = parser.add_mutually_exclusive_group(required=True)
|
|
||||||
mode.add_argument("-g", "--generate", action="store_true", help="dump local schema to JSON")
|
|
||||||
mode.add_argument("-r", "--read", action="store_true", help="load peer JSON and diff against local")
|
|
||||||
parser.add_argument("-f", "--file", default="schema.json", help="JSON file path (default: schema.json)")
|
|
||||||
parser.add_argument("--cereal-dir", required=True, help="path to cereal directory containing log.capnp")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.generate:
|
|
||||||
dump_schema(args.cereal_dir, args.file)
|
|
||||||
return 0
|
|
||||||
return run_read(args.cereal_dir, args.file)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#include "catch2/catch.hpp"
|
|
||||||
#define private public
|
|
||||||
#include "common/params.h"
|
|
||||||
#include "common/util.h"
|
|
||||||
|
|
||||||
TEST_CASE("params_nonblocking_put") {
|
|
||||||
char tmp_path[] = "/tmp/asyncWriter_XXXXXX";
|
|
||||||
const std::string param_path = mkdtemp(tmp_path);
|
|
||||||
auto param_names = {"CarParams", "IsMetric"};
|
|
||||||
{
|
|
||||||
Params params(param_path);
|
|
||||||
for (const auto &name : param_names) {
|
|
||||||
params.putNonBlocking(name, "1");
|
|
||||||
// param is empty
|
|
||||||
REQUIRE(params.get(name).empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if thread is running
|
|
||||||
REQUIRE(params.future.valid());
|
|
||||||
REQUIRE(params.future.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout);
|
|
||||||
}
|
|
||||||
// check results
|
|
||||||
Params p(param_path);
|
|
||||||
for (const auto &name : param_names) {
|
|
||||||
REQUIRE(p.get(name) == "1");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+13
-7
@@ -5,21 +5,27 @@ import pytest
|
|||||||
|
|
||||||
from openpilot.common.prefix import OpenpilotPrefix
|
from openpilot.common.prefix import OpenpilotPrefix
|
||||||
from openpilot.system.manager import manager
|
from openpilot.system.manager import manager
|
||||||
from openpilot.system.hardware import TICI, HARDWARE
|
from openpilot.common.hardware import TICI, HARDWARE
|
||||||
|
|
||||||
# these are heavy CI-only tests, invoked explicitly in .github/workflows/tests.yaml
|
# these are heavy CI-only tests, invoked explicitly in .github/workflows/tests.yaml
|
||||||
collect_ignore = [
|
collect_ignore = [
|
||||||
"selfdrive/test/process_replay/test_processes.py",
|
"openpilot/selfdrive/test/process_replay/test_processes.py",
|
||||||
"selfdrive/test/process_replay/test_regen.py",
|
"openpilot/selfdrive/test/process_replay/test_regen.py",
|
||||||
|
|
||||||
|
"openpilot/tools/sim/",
|
||||||
|
|
||||||
# tinygrad JIT has process-global state. Other test files import modeld → tinygrad,
|
# tinygrad JIT has process-global state. Other test files import modeld → tinygrad,
|
||||||
# which corrupts JIT captures for test_warp.py in the same process. Run separately in CI.
|
# which corrupts JIT captures for test_warp.py in the same process. Run separately in CI.
|
||||||
"sunnypilot/modeld_v2/tests/test_warp.py",
|
"openpilot/sunnypilot/modeld_v2/tests/test_warp.py",
|
||||||
]
|
|
||||||
collect_ignore_glob = [
|
|
||||||
"selfdrive/debug/*.py",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_sessionstart(session):
|
||||||
|
# TODO: fix tests and enable test order randomization
|
||||||
|
if session.config.pluginmanager.hasplugin('randomly'):
|
||||||
|
session.config.option.randomly_reorganize = False
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, trylast=True)
|
@pytest.hookimpl(hookwrapper=True, trylast=True)
|
||||||
def pytest_runtest_call(item):
|
def pytest_runtest_call(item):
|
||||||
# ensure we run as a hook after capturemanager's
|
# ensure we run as a hook after capturemanager's
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# AI policy
|
||||||
|
|
||||||
|
## Why this exists
|
||||||
|
|
||||||
|
We use AI tools ourselves, so this isn't an anti-AI stance. The problem is people submitting code, issues, or comments they don't actually understand. AI makes that very easy to do, and it creates real work for reviewers who have to figure out what you meant when you can't explain it yourself.
|
||||||
|
|
||||||
|
If you're not going to put effort into understanding and verifying your submission, we're not going to put effort into reviewing it.
|
||||||
|
|
||||||
|
## The rule
|
||||||
|
|
||||||
|
You are responsible for everything you submit: code, PR descriptions, issues, bug reports, comments.
|
||||||
|
|
||||||
|
1. Understand what you submit. If a reviewer asks why you did something, you answer from your own understanding, not by re-prompting. If you can't do that, don't submit it.
|
||||||
|
|
||||||
|
2. Test your change. AI gets things wrong all the time. Run it, break it, confirm it actually works.
|
||||||
|
|
||||||
|
3. Driving fixes need real evidence. Attach a dongle ID, upload logs, and include segments that show the fix working. A route hash by itself proves nothing.
|
||||||
|
|
||||||
|
4. No AI-generated media (images, diagrams, videos) in issues or PRs.
|
||||||
|
|
||||||
|
## Disclosure
|
||||||
|
|
||||||
|
If AI tools helped you write something, say so. Add an `Assisted-by:` line in your commit message:
|
||||||
|
|
||||||
|
```
|
||||||
|
Assisted-by: GitHub Copilot
|
||||||
|
Assisted-by: Claude
|
||||||
|
```
|
||||||
|
|
||||||
|
Disclosing won't count against your PR. It helps reviewers know where to look. Hiding it and getting caught will.
|
||||||
|
|
||||||
|
## How we review
|
||||||
|
|
||||||
|
Reviewers are looking at whether you understand your own change. Can you explain it? Can you respond to feedback without re-prompting? Does your PR description say why you made the change, not just list what changed?
|
||||||
|
|
||||||
|
Good code from someone who used AI and understands what they wrote is fine. How you got there doesn't matter as long as you can stand behind it.
|
||||||
|
|
||||||
|
## What happens
|
||||||
|
|
||||||
|
Submissions that don't meet this bar get closed. If it keeps happening, you get blocked.
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
Maintainers use AI at their discretion. They've earned that through sustained contribution and they know the codebase.
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
> sunnypilot follows [commaai/openpilot](https://github.com/commaai/openpilot)'s contributing guidelines. The following applies to all contributions here.
|
||||||
|
|
||||||
# How to contribute
|
# How to contribute
|
||||||
|
|
||||||
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/).
|
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/).
|
||||||
@@ -35,6 +37,7 @@ All of these are examples of good PRs:
|
|||||||
* **UI design**: we do not have a good review process for this yet
|
* **UI design**: we do not have a good review process for this yet
|
||||||
* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't.
|
* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't.
|
||||||
* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged.
|
* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged.
|
||||||
|
* **AI-generated contributions**: see our [AI policy](AI_POLICY.md)
|
||||||
|
|
||||||
### First contribution
|
### First contribution
|
||||||
|
|
||||||
@@ -68,6 +71,6 @@ A good pull request has all of the following:
|
|||||||
### A guide for forks
|
### A guide for forks
|
||||||
|
|
||||||
In order for your fork's data to be eligible for the training set:
|
In order for your fork's data to be eligible for the training set:
|
||||||
* **Your cereal messaging structs must be [compatible](../cereal#custom-forks)**
|
* **Your cereal messaging structs must be [compatible](../openpilot/cereal#custom-forks)**
|
||||||
* **The definitions of all the stock messaging structs must not change**: Do not change how any of the fields are set, including everything from `selfdriveState.enabled` to `carState.steeringAngleDeg`. Instead, create your own structs and set them however you'd like.
|
* **The definitions of all the stock messaging structs must not change**: Do not change how any of the fields are set, including everything from `selfdriveState.enabled` to `carState.steeringAngleDeg`. Instead, create your own structs and set them however you'd like.
|
||||||
* **Do not include cars that are not supported in upstream platforms**: Instead, create new opendbc platforms for cars that you'd like to support outside of upstream, even if it's just a trim-level difference.
|
* **Do not include cars that are not supported in upstream platforms**: Instead, create new opendbc platforms for cars that you'd like to support outside of upstream, even if it's just a trim-level difference.
|
||||||
|
|||||||
+2
-2
@@ -35,8 +35,8 @@ For additional safety implementation details, refer to [panda safety model](http
|
|||||||
|
|
||||||
### Forks of openpilot
|
### Forks of openpilot
|
||||||
|
|
||||||
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/selfdrive/monitoring)
|
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/openpilot/selfdrive/monitoring)
|
||||||
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/selfdrive/selfdrived/helpers.py)
|
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/openpilot/selfdrive/selfdrived/helpers.py)
|
||||||
* If your fork modifies any of the code in `opendbc/safety/`:
|
* If your fork modifies any of the code in `opendbc/safety/`:
|
||||||
* your fork cannot use the openpilot trademark
|
* your fork cannot use the openpilot trademark
|
||||||
* your fork must preserve the full [safety test suite](https://github.com/commaai/opendbc/tree/master/opendbc/safety/tests) and all tests must pass, including any new coverage required by the fork's changes
|
* your fork must preserve the full [safety test suite](https://github.com/commaai/opendbc/tree/master/opendbc/safety/tests) and all tests must pass, including any new coverage required by the fork's changes
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
openpilot records routes in one minute chunks called segments. A route starts on the rising edge of ignition and ends on the falling edge.
|
openpilot records routes in one minute chunks called segments. A route starts on the rising edge of ignition and ends on the falling edge.
|
||||||
|
|
||||||
Check out our [Python library](https://github.com/commaai/openpilot/blob/master/tools/lib/logreader.py) for reading openpilot logs. Also checkout our [tools](https://github.com/commaai/openpilot/tree/master/tools) to replay and view your data. These are the same tools we use to debug and develop openpilot.
|
Check out our [Python library](https://github.com/commaai/openpilot/blob/master/openpilot/tools/lib/logreader.py) for reading openpilot logs. Also checkout our [tools](https://github.com/commaai/openpilot/tree/master/openpilot/tools) to replay and view your data. These are the same tools we use to debug and develop openpilot.
|
||||||
|
|
||||||
For each segment, openpilot records the following log types:
|
For each segment, openpilot records the following log types:
|
||||||
|
|
||||||
## rlog.zst
|
## rlog.zst
|
||||||
|
|
||||||
rlogs contain all the messages passed amongst openpilot's processes. See [cereal/services.py](https://github.com/commaai/openpilot/blob/master/cereal/services.py) for a list of all the logged services. They're a zstd archive of the serialized [Cap’n Proto](https://capnproto.org/) messages.
|
rlogs contain all the messages passed amongst openpilot's processes. See [openpilot/cereal/services.py](https://github.com/commaai/openpilot/blob/master/openpilot/cereal/services.py) for a list of all the logged services. They're a zstd archive of the serialized [Cap’n Proto](https://capnproto.org/) messages.
|
||||||
|
|
||||||
## {f,e,d}camera.hevc
|
## {f,e,d}camera.hevc
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ Each camera stream is H.265 encoded and written to its respective file.
|
|||||||
|
|
||||||
## qlog.zst & qcamera.ts
|
## qlog.zst & qcamera.ts
|
||||||
|
|
||||||
qlogs are a decimated subset of the rlogs. Check out [cereal/services.py](https://github.com/commaai/cereal/blob/master/services.py) for the decimation.
|
qlogs are a decimated subset of the rlogs. Check out [openpilot/cereal/services.py](https://github.com/commaai/openpilot/blob/master/openpilot/cereal/services.py) for the decimation.
|
||||||
|
|
||||||
qcameras are H.264 encoded, lower res versions of the fcamera.hevc. The video shown in [comma connect](https://connect.comma.ai/) is from the qcameras.
|
qcameras are H.264 encoded, lower res versions of the fcamera.hevc. The video shown in [comma connect](https://connect.comma.ai/) is from the qcameras.
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Each car brand is supported by a standard interface structure in `opendbc/car/[b
|
|||||||
|
|
||||||
For historical reasons, openpilot still contains a small amount of car-specific logic. This will eventually be migrated to opendbc or otherwise removed.
|
For historical reasons, openpilot still contains a small amount of car-specific logic. This will eventually be migrated to opendbc or otherwise removed.
|
||||||
|
|
||||||
* `selfdrive/car/car_specific.py`: Brand-specific event logic
|
* `openpilot/selfdrive/car/car_specific.py`: Brand-specific event logic
|
||||||
|
|
||||||
# How do I port car?
|
# How do I port car?
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Replaying is a critical tool for openpilot development and debugging.
|
|||||||
## Replaying a route
|
## Replaying a route
|
||||||
*Hardware required: none*
|
*Hardware required: none*
|
||||||
|
|
||||||
Just run `tools/replay/replay --demo`.
|
Just run `openpilot/tools/replay/replay --demo`.
|
||||||
|
|
||||||
## Replaying CAN data
|
## Replaying CAN data
|
||||||
*Hardware required: jungle and comma four*
|
*Hardware required: jungle and comma four*
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ scons
|
|||||||
We'll run the `replay` tool with the demo route to get data streaming for testing our UI changes.
|
We'll run the `replay` tool with the demo route to get data streaming for testing our UI changes.
|
||||||
```bash
|
```bash
|
||||||
# in terminal 1
|
# in terminal 1
|
||||||
tools/replay/replay --demo
|
openpilot/tools/replay/replay --demo
|
||||||
|
|
||||||
# in terminal 2
|
# in terminal 2
|
||||||
./selfdrive/ui/ui.py
|
./openpilot/selfdrive/ui/ui.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The openpilot UI should launch and show a replay of the demo route.
|
The openpilot UI should launch and show a replay of the demo route.
|
||||||
@@ -45,10 +45,10 @@ Now let’s update the speed display color in the UI.
|
|||||||
|
|
||||||
Search for the function responsible for rendering the current speed:
|
Search for the function responsible for rendering the current speed:
|
||||||
```bash
|
```bash
|
||||||
git grep "_draw_current_speed" selfdrive/ui/onroad/hud_renderer.py
|
git grep "_draw_current_speed" openpilot/selfdrive/ui/onroad/hud_renderer.py
|
||||||
```
|
```
|
||||||
|
|
||||||
You'll find the relevant code inside `selfdrive/ui/onroad/hud_renderer.py`, in this function:
|
You'll find the relevant code inside `openpilot/selfdrive/ui/onroad/hud_renderer.py`, in this function:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def _draw_current_speed(self, rect: rl.Rectangle) -> None:
|
def _draw_current_speed(self, rect: rl.Rectangle) -> None:
|
||||||
@@ -72,7 +72,7 @@ Change `COLORS.white` to make it **blue** instead of white. A nice soft blue is
|
|||||||
|
|
||||||
After making changes, re-run the UI to see your new UI:
|
After making changes, re-run the UI to see your new UI:
|
||||||
```bash
|
```bash
|
||||||
./selfdrive/ui/ui.py
|
./openpilot/selfdrive/ui/ui.py
|
||||||
```
|
```
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
+12
-4
@@ -19,12 +19,12 @@ function agnos_init {
|
|||||||
|
|
||||||
# Check if AGNOS update is required
|
# Check if AGNOS update is required
|
||||||
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
|
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
|
||||||
AGNOS_PY="$DIR/system/hardware/tici/agnos.py"
|
AGNOS_PY="$DIR/openpilot/common/hardware/tici/agnos.py"
|
||||||
MANIFEST="$DIR/system/hardware/tici/agnos.json"
|
MANIFEST="$DIR/openpilot/system/hardware/tici/agnos.json"
|
||||||
if $AGNOS_PY --verify $MANIFEST; then
|
if $AGNOS_PY --verify $MANIFEST; then
|
||||||
sudo reboot
|
sudo reboot
|
||||||
fi
|
fi
|
||||||
$DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST
|
$DIR/openpilot/common/hardware/tici/updater $AGNOS_PY $MANIFEST
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +70,14 @@ function launch {
|
|||||||
ln -sfn $(pwd) /data/pythonpath
|
ln -sfn $(pwd) /data/pythonpath
|
||||||
export PYTHONPATH="$PWD"
|
export PYTHONPATH="$PWD"
|
||||||
|
|
||||||
|
# submodule package symlinks for PYTHONPATH imports on device.
|
||||||
|
# on PC these come from editable installs via pyproject.toml / uv.
|
||||||
|
ln -sfn msgq_repo/msgq msgq
|
||||||
|
ln -sfn opendbc_repo/opendbc opendbc
|
||||||
|
ln -sfn rednose_repo/rednose rednose
|
||||||
|
ln -sfn teleoprtc_repo/teleoprtc teleoprtc
|
||||||
|
ln -sfn tinygrad_repo/tinygrad tinygrad
|
||||||
|
|
||||||
# hardware specific init
|
# hardware specific init
|
||||||
if [ -f /AGNOS ]; then
|
if [ -f /AGNOS ]; then
|
||||||
agnos_init
|
agnos_init
|
||||||
@@ -79,7 +87,7 @@ function launch {
|
|||||||
tmux capture-pane -pq -S-1000 > /tmp/launch_log
|
tmux capture-pane -pq -S-1000 > /tmp/launch_log
|
||||||
|
|
||||||
# start manager
|
# start manager
|
||||||
cd system/manager
|
cd openpilot/system/manager
|
||||||
if [ ! -f $DIR/prebuilt ]; then
|
if [ ! -f $DIR/prebuilt ]; then
|
||||||
./build.py
|
./build.py
|
||||||
fi
|
fi
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
|
|||||||
export QCOM_PRIORITY=12
|
export QCOM_PRIORITY=12
|
||||||
|
|
||||||
if [ -z "$AGNOS_VERSION" ]; then
|
if [ -z "$AGNOS_VERSION" ]; then
|
||||||
export AGNOS_VERSION="18.4"
|
export AGNOS_VERSION="18.5"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export STAGING_ROOT="/data/safe_staging"
|
export STAGING_ROOT="/data/safe_staging"
|
||||||
|
|||||||
@@ -1,20 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
|
||||||
IFS=$'\n\t'
|
|
||||||
|
|
||||||
# On any failure, run the fallback launcher
|
|
||||||
trap 'exec ./launch_chffrplus.sh' ERR
|
|
||||||
C3_LAUNCH_SH="./sunnypilot/system/hardware/c3/launch_chffrplus.sh"
|
|
||||||
|
|
||||||
MODEL="$(tr -d '\0' < "/sys/firmware/devicetree/base/model")"
|
|
||||||
export MODEL
|
|
||||||
|
|
||||||
if [ "$MODEL" = "comma tici" ]; then
|
|
||||||
# Force a failure if the launcher doesn't exist
|
|
||||||
[ -x "$C3_LAUNCH_SH" ] || false
|
|
||||||
|
|
||||||
# If it exists, run it
|
|
||||||
exec "$C3_LAUNCH_SH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec ./launch_chffrplus.sh
|
exec ./launch_chffrplus.sh
|
||||||
|
|||||||
+1
-1
Submodule msgq_repo updated: 9beb84af67...bb6cc57ef4
+1
-1
Submodule opendbc_repo updated: b9712d20ef...063414f63f
@@ -24,17 +24,17 @@ things are not. Read more details [here](https://capnproto.org/language.html).
|
|||||||
### Custom forks
|
### Custom forks
|
||||||
|
|
||||||
Forks of [openpilot](https://github.com/commaai/openpilot) might want to add things to the messaging
|
Forks of [openpilot](https://github.com/commaai/openpilot) might want to add things to the messaging
|
||||||
spec, however this could conflict with future changes made in mainline cereal/openpilot. Rebasing against mainline openpilot
|
spec, however this could conflict with future changes made in mainline openpilot's cereal spec. Rebasing against mainline openpilot
|
||||||
then means breaking backwards-compatibility with all old logs of your fork. So we added reserved events in
|
then means breaking backwards-compatibility with all old logs of your fork. So we added reserved events in
|
||||||
[custom.capnp](custom.capnp) that we will leave empty in mainline cereal/openpilot. **If you only modify those, you can ensure your
|
[custom.capnp](custom.capnp) that we will leave empty in mainline openpilot's cereal spec. **If you only modify those, you can ensure your
|
||||||
fork will remain backwards-compatible with all versions of mainline openpilot and your fork.**
|
fork will remain backwards-compatible with all versions of mainline openpilot and your fork.**
|
||||||
|
|
||||||
An example of compatible changes:
|
An example of compatible changes:
|
||||||
```diff
|
```diff
|
||||||
diff --git a/cereal/custom.capnp b/cereal/custom.capnp
|
diff --git a/openpilot/cereal/custom.capnp b/openpilot/cereal/custom.capnp
|
||||||
index 3348e859e..3365c7b98 100644
|
index 3348e859e..3365c7b98 100644
|
||||||
--- a/cereal/custom.capnp
|
--- a/openpilot/cereal/custom.capnp
|
||||||
+++ b/cereal/custom.capnp
|
+++ b/openpilot/cereal/custom.capnp
|
||||||
@@ -10,7 +10,11 @@ $Cxx.namespace("cereal");
|
@@ -10,7 +10,11 @@ $Cxx.namespace("cereal");
|
||||||
# DO rename the structs
|
# DO rename the structs
|
||||||
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
||||||
@@ -48,10 +48,10 @@ index 3348e859e..3365c7b98 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct CustomReserved1 @0xaedffd8f31e7b55d {
|
struct CustomReserved1 @0xaedffd8f31e7b55d {
|
||||||
diff --git a/cereal/log.capnp b/cereal/log.capnp
|
diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp
|
||||||
index 1209f3fd9..b189f58b6 100644
|
index 1209f3fd9..b189f58b6 100644
|
||||||
--- a/cereal/log.capnp
|
--- a/openpilot/cereal/log.capnp
|
||||||
+++ b/cereal/log.capnp
|
+++ b/openpilot/cereal/log.capnp
|
||||||
@@ -2558,14 +2558,14 @@ struct Event {
|
@@ -2558,14 +2558,14 @@ struct Event {
|
||||||
|
|
||||||
# DO change the name of the field
|
# DO change the name of the field
|
||||||
@@ -76,7 +76,7 @@ index 1209f3fd9..b189f58b6 100644
|
|||||||
Example
|
Example
|
||||||
---
|
---
|
||||||
```python
|
```python
|
||||||
import cereal.messaging as messaging
|
import openpilot.cereal.messaging as messaging
|
||||||
|
|
||||||
# in subscriber
|
# in subscriber
|
||||||
sm = messaging.SubMaster(['sensorEvents'])
|
sm = messaging.SubMaster(['sensorEvents'])
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
Import('env', 'common', 'msgq')
|
||||||
|
|
||||||
|
cereal_dir = Dir('.')
|
||||||
|
gen_dir = Dir('gen')
|
||||||
|
|
||||||
|
# Build cereal
|
||||||
|
schema_files = ['log.capnp', 'deprecated.capnp', 'custom.capnp']
|
||||||
|
car_capnp = '#opendbc_repo/opendbc/car/car.capnp'
|
||||||
|
all_output = schema_files + ['car.capnp']
|
||||||
|
env.Command([f'gen/cpp/{s}.c++' for s in all_output] + [f'gen/cpp/{s}.h' for s in all_output],
|
||||||
|
schema_files + [car_capnp],
|
||||||
|
f"capnpc --src-prefix={cereal_dir.path} --src-prefix=opendbc_repo/opendbc/car --import-path=opendbc_repo/opendbc/car $SOURCES -o c++:{gen_dir.path}/cpp/")
|
||||||
|
|
||||||
|
cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in all_output])
|
||||||
|
|
||||||
|
# Build messaging
|
||||||
|
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'])
|
||||||
|
|
||||||
|
socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc'])
|
||||||
|
|
||||||
|
Export('cereal', 'socketmaster')
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import os
|
||||||
|
import capnp
|
||||||
|
from importlib.resources import as_file, files
|
||||||
|
|
||||||
|
capnp.remove_import_hook()
|
||||||
|
|
||||||
|
with as_file(files("openpilot.cereal")) as fspath, as_file(files("opendbc")) as opendbc_path:
|
||||||
|
CEREAL_PATH = fspath.as_posix()
|
||||||
|
opendbc_import_path = os.path.join(os.path.realpath(opendbc_path.as_posix()), 'car')
|
||||||
|
log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"), imports=[opendbc_import_path])
|
||||||
|
custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp"), imports=[opendbc_import_path])
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Cxx = import "./include/c++.capnp";
|
using Cxx = import "/include/c++.capnp";
|
||||||
$Cxx.namespace("cereal");
|
$Cxx.namespace("cereal");
|
||||||
|
|
||||||
@0xb526ba661d550a59;
|
@0xb526ba661d550a59;
|
||||||
@@ -69,6 +69,8 @@ struct LeadData {
|
|||||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||||
mads @0 :ModularAssistiveDrivingSystem;
|
mads @0 :ModularAssistiveDrivingSystem;
|
||||||
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
||||||
|
buttonsPressed @2 :UInt16;
|
||||||
|
buttonsReleaseToggle @3 :UInt16;
|
||||||
|
|
||||||
enum AudibleAlert {
|
enum AudibleAlert {
|
||||||
none @0;
|
none @0;
|
||||||
@@ -137,10 +139,16 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
|||||||
eta @2 :UInt32;
|
eta @2 :UInt32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Chunk {
|
||||||
|
fileName @0 :Text;
|
||||||
|
sha256 @1 :Text;
|
||||||
|
}
|
||||||
|
|
||||||
struct Artifact {
|
struct Artifact {
|
||||||
fileName @0 :Text;
|
fileName @0 :Text;
|
||||||
downloadUri @1 :DownloadUri;
|
downloadUri @1 :DownloadUri;
|
||||||
downloadProgress @2 :DownloadProgress;
|
downloadProgress @2 :DownloadProgress;
|
||||||
|
chunks @3 :List(Chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Model {
|
struct Model {
|
||||||
@@ -155,6 +163,7 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
|||||||
policy @3;
|
policy @3;
|
||||||
offPolicy @4;
|
offPolicy @4;
|
||||||
onPolicy @5;
|
onPolicy @5;
|
||||||
|
chunked @6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Cxx = import "./include/c++.capnp";
|
using Cxx = import "/include/c++.capnp";
|
||||||
$Cxx.namespace("cereal");
|
$Cxx.namespace("cereal");
|
||||||
|
|
||||||
@0x80ef1ec4889c2a63;
|
@0x80ef1ec4889c2a63;
|
||||||
@@ -760,18 +760,6 @@ struct LateralLQRState @0x9024e2d790c82ade {
|
|||||||
steeringAngleDesiredDeg @6 :Float32;
|
steeringAngleDesiredDeg @6 :Float32;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LateralCurvatureState @0xad9d8095c06f7c61 {
|
|
||||||
active @0 :Bool;
|
|
||||||
actualCurvature @1 :Float32;
|
|
||||||
desiredCurvature @2 :Float32;
|
|
||||||
error @3 :Float32;
|
|
||||||
p @4 :Float32;
|
|
||||||
i @5 :Float32;
|
|
||||||
f @6 :Float32;
|
|
||||||
output @7 :Float32;
|
|
||||||
saturated @8 :Bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LateralPlannerSolution @0x84caeca5a6b4acfe {
|
struct LateralPlannerSolution @0x84caeca5a6b4acfe {
|
||||||
x @0 :List(Float32);
|
x @0 :List(Float32);
|
||||||
y @1 :List(Float32);
|
y @1 :List(Float32);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Cxx = import "./include/c++.capnp";
|
using Cxx = import "/include/c++.capnp";
|
||||||
$Cxx.namespace("cereal");
|
$Cxx.namespace("cereal");
|
||||||
|
|
||||||
using Car = import "car.capnp";
|
using Car = import "/car.capnp";
|
||||||
using Deprecated = import "deprecated.capnp";
|
using Deprecated = import "deprecated.capnp";
|
||||||
using Custom = import "custom.capnp";
|
using Custom = import "custom.capnp";
|
||||||
|
|
||||||
@@ -414,6 +414,10 @@ struct CanData {
|
|||||||
struct DeviceState @0xa4d8b5af2aa492eb {
|
struct DeviceState @0xa4d8b5af2aa492eb {
|
||||||
deviceType @45 :InitData.DeviceType;
|
deviceType @45 :InitData.DeviceType;
|
||||||
|
|
||||||
|
# usb
|
||||||
|
chestnutPresent @51 :Bool;
|
||||||
|
usbState @52 :UsbState;
|
||||||
|
|
||||||
networkType @22 :NetworkType;
|
networkType @22 :NetworkType;
|
||||||
networkInfo @31 :NetworkInfo;
|
networkInfo @31 :NetworkInfo;
|
||||||
networkStrength @24 :NetworkStrength;
|
networkStrength @24 :NetworkStrength;
|
||||||
@@ -684,33 +688,47 @@ struct PeripheralState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct UsbState {
|
||||||
|
devices @0 :List(Device);
|
||||||
|
|
||||||
|
struct Device {
|
||||||
|
busnum @0 :UInt8;
|
||||||
|
devnum @1 :UInt8;
|
||||||
|
vendorId @2 :UInt16;
|
||||||
|
productId @3 :UInt16;
|
||||||
|
speedMbps @4 :UInt16;
|
||||||
|
manufacturer @6 :Text;
|
||||||
|
product @5 :Text;
|
||||||
|
linkErrorCount @7 :UInt16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct RadarState @0x9a185389d6fdd05f {
|
struct RadarState @0x9a185389d6fdd05f {
|
||||||
mdMonoTime @6 :UInt64;
|
mdMonoTime @6 :UInt64; # for debugging
|
||||||
carStateMonoTime @11 :UInt64;
|
|
||||||
radarErrors @13 :Car.RadarData.Error;
|
radarErrors @13 :Car.RadarData.Error;
|
||||||
|
|
||||||
leadOne @3 :LeadData;
|
leadOne @3 :LeadData;
|
||||||
leadTwo @4 :LeadData;
|
leadTwo @4 :LeadData;
|
||||||
|
|
||||||
struct LeadData {
|
struct LeadData {
|
||||||
dRel @0 :Float32;
|
dRel @0 :Float32; # m from the front bumper of the car
|
||||||
yRel @1 :Float32;
|
yRel @1 :Float32; # m in car frame, left positive
|
||||||
vRel @2 :Float32;
|
vRel @2 :Float32; # m/s relative longitudinal speed
|
||||||
aRel @3 :Float32;
|
vLead @4 :Float32; # m/s absolute lead speed
|
||||||
vLead @4 :Float32;
|
vLeadK @8 :Float32; # kalman-filtered lead speed
|
||||||
dPath @6 :Float32;
|
aLeadK @9 :Float32; # kalman-filtered lead accel
|
||||||
vLat @7 :Float32;
|
present @11 :Bool; # true if a lead is present
|
||||||
vLeadK @8 :Float32;
|
aLeadTau @12 :Float32; # lead accel time constant
|
||||||
aLeadK @9 :Float32;
|
modelProb @13 :Float32; # vision model lead probability
|
||||||
fcw @10 :Bool;
|
radar @14 :Bool; # true if lead is radar-matched (vs vision-only)
|
||||||
status @11 :Bool;
|
radarTrackId @15 :Int32 = -1; # for debugging
|
||||||
aLeadTau @12 :Float32;
|
|
||||||
modelProb @13 :Float32;
|
|
||||||
radar @14 :Bool;
|
|
||||||
radarTrackId @15 :Int32 = -1;
|
|
||||||
|
|
||||||
deprecated :group {
|
deprecated :group {
|
||||||
|
aRel @3 :Float32;
|
||||||
aLead @5 :Float32;
|
aLead @5 :Float32;
|
||||||
|
dPath @6 :Float32;
|
||||||
|
vLat @7 :Float32;
|
||||||
|
fcw @10 :Bool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,6 +741,7 @@ struct RadarState @0x9a185389d6fdd05f {
|
|||||||
calPerc @9 :Int8;
|
calPerc @9 :Int8;
|
||||||
canMonoTimes @10 :List(UInt64);
|
canMonoTimes @10 :List(UInt64);
|
||||||
cumLagMs @5 :Float32;
|
cumLagMs @5 :Float32;
|
||||||
|
carStateMonoTime @11 :UInt64;
|
||||||
radarErrors @12 :List(Car.RadarData.ErrorDEPRECATED);
|
radarErrors @12 :List(Car.RadarData.ErrorDEPRECATED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -771,13 +790,31 @@ struct SelfdriveState {
|
|||||||
alertStatus @5 :AlertStatus;
|
alertStatus @5 :AlertStatus;
|
||||||
alertSize @6 :AlertSize;
|
alertSize @6 :AlertSize;
|
||||||
alertType @7 :Text;
|
alertType @7 :Text;
|
||||||
alertSound @8 :Car.CarControl.HUDControl.AudibleAlert;
|
alertSound @13 :AudibleAlert;
|
||||||
alertHudVisual @12 :Car.CarControl.HUDControl.VisualAlert;
|
alertHudVisual @12 :Car.CarControl.HUDControl.VisualAlert;
|
||||||
|
|
||||||
# configurable driving settings
|
# configurable driving settings
|
||||||
experimentalMode @10 :Bool;
|
experimentalMode @10 :Bool;
|
||||||
personality @11 :LongitudinalPersonality;
|
personality @11 :LongitudinalPersonality;
|
||||||
|
|
||||||
|
enum AudibleAlert {
|
||||||
|
none @0;
|
||||||
|
|
||||||
|
engage @1;
|
||||||
|
disengage @2;
|
||||||
|
refuse @3;
|
||||||
|
|
||||||
|
warningSoft @4;
|
||||||
|
warningImmediate @5;
|
||||||
|
|
||||||
|
prompt @6;
|
||||||
|
promptRepeat @7;
|
||||||
|
promptDistracted @8;
|
||||||
|
|
||||||
|
preAlert @9;
|
||||||
|
complete @10;
|
||||||
|
}
|
||||||
|
|
||||||
enum OpenpilotState @0xdbe58b96d2d1ac61 {
|
enum OpenpilotState @0xdbe58b96d2d1ac61 {
|
||||||
disabled @0;
|
disabled @0;
|
||||||
preEnabled @1;
|
preEnabled @1;
|
||||||
@@ -798,6 +835,10 @@ struct SelfdriveState {
|
|||||||
mid @2;
|
mid @2;
|
||||||
full @3;
|
full @3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deprecated :group {
|
||||||
|
alertSound @8 :Car.CarControl.HUDControl.AudibleAlert;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ControlsState @0x97ff69c53601abf1 {
|
struct ControlsState @0x97ff69c53601abf1 {
|
||||||
@@ -818,7 +859,7 @@ struct ControlsState @0x97ff69c53601abf1 {
|
|||||||
debugState @59 :LateralDebugState;
|
debugState @59 :LateralDebugState;
|
||||||
torqueState @60 :LateralTorqueState;
|
torqueState @60 :LateralTorqueState;
|
||||||
|
|
||||||
curvatureStateDEPRECATED @65 :Deprecated.LateralCurvatureState;
|
curvatureState @65 :LateralCurvatureState;
|
||||||
lqrStateDEPRECATED @55 :Deprecated.LateralLQRState;
|
lqrStateDEPRECATED @55 :Deprecated.LateralLQRState;
|
||||||
indiStateDEPRECATED @52 :Deprecated.LateralINDIState;
|
indiStateDEPRECATED @52 :Deprecated.LateralINDIState;
|
||||||
}
|
}
|
||||||
@@ -867,6 +908,18 @@ struct ControlsState @0x97ff69c53601abf1 {
|
|||||||
saturated @3 :Bool;
|
saturated @3 :Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LateralCurvatureState @0xad9d8095c06f7c61 {
|
||||||
|
active @0 :Bool;
|
||||||
|
actualCurvature @1 :Float32;
|
||||||
|
desiredCurvature @2 :Float32;
|
||||||
|
error @3 :Float32;
|
||||||
|
p @4 :Float32;
|
||||||
|
i @5 :Float32;
|
||||||
|
f @6 :Float32;
|
||||||
|
output @7 :Float32;
|
||||||
|
saturated @8 :Bool;
|
||||||
|
}
|
||||||
|
|
||||||
deprecated :group {
|
deprecated :group {
|
||||||
vEgo @0 :Float32;
|
vEgo @0 :Float32;
|
||||||
vEgoRaw @32 :Float32;
|
vEgoRaw @32 :Float32;
|
||||||
@@ -906,7 +959,7 @@ struct ControlsState @0x97ff69c53601abf1 {
|
|||||||
alertStatus @38 :SelfdriveState.AlertStatus;
|
alertStatus @38 :SelfdriveState.AlertStatus;
|
||||||
alertSize @39 :SelfdriveState.AlertSize;
|
alertSize @39 :SelfdriveState.AlertSize;
|
||||||
alertType @44 :Text;
|
alertType @44 :Text;
|
||||||
alertSound2 @56 :Car.CarControl.HUDControl.AudibleAlert;
|
alertSound2 @56 :SelfdriveState.AudibleAlert;
|
||||||
engageable @41 :Bool; # can OP be engaged?
|
engageable @41 :Bool; # can OP be engaged?
|
||||||
state @31 :SelfdriveState.OpenpilotState;
|
state @31 :SelfdriveState.OpenpilotState;
|
||||||
enabled @19 :Bool;
|
enabled @19 :Bool;
|
||||||
@@ -989,7 +1042,6 @@ struct ModelDataV2 {
|
|||||||
roadEdgeStds @14 :List(Float32);
|
roadEdgeStds @14 :List(Float32);
|
||||||
|
|
||||||
# predicted lead cars
|
# predicted lead cars
|
||||||
leads @11 :List(LeadDataV2);
|
|
||||||
leadsV3 @18 :List(LeadDataV3);
|
leadsV3 @18 :List(LeadDataV3);
|
||||||
|
|
||||||
meta @12 :MetaData;
|
meta @12 :MetaData;
|
||||||
@@ -999,8 +1051,9 @@ struct ModelDataV2 {
|
|||||||
action @26: Action;
|
action @26: Action;
|
||||||
|
|
||||||
lateralPlannerSolutionDEPRECATED @25: Deprecated.LateralPlannerSolution;
|
lateralPlannerSolutionDEPRECATED @25: Deprecated.LateralPlannerSolution;
|
||||||
|
leadsDEPRECATED @11 :List(LeadDataV2DEPRECATED);
|
||||||
|
|
||||||
struct LeadDataV2 {
|
struct LeadDataV2DEPRECATED {
|
||||||
prob @0 :Float32; # probability that car is your lead at time t
|
prob @0 :Float32; # probability that car is your lead at time t
|
||||||
t @1 :Float32;
|
t @1 :Float32;
|
||||||
|
|
||||||
@@ -1120,7 +1173,7 @@ struct EncodeIndex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AndroidLogEntry {
|
struct OperatingSystemLogEntry {
|
||||||
id @0 :UInt8;
|
id @0 :UInt8;
|
||||||
ts @1 :UInt64;
|
ts @1 :UInt64;
|
||||||
priority @2 :UInt8;
|
priority @2 :UInt8;
|
||||||
@@ -1258,7 +1311,7 @@ struct LateralPlan @0xe1e9318e2ae8b51e {
|
|||||||
struct LiveLocationKalman {
|
struct LiveLocationKalman {
|
||||||
|
|
||||||
# More info on reference frames:
|
# More info on reference frames:
|
||||||
# https://github.com/commaai/openpilot/tree/master/common/transformations
|
# https://github.com/commaai/openpilot/tree/master/openpilot/common/transformations
|
||||||
|
|
||||||
positionECEF @0 : Measurement;
|
positionECEF @0 : Measurement;
|
||||||
positionGeodetic @1 : Measurement;
|
positionGeodetic @1 : Measurement;
|
||||||
@@ -1315,7 +1368,7 @@ struct LiveLocationKalman {
|
|||||||
|
|
||||||
struct LivePose {
|
struct LivePose {
|
||||||
# More info on reference frames:
|
# More info on reference frames:
|
||||||
# https://github.com/commaai/openpilot/tree/master/common/transformations
|
# https://github.com/commaai/openpilot/tree/master/openpilot/common/transformations
|
||||||
orientationNED @0 :XYZMeasurement;
|
orientationNED @0 :XYZMeasurement;
|
||||||
velocityDevice @1 :XYZMeasurement;
|
velocityDevice @1 :XYZMeasurement;
|
||||||
accelerationDevice @2 :XYZMeasurement;
|
accelerationDevice @2 :XYZMeasurement;
|
||||||
@@ -2108,8 +2161,11 @@ struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 {
|
|||||||
|
|
||||||
struct DriverMonitoringState {
|
struct DriverMonitoringState {
|
||||||
lockout @0 :Bool;
|
lockout @0 :Bool;
|
||||||
alertCountLockoutPercent @1 :Int8;
|
lockoutCount @15 :Int8;
|
||||||
alertTimeLockoutPercent @2 :Int8;
|
lockoutMinutesRemaining @11 :Int8;
|
||||||
|
alert3Count @12 :Int8;
|
||||||
|
noResponseCount @13 :Int8;
|
||||||
|
noResponseForceDecel @14 :Bool;
|
||||||
|
|
||||||
alwaysOn @3 :Bool;
|
alwaysOn @3 :Bool;
|
||||||
alwaysOnLockout @4 :Bool;
|
alwaysOnLockout @4 :Bool;
|
||||||
@@ -2173,6 +2229,11 @@ struct DriverMonitoringState {
|
|||||||
calibratedPercent @0 :Int8;
|
calibratedPercent @0 :Int8;
|
||||||
offset @1 :Float32;
|
offset @1 :Float32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deprecated :group {
|
||||||
|
alertCountLockoutPercent @1 :Int8;
|
||||||
|
alertTimeLockoutPercent @2 :Int8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Boot {
|
struct Boot {
|
||||||
@@ -2247,6 +2308,7 @@ struct LiveDelayData {
|
|||||||
lateralDelayEstimateStd @5 :Float32;
|
lateralDelayEstimateStd @5 :Float32;
|
||||||
points @4 :List(Float32);
|
points @4 :List(Float32);
|
||||||
calPerc @6 :Int8;
|
calPerc @6 :Int8;
|
||||||
|
version @7 :Int32;
|
||||||
|
|
||||||
enum Status {
|
enum Status {
|
||||||
unestimated @0;
|
unestimated @0;
|
||||||
@@ -2504,7 +2566,7 @@ struct Event {
|
|||||||
rawAudioData @147 :AudioData;
|
rawAudioData @147 :AudioData;
|
||||||
|
|
||||||
# systems stuff
|
# systems stuff
|
||||||
androidLog @20 :AndroidLogEntry;
|
operatingSystemLog @20 :OperatingSystemLogEntry;
|
||||||
managerState @78 :ManagerState;
|
managerState @78 :ManagerState;
|
||||||
procLog @33 :ProcLog;
|
procLog @33 :ProcLog;
|
||||||
clocks @35 :Clocks;
|
clocks @35 :Clocks;
|
||||||
@@ -7,12 +7,43 @@ import os
|
|||||||
import capnp
|
import capnp
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from typing import Optional, List, Union, Dict
|
from typing import Union
|
||||||
|
|
||||||
from cereal import log
|
from openpilot.cereal import log
|
||||||
from cereal.services import SERVICE_LIST
|
from openpilot.cereal.services import SERVICE_LIST
|
||||||
from openpilot.common.utils import MovingAverage
|
from openpilot.common.utils import MovingAverage
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"NO_TRAVERSAL_LIMIT",
|
||||||
|
"Context",
|
||||||
|
"FrequencyTracker",
|
||||||
|
"IpcError",
|
||||||
|
"MultiplePublishersError",
|
||||||
|
"Poller",
|
||||||
|
"PubMaster",
|
||||||
|
"PubSocket",
|
||||||
|
"SocketEventHandle",
|
||||||
|
"SubMaster",
|
||||||
|
"SubSocket",
|
||||||
|
"delete_fake_prefix",
|
||||||
|
"drain_sock",
|
||||||
|
"drain_sock_raw",
|
||||||
|
"fake_event_handle",
|
||||||
|
"get_fake_prefix",
|
||||||
|
"log_from_bytes",
|
||||||
|
"new_message",
|
||||||
|
"pub_sock",
|
||||||
|
"recv_one",
|
||||||
|
"recv_one_or_none",
|
||||||
|
"recv_one_retry",
|
||||||
|
"recv_sock",
|
||||||
|
"reset_context",
|
||||||
|
"set_fake_prefix",
|
||||||
|
"sub_sock",
|
||||||
|
"toggle_fake_events",
|
||||||
|
"wait_for_one_event",
|
||||||
|
)
|
||||||
|
|
||||||
NO_TRAVERSAL_LIMIT = 2**64-1
|
NO_TRAVERSAL_LIMIT = 2**64-1
|
||||||
|
|
||||||
|
|
||||||
@@ -22,8 +53,8 @@ def pub_sock(endpoint: str) -> PubSocket:
|
|||||||
return msgq.pub_sock(endpoint, segment_size)
|
return msgq.pub_sock(endpoint, segment_size)
|
||||||
|
|
||||||
|
|
||||||
def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
|
def sub_sock(endpoint: str, poller: Poller | None = None, addr: str = "127.0.0.1",
|
||||||
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
|
conflate: bool = False, timeout: int | None = None) -> SubSocket:
|
||||||
service = SERVICE_LIST.get(endpoint)
|
service = SERVICE_LIST.get(endpoint)
|
||||||
segment_size = service.queue_size if service else 0
|
segment_size = service.queue_size if service else 0
|
||||||
return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate,
|
return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate,
|
||||||
@@ -39,7 +70,7 @@ def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
|
def new_message(service: str | None, size: int | None = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||||
args = {
|
args = {
|
||||||
'valid': False,
|
'valid': False,
|
||||||
'logMonoTime': int(time.monotonic() * 1e9),
|
'logMonoTime': int(time.monotonic() * 1e9),
|
||||||
@@ -54,14 +85,14 @@ def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) ->
|
|||||||
return dat
|
return dat
|
||||||
|
|
||||||
|
|
||||||
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]:
|
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> list[capnp.lib.capnp._DynamicStructReader]:
|
||||||
"""Receive all message currently available on the queue"""
|
"""Receive all message currently available on the queue"""
|
||||||
msgs = drain_sock_raw(sock, wait_for_one=wait_for_one)
|
msgs = drain_sock_raw(sock, wait_for_one=wait_for_one)
|
||||||
return [log_from_bytes(m) for m in msgs]
|
return [log_from_bytes(m) for m in msgs]
|
||||||
|
|
||||||
|
|
||||||
# TODO: print when we drop packets?
|
# TODO: print when we drop packets?
|
||||||
def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
def recv_sock(sock: SubSocket, wait: bool = False) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||||
"""Same as drain sock, but only returns latest message. Consider using conflate instead."""
|
"""Same as drain sock, but only returns latest message. Consider using conflate instead."""
|
||||||
dat = None
|
dat = None
|
||||||
|
|
||||||
@@ -82,14 +113,14 @@ def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._
|
|||||||
return dat
|
return dat
|
||||||
|
|
||||||
|
|
||||||
def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
def recv_one(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||||
dat = sock.receive()
|
dat = sock.receive()
|
||||||
if dat is not None:
|
if dat is not None:
|
||||||
dat = log_from_bytes(dat)
|
dat = log_from_bytes(dat)
|
||||||
return dat
|
return dat
|
||||||
|
|
||||||
|
|
||||||
def recv_one_or_none(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
def recv_one_or_none(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||||
dat = sock.receive(non_blocking=True)
|
dat = sock.receive(non_blocking=True)
|
||||||
if dat is not None:
|
if dat is not None:
|
||||||
dat = log_from_bytes(dat)
|
dat = log_from_bytes(dat)
|
||||||
@@ -148,27 +179,27 @@ class FrequencyTracker:
|
|||||||
|
|
||||||
|
|
||||||
class SubMaster:
|
class SubMaster:
|
||||||
def __init__(self, services: List[str], poll: Optional[str] = None,
|
def __init__(self, services: list[str], poll: str | None = None,
|
||||||
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
|
ignore_alive: list[str] | None = None, ignore_avg_freq: list[str] | None = None,
|
||||||
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
|
ignore_valid: list[str] | None = None, addr: str = "127.0.0.1", frequency: float | None = None):
|
||||||
self.frame = -1
|
self.frame = -1
|
||||||
self.services = services
|
self.services = services
|
||||||
self.seen = {s: False for s in services}
|
self.seen = dict.fromkeys(services, False)
|
||||||
self.updated = {s: False for s in services}
|
self.updated = dict.fromkeys(services, False)
|
||||||
self.recv_time = {s: 0. for s in services}
|
self.recv_time = dict.fromkeys(services, 0.0)
|
||||||
self.recv_frame = {s: 0 for s in services}
|
self.recv_frame = dict.fromkeys(services, 0)
|
||||||
self.sock = {}
|
self.sock = {}
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.logMonoTime = {s: 0 for s in services}
|
self.logMonoTime = dict.fromkeys(services, 0)
|
||||||
|
|
||||||
# zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks
|
# zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks
|
||||||
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services}
|
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services}
|
||||||
self.static_freq_services = set(s for s in services if not on_demand[s])
|
self.static_freq_services = {s for s in services if not on_demand[s]}
|
||||||
self.alive = {s: on_demand[s] for s in services}
|
self.alive = {s: on_demand[s] for s in services}
|
||||||
self.freq_ok = {s: on_demand[s] for s in services}
|
self.freq_ok = {s: on_demand[s] for s in services}
|
||||||
self.valid = {s: on_demand[s] for s in services}
|
self.valid = {s: on_demand[s] for s in services}
|
||||||
|
|
||||||
self.freq_tracker: Dict[str, FrequencyTracker] = {}
|
self.freq_tracker: dict[str, FrequencyTracker] = {}
|
||||||
self.poller = Poller()
|
self.poller = Poller()
|
||||||
polled_services = set([poll, ] if poll is not None else services)
|
polled_services = set([poll, ] if poll is not None else services)
|
||||||
self.non_polled_services = set(services) - polled_services
|
self.non_polled_services = set(services) - polled_services
|
||||||
@@ -211,7 +242,7 @@ class SubMaster:
|
|||||||
msgs.append(recv_one_or_none(self.sock[s]))
|
msgs.append(recv_one_or_none(self.sock[s]))
|
||||||
self.update_msgs(time.monotonic(), msgs)
|
self.update_msgs(time.monotonic(), msgs)
|
||||||
|
|
||||||
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
|
def update_msgs(self, cur_time: float, msgs: list[capnp.lib.capnp._DynamicStructReader]) -> None:
|
||||||
self.frame += 1
|
self.frame += 1
|
||||||
self.updated = dict.fromkeys(self.services, False)
|
self.updated = dict.fromkeys(self.services, False)
|
||||||
for msg in msgs:
|
for msg in msgs:
|
||||||
@@ -234,21 +265,21 @@ class SubMaster:
|
|||||||
self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation)
|
self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation)
|
||||||
self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation
|
self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation
|
||||||
|
|
||||||
def all_alive(self, service_list: Optional[List[str]] = None) -> bool:
|
def all_alive(self, service_list: list[str] | None = None) -> bool:
|
||||||
return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive)
|
return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive)
|
||||||
|
|
||||||
def all_freq_ok(self, service_list: Optional[List[str]] = None) -> bool:
|
def all_freq_ok(self, service_list: list[str] | None = None) -> bool:
|
||||||
return all(self.freq_ok[s] for s in (service_list or self.services) if self._check_avg_freq(s))
|
return all(self.freq_ok[s] for s in (service_list or self.services) if self._check_avg_freq(s))
|
||||||
|
|
||||||
def all_valid(self, service_list: Optional[List[str]] = None) -> bool:
|
def all_valid(self, service_list: list[str] | None = None) -> bool:
|
||||||
return all(self.valid[s] for s in (service_list or self.services) if s not in self.ignore_valid)
|
return all(self.valid[s] for s in (service_list or self.services) if s not in self.ignore_valid)
|
||||||
|
|
||||||
def all_checks(self, service_list: Optional[List[str]] = None) -> bool:
|
def all_checks(self, service_list: list[str] | None = None) -> bool:
|
||||||
return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list)
|
return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list)
|
||||||
|
|
||||||
|
|
||||||
class PubMaster:
|
class PubMaster:
|
||||||
def __init__(self, services: List[str]):
|
def __init__(self, services: list[str]):
|
||||||
self.sock = {}
|
self.sock = {}
|
||||||
for s in services:
|
for s in services:
|
||||||
self.sock[s] = pub_sock(s)
|
self.sock[s] = pub_sock(s)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "cereal/messaging/msgq_to_zmq.h"
|
#include "openpilot/cereal/messaging/msgq_to_zmq.h"
|
||||||
#include "cereal/services.h"
|
#include "openpilot/cereal/services.h"
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
|
|
||||||
ExitHandler do_exit;
|
ExitHandler do_exit;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "cereal/messaging/bridge_zmq.h"
|
#include "openpilot/cereal/messaging/bridge_zmq.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include <capnp/serialize.h>
|
#include <capnp/serialize.h>
|
||||||
|
|
||||||
#include "cereal/gen/cpp/log.capnp.h"
|
#include "openpilot/cereal/gen/cpp/log.capnp.h"
|
||||||
#include "common/timing.h"
|
#include "common/timing.h"
|
||||||
#include "msgq/ipc.h"
|
#include "msgq/ipc.h"
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "cereal/messaging/msgq_to_zmq.h"
|
#include "openpilot/cereal/messaging/msgq_to_zmq.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "cereal/services.h"
|
#include "openpilot/cereal/services.h"
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
|
|
||||||
extern ExitHandler do_exit;
|
extern ExitHandler do_exit;
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "msgq/impl_msgq.h"
|
#include "msgq/impl_msgq.h"
|
||||||
#include "cereal/messaging/bridge_zmq.h"
|
#include "openpilot/cereal/messaging/bridge_zmq.h"
|
||||||
|
|
||||||
class MsgqToZmq {
|
class MsgqToZmq {
|
||||||
public:
|
public:
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "cereal/services.h"
|
#include "openpilot/cereal/services.h"
|
||||||
#include "cereal/messaging/messaging.h"
|
#include "openpilot/cereal/messaging/messaging.h"
|
||||||
|
|
||||||
const bool SIMULATION = (getenv("SIMULATION") != nullptr) && (std::string(getenv("SIMULATION")) == "1");
|
const bool SIMULATION = (getenv("SIMULATION") != nullptr) && (std::string(getenv("SIMULATION")) == "1");
|
||||||
|
|
||||||
+10
-33
@@ -1,4 +1,3 @@
|
|||||||
import os
|
|
||||||
import capnp
|
import capnp
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import numbers
|
import numbers
|
||||||
@@ -6,11 +5,11 @@ import random
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from openpilot.common.parameterized import parameterized
|
from openpilot.common.parameterized import parameterized
|
||||||
import pytest
|
|
||||||
|
|
||||||
from cereal import log, car
|
from openpilot.cereal import log
|
||||||
import cereal.messaging as messaging
|
from opendbc.car.structs import car
|
||||||
from cereal.services import SERVICE_LIST
|
import openpilot.cereal.messaging as messaging
|
||||||
|
from openpilot.cereal.services import SERVICE_LIST
|
||||||
|
|
||||||
events = [evt for evt in log.Event.schema.union_fields if evt in SERVICE_LIST.keys()]
|
events = [evt for evt in log.Event.schema.union_fields if evt in SERVICE_LIST.keys()]
|
||||||
|
|
||||||
@@ -23,10 +22,6 @@ def random_socks(num_socks=10):
|
|||||||
def random_bytes(length=1000):
|
def random_bytes(length=1000):
|
||||||
return bytes([random.randrange(0xFF) for _ in range(length)])
|
return bytes([random.randrange(0xFF) for _ in range(length)])
|
||||||
|
|
||||||
def zmq_sleep(t=1):
|
|
||||||
if "ZMQ" in os.environ:
|
|
||||||
time.sleep(t)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: this should take any capnp struct and returrn a msg with random populated data
|
# TODO: this should take any capnp struct and returrn a msg with random populated data
|
||||||
def random_carstate():
|
def random_carstate():
|
||||||
@@ -52,16 +47,6 @@ def delayed_send(delay, sock, dat):
|
|||||||
|
|
||||||
|
|
||||||
class TestMessaging:
|
class TestMessaging:
|
||||||
def setUp(self):
|
|
||||||
# TODO: ZMQ tests are too slow; all sleeps will need to be
|
|
||||||
# replaced with logic to block on the necessary condition
|
|
||||||
if "ZMQ" in os.environ:
|
|
||||||
pytest.skip()
|
|
||||||
|
|
||||||
# ZMQ pub socket takes too long to die
|
|
||||||
# sleep to prevent multiple publishers error between tests
|
|
||||||
zmq_sleep()
|
|
||||||
|
|
||||||
@parameterized.expand(events)
|
@parameterized.expand(events)
|
||||||
def test_new_message(self, evt):
|
def test_new_message(self, evt):
|
||||||
try:
|
try:
|
||||||
@@ -88,7 +73,6 @@ class TestMessaging:
|
|||||||
sock = "carState"
|
sock = "carState"
|
||||||
pub_sock = messaging.pub_sock(sock)
|
pub_sock = messaging.pub_sock(sock)
|
||||||
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
||||||
zmq_sleep()
|
|
||||||
|
|
||||||
# no wait and no msgs in queue
|
# no wait and no msgs in queue
|
||||||
msgs = func(sub_sock)
|
msgs = func(sub_sock)
|
||||||
@@ -109,7 +93,6 @@ class TestMessaging:
|
|||||||
sock = "carState"
|
sock = "carState"
|
||||||
pub_sock = messaging.pub_sock(sock)
|
pub_sock = messaging.pub_sock(sock)
|
||||||
sub_sock = messaging.sub_sock(sock, timeout=100)
|
sub_sock = messaging.sub_sock(sock, timeout=100)
|
||||||
zmq_sleep()
|
|
||||||
|
|
||||||
# no wait and no msg in queue, socket should timeout
|
# no wait and no msg in queue, socket should timeout
|
||||||
recvd = messaging.recv_sock(sub_sock)
|
recvd = messaging.recv_sock(sub_sock)
|
||||||
@@ -128,7 +111,6 @@ class TestMessaging:
|
|||||||
sock = "carState"
|
sock = "carState"
|
||||||
pub_sock = messaging.pub_sock(sock)
|
pub_sock = messaging.pub_sock(sock)
|
||||||
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
||||||
zmq_sleep()
|
|
||||||
|
|
||||||
# no msg in queue, socket should timeout
|
# no msg in queue, socket should timeout
|
||||||
recvd = messaging.recv_one(sub_sock)
|
recvd = messaging.recv_one(sub_sock)
|
||||||
@@ -141,12 +123,10 @@ class TestMessaging:
|
|||||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||||
assert_carstate(msg.carState, recvd.carState)
|
assert_carstate(msg.carState, recvd.carState)
|
||||||
|
|
||||||
@pytest.mark.xfail(condition="ZMQ" in os.environ, reason='ZMQ detected')
|
|
||||||
def test_recv_one_or_none(self):
|
def test_recv_one_or_none(self):
|
||||||
sock = "carState"
|
sock = "carState"
|
||||||
pub_sock = messaging.pub_sock(sock)
|
pub_sock = messaging.pub_sock(sock)
|
||||||
sub_sock = messaging.sub_sock(sock)
|
sub_sock = messaging.sub_sock(sock)
|
||||||
zmq_sleep()
|
|
||||||
|
|
||||||
# no msg in queue, socket shouldn't block
|
# no msg in queue, socket shouldn't block
|
||||||
recvd = messaging.recv_one_or_none(sub_sock)
|
recvd = messaging.recv_one_or_none(sub_sock)
|
||||||
@@ -164,16 +144,13 @@ class TestMessaging:
|
|||||||
sock_timeout = 0.1
|
sock_timeout = 0.1
|
||||||
pub_sock = messaging.pub_sock(sock)
|
pub_sock = messaging.pub_sock(sock)
|
||||||
sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000))
|
sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000))
|
||||||
zmq_sleep()
|
|
||||||
|
|
||||||
# this test doesn't work with ZMQ since multiprocessing interrupts it
|
# wait 5 socket timeouts and make sure it's still retrying
|
||||||
if "ZMQ" not in os.environ:
|
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
|
||||||
# wait 5 socket timeouts and make sure it's still retrying
|
p.start()
|
||||||
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
|
time.sleep(sock_timeout*5)
|
||||||
p.start()
|
assert p.is_alive()
|
||||||
time.sleep(sock_timeout*5)
|
p.terminate()
|
||||||
assert p.is_alive()
|
|
||||||
p.terminate()
|
|
||||||
|
|
||||||
# wait 5 socket timeouts before sending
|
# wait 5 socket timeouts before sending
|
||||||
msg = random_carstate()
|
msg = random_carstate()
|
||||||
+7
-28
@@ -1,21 +1,16 @@
|
|||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
from typing import Sized, cast
|
from typing import cast
|
||||||
|
from collections.abc import Sized
|
||||||
|
|
||||||
import cereal.messaging as messaging
|
import openpilot.cereal.messaging as messaging
|
||||||
from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
|
from openpilot.cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
|
||||||
random_bytes, random_carstate, assert_carstate, \
|
random_bytes, random_carstate, assert_carstate
|
||||||
zmq_sleep
|
from openpilot.cereal.services import SERVICE_LIST
|
||||||
from cereal.services import SERVICE_LIST
|
|
||||||
|
|
||||||
|
|
||||||
class TestSubMaster:
|
class TestSubMaster:
|
||||||
|
|
||||||
def setup_method(self):
|
|
||||||
# ZMQ pub socket takes too long to die
|
|
||||||
# sleep to prevent multiple publishers error between tests
|
|
||||||
zmq_sleep(3)
|
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
sm = messaging.SubMaster(events)
|
sm = messaging.SubMaster(events)
|
||||||
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
||||||
@@ -42,7 +37,6 @@ class TestSubMaster:
|
|||||||
sock = "carState"
|
sock = "carState"
|
||||||
pub_sock = messaging.pub_sock(sock)
|
pub_sock = messaging.pub_sock(sock)
|
||||||
sm = messaging.SubMaster([sock,])
|
sm = messaging.SubMaster([sock,])
|
||||||
zmq_sleep()
|
|
||||||
|
|
||||||
msg = random_carstate()
|
msg = random_carstate()
|
||||||
pub_sock.send(msg.to_bytes())
|
pub_sock.send(msg.to_bytes())
|
||||||
@@ -54,7 +48,6 @@ class TestSubMaster:
|
|||||||
sock = "carState"
|
sock = "carState"
|
||||||
pub_sock = messaging.pub_sock(sock)
|
pub_sock = messaging.pub_sock(sock)
|
||||||
sm = messaging.SubMaster([sock,])
|
sm = messaging.SubMaster([sock,])
|
||||||
zmq_sleep()
|
|
||||||
|
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
msg = messaging.new_message(sock)
|
msg = messaging.new_message(sock)
|
||||||
@@ -91,21 +84,13 @@ class TestSubMaster:
|
|||||||
|
|
||||||
for service, (max_freq, min_freq) in checks.items():
|
for service, (max_freq, min_freq) in checks.items():
|
||||||
if max_freq is not None:
|
if max_freq is not None:
|
||||||
|
assert min_freq is not None
|
||||||
assert sm._check_avg_freq(service)
|
assert sm._check_avg_freq(service)
|
||||||
assert sm.freq_tracker[service].max_freq == max_freq*1.2
|
assert sm.freq_tracker[service].max_freq == max_freq*1.2
|
||||||
assert sm.freq_tracker[service].min_freq == min_freq*0.8
|
assert sm.freq_tracker[service].min_freq == min_freq*0.8
|
||||||
else:
|
else:
|
||||||
assert not sm._check_avg_freq(service)
|
assert not sm._check_avg_freq(service)
|
||||||
|
|
||||||
def test_alive(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_ignore_alive(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_valid(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# SubMaster should always conflate
|
# SubMaster should always conflate
|
||||||
def test_conflate(self):
|
def test_conflate(self):
|
||||||
sock = "carState"
|
sock = "carState"
|
||||||
@@ -124,11 +109,6 @@ class TestSubMaster:
|
|||||||
|
|
||||||
class TestPubMaster:
|
class TestPubMaster:
|
||||||
|
|
||||||
def setup_method(self):
|
|
||||||
# ZMQ pub socket takes too long to die
|
|
||||||
# sleep to prevent multiple publishers error between tests
|
|
||||||
zmq_sleep(3)
|
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
messaging.PubMaster(events)
|
messaging.PubMaster(events)
|
||||||
|
|
||||||
@@ -136,7 +116,6 @@ class TestPubMaster:
|
|||||||
socks = random_socks()
|
socks = random_socks()
|
||||||
pm = messaging.PubMaster(socks)
|
pm = messaging.PubMaster(socks)
|
||||||
sub_socks = {s: messaging.sub_sock(s, conflate=True, timeout=1000) for s in socks}
|
sub_socks = {s: messaging.sub_sock(s, conflate=True, timeout=1000) for s in socks}
|
||||||
zmq_sleep()
|
|
||||||
|
|
||||||
# PubMaster accepts either a capnp msg builder or bytes
|
# PubMaster accepts either a capnp msg builder or bytes
|
||||||
for capnp in [True, False]:
|
for capnp in [True, False]:
|
||||||
+5
-5
@@ -1,10 +1,10 @@
|
|||||||
import os
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import Dict
|
|
||||||
from openpilot.common.parameterized import parameterized
|
from openpilot.common.parameterized import parameterized
|
||||||
|
|
||||||
import cereal.services as services
|
import openpilot.cereal.services as services
|
||||||
from cereal.services import SERVICE_LIST
|
from openpilot.cereal.services import SERVICE_LIST
|
||||||
|
|
||||||
|
|
||||||
class TestServices:
|
class TestServices:
|
||||||
@@ -17,5 +17,5 @@ class TestServices:
|
|||||||
|
|
||||||
def test_generated_header(self):
|
def test_generated_header(self):
|
||||||
with tempfile.NamedTemporaryFile(suffix=".h") as f:
|
with tempfile.NamedTemporaryFile(suffix=".h") as f:
|
||||||
ret = os.system(f"python3 {services.__file__} > {f.name} && clang++ {f.name} -std=c++11")
|
ret = subprocess.run(f"python3 {services.__file__} > {f.name} && clang++ {f.name} -std=c++11", shell=True).returncode
|
||||||
assert ret == 0, "generated services header is not valid C"
|
assert ret == 0, "generated services header is not valid C"
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Validate sunnypilot routes are parseable by stock commaai/openpilot.
|
||||||
|
|
||||||
|
Cap'n Proto is wire-compatible across renames, type relocations, and
|
||||||
|
additive fields. The only breaking change is a union variant that
|
||||||
|
upstream doesn't recognize — an unknown discriminant makes the entire
|
||||||
|
union unreadable.
|
||||||
|
|
||||||
|
This script checks: for every struct with a union that exists in both
|
||||||
|
schemas, does sunnypilot introduce union variants upstream doesn't have?
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
NO_DISCRIMINANT = 0xFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def hex_id(value: int) -> str:
|
||||||
|
return f"0x{value:016x}"
|
||||||
|
|
||||||
|
|
||||||
|
def encode_struct(schema: Any) -> dict:
|
||||||
|
node = schema.node
|
||||||
|
fields = []
|
||||||
|
for name, field in schema.fields.items():
|
||||||
|
proto = field.proto
|
||||||
|
ordinal = proto.ordinal.explicit if proto.ordinal.which() == "explicit" else None
|
||||||
|
discriminant = proto.discriminantValue if proto.discriminantValue != NO_DISCRIMINANT else None
|
||||||
|
fields.append({"name": name, "ordinal": ordinal, "discriminant": discriminant})
|
||||||
|
return {
|
||||||
|
"typeId": hex_id(node.id),
|
||||||
|
"displayName": node.displayName,
|
||||||
|
"hasUnion": node.struct.discriminantCount > 0,
|
||||||
|
"fields": fields,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _child_struct_schema(field: Any) -> Any:
|
||||||
|
proto = field.proto
|
||||||
|
if proto.which() == "group":
|
||||||
|
return field.schema
|
||||||
|
type_node = proto.slot.type
|
||||||
|
which = type_node.which()
|
||||||
|
if which == "struct":
|
||||||
|
return field.schema
|
||||||
|
if which == "list":
|
||||||
|
container = field.schema
|
||||||
|
element_type = type_node.list.elementType
|
||||||
|
while element_type.which() == "list":
|
||||||
|
container = container.elementType
|
||||||
|
element_type = element_type.list.elementType
|
||||||
|
if element_type.which() == "struct":
|
||||||
|
return container.elementType
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def collect_schema(root: Any) -> dict[str, dict]:
|
||||||
|
structs: dict[str, dict] = {}
|
||||||
|
stack = [root]
|
||||||
|
while stack:
|
||||||
|
schema = stack.pop()
|
||||||
|
type_id = hex_id(schema.node.id)
|
||||||
|
if type_id in structs:
|
||||||
|
continue
|
||||||
|
structs[type_id] = encode_struct(schema)
|
||||||
|
for _name, field in schema.fields.items():
|
||||||
|
try:
|
||||||
|
child = _child_struct_schema(field)
|
||||||
|
except Exception:
|
||||||
|
child = None
|
||||||
|
if child is not None:
|
||||||
|
stack.append(child)
|
||||||
|
return structs
|
||||||
|
|
||||||
|
|
||||||
|
def load_log(cereal_dir: str, extra_imports: list[str] | None = None) -> Any:
|
||||||
|
import capnp
|
||||||
|
cereal_dir = os.path.abspath(cereal_dir)
|
||||||
|
capnp.remove_import_hook()
|
||||||
|
imports = [cereal_dir] + [os.path.abspath(p) for p in (extra_imports or [])]
|
||||||
|
return capnp.load(os.path.join(cereal_dir, "log.capnp"), imports=imports)
|
||||||
|
|
||||||
|
|
||||||
|
def dump_schema(cereal_dir: str, path: str, extra_imports: list[str] | None = None) -> None:
|
||||||
|
log = load_log(cereal_dir, extra_imports)
|
||||||
|
payload = {
|
||||||
|
"root": hex_id(log.Event.schema.node.id),
|
||||||
|
"structs": collect_schema(log.Event.schema),
|
||||||
|
}
|
||||||
|
with open(path, "w", encoding="utf-8") as handle:
|
||||||
|
json.dump(payload, handle, indent=2, sort_keys=True)
|
||||||
|
print(f"wrote schema dump with {len(payload['structs'])} structs to {path}")
|
||||||
|
|
||||||
|
|
||||||
|
def compare(sunnypilot_dump: dict, upstream_dump: dict) -> list[str]:
|
||||||
|
violations: list[str] = []
|
||||||
|
sunnypilot_structs = sunnypilot_dump["structs"]
|
||||||
|
upstream_structs = upstream_dump["structs"]
|
||||||
|
|
||||||
|
for type_id, sp_struct in sunnypilot_structs.items():
|
||||||
|
if not sp_struct["hasUnion"]:
|
||||||
|
continue
|
||||||
|
up_struct = upstream_structs.get(type_id)
|
||||||
|
if up_struct is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
up_ordinals = {f["ordinal"] for f in up_struct["fields"] if f.get("discriminant") is not None}
|
||||||
|
display = sp_struct["displayName"]
|
||||||
|
|
||||||
|
for field in sp_struct["fields"]:
|
||||||
|
if field.get("discriminant") is None:
|
||||||
|
continue
|
||||||
|
if field["ordinal"] not in up_ordinals:
|
||||||
|
violations.append(
|
||||||
|
f"{display} @{field['ordinal']} '{field['name']}': "
|
||||||
|
f"union variant not present upstream (discriminant={field['discriminant']})"
|
||||||
|
)
|
||||||
|
|
||||||
|
return violations
|
||||||
|
|
||||||
|
|
||||||
|
def run_read(cereal_dir: str, peer_path: str, extra_imports: list[str] | None = None) -> int:
|
||||||
|
log = load_log(cereal_dir, extra_imports)
|
||||||
|
with open(peer_path, "r", encoding="utf-8") as f:
|
||||||
|
peer_dump = json.load(f)
|
||||||
|
local_dump = {
|
||||||
|
"root": hex_id(log.Event.schema.node.id),
|
||||||
|
"structs": collect_schema(log.Event.schema),
|
||||||
|
}
|
||||||
|
violations = compare(sunnypilot_dump=peer_dump, upstream_dump=local_dump)
|
||||||
|
|
||||||
|
if not violations:
|
||||||
|
print("cereal compat OK: upstream can parse sunnypilot routes.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(f"cereal compat FAIL ({len(violations)} leaked union variant(s)):")
|
||||||
|
for v in violations:
|
||||||
|
print(f" {v}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="sunnypilot cereal upstream compat check")
|
||||||
|
mode = parser.add_mutually_exclusive_group(required=True)
|
||||||
|
mode.add_argument("-g", "--generate", action="store_true", help="dump local schema to JSON")
|
||||||
|
mode.add_argument("-r", "--read", action="store_true", help="validate against peer schema")
|
||||||
|
parser.add_argument("-f", "--file", default="schema.json", help="JSON file path")
|
||||||
|
parser.add_argument("--cereal-dir", required=True, help="path to cereal directory")
|
||||||
|
parser.add_argument("-I", "--import-path", action="append", default=[], help="extra capnp import paths")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.generate:
|
||||||
|
dump_schema(args.cereal_dir, args.file, args.import_path)
|
||||||
|
return 0
|
||||||
|
return run_read(args.cereal_dir, args.file, args.import_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: this should be automatically determined using the capnp schema
|
# TODO: this should be automatically determined using the capnp schema
|
||||||
@@ -11,7 +10,7 @@ class QueueSize(IntEnum):
|
|||||||
|
|
||||||
|
|
||||||
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: int | None = None,
|
||||||
queue_size: QueueSize = QueueSize.SMALL):
|
queue_size: QueueSize = QueueSize.SMALL):
|
||||||
self.should_log = should_log
|
self.should_log = should_log
|
||||||
self.frequency = frequency
|
self.frequency = frequency
|
||||||
@@ -41,7 +40,7 @@ _services: dict[str, tuple] = {
|
|||||||
"liveCalibration": (True, 4., 4),
|
"liveCalibration": (True, 4., 4),
|
||||||
"liveTorqueParameters": (True, 4., 1),
|
"liveTorqueParameters": (True, 4., 1),
|
||||||
"liveDelay": (True, 4., 1),
|
"liveDelay": (True, 4., 1),
|
||||||
"androidLog": (True, 0.),
|
"operatingSystemLog": (True, 0.),
|
||||||
"carState": (True, 100., 10),
|
"carState": (True, 100., 10),
|
||||||
"carControl": (True, 100., 10),
|
"carControl": (True, 100., 10),
|
||||||
"carOutput": (True, 100., 10),
|
"carOutput": (True, 100., 10),
|
||||||
@@ -125,8 +124,7 @@ def build_header():
|
|||||||
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 += f' {{ "{k}", {{"{k}", {should_log}, {v.frequency:f}, {decimation:d}, {v.queue_size:d}}}}},\n'
|
||||||
(k, k, should_log, v.frequency, decimation, v.queue_size)
|
|
||||||
h += "};\n"
|
h += "};\n"
|
||||||
|
|
||||||
h += "#endif\n"
|
h += "#endif\n"
|
||||||
@@ -1 +0,0 @@
|
|||||||
../common
|
|
||||||
@@ -5,6 +5,7 @@ common_libs = [
|
|||||||
'swaglog.cc',
|
'swaglog.cc',
|
||||||
'util.cc',
|
'util.cc',
|
||||||
'ratekeeper.cc',
|
'ratekeeper.cc',
|
||||||
|
'yuv.cc',
|
||||||
]
|
]
|
||||||
|
|
||||||
_common = env.Library('common', common_libs, LIBS="json11")
|
_common = env.Library('common', common_libs, LIBS="json11")
|
||||||
@@ -12,7 +13,7 @@ Export('_common')
|
|||||||
|
|
||||||
if GetOption('extras'):
|
if GetOption('extras'):
|
||||||
env.Program('tests/test_common',
|
env.Program('tests/test_common',
|
||||||
['tests/test_runner.cc', 'tests/test_params.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'],
|
['tests/test_runner.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'],
|
||||||
LIBS=[_common, 'json11', 'zmq', 'pthread'])
|
LIBS=[_common, 'json11', 'zmq', 'pthread'])
|
||||||
|
|
||||||
# Cython bindings
|
# Cython bindings
|
||||||
@@ -3,8 +3,8 @@ 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.common.hardware.hw import Paths
|
||||||
from openpilot.system.version import get_version
|
from openpilot.common.version import get_version
|
||||||
|
|
||||||
# name: jwt signature algorithm
|
# name: jwt signature algorithm
|
||||||
KEYS = {"id_rsa": "RS256",
|
KEYS = {"id_rsa": "RS256",
|
||||||
@@ -38,6 +38,7 @@ class BaseApi:
|
|||||||
}
|
}
|
||||||
if payload_extra is not None:
|
if payload_extra is not None:
|
||||||
payload.update(payload_extra)
|
payload.update(payload_extra)
|
||||||
|
assert self.private_key is not None
|
||||||
token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm)
|
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')
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../"))
|
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../.."))
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from openpilot.common.esim.base import LPABase, LPAError, LPAProfileNotFoundError, Profile
|
||||||
|
|
||||||
|
__all__ = ["LPABase", "LPAError", "LPAProfileNotFoundError", "Profile"]
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
class LPAError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LPAProfileNotFoundError(LPAError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Profile:
|
||||||
|
iccid: str
|
||||||
|
nickname: str
|
||||||
|
enabled: bool
|
||||||
|
provider: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_comma(self) -> bool:
|
||||||
|
return self.provider == 'Webbing' and self.iccid.startswith('8985235')
|
||||||
|
|
||||||
|
|
||||||
|
class LPABase(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def list_profiles(self) -> list[Profile]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_active_profile(self) -> Profile | None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def delete_profile(self, iccid: str) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def download_profile(self, qr: str, nickname: str | None = None) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def nickname_profile(self, iccid: str, nickname: str) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def switch_profile(self, iccid: str) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def process_notifications(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def is_euicc(self) -> bool:
|
||||||
|
pass
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from openpilot.system.hardware import HARDWARE
|
from openpilot.common.hardware import HARDWARE
|
||||||
from openpilot.system.hardware.base import LPABase, Profile
|
from openpilot.common.esim.base import LPABase, Profile
|
||||||
|
|
||||||
|
|
||||||
def sorted_profiles(lpa: LPABase) -> list[Profile]:
|
def sorted_profiles(lpa: LPABase) -> list[Profile]:
|
||||||
@@ -6,7 +6,6 @@ import fcntl
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import serial
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import termios
|
import termios
|
||||||
@@ -19,7 +18,8 @@ from typing import Any
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from openpilot.common.time_helpers import system_time_valid
|
from openpilot.common.time_helpers import system_time_valid
|
||||||
from openpilot.system.hardware.base import LPABase, LPAError, LPAProfileNotFoundError, Profile
|
from openpilot.common.esim.base import LPABase, LPAError, LPAProfileNotFoundError, Profile
|
||||||
|
from openpilot.common.serial import Serial, SerialException
|
||||||
|
|
||||||
GSMA_CI_BUNDLE = str(Path(__file__).parent / "gsma_ci_bundle.pem")
|
GSMA_CI_BUNDLE = str(Path(__file__).parent / "gsma_ci_bundle.pem")
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ class AtClient:
|
|||||||
self._device = device
|
self._device = device
|
||||||
self._baud = baud
|
self._baud = baud
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
self._serial: serial.Serial | None = None
|
self._serial: Serial | None = None
|
||||||
|
|
||||||
def send_raw(self, data: bytes) -> None:
|
def send_raw(self, data: bytes) -> None:
|
||||||
self._ensure_serial()
|
self._ensure_serial()
|
||||||
@@ -185,14 +185,14 @@ class AtClient:
|
|||||||
pass
|
pass
|
||||||
self._serial = None
|
self._serial = None
|
||||||
if self._serial is None:
|
if self._serial is None:
|
||||||
self._serial = serial.Serial(self._device, baudrate=self._baud, timeout=self._timeout)
|
self._serial = Serial(self._device, baudrate=self._baud, timeout=self._timeout)
|
||||||
|
|
||||||
def query(self, cmd: str) -> list[str]:
|
def query(self, cmd: str) -> list[str]:
|
||||||
self._ensure_serial()
|
self._ensure_serial()
|
||||||
try:
|
try:
|
||||||
self._send(cmd)
|
self._send(cmd)
|
||||||
return self._expect()
|
return self._expect()
|
||||||
except serial.SerialException:
|
except SerialException:
|
||||||
self._ensure_serial(reconnect=True)
|
self._ensure_serial(reconnect=True)
|
||||||
self._send(cmd)
|
self._send(cmd)
|
||||||
return self._expect()
|
return self._expect()
|
||||||
@@ -208,7 +208,7 @@ class AtClient:
|
|||||||
if self._serial:
|
if self._serial:
|
||||||
try:
|
try:
|
||||||
self._serial.reset_input_buffer()
|
self._serial.reset_input_buffer()
|
||||||
except (OSError, serial.SerialException, termios.error):
|
except (OSError, SerialException, termios.error):
|
||||||
self._ensure_serial(reconnect=True)
|
self._ensure_serial(reconnect=True)
|
||||||
for line in self.query(f'AT+CCHO="{ISDR_AID}"'):
|
for line in self.query(f'AT+CCHO="{ISDR_AID}"'):
|
||||||
if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()):
|
if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()):
|
||||||
@@ -230,7 +230,7 @@ class AtClient:
|
|||||||
try:
|
try:
|
||||||
self._open_isdr_once()
|
self._open_isdr_once()
|
||||||
return
|
return
|
||||||
except (RuntimeError, TimeoutError, termios.error, serial.SerialException):
|
except (RuntimeError, TimeoutError, termios.error, SerialException):
|
||||||
time.sleep(OPEN_ISDR_RETRY_DELAY_S)
|
time.sleep(OPEN_ISDR_RETRY_DELAY_S)
|
||||||
if attempt == OPEN_ISDR_RESET_ATTEMPT:
|
if attempt == OPEN_ISDR_RESET_ATTEMPT:
|
||||||
self._reset_modem()
|
self._reset_modem()
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import io
|
||||||
import sys
|
import sys
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
@@ -21,13 +22,12 @@ def get_chunk_targets(path, file_size):
|
|||||||
|
|
||||||
def chunk_file(path, targets):
|
def chunk_file(path, targets):
|
||||||
manifest_path, *chunk_paths = targets
|
manifest_path, *chunk_paths = targets
|
||||||
with open(path, 'rb') as f:
|
actual_num_chunks = max(1, math.ceil(os.path.getsize(path) / CHUNK_SIZE))
|
||||||
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}"
|
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(path, 'rb') as f:
|
||||||
with open(chunk_path, 'wb') as f:
|
for chunk_path in chunk_paths:
|
||||||
f.write(data[i * CHUNK_SIZE:(i + 1) * CHUNK_SIZE])
|
with open(chunk_path, 'wb') as out:
|
||||||
|
out.write(f.read(CHUNK_SIZE))
|
||||||
Path(manifest_path).write_text(str(len(chunk_paths)))
|
Path(manifest_path).write_text(str(len(chunk_paths)))
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
@@ -39,14 +39,41 @@ def get_existing_chunks(path):
|
|||||||
return _chunk_paths(path, num_chunks)
|
return _chunk_paths(path, num_chunks)
|
||||||
raise FileNotFoundError(path)
|
raise FileNotFoundError(path)
|
||||||
|
|
||||||
def read_file_chunked(path):
|
class ChunkStream(io.RawIOBase):
|
||||||
|
def __init__(self, paths):
|
||||||
|
self._paths = iter(paths)
|
||||||
|
self._f = None
|
||||||
|
|
||||||
|
def readable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def readinto(self, b):
|
||||||
|
n = 0
|
||||||
|
view = memoryview(b)
|
||||||
|
while n < len(b):
|
||||||
|
if self._f is None:
|
||||||
|
p = next(self._paths, None)
|
||||||
|
if p is None:
|
||||||
|
break
|
||||||
|
self._f = open(p, 'rb')
|
||||||
|
count = self._f.readinto(view[n:])
|
||||||
|
if not count:
|
||||||
|
self._f.close()
|
||||||
|
self._f = None
|
||||||
|
continue
|
||||||
|
n += count
|
||||||
|
return n
|
||||||
|
|
||||||
|
def open_file_chunked(path):
|
||||||
manifest_path = get_manifest_path(path)
|
manifest_path = get_manifest_path(path)
|
||||||
if os.path.isfile(manifest_path):
|
if os.path.isfile(manifest_path):
|
||||||
num_chunks = int(Path(manifest_path).read_text().strip())
|
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))
|
paths = [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)]
|
||||||
if os.path.isfile(path):
|
elif os.path.isfile(path):
|
||||||
return Path(path).read_bytes()
|
paths = [path]
|
||||||
raise FileNotFoundError(path)
|
else:
|
||||||
|
raise FileNotFoundError(path)
|
||||||
|
return io.BufferedReader(ChunkStream(paths))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from openpilot.system.hardware.base import HardwareBase
|
from openpilot.common.hardware.base import HardwareBase
|
||||||
from openpilot.system.hardware.tici.hardware import Tici
|
from openpilot.common.hardware.tici.hardware import Tici
|
||||||
from openpilot.system.hardware.pc.hardware import Pc
|
from openpilot.common.hardware.pc.hardware import Pc
|
||||||
|
|
||||||
TICI = os.path.isfile('/TICI')
|
TICI = os.path.isfile('/TICI')
|
||||||
AGNOS = os.path.isfile('/AGNOS')
|
AGNOS = os.path.isfile('/AGNOS')
|
||||||
@@ -5,15 +5,13 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "cereal/gen/cpp/log.capnp.h"
|
#include "openpilot/cereal/gen/cpp/log.capnp.h"
|
||||||
|
|
||||||
// no-op base hw class
|
// no-op base hw class
|
||||||
class HardwareNone {
|
class HardwareNone {
|
||||||
public:
|
public:
|
||||||
static std::string get_name() { return ""; }
|
static std::string get_name() { return ""; }
|
||||||
static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::UNKNOWN; }
|
static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::UNKNOWN; }
|
||||||
static int get_voltage() { return 0; }
|
|
||||||
static int get_current() { return 0; }
|
|
||||||
|
|
||||||
static std::string get_serial() { return "cccccc"; }
|
static std::string get_serial() { return "cccccc"; }
|
||||||
|
|
||||||
@@ -24,6 +22,4 @@ public:
|
|||||||
static void set_ir_power(int percentage) {}
|
static void set_ir_power(int percentage) {}
|
||||||
|
|
||||||
static bool PC() { return false; }
|
static bool PC() { return false; }
|
||||||
static bool TICI() { return false; }
|
|
||||||
static bool AGNOS() { return false; }
|
|
||||||
};
|
};
|
||||||
@@ -2,28 +2,12 @@ import os
|
|||||||
from abc import abstractmethod, ABC
|
from abc import abstractmethod, ABC
|
||||||
from dataclasses import dataclass, fields
|
from dataclasses import dataclass, fields
|
||||||
|
|
||||||
from cereal import log
|
from openpilot.cereal import log
|
||||||
|
from openpilot.common.esim.base import LPABase
|
||||||
|
|
||||||
NetworkType = log.DeviceState.NetworkType
|
NetworkType = log.DeviceState.NetworkType
|
||||||
NetworkStrength = log.DeviceState.NetworkStrength
|
NetworkStrength = log.DeviceState.NetworkStrength
|
||||||
|
|
||||||
class LPAError(RuntimeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class LPAProfileNotFoundError(LPAError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Profile:
|
|
||||||
iccid: str
|
|
||||||
nickname: str
|
|
||||||
enabled: bool
|
|
||||||
provider: str
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_comma(self) -> bool:
|
|
||||||
return self.provider == 'Webbing' and self.iccid.startswith('8985235')
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ThermalZone:
|
class ThermalZone:
|
||||||
# a zone from /sys/class/thermal/thermal_zone*
|
# a zone from /sys/class/thermal/thermal_zone*
|
||||||
@@ -70,38 +54,6 @@ class ThermalConfig:
|
|||||||
ret[f.name + "TempC"] = v.read()
|
ret[f.name + "TempC"] = v.read()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
class LPABase(ABC):
|
|
||||||
@abstractmethod
|
|
||||||
def list_profiles(self) -> list[Profile]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_active_profile(self) -> Profile | None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def delete_profile(self, iccid: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def download_profile(self, qr: str, nickname: str | None = None) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def nickname_profile(self, iccid: str, nickname: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def switch_profile(self, iccid: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def process_notifications(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def is_euicc(self) -> bool:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class HardwareBase(ABC):
|
class HardwareBase(ABC):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_cmdline() -> dict[str, str]:
|
def get_cmdline() -> dict[str, str]:
|
||||||
@@ -133,7 +85,7 @@ class HardwareBase(ABC):
|
|||||||
def get_device_type(self):
|
def get_device_type(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_imei(self, slot) -> str:
|
def get_imei(self) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def get_serial(self):
|
def get_serial(self):
|
||||||
@@ -190,21 +142,12 @@ class HardwareBase(ABC):
|
|||||||
def get_gpu_usage_percent(self):
|
def get_gpu_usage_percent(self):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def get_modem_version(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_modem_temperatures(self):
|
def get_modem_temperatures(self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def initialize_hardware(self):
|
def initialize_hardware(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_networks(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def has_internal_panda(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def reset_internal_panda(self):
|
def reset_internal_panda(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -214,11 +157,5 @@ class HardwareBase(ABC):
|
|||||||
def get_modem_data_usage(self):
|
def get_modem_data_usage(self):
|
||||||
return -1, -1
|
return -1, -1
|
||||||
|
|
||||||
def get_voltage(self) -> float:
|
|
||||||
return 0.
|
|
||||||
|
|
||||||
def get_current(self) -> float:
|
|
||||||
return 0.
|
|
||||||
|
|
||||||
def set_ir_power(self, percent: int):
|
def set_ir_power(self, percent: int):
|
||||||
pass
|
pass
|
||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "system/hardware/base.h"
|
#include "common/hardware/base.h"
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
|
|
||||||
#if __TICI__
|
#if __TICI__
|
||||||
#include "system/hardware/tici/hardware.h"
|
#include "common/hardware/tici/hardware.h"
|
||||||
#define Hardware HardwareTici
|
#define Hardware HardwareTici
|
||||||
#else
|
#else
|
||||||
#include "system/hardware/pc/hardware.h"
|
#include "common/hardware/pc/hardware.h"
|
||||||
#define Hardware HardwarePC
|
#define Hardware HardwarePC
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from openpilot.system.hardware import PC
|
from openpilot.common.hardware import PC
|
||||||
|
|
||||||
DEFAULT_DOWNLOAD_CACHE_ROOT = "/tmp/comma_download_cache"
|
DEFAULT_DOWNLOAD_CACHE_ROOT = "/tmp/comma_download_cache"
|
||||||
|
|
||||||
@@ -2,13 +2,11 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "system/hardware/base.h"
|
#include "common/hardware/base.h"
|
||||||
|
|
||||||
class HardwarePC : public HardwareNone {
|
class HardwarePC : public HardwareNone {
|
||||||
public:
|
public:
|
||||||
static std::string get_name() { return "pc"; }
|
static std::string get_name() { return "pc"; }
|
||||||
static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; }
|
static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; }
|
||||||
static bool PC() { return true; }
|
static bool PC() { return true; }
|
||||||
static bool TICI() { return util::getenv("TICI", 0) == 1; }
|
|
||||||
static bool AGNOS() { return util::getenv("TICI", 0) == 1; }
|
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from openpilot.cereal import log
|
||||||
|
from openpilot.common.hardware.base import HardwareBase
|
||||||
|
|
||||||
|
class Pc(HardwareBase):
|
||||||
|
def get_device_type(self):
|
||||||
|
return "pc"
|
||||||
|
|
||||||
|
def get_network_type(self):
|
||||||
|
# some stuff is gated on wifi, so just assume for now
|
||||||
|
return log.DeviceState.NetworkType.wifi
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -56,28 +56,28 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "boot",
|
"name": "boot",
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8.img.xz",
|
"url": "https://commadist.azureedge.net/agnosupdate/boot-19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39.img.xz",
|
||||||
"hash": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
|
"hash": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39",
|
||||||
"hash_raw": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
|
"hash_raw": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39",
|
||||||
"size": 17487872,
|
"size": 17487872,
|
||||||
"sparse": false,
|
"sparse": false,
|
||||||
"full_check": true,
|
"full_check": true,
|
||||||
"has_ab": true,
|
"has_ab": true,
|
||||||
"ondevice_hash": "edca8bee1531e66953d107eeceeed2dc7b3ca46417e49d55508f94e58bf95db8"
|
"ondevice_hash": "ddfe93cc6a8531af92ee331d9bbaeae2f1d933bdb38e579769dc9fe7998eb626"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "system",
|
"name": "system",
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img.xz",
|
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img.xz",
|
||||||
"hash": "78acfe16a7b62a3a91fc7a81f40a693e4468cec1c69df7d0b1e550aacc646113",
|
"hash": "4dc41c2c072f5f5d5cd484cd6173049cd96acfb9a67bc20049775585fe881539",
|
||||||
"hash_raw": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
|
"hash_raw": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724",
|
||||||
"size": 4718592000,
|
"size": 4718592000,
|
||||||
"sparse": true,
|
"sparse": true,
|
||||||
"full_check": false,
|
"full_check": false,
|
||||||
"has_ab": true,
|
"has_ab": true,
|
||||||
"ondevice_hash": "743142c5a898f27b2a1029cca42c8a5d5d1fc0096414422b850fe84c8d0b8342",
|
"ondevice_hash": "cf1229630b7a2b8497705bca4ba947dbf0c217418ff4febff571aa4f4a878134",
|
||||||
"alt": {
|
"alt": {
|
||||||
"hash": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
|
"hash": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724",
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img",
|
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img",
|
||||||
"size": 4718592000
|
"size": 4718592000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,19 +10,16 @@ from collections.abc import Generator
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import openpilot.system.updated.casync.casync as casync
|
|
||||||
|
|
||||||
SPARSE_CHUNK_FMT = struct.Struct('H2xI4x')
|
SPARSE_CHUNK_FMT = struct.Struct('H2xI4x')
|
||||||
CAIBX_URL = "https://commadist.azureedge.net/agnosupdate/"
|
|
||||||
|
|
||||||
AGNOS_MANIFEST_FILE = "system/hardware/tici/agnos.json"
|
AGNOS_MANIFEST_FILE = "openpilot/system/hardware/tici/agnos.json"
|
||||||
|
|
||||||
|
|
||||||
class StreamingDecompressor:
|
class StreamingDecompressor:
|
||||||
def __init__(self, url: str) -> None:
|
def __init__(self, url: str) -> None:
|
||||||
self.buf = b""
|
self.buf = b""
|
||||||
|
|
||||||
self.req = requests.get(url, stream=True, headers={'Accept-Encoding': None}, timeout=60)
|
self.req = requests.get(url, stream=True, headers={'Accept-Encoding': 'identity'}, timeout=60)
|
||||||
self.it = self.req.iter_content(chunk_size=1024 * 1024)
|
self.it = self.req.iter_content(chunk_size=1024 * 1024)
|
||||||
self.decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO)
|
self.decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO)
|
||||||
self.eof = False
|
self.eof = False
|
||||||
@@ -187,50 +184,6 @@ def extract_compressed_image(target_slot_number: int, partition: dict, cloudlog)
|
|||||||
os.sync()
|
os.sync()
|
||||||
|
|
||||||
|
|
||||||
def extract_casync_image(target_slot_number: int, partition: dict, cloudlog):
|
|
||||||
path = get_partition_path(target_slot_number, partition)
|
|
||||||
seed_path = path[:-1] + ('b' if path[-1] == 'a' else 'a')
|
|
||||||
|
|
||||||
target = casync.parse_caibx(partition['casync_caibx'])
|
|
||||||
|
|
||||||
sources: list[tuple[str, casync.ChunkReader, casync.ChunkDict]] = []
|
|
||||||
|
|
||||||
# First source is the current partition.
|
|
||||||
try:
|
|
||||||
raw_hash = get_raw_hash(seed_path, partition['size'])
|
|
||||||
caibx_url = f"{CAIBX_URL}{partition['name']}-{raw_hash}.caibx"
|
|
||||||
|
|
||||||
try:
|
|
||||||
cloudlog.info(f"casync fetching {caibx_url}")
|
|
||||||
sources += [('seed', casync.FileChunkReader(seed_path), casync.build_chunk_dict(casync.parse_caibx(caibx_url)))]
|
|
||||||
except requests.RequestException:
|
|
||||||
cloudlog.error(f"casync failed to load {caibx_url}")
|
|
||||||
except Exception:
|
|
||||||
cloudlog.exception("casync failed to hash seed partition")
|
|
||||||
|
|
||||||
# Second source is the target partition, this allows for resuming
|
|
||||||
sources += [('target', casync.FileChunkReader(path), casync.build_chunk_dict(target))]
|
|
||||||
|
|
||||||
# Finally we add the remote source to download any missing chunks
|
|
||||||
sources += [('remote', casync.RemoteChunkReader(partition['casync_store']), casync.build_chunk_dict(target))]
|
|
||||||
|
|
||||||
last_p = 0
|
|
||||||
|
|
||||||
def progress(cur):
|
|
||||||
nonlocal last_p
|
|
||||||
p = int(cur / partition['size'] * 100)
|
|
||||||
if p != last_p:
|
|
||||||
last_p = p
|
|
||||||
print(f"Installing {partition['name']}: {p}", flush=True)
|
|
||||||
|
|
||||||
stats = casync.extract(target, sources, path, progress)
|
|
||||||
cloudlog.error(f'casync done {json.dumps(stats)}')
|
|
||||||
|
|
||||||
os.sync()
|
|
||||||
if not verify_partition(target_slot_number, partition, force_full_check=True):
|
|
||||||
raise Exception(f"Raw hash mismatch '{partition['hash_raw'].lower()}'")
|
|
||||||
|
|
||||||
|
|
||||||
def flash_partition(target_slot_number: int, partition: dict, cloudlog, standalone=False):
|
def flash_partition(target_slot_number: int, partition: dict, cloudlog, standalone=False):
|
||||||
cloudlog.info(f"Downloading and writing {partition['name']}")
|
cloudlog.info(f"Downloading and writing {partition['name']}")
|
||||||
|
|
||||||
@@ -245,10 +198,7 @@ def flash_partition(target_slot_number: int, partition: dict, cloudlog, standalo
|
|||||||
|
|
||||||
path = get_partition_path(target_slot_number, partition)
|
path = get_partition_path(target_slot_number, partition)
|
||||||
|
|
||||||
if ('casync_caibx' in partition) and not standalone:
|
extract_compressed_image(target_slot_number, partition, cloudlog)
|
||||||
extract_casync_image(target_slot_number, partition, cloudlog)
|
|
||||||
else:
|
|
||||||
extract_compressed_image(target_slot_number, partition, cloudlog)
|
|
||||||
|
|
||||||
# Write hash after successful flash
|
# Write hash after successful flash
|
||||||
if not full_check:
|
if not full_check:
|
||||||
@@ -278,7 +228,7 @@ def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog, st
|
|||||||
cloudlog.info(f"Target slot {target_slot_number}")
|
cloudlog.info(f"Target slot {target_slot_number}")
|
||||||
|
|
||||||
# set target slot as unbootable
|
# set target slot as unbootable
|
||||||
os.system(f"abctl --set_unbootable {target_slot_number}")
|
subprocess.run(f"abctl --set_unbootable {target_slot_number}", shell=True)
|
||||||
|
|
||||||
for partition in update:
|
for partition in update:
|
||||||
success = False
|
success = False
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user