mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-24 17:53:43 +08:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b111277f46 | |||
| 9d3963559a | |||
| 0fba33b093 | |||
| 1b8c44b506 | |||
| 6f46f988d9 | |||
| 68485aa4e4 | |||
| 1581fdc198 | |||
| 317ae0fb37 | |||
| 5bf4196aed | |||
| 38aa03e0f7 | |||
| 9a9dc3ab23 | |||
| e4aa959e2c | |||
| 2aa9a56f40 | |||
| 721ed4ec0e | |||
| 70be4ceab1 | |||
| 5cf91d0496 | |||
| 6fee0bdb2d | |||
| e40c161125 | |||
| 97eb55cc55 | |||
| 65134be0d1 | |||
| dbf71a23aa | |||
| 7fec3db1d6 | |||
| 26b573c1d0 | |||
| 5ec1e7307e | |||
| 8bc36b7f21 | |||
| 3b909eb693 | |||
| 57e39c4472 | |||
| ff7672339c | |||
| 6e824a2c22 | |||
| 32fa49e093 | |||
| 2250eac58f | |||
| 97be6b3a0e | |||
| 942655c947 | |||
| 615db3f7fd | |||
| 0bb75c5389 | |||
| 7b5ee81d2d | |||
| 7fe46f1e1d | |||
| 50c0d1c9da | |||
| 6dbf544d06 | |||
| 41e3a0f699 | |||
| c5d8aec28b | |||
| 4653a9aef0 | |||
| ab3492bb90 | |||
| ed7cbb3866 | |||
| a30626cfe3 | |||
| d2c087b3e2 | |||
| 0a747f991d | |||
| f5a1e86d85 | |||
| 693bcb0f83 | |||
| 0d0daed86e | |||
| 95a349abcc | |||
| c6ba5dc539 | |||
| 6c3afeec0f | |||
| 29c58b4588 | |||
| ecc565aa3f | |||
| db61810f98 | |||
| 48f203ad5b | |||
| 6ab4ac2dfb | |||
| 9cb3c7b6e6 | |||
| adaa4ed350 | |||
| a64b9aa9b8 | |||
| 0138eca61d | |||
| 139a40de29 | |||
| 17d9becd3c | |||
| 449b482cc3 | |||
| a7e099c946 | |||
| 610462be5a | |||
| 207d32668f | |||
| e94a30bec0 |
-19
@@ -1,19 +0,0 @@
|
||||
---
|
||||
Checks: '
|
||||
bugprone-*,
|
||||
-bugprone-integer-division,
|
||||
-bugprone-narrowing-conversions,
|
||||
performance-*,
|
||||
clang-analyzer-*,
|
||||
misc-*,
|
||||
-misc-unused-parameters,
|
||||
modernize-*,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-deprecated-headers,
|
||||
-modernize-use-auto,
|
||||
-modernize-use-using,
|
||||
-modernize-use-nullptr,
|
||||
-modernize-use-trailing-return-type,
|
||||
'
|
||||
CheckOptions:
|
||||
...
|
||||
@@ -1,5 +0,0 @@
|
||||
Wen
|
||||
REGIST
|
||||
PullRequest
|
||||
cancelled
|
||||
FOF
|
||||
@@ -1,39 +0,0 @@
|
||||
**/.git
|
||||
.DS_Store
|
||||
*.dylib
|
||||
*.DSYM
|
||||
*.d
|
||||
*.pyc
|
||||
*.pyo
|
||||
.*.swp
|
||||
.*.swo
|
||||
.*.un~
|
||||
*.tmp
|
||||
*.o
|
||||
*.o-*
|
||||
*.os
|
||||
*.os-*
|
||||
*.so
|
||||
*.a
|
||||
|
||||
venv/
|
||||
.venv/
|
||||
|
||||
notebooks
|
||||
phone
|
||||
massivemap
|
||||
neos
|
||||
installer
|
||||
chffr/app2
|
||||
chffr/backend/env
|
||||
selfdrive/nav
|
||||
selfdrive/baseui
|
||||
selfdrive/test/simulator2
|
||||
**/cache_data
|
||||
xx/plus
|
||||
xx/community
|
||||
xx/projects
|
||||
!xx/projects/eon_testing_master
|
||||
!xx/projects/map3d
|
||||
xx/ops
|
||||
xx/junk
|
||||
@@ -1,11 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{py,pyx,pxd}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -1,21 +0,0 @@
|
||||
* text=auto
|
||||
|
||||
# to move existing files into LFS:
|
||||
# git add --renormalize .
|
||||
*.onnx filter=lfs diff=lfs merge=lfs -text
|
||||
*.svg filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.gif filter=lfs diff=lfs merge=lfs -text
|
||||
*.ttf 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
|
||||
system/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text
|
||||
third_party/**/*.a filter=lfs diff=lfs merge=lfs -text
|
||||
third_party/**/*.so filter=lfs diff=lfs merge=lfs -text
|
||||
third_party/**/*.so.* filter=lfs diff=lfs merge=lfs -text
|
||||
third_party/**/*.dylib filter=lfs diff=lfs merge=lfs -text
|
||||
third_party/acados/*/t_renderer filter=lfs diff=lfs merge=lfs -text
|
||||
third_party/qt5/larch64/bin/lrelease filter=lfs diff=lfs merge=lfs -text
|
||||
third_party/qt5/larch64/bin/lupdate filter=lfs diff=lfs merge=lfs -text
|
||||
third_party/catch2/include/catch2/catch.hpp filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -1,11 +0,0 @@
|
||||
* @sunnypilot/dev-internal
|
||||
/.github/ @devtekve @sunnyhaibin
|
||||
/release/ci/ @devtekve @sunnyhaibin
|
||||
/tinygrad_repo @devtekve @Discountchubbs
|
||||
/tinygrad/ @devtekve @Discountchubbs
|
||||
/selfdrive/controls/lib/longitudinal_planner.py @devtekve @Discountchubbs
|
||||
/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @devtekve @Discountchubbs
|
||||
/selfdrive/modeld/ @devtekve @Discountchubbs
|
||||
/sunnypilot/model* @devtekve @Discountchubbs
|
||||
/sunnypilot/sunnylink/ @devtekve
|
||||
/system/athena/ @devtekve
|
||||
@@ -1,47 +0,0 @@
|
||||
name: Bug report
|
||||
description: For issues with running openpilot on your comma device
|
||||
labels: ["bug"]
|
||||
body:
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Before creating a **bug report**, please check the following:
|
||||
* If the issue likely only affects your car model or make, go back and open a **car bug report** instead.
|
||||
* If the issue is related to the driving or driver monitoring models, you should open a [discussion](https://github.com/commaai/openpilot/discussions/categories/model-feedback) instead.
|
||||
* Ensure you're running the latest openpilot release.
|
||||
* Ensure you're using officially supported hardware. Issues running on PCs have a different issue template.
|
||||
* Ensure there isn't an existing issue for your bug. If there is, leave a comment on the existing issue.
|
||||
* Ensure you're running stock openpilot. We cannot look into bug reports from forks.
|
||||
|
||||
If you're unsure whether you've hit a bug, check out the #installation-help channel in the [community Discord server](https://discord.comma.ai).
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: Also include a description of how to reproduce the bug
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: route
|
||||
attributes:
|
||||
label: Provide a route where the issue occurs
|
||||
description: Ensure the route is fully uploaded at https://useradmin.comma.ai. We cannot look into issues without routes, or at least a Dongle ID.
|
||||
placeholder: 77611a1fac303767|2020-05-11--16-37-07
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: openpilot version
|
||||
description: If you're not on release, provide the commit hash
|
||||
placeholder: 0.8.10
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Car bug report
|
||||
url: https://github.com/commaai/opendbc/issues/new
|
||||
about: For issues with a particular car make or model
|
||||
- name: Join the Discord
|
||||
url: https://discord.comma.ai
|
||||
about: The community Discord is for both openpilot development and experience discussion
|
||||
- name: Report driving behavior feedback
|
||||
url: https://discord.com/channels/469524606043160576/1254834193066623017
|
||||
about: Feedback for the driving and driver monitoring models goes in the #driving-feedback in Discord
|
||||
- name: Community Wiki
|
||||
url: https://github.com/commaai/openpilot/wiki
|
||||
about: Check out our community wiki
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
name: Enhancement
|
||||
about: For openpilot enhancement suggestions
|
||||
title: ''
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
name: PC bug report
|
||||
description: For issues with running openpilot on PC
|
||||
labels: ["PC"]
|
||||
body:
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Before creating a **bug report**, please check the following:
|
||||
* Ensure you're running the latest openpilot release.
|
||||
* Ensure there isn't an existing issue for your bug. If there is, leave a comment on the existing issue.
|
||||
* Ensure you're running stock openpilot. We cannot look into bug reports from forks.
|
||||
|
||||
If you're unsure whether you've hit a bug, check out the #installation-help channel in the [community Discord server](https://discord.comma.ai).
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: Also include a description of how to reproduce the bug
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: os-version
|
||||
attributes:
|
||||
label: OS Version
|
||||
placeholder: Ubuntu 24.04
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: openpilot version or commit
|
||||
placeholder: bd36f2ec8d3559909678eff2690c10a520938367
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
ci:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: "{.github/**,**/test_*,**/test/**,Jenkinsfile}"
|
||||
|
||||
chore:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: "{.github/**}"
|
||||
|
||||
car:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: '{selfdrive/car/**,opendbc_repo}'
|
||||
|
||||
simulation:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: 'tools/sim/**'
|
||||
|
||||
ui:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: '{selfdrive/ui/**,system/ui/**}'
|
||||
|
||||
tools:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: 'tools/**'
|
||||
|
||||
multilanguage:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: 'selfdrive/ui/translations/**'
|
||||
|
||||
autonomy:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: "{selfdrive/modeld/models/**,selfdrive/test/process_replay/model_replay_ref_commit,sunnypilot/modeld*/models/**}"
|
||||
@@ -1,68 +0,0 @@
|
||||
<!-- Please copy and paste the relevant template -->
|
||||
|
||||
<!--- ***** Template: Fingerprint *****
|
||||
|
||||
**Car**
|
||||
Which car (make, model, year) this fingerprint is for
|
||||
|
||||
**Route**
|
||||
A route with the fingerprint
|
||||
|
||||
-->
|
||||
|
||||
<!--- ***** Template: Car Bugfix *****
|
||||
|
||||
**Description**
|
||||
|
||||
A description of the bug and the fix. Also link the issue if it exists.
|
||||
|
||||
**Verification**
|
||||
|
||||
Explain how you tested this bug fix.
|
||||
|
||||
**Route**
|
||||
|
||||
Route: [a route with the bug fix]
|
||||
|
||||
|
||||
-->
|
||||
|
||||
<!--- ***** Template: Bugfix *****
|
||||
|
||||
**Description**
|
||||
|
||||
A description of the bug and the fix. Also link the issue if it exists.
|
||||
|
||||
**Verification**
|
||||
|
||||
Explain how you tested this bug fix.
|
||||
|
||||
|
||||
-->
|
||||
|
||||
<!--- ***** Template: Car Port *****
|
||||
|
||||
**Checklist**
|
||||
|
||||
- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `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)
|
||||
- [ ] route with openpilot:
|
||||
- [ ] route with stock system:
|
||||
- [ ] car harness used (if comma doesn't sell it, put N/A):
|
||||
|
||||
|
||||
-->
|
||||
|
||||
<!--- ***** Template: Refactor *****
|
||||
|
||||
**Description**
|
||||
|
||||
A description of the refactor, including the goals it accomplishes.
|
||||
|
||||
**Verification**
|
||||
|
||||
Explain how you tested the refactor for regressions.
|
||||
|
||||
|
||||
-->
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
exclude-labels:
|
||||
- 'no-changelog'
|
||||
categories:
|
||||
- title: '🚀 Features'
|
||||
labels:
|
||||
- 'feature'
|
||||
- 'enhancement'
|
||||
- title: '🐛 Bug Fixes'
|
||||
collapse-after: 5
|
||||
labels:
|
||||
- 'fix'
|
||||
- 'bugfix'
|
||||
- 'bug'
|
||||
- title: '🧰 Maintenance'
|
||||
collapse-after: 5
|
||||
label: 'chore'
|
||||
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
||||
change-title-escapes: '\<*_&'
|
||||
replacers:
|
||||
- search: '/[Ss][Uu][Nn][Nn][Yy][Pp][Ii][Ll][Oo][Tt]/g'
|
||||
replace: 'sunnypilot'
|
||||
- search: '/\b[Ss][Pp]\b/g'
|
||||
replace: 'SP'
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- 'major'
|
||||
minor:
|
||||
labels:
|
||||
- 'minor'
|
||||
patch:
|
||||
labels:
|
||||
- 'patch'
|
||||
default: patch
|
||||
name-template: 'v$RESOLVED_VERSION 🚀'
|
||||
tag-template: 'v$RESOLVED_VERSION'
|
||||
version-template: "0.$MAJOR.$MINOR.$PATCH" # The day OP becomes v1, we need to bump this
|
||||
tag-prefix: "v0." # The day OP becomes v1, we need to bump this
|
||||
prerelease-identifier: "staging"
|
||||
template: |
|
||||
## Changes
|
||||
|
||||
$CHANGES
|
||||
@@ -1,58 +0,0 @@
|
||||
name: 'automatically cache based on current runner'
|
||||
|
||||
inputs:
|
||||
path:
|
||||
description: 'path to cache'
|
||||
required: true
|
||||
key:
|
||||
description: 'key'
|
||||
required: true
|
||||
restore-keys:
|
||||
description: 'restore-keys'
|
||||
required: true
|
||||
save:
|
||||
description: 'whether to save the cache'
|
||||
default: 'true'
|
||||
required: false
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: 'cache hit occurred'
|
||||
value: ${{ (contains(runner.name, 'nsc') && steps.ns-cache.outputs.cache-hit) ||
|
||||
(!contains(runner.name, 'nsc') && inputs.save != 'false' && steps.gha-cache.outputs.cache-hit) ||
|
||||
(!contains(runner.name, 'nsc') && inputs.save == 'false' && steps.gha-cache-ro.outputs.cache-hit) }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: setup namespace cache
|
||||
id: ns-cache
|
||||
if: ${{ contains(runner.name, 'nsc') }}
|
||||
uses: namespacelabs/nscloud-cache-action@v1
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
|
||||
- name: setup github cache
|
||||
id: gha-cache
|
||||
if: ${{ !contains(runner.name, 'nsc') && inputs.save != 'false' }}
|
||||
uses: 'actions/cache@v4'
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ inputs.key }}
|
||||
restore-keys: ${{ inputs.restore-keys }}
|
||||
|
||||
- name: setup github cache
|
||||
id: gha-cache-ro
|
||||
if: ${{ !contains(runner.name, 'nsc') && inputs.save == 'false' }}
|
||||
uses: 'actions/cache/restore@v4'
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ inputs.key }}
|
||||
restore-keys: ${{ inputs.restore-keys }}
|
||||
|
||||
# make the directory manually in case we didn't get a hit, so it doesn't fail on future steps
|
||||
- id: scons-cache-setup
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p ${{ inputs.path }}
|
||||
sudo chmod -R 777 ${{ inputs.path }}
|
||||
sudo chown -R $USER ${{ inputs.path }}
|
||||
@@ -1,91 +0,0 @@
|
||||
name: "PR review"
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ opened, reopened, synchronize, edited ]
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: review
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
# Label PRs
|
||||
- uses: actions/labeler@v5.0.0
|
||||
with:
|
||||
dot: true
|
||||
configuration-path: .github/labeler.yaml
|
||||
|
||||
# Check PR target branch
|
||||
- name: check branch
|
||||
uses: Vankka/pr-target-branch-action@def32ec9d93514138d6ac0132ee62e120a72aed5
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
target: /^(?!master$).*/
|
||||
exclude: /sunnypilot:.*/
|
||||
change-to: ${{ github.base_ref }}
|
||||
already-exists-action: close_this
|
||||
already-exists-comment: "Your PR should be made against the `master` branch"
|
||||
|
||||
update-pr-labels:
|
||||
name: Update fork's PR Labels
|
||||
runs-on: ubuntu-latest
|
||||
if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize'))
|
||||
env:
|
||||
PR_LABEL: 'dev-c3'
|
||||
TRUST_FORK_PR_LABEL: 'trust-fork-pr'
|
||||
steps:
|
||||
- name: Check if PR has dev-c3 label
|
||||
id: check-labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const hasDevC3Label = labels.some(label => label.name === process.env.PR_LABEL);
|
||||
const hasTrustLabel = labels.some(label => label.name === process.env.TRUST_FORK_PR_LABEL);
|
||||
|
||||
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
|
||||
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
|
||||
|
||||
core.setOutput('has-dev-c3', hasDevC3Label ? 'true' : 'false');
|
||||
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
|
||||
|
||||
- name: Remove trust-fork-pr label if present
|
||||
if: steps.check-labels.outputs.has-dev-c3 == 'true' && steps.check-labels.outputs.has-trust == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
name: process.env.TRUST_FORK_PR_LABEL
|
||||
});
|
||||
|
||||
console.log(`Removed '${process.env.TRUST_FORK_PR_LABEL}' label from PR #${prNumber} as it received new commits`);
|
||||
|
||||
// Add a comment to the PR
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: `The \`${process.env.TRUST_FORK_PR_LABEL}\` label has been automatically removed because new commits were pushed to this PR. This PR will need to be re-reviewed before the label can be applied again.`
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
name: badges
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 * * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BASE_IMAGE: sunnypilot-base
|
||||
DOCKER_REGISTRY: ghcr.io/sunnypilot
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/bash -c
|
||||
|
||||
jobs:
|
||||
badges:
|
||||
name: create badges
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Push badges
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc) && python3 selfdrive/ui/translations/create_badges.py"
|
||||
|
||||
rm .gitattributes
|
||||
|
||||
git checkout --orphan badges
|
||||
git rm -rf --cached .
|
||||
git config user.email "badge-researcher@sunnypilot.ai"
|
||||
git config user.name "Badge Researcher"
|
||||
|
||||
git add translation_badge.svg
|
||||
git commit -m "Add/Update badges"
|
||||
git push -f origin HEAD
|
||||
@@ -1,303 +0,0 @@
|
||||
name: Build and push all tinygrad models
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
set_min_version:
|
||||
description: 'Minimum selector version required for the models (see helpers.py or readme.md)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
json_version: ${{ steps.get-json.outputs.json_version }}
|
||||
recompiled_dir: ${{ steps.create-recompiled-dir.outputs.recompiled_dir }}
|
||||
json_file: ${{ steps.get-json.outputs.json_file }}
|
||||
model_matrix: ${{ steps.set-matrix.outputs.model_matrix }}
|
||||
tinygrad_ref: ${{ steps.get-tinygrad-ref.outputs.tinygrad_ref }}
|
||||
steps:
|
||||
- name: Checkout sunnypilot repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: sunnypilot/sunnypilot
|
||||
path: sunnypilot
|
||||
submodules: recursive
|
||||
|
||||
- name: Get tinygrad_repo ref
|
||||
id: get-tinygrad-ref
|
||||
run: |
|
||||
cd sunnypilot
|
||||
export PYTHONPATH=$(pwd)
|
||||
ref=$(python3 sunnypilot/models/tinygrad_ref.py)
|
||||
echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT
|
||||
echo "tinygrad_ref is $ref"
|
||||
|
||||
- name: Checkout docs repo (sunnypilot-docs, gh-pages)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: sunnypilot/sunnypilot-docs
|
||||
ref: gh-pages
|
||||
path: docs
|
||||
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
||||
|
||||
- name: Get next JSON version to use (from GitHub docs repo)
|
||||
id: get-json
|
||||
run: |
|
||||
cd docs/docs
|
||||
latest=$(ls driving_models_v*.json | sed -E 's/.*_v([0-9]+)\.json/\1/' | sort -n | tail -1)
|
||||
next=$((latest+1))
|
||||
json_file="driving_models_v${next}.json"
|
||||
cp "driving_models_v${latest}.json" "$json_file"
|
||||
echo "json_file=docs/docs/$json_file" >> $GITHUB_OUTPUT
|
||||
echo "json_version=$((next+0))" >> $GITHUB_OUTPUT
|
||||
echo "SRC_JSON_FILE=docs/docs/driving_models_v${latest}.json" >> $GITHUB_ENV
|
||||
|
||||
- name: Extract tinygrad models
|
||||
id: set-matrix
|
||||
working-directory: docs/docs
|
||||
run: |
|
||||
jq -c '[.bundles[] | select(.runner=="tinygrad") | {ref, display_name: (.display_name | gsub(" \\([^)]*\\)"; "")), is_20hz}]' "$(basename "${SRC_JSON_FILE}")" > matrix.json
|
||||
echo "model_matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
|
||||
- run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Clone GitLab docs repo and create new recompiled dir
|
||||
id: create-recompiled-dir
|
||||
env:
|
||||
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
||||
run: |
|
||||
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
|
||||
cd gitlab_docs
|
||||
git checkout main
|
||||
git sparse-checkout set --no-cone models/
|
||||
cd models
|
||||
latest_dir=$(ls -d recompiled* 2>/dev/null | sed -E 's/recompiled([0-9]+)/\1/' | sort -n | tail -1)
|
||||
if [[ -z "$latest_dir" ]]; then
|
||||
next_dir=1
|
||||
else
|
||||
next_dir=$((latest_dir+1))
|
||||
fi
|
||||
recompiled_dir="${next_dir}"
|
||||
mkdir -p "recompiled${recompiled_dir}"
|
||||
touch "recompiled${recompiled_dir}/.gitkeep"
|
||||
cd ../..
|
||||
echo "recompiled_dir=$recompiled_dir" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Push empty recompiled dir to GitLab
|
||||
run: |
|
||||
cd gitlab_docs
|
||||
git add models/recompiled${{ steps.create-recompiled-dir.outputs.recompiled_dir }}
|
||||
git config --global user.name "GitHub Action"
|
||||
git config --global user.email "action@github.com"
|
||||
git commit -m "Add recompiled${{ steps.create-recompiled-dir.outputs.recompiled_dir }} for build-all" || echo "No changes to commit"
|
||||
git push origin main
|
||||
|
||||
- name: Push new JSON to GitHub docs repo
|
||||
run: |
|
||||
cd docs
|
||||
git pull origin gh-pages
|
||||
git add docs/"$(basename ${{ steps.get-json.outputs.json_file }})"
|
||||
git config --global user.name "GitHub Action"
|
||||
git config --global user.email "action@github.com"
|
||||
git commit -m "Add new ${{ steps.get-json.outputs.json_file }} for build-all" || echo "No changes to commit"
|
||||
git push origin gh-pages
|
||||
|
||||
get_and_build:
|
||||
needs: [setup]
|
||||
strategy:
|
||||
matrix:
|
||||
model: ${{ fromJson(needs.setup.outputs.model_matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/build-single-tinygrad-model.yaml
|
||||
with:
|
||||
upstream_branch: ${{ matrix.model.ref }}
|
||||
custom_name: ${{ matrix.model.display_name }}
|
||||
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
|
||||
json_version: ${{ needs.setup.outputs.json_version }}
|
||||
secrets: inherit
|
||||
|
||||
retry_failed_models:
|
||||
needs: [setup, get_and_build]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ needs.setup.result != 'failure' && !cancelled() }}
|
||||
outputs:
|
||||
retry_matrix: ${{ steps.set-retry-matrix.outputs.retry_matrix }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: model-*
|
||||
path: output
|
||||
|
||||
- id: set-retry-matrix
|
||||
run: |
|
||||
echo '${{ needs.setup.outputs.model_matrix }}' > matrix.json
|
||||
built=(); while IFS= read -r line; do built+=("$line"); done < <(
|
||||
ls output | sed -E 's/^model-//' | sed -E 's/-[0-9]+$//' | sed -E 's/ \([^)]*\)//' | awk '{gsub(/^ +| +$/, ""); print}'
|
||||
)
|
||||
jq -c --argjson built "$(printf '%s\n' "${built[@]}" | jq -R . | jq -s .)" \
|
||||
'map(select(.display_name as $n | ($built | index($n | gsub("^ +| +$"; "")) | not)))' matrix.json > retry_matrix.json
|
||||
echo "retry_matrix=$(cat retry_matrix.json)" >> $GITHUB_OUTPUT
|
||||
|
||||
retry_get_and_build:
|
||||
needs: [setup, get_and_build, retry_failed_models]
|
||||
if: ${{ needs.get_and_build.result == 'failure' || (needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '') }}
|
||||
strategy:
|
||||
matrix:
|
||||
model: ${{ fromJson(needs.retry_failed_models.outputs.retry_matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/build-single-tinygrad-model.yaml
|
||||
with:
|
||||
upstream_branch: ${{ matrix.model.ref }}
|
||||
custom_name: ${{ matrix.model.display_name }}
|
||||
recompiled_dir: ${{ needs.setup.outputs.recompiled_dir }}
|
||||
json_version: ${{ needs.setup.outputs.json_version }}
|
||||
artifact_suffix: -retry
|
||||
secrets: inherit
|
||||
|
||||
publish_models:
|
||||
name: Publish models sequentially
|
||||
needs: [setup, get_and_build, retry_failed_models, retry_get_and_build]
|
||||
if: ${{ !cancelled() && (needs.get_and_build.result != 'failure' || needs.retry_get_and_build.result == 'success' || (needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '')) }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
model: ${{ fromJson(needs.setup.outputs.model_matrix) }}
|
||||
env:
|
||||
RECOMPILED_DIR: recompiled${{ needs.setup.outputs.recompiled_dir }}
|
||||
JSON_FILE: ${{ needs.setup.outputs.json_file }}
|
||||
ARTIFACT_NAME_INPUT: ${{ matrix.model.display_name }}
|
||||
steps:
|
||||
- name: Set up SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Add GitLab.com SSH key to known_hosts
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Clone GitLab docs repo
|
||||
env:
|
||||
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
||||
run: |
|
||||
echo "Cloning GitLab"
|
||||
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
|
||||
cd gitlab_docs
|
||||
echo "checkout models/${RECOMPILED_DIR}"
|
||||
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
|
||||
git checkout main
|
||||
cd ..
|
||||
|
||||
- name: Checkout docs repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: sunnypilot/sunnypilot-docs
|
||||
ref: gh-pages
|
||||
path: docs
|
||||
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
||||
|
||||
- name: Validate recompiled dir and JSON version
|
||||
run: |
|
||||
if [ ! -d "gitlab_docs/models/$RECOMPILED_DIR" ]; then
|
||||
echo "Recompiled dir $RECOMPILED_DIR does not exist in GitLab repo"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$JSON_FILE" ]; then
|
||||
echo "JSON file $JSON_FILE does not exist!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download artifact name file
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: artifact-name-${{ env.ARTIFACT_NAME_INPUT }}
|
||||
path: artifact_name
|
||||
|
||||
- name: Read artifact name
|
||||
id: read-artifact-name
|
||||
run: |
|
||||
ARTIFACT_NAME=$(cat artifact_name/artifact_name.txt)
|
||||
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download model artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
||||
path: output
|
||||
|
||||
- name: Remove onnx files bc not needed for recompiled dir since they already exist from single build
|
||||
run: |
|
||||
find output -type f -name '*.onnx' -delete
|
||||
find output -type f -name 'big_*.pkl' -delete
|
||||
find output -type f -name 'dmonitoring_model_tinygrad.pkl' -delete
|
||||
|
||||
- name: Copy model artifacts to gitlab
|
||||
env:
|
||||
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
||||
run: |
|
||||
ARTIFACT_DIR="gitlab_docs/models/${RECOMPILED_DIR}/${ARTIFACT_NAME}"
|
||||
mkdir -p "$ARTIFACT_DIR"
|
||||
for path in output/*; do
|
||||
if [ "$(basename "$path")" = "artifact_name.txt" ]; then
|
||||
continue
|
||||
fi
|
||||
name="$(basename "$path")"
|
||||
if [ -d "$path" ]; then
|
||||
mkdir -p "$ARTIFACT_DIR/$name"
|
||||
cp -r "$path"/* "$ARTIFACT_DIR/$name/"
|
||||
echo "Copied dir $name -> $ARTIFACT_DIR/$name"
|
||||
else
|
||||
cp "$path" "$ARTIFACT_DIR/"
|
||||
echo "Copied file $name -> $ARTIFACT_DIR/"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Push recompiled dir to GitLab
|
||||
env:
|
||||
GITLAB_SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
cd gitlab_docs
|
||||
git checkout main
|
||||
git pull origin main
|
||||
for d in models/"$RECOMPILED_DIR"/*/; do
|
||||
git sparse-checkout add "$d"
|
||||
done
|
||||
git add models/"$RECOMPILED_DIR"
|
||||
git config --global user.name "GitHub Action"
|
||||
git config --global user.email "action@github.com"
|
||||
git commit -m "Update $RECOMPILED_DIR with model from build-all-tinygrad-models" || echo "No changes to commit"
|
||||
git push origin main
|
||||
- run: |
|
||||
cd docs
|
||||
git pull origin gh-pages
|
||||
|
||||
- name: update json
|
||||
run: |
|
||||
ARGS=""
|
||||
[ -n "${{ inputs.set_min_version }}" ] && ARGS="$ARGS --set-min-version \"${{ inputs.set_min_version }}\""
|
||||
ARGS="$ARGS --sort-by-date"
|
||||
ARGS="$ARGS --tinygrad-ref \"${{ needs.setup.outputs.tinygrad_ref }}\""
|
||||
eval python3 docs/json_parser.py \
|
||||
--json-path "$JSON_FILE" \
|
||||
--recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR" \
|
||||
$ARGS
|
||||
|
||||
- name: Push updated json to GitHub
|
||||
run: |
|
||||
cd docs
|
||||
git config --global user.name "GitHub Action"
|
||||
git config --global user.email "action@github.com"
|
||||
git checkout gh-pages
|
||||
git add docs/"$(basename $JSON_FILE)"
|
||||
git commit -m "Update $(basename $JSON_FILE) after recompiling model" || echo "No changes to commit"
|
||||
git push origin gh-pages
|
||||
@@ -1,228 +0,0 @@
|
||||
name: Build Single Tinygrad Model and Push
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
upstream_branch:
|
||||
description: 'Upstream commit to build from'
|
||||
required: true
|
||||
type: string
|
||||
custom_name:
|
||||
description: 'Custom name for the model (no date, only name)'
|
||||
required: false
|
||||
type: string
|
||||
recompiled_dir:
|
||||
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
|
||||
required: true
|
||||
type: string
|
||||
json_version:
|
||||
description: 'driving_models version number to update (e.g. 5 for driving_models_v5.json)'
|
||||
required: true
|
||||
type: string
|
||||
artifact_suffix:
|
||||
description: 'Suffix for artifact name'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
bypass_push:
|
||||
description: 'Bypass pushing to GitLab for build-all'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
upstream_branch:
|
||||
description: 'Upstream commit to build from'
|
||||
required: true
|
||||
type: string
|
||||
custom_name:
|
||||
description: 'Custom name for the model (no date, only name)'
|
||||
required: false
|
||||
type: string
|
||||
recompiled_dir:
|
||||
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
|
||||
required: true
|
||||
type: string
|
||||
json_version:
|
||||
description: 'driving_models version number to update (e.g. 5 for driving_models_v5.json)'
|
||||
required: true
|
||||
type: string
|
||||
model_folder:
|
||||
description: 'Model folder'
|
||||
type: choice
|
||||
default: 'None'
|
||||
options:
|
||||
- None
|
||||
- Simple Plan Models
|
||||
- Space Lab Models
|
||||
- TR Models
|
||||
- DTR Models
|
||||
- Custom Merge Models
|
||||
- FOF series models
|
||||
- Other
|
||||
custom_model_folder:
|
||||
description: 'Custom model folder name (if "Other" selected)'
|
||||
required: false
|
||||
type: string
|
||||
generation:
|
||||
description: 'Model generation'
|
||||
required: false
|
||||
type: string
|
||||
version:
|
||||
description: 'Minimum selector version'
|
||||
required: false
|
||||
type: string
|
||||
env:
|
||||
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
||||
JSON_FILE: docs/docs/driving_models_v${{ inputs.json_version }}.json
|
||||
|
||||
jobs:
|
||||
build_model:
|
||||
uses: ./.github/workflows/sunnypilot-build-model.yaml
|
||||
with:
|
||||
upstream_branch: ${{ inputs.upstream_branch }}
|
||||
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
||||
is_20hz: true
|
||||
artifact_suffix: ${{ inputs.artifact_suffix }}
|
||||
secrets: inherit
|
||||
|
||||
publish_model:
|
||||
if: ${{ !inputs.bypass_push && !cancelled() }}
|
||||
concurrency:
|
||||
group: gitlab-push-${{ inputs.recompiled_dir }}
|
||||
cancel-in-progress: false
|
||||
needs: build_model
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Add GitLab.com SSH key to known_hosts
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Clone GitLab docs repo
|
||||
env:
|
||||
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
|
||||
run: |
|
||||
echo "Cloning GitLab"
|
||||
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
|
||||
cd gitlab_docs
|
||||
echo "checkout models/${RECOMPILED_DIR}"
|
||||
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
|
||||
git checkout main
|
||||
cd ..
|
||||
|
||||
- name: Checkout docs repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: sunnypilot/sunnypilot-docs
|
||||
ref: gh-pages
|
||||
path: docs
|
||||
ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }}
|
||||
|
||||
- name: Validate recompiled dir and JSON version
|
||||
run: |
|
||||
if [ ! -d "gitlab_docs/models/$RECOMPILED_DIR" ]; then
|
||||
echo "Recompiled dir $RECOMPILED_DIR does not exist in GitLab repo"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$JSON_FILE" ]; then
|
||||
echo "JSON file $JSON_FILE does not exist!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download artifact name file
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: artifact-name-${{ inputs.custom_name || inputs.upstream_branch }}
|
||||
path: artifact_name
|
||||
|
||||
- name: Read artifact name
|
||||
id: read-artifact-name
|
||||
run: |
|
||||
ARTIFACT_NAME=$(cat artifact_name/artifact_name.txt)
|
||||
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download and extract model artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
||||
path: output
|
||||
|
||||
- name: Remove unwanted files
|
||||
run: |
|
||||
find output -type f -name 'dmonitoring_model_tinygrad.pkl' -delete
|
||||
find output -type f -name 'dmonitoring_model.onnx' -delete
|
||||
|
||||
- name: Copy model artifact(s) to GitLab recompiled dir
|
||||
env:
|
||||
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
||||
run: |
|
||||
ARTIFACT_DIR="gitlab_docs/models/${RECOMPILED_DIR}/${ARTIFACT_NAME}"
|
||||
mkdir -p "$ARTIFACT_DIR"
|
||||
for path in output/*; do
|
||||
if [ "$(basename "$path")" = "artifact_name.txt" ]; then
|
||||
continue
|
||||
fi
|
||||
name="$(basename "$path")"
|
||||
if [ -d "$path" ]; then
|
||||
mkdir -p "$ARTIFACT_DIR/$name"
|
||||
cp -r "$path"/* "$ARTIFACT_DIR/$name/"
|
||||
echo "Copied dir $name -> $ARTIFACT_DIR/$name"
|
||||
else
|
||||
cp "$path" "$ARTIFACT_DIR/"
|
||||
echo "Copied file $name -> $ARTIFACT_DIR/"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Push recompiled dir to GitLab
|
||||
env:
|
||||
GITLAB_SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
cd gitlab_docs
|
||||
git checkout main
|
||||
git pull origin main
|
||||
for d in models/"$RECOMPILED_DIR"/*/; do
|
||||
git sparse-checkout add "$d"
|
||||
done
|
||||
git add models/"$RECOMPILED_DIR"
|
||||
git config --global user.name "GitHub Action"
|
||||
git config --global user.email "action@github.com"
|
||||
git commit -m "Create/Update $RECOMPILED_DIR with new/updated model from build-single-tinygrad-model" || echo "No changes to commit"
|
||||
git push origin main
|
||||
|
||||
- run: |
|
||||
cd docs
|
||||
git pull origin gh-pages
|
||||
|
||||
- name: Run json_parser.py to update JSON
|
||||
run: |
|
||||
FOLDER="${{ inputs.model_folder }}"
|
||||
if [ "$FOLDER" = "Other" ]; then
|
||||
FOLDER="${{ inputs.custom_model_folder }}"
|
||||
fi
|
||||
ARGS=""
|
||||
if [ "$FOLDER" != "None" ] && [ -n "$FOLDER" ]; then
|
||||
ARGS="$ARGS --model-folder \"$FOLDER\""
|
||||
fi
|
||||
[ -n "${{ inputs.generation }}" ] && ARGS="$ARGS --generation \"${{ inputs.generation }}\""
|
||||
[ -n "${{ inputs.version }}" ] && ARGS="$ARGS --version \"${{ inputs.version }}\""
|
||||
eval python3 docs/json_parser.py \
|
||||
--json-path "$JSON_FILE" \
|
||||
--recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR" \
|
||||
--sort-by-date \
|
||||
$ARGS
|
||||
|
||||
- name: Push updated JSON to GitHub docs repo
|
||||
run: |
|
||||
cd docs
|
||||
git config --global user.name "GitHub Action"
|
||||
git config --global user.email "action@github.com"
|
||||
git checkout gh-pages
|
||||
git add docs/"$(basename $JSON_FILE)"
|
||||
git commit -m "Update $(basename $JSON_FILE) after recompiling model" || echo "No changes to commit"
|
||||
git push origin gh-pages
|
||||
@@ -1,76 +0,0 @@
|
||||
name: cereal validation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
paths:
|
||||
- 'cereal/**'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_number:
|
||||
default: '1'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: cereal-validation-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PYTHONWARNINGS: error
|
||||
BASE_IMAGE: openpilot-base
|
||||
BUILD: selfdrive/test/docker_build.sh base
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||
|
||||
jobs:
|
||||
generate_cereal_artifact:
|
||||
name: Generate cereal validation artifacts
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc) cereal"
|
||||
- name: Generate the log file
|
||||
run: |
|
||||
${{ env.RUN }} "cereal/messaging/tests/validate_sp_cereal_upstream.py -g -f schema_instances.bin" && \
|
||||
ls -la
|
||||
ls -la cereal/messaging/tests
|
||||
- name: 'Prepare artifact'
|
||||
run: |
|
||||
mkdir -p "cereal/messaging/tests/cereal_validations"
|
||||
cp cereal/messaging/tests/validate_sp_cereal_upstream.py "cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py"
|
||||
cp schema_instances.bin "cereal/messaging/tests/cereal_validations/schema_instances.bin"
|
||||
- name: 'Upload Artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cereal_validations
|
||||
path: cereal/messaging/tests/cereal_validations
|
||||
|
||||
validate_cereal_with_upstream:
|
||||
name: Validate cereal with Upstream
|
||||
runs-on: ubuntu-24.04
|
||||
needs: generate_cereal_artifact
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'commaai/openpilot'
|
||||
submodules: true
|
||||
ref: "refs/heads/master"
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc) cereal"
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cereal_validations
|
||||
path: cereal/messaging/tests/cereal_validations
|
||||
- name: 'Run the validation'
|
||||
run: |
|
||||
chmod +x cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py
|
||||
${{ env.RUN }} "cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py -r -f cereal/messaging/tests/cereal_validations/schema_instances.bin"
|
||||
@@ -1,101 +0,0 @@
|
||||
name: weekly CI test report
|
||||
on:
|
||||
schedule:
|
||||
- cron: '37 9 * * 1' # 9:37AM UTC -> 2:37AM PST every monday
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ci_runs:
|
||||
description: 'The amount of runs to trigger in CI test report'
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CI_RUNS: ${{ github.event.inputs.ci_runs || '50' }}
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
ci_runs: ${{ steps.ci_runs_setup.outputs.matrix }}
|
||||
steps:
|
||||
- id: ci_runs_setup
|
||||
name: CI_RUNS=${{ env.CI_RUNS }}
|
||||
run: |
|
||||
matrix=$(python3 -c "import json; print(json.dumps({ 'run_number' : list(range(${{ env.CI_RUNS }})) }))")
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
|
||||
ci_matrix_run:
|
||||
needs: [ setup ]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{fromJSON(needs.setup.outputs.ci_runs)}}
|
||||
uses: sunnypilot/sunnypilot/.github/workflows/ci_weekly_run.yaml@master
|
||||
with:
|
||||
run_number: ${{ matrix.run_number }}
|
||||
|
||||
report:
|
||||
needs: [ci_matrix_run]
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- name: Get job results
|
||||
uses: actions/github-script@v7
|
||||
id: get-job-results
|
||||
with:
|
||||
script: |
|
||||
const jobs = await github
|
||||
.paginate("GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt}/jobs", {
|
||||
owner: "commaai",
|
||||
repo: "${{ github.event.repository.name }}",
|
||||
run_id: "${{ github.run_id }}",
|
||||
attempt: "${{ github.run_attempt }}",
|
||||
})
|
||||
var report = {}
|
||||
jobs.slice(1, jobs.length-1).forEach(job => {
|
||||
if (job.conclusion === "skipped") return;
|
||||
const jobName = job.name.split(" / ")[2];
|
||||
const runRegex = /\((.*?)\)/;
|
||||
const run = job.name.match(runRegex)[1];
|
||||
report[jobName] = report[jobName] || { successes: [], failures: [], canceled: [] };
|
||||
switch (job.conclusion) {
|
||||
case "success":
|
||||
report[jobName].successes.push({ "run_number": run, "link": job.html_url}); break;
|
||||
case "failure":
|
||||
report[jobName].failures.push({ "run_number": run, "link": job.html_url }); break;
|
||||
case "canceled":
|
||||
report[jobName].canceled.push({ "run_number": run, "link": job.html_url }); break;
|
||||
}
|
||||
});
|
||||
return JSON.stringify({"jobs": report});
|
||||
|
||||
- name: Add job results to summary
|
||||
env:
|
||||
JOB_RESULTS: ${{ fromJSON(steps.get-job-results.outputs.result) }}
|
||||
run: |
|
||||
cat <<EOF >> template.html
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Job</th>
|
||||
<th>✅ Passing</th>
|
||||
<th>❌ Failure Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for key in jobs.keys() %}<tr>
|
||||
<td>{% for i in range(5) %}{% if i+1 <= (5 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }}) %}🟩{% else %}🟥{% endif %}{% endfor%}</td>
|
||||
<td>{{ key }}</td>
|
||||
<td>{{ 100 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }} }}%</td>
|
||||
<td>{% if jobs[key]["failures"]|length > 0 %}<details>{% for failure in jobs[key]["failures"] %}<a href="{{ failure['link'] }}">Log for run #{{ failure['run_number'] }}</a><br>{% endfor %}</details>{% else %}{% endif %}</td>
|
||||
</td>
|
||||
</tr>{% endfor %}
|
||||
</table>
|
||||
EOF
|
||||
|
||||
pip install jinja2-cli
|
||||
echo $JOB_RESULTS | jinja2 template.html > report.html
|
||||
echo "# CI Test Report - ${{ env.CI_RUNS }} Runs" >> $GITHUB_STEP_SUMMARY
|
||||
cat report.html >> $GITHUB_STEP_SUMMARY
|
||||
@@ -1,17 +0,0 @@
|
||||
name: weekly CI test run
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_number:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ci-run-${{ inputs.run_number }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
selfdrive_tests:
|
||||
uses: sunnypilot/sunnypilot/.github/workflows/selfdrive_tests.yaml@master
|
||||
with:
|
||||
run_number: ${{ inputs.run_number }}
|
||||
@@ -1,21 +0,0 @@
|
||||
name: 'compile openpilot'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- shell: bash
|
||||
name: Build openpilot with all flags
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
${{ env.RUN }} "release/check-dirty.sh"
|
||||
- shell: bash
|
||||
name: Cleanup scons cache and rebuild
|
||||
run: |
|
||||
${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \
|
||||
scons -j$(nproc) --cache-populate"
|
||||
- name: Save scons cache
|
||||
uses: actions/cache/save@v4
|
||||
if: github.ref == 'refs/heads/master'
|
||||
with:
|
||||
path: .ci_cache/scons_cache
|
||||
key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
@@ -1,65 +0,0 @@
|
||||
name: docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_number:
|
||||
default: '1'
|
||||
required: true
|
||||
type: string
|
||||
concurrency:
|
||||
group: docs-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: build docs
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: commaai/timeout@v1
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
# Build
|
||||
- name: Build docs
|
||||
run: |
|
||||
# TODO: can we install just the "docs" dependency group without the normal deps?
|
||||
pip install mkdocs
|
||||
mkdocs build
|
||||
|
||||
# Push to docs.comma.ai
|
||||
- uses: actions/checkout@v4
|
||||
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
|
||||
with:
|
||||
path: openpilot-docs
|
||||
ssh-key: ${{ secrets.OPENPILOT_DOCS_KEY }}
|
||||
repository: sunnypilot/sunnypilot-docs
|
||||
- name: Push
|
||||
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
|
||||
run: |
|
||||
set -x
|
||||
|
||||
source release/identity.sh
|
||||
|
||||
cd openpilot-docs
|
||||
git checkout --orphan tmp
|
||||
git rm -rf .
|
||||
|
||||
# copy over docs
|
||||
cp -r ../docs_site/ docs/
|
||||
|
||||
# GitHub pages config
|
||||
touch docs/.nojekyll
|
||||
echo -n docs.comma.ai > docs/CNAME
|
||||
|
||||
git add -f .
|
||||
git commit -m "build docs"
|
||||
|
||||
# docs live in different repo to not bloat openpilot's full clone size
|
||||
git push -f origin tmp:gh-pages
|
||||
@@ -1,59 +0,0 @@
|
||||
name: jenkins scan
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
|
||||
jobs:
|
||||
# TODO: gc old branches in a separate job in this workflow
|
||||
scan-comments:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.issue.pull_request }}
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
steps:
|
||||
- name: Check for trigger phrase
|
||||
id: check_comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const triggerPhrase = "trigger-jenkins";
|
||||
const comment = context.payload.comment.body;
|
||||
const commenter = context.payload.comment.user.login;
|
||||
|
||||
const { data: permissions } = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
username: commenter
|
||||
});
|
||||
|
||||
const hasWriteAccess = permissions.permission === 'write' || permissions.permission === 'admin';
|
||||
|
||||
return (hasWriteAccess && comment.includes(triggerPhrase));
|
||||
result-encoding: json
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.check_comment.outputs.result == 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: refs/pull/${{ github.event.issue.number }}/head
|
||||
|
||||
- name: Push to tmp-jenkins branch
|
||||
if: steps.check_comment.outputs.result == 'true'
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git checkout -b tmp-jenkins-${{ github.event.issue.number }}
|
||||
GIT_LFS_SKIP_PUSH=1 git push -f origin tmp-jenkins-${{ github.event.issue.number }}
|
||||
|
||||
- name: Delete trigger comment
|
||||
if: steps.check_comment.outputs.result == 'true' && always()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.deleteComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: context.payload.comment.id,
|
||||
});
|
||||
@@ -1,72 +0,0 @@
|
||||
name: Sync comma's LFS
|
||||
|
||||
env:
|
||||
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
|
||||
LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Runs at 00:00 UTC every day
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
workflow_dispatch: # enables manual triggering
|
||||
inputs:
|
||||
upstream_branch:
|
||||
default: 'master'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
# Skip if PR is in draft mode
|
||||
if: (github.event_name != 'pull_request' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false)) && !github.event.pull_request.head.repo.fork
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'commaai/openpilot'
|
||||
ref: ${{ inputs.upstream_branch }}
|
||||
|
||||
- name: LFS Fetch
|
||||
run: |
|
||||
git lfs fetch
|
||||
|
||||
- name: Set up Git
|
||||
run: |
|
||||
git config --global user.name 'GitHub Action'
|
||||
git config --global user.email 'action@github.com'
|
||||
|
||||
- name: Set up SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Add GitLab public keys
|
||||
run: |
|
||||
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Ensure branch
|
||||
run: |
|
||||
if git symbolic-ref -q HEAD >/dev/null; then
|
||||
echo "Already on a branch, proceeding with push"
|
||||
else
|
||||
echo "Detached HEAD state detected, creating temporary branch"
|
||||
git checkout -b temp_branch
|
||||
fi
|
||||
|
||||
- name: Update LFS Config
|
||||
run: |
|
||||
echo '[lfs]' > .lfsconfig
|
||||
echo ' url = ${{ env.LFS_URL }}' >> .lfsconfig
|
||||
echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig
|
||||
echo ' locksverify = false' >> .lfsconfig
|
||||
|
||||
- name: Push LFS
|
||||
id: sync-and-commit
|
||||
run: |
|
||||
git lfs ls-files -l
|
||||
git lfs push --all origin
|
||||
@@ -1,42 +0,0 @@
|
||||
name: "model review"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
paths:
|
||||
- 'selfdrive/modeld/models/*.onnx'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
path: base
|
||||
- run: git lfs pull
|
||||
- run: cd base && git lfs pull
|
||||
|
||||
- run: pip install onnx
|
||||
|
||||
- name: scripts/reporter.py
|
||||
id: report
|
||||
run: |
|
||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "## Model Review" >> $GITHUB_OUTPUT
|
||||
MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Post model report comment
|
||||
uses: marocchino/sticky-pull-request-comment@baa7203ed60924babbe5dcd0ac8eae3b66ec5e16
|
||||
with:
|
||||
header: model-review
|
||||
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 prebuilt
|
||||
|
||||
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).*).)*$
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: git lfs pull
|
||||
- name: Build and Push docker image
|
||||
run: |
|
||||
$DOCKER_LOGIN
|
||||
eval "$BUILD"
|
||||
@@ -1,28 +0,0 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
with:
|
||||
config-name: release-drafter.yml
|
||||
prerelease: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,42 +0,0 @@
|
||||
name: release
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 9 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build___nightly:
|
||||
name: build __nightly
|
||||
env:
|
||||
ImageOS: ubuntu24
|
||||
container:
|
||||
image: ghcr.io/sunnypilot/sunnypilot-base:latest
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
permissions:
|
||||
checks: read
|
||||
contents: write
|
||||
steps:
|
||||
- name: Install wait-on-check-action dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libyaml-dev
|
||||
- name: Wait for green check mark
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
|
||||
with:
|
||||
ref: master
|
||||
wait-interval: 30
|
||||
running-workflow-name: 'build __nightly'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
check-regexp: ^((?!.*(build prebuilt).*).)*$
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
- name: Pull LFS
|
||||
run: |
|
||||
git config --global --add safe.directory '*'
|
||||
git lfs pull
|
||||
- name: Push __nightly
|
||||
run: BRANCH=__nightly release/build_stripped.sh
|
||||
@@ -1,73 +0,0 @@
|
||||
name: repo maintenance
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 14 * * 1" # every Monday at 2am UTC (6am PST)
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BASE_IMAGE: sunnypilot-base
|
||||
BUILD: release/ci/docker_build_sp.sh base
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||
|
||||
jobs:
|
||||
update_translations:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Update translations
|
||||
run: |
|
||||
${{ env.RUN }} "python3 selfdrive/ui/update_translations.py --vanish"
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
|
||||
with:
|
||||
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||
commit-message: "Update translations"
|
||||
title: "[bot] Update translations"
|
||||
body: "Automatic PR from repo-maintenance -> update_translations"
|
||||
branch: "update-translations"
|
||||
base: "master"
|
||||
delete-branch: true
|
||||
labels: bot
|
||||
|
||||
package_updates:
|
||||
name: package_updates
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/sunnypilot/sunnypilot-base:latest
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: uv lock
|
||||
run: |
|
||||
python3 -m ensurepip --upgrade
|
||||
pip3 install uv
|
||||
uv lock --upgrade
|
||||
- name: bump submodules
|
||||
run: |
|
||||
git config --global --add safe.directory '*'
|
||||
git config submodule.tinygrad.update none
|
||||
git submodule update --remote
|
||||
git add .
|
||||
- name: update car docs
|
||||
run: |
|
||||
export PYTHONPATH="$PWD"
|
||||
scons -j$(nproc) --minimal opendbc_repo
|
||||
python selfdrive/car/docs.py
|
||||
git add docs/CARS.md
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
|
||||
with:
|
||||
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||
token: ${{ github.repository == 'commaai/openpilot' && secrets.ACTIONS_CREATE_PR_PAT || secrets.GITHUB_TOKEN }}
|
||||
commit-message: Update Python packages
|
||||
title: '[bot] Update Python packages'
|
||||
branch: auto-package-updates
|
||||
base: master
|
||||
delete-branch: true
|
||||
body: 'Automatic PR from repo-maintenance -> package_updates'
|
||||
labels: bot
|
||||
@@ -1,309 +0,0 @@
|
||||
name: selfdrive
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_number:
|
||||
default: '1'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: selfdrive-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PYTHONWARNINGS: error
|
||||
BASE_IMAGE: sunnypilot-base
|
||||
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
|
||||
|
||||
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD: release/ci/docker_build_sp.sh base
|
||||
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||
|
||||
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical
|
||||
|
||||
jobs:
|
||||
build_release:
|
||||
name: build release
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
env:
|
||||
STRIPPED_DIR: /tmp/releasepilot
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Getting LFS files
|
||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: git lfs pull
|
||||
- name: Build devel
|
||||
timeout-minutes: 1
|
||||
run: TARGET_DIR=$STRIPPED_DIR release/build_stripped.sh
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot and run checks
|
||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache
|
||||
run: |
|
||||
cd $STRIPPED_DIR
|
||||
${{ env.RUN }} "python3 system/manager/build.py"
|
||||
- name: Run tests
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
cd $STRIPPED_DIR
|
||||
${{ env.RUN }} "release/check-dirty.sh"
|
||||
- name: Check submodules
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
if [ "${{ github.ref }}" != "refs/heads/master" ]; then
|
||||
git fetch origin master:refs/remotes/origin/master
|
||||
|
||||
SUBMODULE_PATHS=$(git diff origin/master HEAD --name-only | grep -E '^[^/]+$' | while read path; do
|
||||
if git ls-files --stage "$path" | grep -q "^160000"; then
|
||||
echo "$path"
|
||||
fi
|
||||
done | tr '\n' ' ')
|
||||
|
||||
if [ -n "$SUBMODULE_PATHS" ]; then
|
||||
echo "Changed submodule paths: $SUBMODULE_PATHS"
|
||||
export SUBMODULE_PATHS="$SUBMODULE_PATHS"
|
||||
export CHECK_PR_REFS=true
|
||||
fi
|
||||
fi
|
||||
release/check-submodules.sh
|
||||
|
||||
build:
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup docker push
|
||||
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'sunnypilot/sunnypilot'
|
||||
run: |
|
||||
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
|
||||
$DOCKER_LOGIN
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- uses: ./.github/workflows/compile-openpilot
|
||||
timeout-minutes: 30
|
||||
|
||||
build_mac:
|
||||
name: build macOS
|
||||
if: false # temp disable since homebrew install is getting stuck
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
||||
- name: Homebrew cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
path: ~/Library/Caches/Homebrew
|
||||
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
brew-macos-${{ env.CACHE_COMMIT_DATE }}
|
||||
brew-macos
|
||||
- name: Install dependencies
|
||||
run: ./tools/mac_setup.sh
|
||||
env:
|
||||
# package install has DeprecationWarnings
|
||||
PYTHONWARNINGS: default
|
||||
- name: Save Homebrew cache
|
||||
uses: actions/cache/save@v4
|
||||
if: github.ref == 'refs/heads/master'
|
||||
with:
|
||||
path: ~/Library/Caches/Homebrew
|
||||
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
- run: git lfs pull
|
||||
- name: Getting scons cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
path: /tmp/scons_cache
|
||||
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}
|
||||
scons-${{ runner.arch }}-macos
|
||||
- name: Building openpilot
|
||||
run: . .venv/bin/activate && scons -j$(nproc)
|
||||
- name: Save scons cache
|
||||
uses: actions/cache/save@v4
|
||||
if: github.ref == 'refs/heads/master'
|
||||
with:
|
||||
path: /tmp/scons_cache
|
||||
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
|
||||
static_analysis:
|
||||
name: static analysis
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
env:
|
||||
PYTHONWARNINGS: default
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Static analysis
|
||||
timeout-minutes: 1
|
||||
run: ${{ env.RUN }} "scripts/lint/lint.sh"
|
||||
|
||||
unit_tests:
|
||||
name: unit tests
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
id: setup-step
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run unit tests
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 999 }}
|
||||
run: |
|
||||
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
|
||||
# Pre-compile Python bytecode so each pytest worker doesn't need to
|
||||
$PYTEST --collect-only -m 'not slow' -qq && \
|
||||
MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \
|
||||
./selfdrive/ui/tests/create_test_translations.sh && \
|
||||
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
|
||||
process_replay:
|
||||
name: process replay
|
||||
if: false # disable process_replay for forks
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
id: setup-step
|
||||
- name: Cache test routes
|
||||
id: dependency-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .ci_cache/comma_download_cache
|
||||
key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_processes.py') }}
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run replay
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.dependency-cache.outputs.cache-hit == 'true') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }}
|
||||
run: |
|
||||
${{ env.RUN }} "selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
- name: Print diff
|
||||
id: print-diff
|
||||
if: always()
|
||||
run: cat selfdrive/test/process_replay/diff.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: process_replay_diff.txt
|
||||
path: selfdrive/test/process_replay/diff.txt
|
||||
- name: Upload reference logs
|
||||
if: false # TODO: move this to github instead of azure
|
||||
run: |
|
||||
${{ env.RUN }} "unset PYTHONWARNINGS && AZURE_TOKEN='$AZURE_TOKEN' python3 selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
|
||||
- name: Run regen
|
||||
if: false
|
||||
timeout-minutes: 4
|
||||
run: |
|
||||
${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
|
||||
simulator_driving:
|
||||
name: simulator driving
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
if: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
id: setup-step
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Driving test
|
||||
timeout-minutes: ${{ (steps.setup-step.outputs.duration < 18) && 1 || 2 }}
|
||||
run: |
|
||||
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
|
||||
source selfdrive/test/setup_vsound.sh && \
|
||||
CI=1 pytest -s tools/sim/tests/test_metadrive_bridge.py"
|
||||
|
||||
create_ui_report:
|
||||
# This job name needs to be the same as UI_JOB_NAME in ui_preview.yaml
|
||||
name: Create UI Report
|
||||
runs-on: ${{
|
||||
(github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
|
||||
&& fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
|
||||
|| fromJSON('["ubuntu-24.04"]') }}
|
||||
if: false # FIXME: FrameReader is broken on CI runners
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: caching frames
|
||||
id: frames-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .ci_cache/comma_download_cache
|
||||
key: ui_screenshots_test_${{ hashFiles('selfdrive/ui/tests/test_ui/run.py') }}
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Create Test Report
|
||||
timeout-minutes: ${{ ((steps.frames-cache.outputs.cache-hit == 'true') && 2 || 4) }}
|
||||
run: >
|
||||
${{ env.RUN }} "PYTHONWARNINGS=ignore &&
|
||||
source selfdrive/test/setup_xvfb.sh &&
|
||||
CACHE_ROOT=/tmp/comma_download_cache python3 selfdrive/ui/tests/test_ui/run.py &&
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
- name: Upload Test Report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||
path: selfdrive/ui/tests/test_ui/report_1/screenshots
|
||||
@@ -1,52 +0,0 @@
|
||||
name: 'openpilot env setup, with retry on failure'
|
||||
|
||||
inputs:
|
||||
docker_hub_pat:
|
||||
description: 'Auth token for Docker Hub, required for BuildJet jobs'
|
||||
required: false
|
||||
default: ''
|
||||
sleep_time:
|
||||
description: 'Time to sleep between retries'
|
||||
required: false
|
||||
default: 30
|
||||
|
||||
outputs:
|
||||
duration:
|
||||
description: 'Duration of the setup process in seconds'
|
||||
value: ${{ steps.get_duration.outputs.duration }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- id: start_time
|
||||
shell: bash
|
||||
run: echo "START_TIME=$(date +%s)" >> $GITHUB_ENV
|
||||
- id: setup1
|
||||
uses: ./.github/workflows/setup
|
||||
continue-on-error: true
|
||||
with:
|
||||
is_retried: true
|
||||
- if: steps.setup1.outcome == 'failure'
|
||||
shell: bash
|
||||
run: sleep ${{ inputs.sleep_time }}
|
||||
- id: setup2
|
||||
if: steps.setup1.outcome == 'failure'
|
||||
uses: ./.github/workflows/setup
|
||||
continue-on-error: true
|
||||
with:
|
||||
is_retried: true
|
||||
- if: steps.setup2.outcome == 'failure'
|
||||
shell: bash
|
||||
run: sleep ${{ inputs.sleep_time }}
|
||||
- id: setup3
|
||||
if: steps.setup2.outcome == 'failure'
|
||||
uses: ./.github/workflows/setup
|
||||
with:
|
||||
is_retried: true
|
||||
- id: get_duration
|
||||
shell: bash
|
||||
run: |
|
||||
END_TIME=$(date +%s)
|
||||
DURATION=$((END_TIME - START_TIME))
|
||||
echo "Total duration: $DURATION seconds"
|
||||
echo "duration=$DURATION" >> $GITHUB_OUTPUT
|
||||
@@ -1,56 +0,0 @@
|
||||
name: 'openpilot env setup'
|
||||
|
||||
inputs:
|
||||
is_retried:
|
||||
description: 'A mock param that asserts that we use the setup-with-retry instead of this action directly'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
# assert that this action is retried using the setup-with-retry
|
||||
- shell: bash
|
||||
if: ${{ inputs.is_retried == 'false' }}
|
||||
run: |
|
||||
echo "You should not run this action directly. Use setup-with-retry instead"
|
||||
exit 1
|
||||
|
||||
- shell: bash
|
||||
name: No retries!
|
||||
run: |
|
||||
if [ "${{ github.run_attempt }}" -gt ${{ github.event.pull_request.head.repo.fork && github.event.pull_request.author_association == 'NONE' && 2 || 1}} ]; then
|
||||
echo -e "\033[0;31m##################################################"
|
||||
echo -e "\033[0;31m Retries not allowed! Fix the flaky test! "
|
||||
echo -e "\033[0;31m##################################################\033[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# do this after checkout to ensure our custom LFS config is used to pull from GitLab
|
||||
- shell: bash
|
||||
run: git lfs pull
|
||||
|
||||
# build cache
|
||||
- id: date
|
||||
shell: bash
|
||||
run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
||||
- shell: bash
|
||||
run: echo "$CACHE_COMMIT_DATE"
|
||||
- id: scons-cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
path: .ci_cache/scons_cache
|
||||
key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}
|
||||
scons-${{ runner.arch }}
|
||||
# as suggested here: https://github.com/moby/moby/issues/32816#issuecomment-910030001
|
||||
- id: normalize-file-permissions
|
||||
shell: bash
|
||||
name: Normalize file permissions to ensure a consistent docker build cache
|
||||
run: |
|
||||
find . -type f -executable -not -perm 755 -exec chmod 755 {} \;
|
||||
find . -type f -not -executable -not -perm 644 -exec chmod 644 {} \;
|
||||
# build our docker image
|
||||
- shell: bash
|
||||
run: eval ${{ env.BUILD }}
|
||||
@@ -1,52 +0,0 @@
|
||||
name: stale
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DAYS_BEFORE_PR_CLOSE: 2
|
||||
DAYS_BEFORE_PR_STALE: 9
|
||||
DAYS_BEFORE_PR_STALE_DRAFT: 30
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
exempt-all-milestones: true
|
||||
|
||||
# pull request config
|
||||
stale-pr-message: 'This PR has had no activity for ${{ env.DAYS_BEFORE_PR_STALE }} days. It will be automatically closed in ${{ env.DAYS_BEFORE_PR_CLOSE }} days if there is no activity.'
|
||||
close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.'
|
||||
stale-pr-label: stale
|
||||
delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'sunnypilot/sunnypilot' }} # only delete branches on the main repo
|
||||
exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale
|
||||
days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }}
|
||||
days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }}
|
||||
exempt-draft-pr: false
|
||||
|
||||
# issue config
|
||||
days-before-issue-stale: -1 # ignore issues for now
|
||||
|
||||
# same as above, but give draft PRs more time
|
||||
stale_drafts:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
exempt-all-milestones: true
|
||||
|
||||
# pull request config
|
||||
stale-pr-message: 'This PR has had no activity for ${{ env.DAYS_BEFORE_PR_STALE_DRAFT }} days. It will be automatically closed in ${{ env.DAYS_BEFORE_PR_CLOSE }} days if there is no activity.'
|
||||
close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.'
|
||||
stale-pr-label: stale
|
||||
delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo
|
||||
exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale
|
||||
days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE_DRAFT }}
|
||||
days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }}
|
||||
exempt-draft-pr: true
|
||||
|
||||
# issue config
|
||||
days-before-issue-stale: -1 # ignore issues for now
|
||||
@@ -1,219 +0,0 @@
|
||||
name: Build Model from Upstream
|
||||
|
||||
env:
|
||||
BUILD_DIR: "/data/openpilot"
|
||||
OUTPUT_DIR: ${{ github.workspace }}/output
|
||||
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
|
||||
UPSTREAM_REPO: "commaai/openpilot"
|
||||
TINYGRAD_PATH: ${{ github.workspace }}/tinygrad_repo
|
||||
MODELS_DIR: ${{ github.workspace }}/selfdrive/modeld/models
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
upstream_branch:
|
||||
description: 'Upstream branch to build from'
|
||||
required: true
|
||||
default: 'master'
|
||||
type: string
|
||||
custom_name:
|
||||
description: 'Custom name for the model (no date, only name)'
|
||||
required: false
|
||||
type: string
|
||||
is_20hz:
|
||||
description: 'Is this a 20Hz model'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
artifact_suffix:
|
||||
description: 'Suffix for artifact name'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
upstream_branch:
|
||||
description: 'Upstream branch to build from'
|
||||
required: true
|
||||
default: 'master'
|
||||
type: string
|
||||
custom_name:
|
||||
description: 'Custom name for the model (no date, only name)'
|
||||
required: false
|
||||
type: string
|
||||
is_20hz:
|
||||
description: 'Is this a 20Hz model'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
|
||||
run-name: Build model [${{ inputs.custom_name || inputs.upstream_branch }}] from ref [${{ inputs.upstream_branch }}]
|
||||
|
||||
jobs:
|
||||
get_model:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
REF: ${{ inputs.upstream_branch }}
|
||||
outputs:
|
||||
model_date: ${{ steps.commit-date.outputs.model_date }}
|
||||
steps:
|
||||
# Note: To allow dynamic models from both openpilot and sunnypilot (merges/mashups), we try commaai as default,
|
||||
# and fallback to sunnypilot if the ref checkout fails.
|
||||
- name: Checkout commaai/openpilot
|
||||
id: checkout_upstream
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: commaai/openpilot
|
||||
ref: ${{ inputs.upstream_branch }}
|
||||
submodules: recursive
|
||||
path: openpilot
|
||||
|
||||
- name: Fallback to sunnypilot/sunnypilot
|
||||
if: steps.checkout_upstream.outcome == 'failure'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: sunnypilot/sunnypilot
|
||||
ref: ${{ inputs.upstream_branch }}
|
||||
submodules: recursive
|
||||
path: openpilot
|
||||
- name: Get commit date
|
||||
id: commit-date
|
||||
run: |
|
||||
cd ${{ github.workspace }}/openpilot
|
||||
commit_date=$(git log -1 --format=%cd --date=format:'%B %d, %Y')
|
||||
echo "model_date=${commit_date}" >> $GITHUB_OUTPUT
|
||||
cat $GITHUB_OUTPUT
|
||||
- run: |
|
||||
cd ${{ github.workspace }}/openpilot
|
||||
git lfs pull
|
||||
- name: 'Upload Artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
||||
path: ${{ github.workspace }}/openpilot/selfdrive/modeld/models/*.onnx
|
||||
|
||||
build_model:
|
||||
runs-on: [self-hosted, tici]
|
||||
needs: get_model
|
||||
env:
|
||||
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
|
||||
REF: ${{ inputs.upstream_branch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- run: git lfs pull
|
||||
- name: Cache SCons
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{env.SCONS_CACHE_DIR}}
|
||||
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}-model-${{ github.sha }}
|
||||
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
|
||||
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
||||
restore-keys: |
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}-model
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ github.head_ref || github.ref_name }}
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_NEW_BRANCH }}-model
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_BRANCH }}-model
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_NEW_BRANCH }}
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.MASTER_BRANCH }}
|
||||
scons-${{ runner.os }}-${{ runner.arch }}
|
||||
|
||||
- name: Set environment variables
|
||||
id: set-env
|
||||
run: |
|
||||
# Set up common environment
|
||||
source /etc/profile;
|
||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||
printenv >> $GITHUB_ENV
|
||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||
cat $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup build environment
|
||||
run: |
|
||||
mkdir -p "${BUILD_DIR}/"
|
||||
sudo find $BUILD_DIR/ -mindepth 1 -delete
|
||||
echo "Starting build stage..."
|
||||
echo "BUILD_DIR: ${BUILD_DIR}"
|
||||
echo "CI_DIR: ${CI_DIR}"
|
||||
echo "VERSION: ${{ steps.set-env.outputs.version }}"
|
||||
echo "UV_PROJECT_ENVIRONMENT: ${UV_PROJECT_ENVIRONMENT}"
|
||||
echo "VIRTUAL_ENV: ${VIRTUAL_ENV}"
|
||||
echo "-------"
|
||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||
printenv
|
||||
fi
|
||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||
rm -rf ${{ env.MODELS_DIR }}/*.onnx
|
||||
|
||||
- name: Download model artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
||||
path: ${{ github.workspace }}/selfdrive/modeld/models
|
||||
|
||||
- name: Build Model
|
||||
run: |
|
||||
source /etc/profile
|
||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}"
|
||||
|
||||
# Loop through all .onnx files
|
||||
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
||||
base_name=$(basename "$onnx_file" .onnx)
|
||||
output_file="${{ env.MODELS_DIR }}/${base_name}_tinygrad.pkl"
|
||||
|
||||
echo "Compiling: $onnx_file -> $output_file"
|
||||
QCOM=1 python3 "${{ env.TINYGRAD_PATH }}/examples/openpilot/compile3.py" "$onnx_file" "$output_file"
|
||||
QCOM=1 python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
||||
done
|
||||
|
||||
- name: Prepare Output
|
||||
run: |
|
||||
sudo rm -rf ${{ env.OUTPUT_DIR }}
|
||||
mkdir -p ${{ env.OUTPUT_DIR }}
|
||||
|
||||
# Copy the model files
|
||||
rsync -avm \
|
||||
--include='*.dlc' \
|
||||
--include='*.thneed' \
|
||||
--include='*.pkl' \
|
||||
--include='*.onnx' \
|
||||
--exclude='*' \
|
||||
--delete-excluded \
|
||||
--chown=comma:comma \
|
||||
${{ env.MODELS_DIR }}/ ${{ env.OUTPUT_DIR }}/
|
||||
|
||||
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
|
||||
--model-dir "${{ env.MODELS_DIR }}" \
|
||||
--output-dir "${{ env.OUTPUT_DIR }}" \
|
||||
--custom-name "${{ env.MODEL_NAME }}" \
|
||||
--upstream-branch "${{ inputs.upstream_branch }}" \
|
||||
${{ inputs.is_20hz && '--is-20hz' || '' }}
|
||||
|
||||
- name: Write artifact name to file
|
||||
run: echo "model-${{ env.MODEL_NAME }}${{ inputs.artifact_suffix }}-${{ github.run_number }}" > ${{ env.OUTPUT_DIR }}/artifact_name.txt
|
||||
|
||||
- name: Upload Build Artifacts
|
||||
id: upload-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: model-${{ env.MODEL_NAME }}${{ inputs.artifact_suffix }}-${{ github.run_number }}
|
||||
path: ${{ env.OUTPUT_DIR }}
|
||||
|
||||
- name: Upload artifact name file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifact-name-${{ inputs.custom_name || inputs.upstream_branch }}
|
||||
path: ${{ env.OUTPUT_DIR }}/artifact_name.txt
|
||||
|
||||
- name: Re-enable powersave
|
||||
if: always()
|
||||
run: |
|
||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||
@@ -1,331 +0,0 @@
|
||||
name: sunnypilot prebuilt action
|
||||
|
||||
env:
|
||||
BUILD_DIR: "/data/openpilot"
|
||||
OUTPUT_DIR: ${{ github.workspace }}/output
|
||||
CI_DIR: ${{ github.workspace }}/release/ci
|
||||
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
|
||||
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
||||
|
||||
# Branch configurations
|
||||
STAGING_C3_SOURCE_BRANCH: ${{ vars.STAGING_C3_SOURCE_BRANCH || 'master' }} # vars are set on repo settings.
|
||||
DEV_C3_SOURCE_BRANCH: ${{ vars.DEV_C3_SOURCE_BRANCH || 'master-dev-c3-new' }} # vars are set on repo settings.
|
||||
|
||||
# Target branch configurations
|
||||
STAGING_TARGET_BRANCH: ${{ vars.STAGING_TARGET_BRANCH || 'staging-c3-new' }} # vars are set on repo settings.
|
||||
DEV_TARGET_BRANCH: ${{ vars.DEV_TARGET_BRANCH || 'dev-c3-new' }} # vars are set on repo settings.
|
||||
RELEASE_TARGET_BRANCH: ${{ vars.RELEASE_TARGET_BRANCH || 'release-c3-new' }} # vars are set on repo settings.
|
||||
|
||||
# Runtime configuration
|
||||
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, master-dev-c3-new ]
|
||||
tags: [ '*' ]
|
||||
pull_request_target:
|
||||
types: [ labeled ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
wait_for_tests:
|
||||
description: 'Wait for selfdrive_tests to finish'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
validate_tests:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ((github.event_name == 'workflow_dispatch' && inputs.wait_for_tests) || contains(github.event_name, 'pull_request') && (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Wait for Tests
|
||||
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
||||
with:
|
||||
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build:
|
||||
needs: [ validate_tests ]
|
||||
concurrency:
|
||||
group: build-${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
runs-on: [self-hosted, tici]
|
||||
outputs:
|
||||
new_branch: ${{ steps.set-env.outputs.new_branch }}
|
||||
version: ${{ steps.set-env.outputs.version }}
|
||||
extra_version_identifier: ${{ steps.set-env.outputs.extra_version_identifier }}
|
||||
commit_sha: ${{ steps.set-env.outputs.commit_sha }}
|
||||
if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{ env.SOURCE_BRANCH }}
|
||||
repository: ${{ github.event.pull_request.head.repo.fork && github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
- run: git lfs pull
|
||||
|
||||
- name: Cache SCons
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{env.SCONS_CACHE_DIR}}
|
||||
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}-${{ github.sha }}
|
||||
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
|
||||
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
||||
restore-keys: |
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
|
||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_C3_SOURCE_BRANCH }}
|
||||
scons-${{ runner.os }}-${{ runner.arch }}
|
||||
|
||||
- name: Set Feature Branch Prebuilt Configuration
|
||||
id: set_feature_configuration
|
||||
if: (
|
||||
!(env.SOURCE_BRANCH == env.DEV_C3_SOURCE_BRANCH) &&
|
||||
!(env.SOURCE_BRANCH == env.STAGING_C3_SOURCE_BRANCH) &&
|
||||
!(startsWith(github.ref, 'refs/tags/'))
|
||||
)
|
||||
run: |
|
||||
echo "NEW_BRANCH=${{ env.SOURCE_BRANCH }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-prebuilt" >> $GITHUB_ENV
|
||||
echo "VERSION=$(date '+%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set dev-c3-new prebuilt Configuration
|
||||
id: set_dev_configuration
|
||||
if: (
|
||||
steps.set_feature_configuration.outcome == 'skipped' &&
|
||||
env.SOURCE_BRANCH == env.DEV_C3_SOURCE_BRANCH
|
||||
)
|
||||
run: |
|
||||
echo "NEW_BRANCH=${{ env.DEV_TARGET_BRANCH }}" >> $GITHUB_ENV
|
||||
echo "VERSION=$(date '+%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_ENV
|
||||
echo "EXTRA_VERSION_IDENTIFIER=${{ github.run_number }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set staging-c3-new prebuilt Configuration
|
||||
id: set_staging_configuration
|
||||
if: (
|
||||
steps.set_feature_configuration.outcome == 'skipped' &&
|
||||
!contains(github.event_name, 'pull_request') &&
|
||||
steps.set_dev_configuration.outcome == 'skipped' &&
|
||||
(env.SOURCE_BRANCH == env.STAGING_C3_SOURCE_BRANCH)
|
||||
)
|
||||
run: |
|
||||
echo "NEW_BRANCH=${{ env.STAGING_TARGET_BRANCH }}" >> $GITHUB_ENV
|
||||
echo "EXTRA_VERSION_IDENTIFIER=staging" >> $GITHUB_ENV
|
||||
echo "VERSION=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g')-staging" >> $GITHUB_ENV
|
||||
|
||||
- name: Set release-c3-new prebuilt Configuration
|
||||
id: set_tag_configuration
|
||||
if: (
|
||||
steps.set_feature_configuration.outcome == 'skipped' &&
|
||||
!contains(github.event_name, 'pull_request') &&
|
||||
steps.set_staging_configuration.outcome == 'skipped' &&
|
||||
startsWith(github.ref, 'refs/tags/')
|
||||
)
|
||||
run: |
|
||||
echo "NEW_BRANCH=${{ env.RELEASE_TARGET_BRANCH }}" >> $GITHUB_ENV
|
||||
echo "EXTRA_VERSION_IDENTIFIER=release" >> $GITHUB_ENV
|
||||
echo "VERSION=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g')-release" >> $GITHUB_ENV
|
||||
|
||||
- name: Set environment variables
|
||||
id: set-env
|
||||
run: |
|
||||
# Write to GITHUB_OUTPUT from environment variables
|
||||
echo "new_branch=$NEW_BRANCH" >> $GITHUB_OUTPUT
|
||||
[[ ! -z "$EXTRA_VERSION_IDENTIFIER" ]] && echo "extra_version_identifier=$EXTRA_VERSION_IDENTIFIER" >> $GITHUB_OUTPUT
|
||||
[[ ! -z "$VERSION" ]] && echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
|
||||
|
||||
# Set up common environment
|
||||
source /etc/profile;
|
||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||
printenv >> $GITHUB_ENV
|
||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||
cat $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup build environment
|
||||
run: |
|
||||
mkdir -p "${BUILD_DIR}/"
|
||||
sudo find $BUILD_DIR/ -mindepth 1 -delete
|
||||
echo "Starting build stage..."
|
||||
echo "BUILD_DIR: ${BUILD_DIR}"
|
||||
echo "CI_DIR: ${CI_DIR}"
|
||||
echo "VERSION: ${{ steps.set-env.outputs.version }}"
|
||||
echo "UV_PROJECT_ENVIRONMENT: ${UV_PROJECT_ENVIRONMENT}"
|
||||
echo "VIRTUAL_ENV: ${VIRTUAL_ENV}"
|
||||
echo "-------"
|
||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||
printenv
|
||||
fi
|
||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||
|
||||
- name: Build Main Project
|
||||
run: |
|
||||
export PYTHONPATH="$BUILD_DIR"
|
||||
./release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/
|
||||
cd $BUILD_DIR
|
||||
sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py
|
||||
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal
|
||||
touch ${BUILD_DIR}/prebuilt
|
||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||
ls -la ${BUILD_DIR}
|
||||
fi
|
||||
|
||||
- name: Prepare Output
|
||||
run: |
|
||||
sudo rm -rf ${OUTPUT_DIR}
|
||||
mkdir -p ${OUTPUT_DIR}
|
||||
rsync -am${RUNNER_DEBUG:+v} \
|
||||
--include='**/panda/board/' \
|
||||
--include='**/panda/board/obj' \
|
||||
--include='**/panda/board/obj/panda.bin.signed' \
|
||||
--include='**/panda/board/obj/panda_h7.bin.signed' \
|
||||
--include='**/panda/board/obj/bootstub.panda.bin' \
|
||||
--include='**/panda/board/obj/bootstub.panda_h7.bin' \
|
||||
--exclude='.sconsign.dblite' \
|
||||
--exclude='*.a' \
|
||||
--exclude='*.o' \
|
||||
--exclude='*.os' \
|
||||
--exclude='*.pyc' \
|
||||
--exclude='moc_*' \
|
||||
--exclude='*.cc' \
|
||||
--exclude='Jenkinsfile' \
|
||||
--exclude='supercombo.onnx' \
|
||||
--exclude='**/panda/board/*' \
|
||||
--exclude='**/panda/board/obj/**' \
|
||||
--exclude='**/panda/certs/' \
|
||||
--exclude='**/panda/crypto/' \
|
||||
--exclude='**/release/' \
|
||||
--exclude='**/.github/' \
|
||||
--exclude='**/selfdrive/ui/replay/' \
|
||||
--exclude='**/__pycache__/' \
|
||||
--exclude='**/selfdrive/ui/*.h' \
|
||||
--exclude='**/selfdrive/ui/**/*.h' \
|
||||
--exclude='**/selfdrive/ui/qt/offroad/sunnypilot/' \
|
||||
--exclude='${{env.SCONS_CACHE_DIR}}' \
|
||||
--exclude='**/.git/' \
|
||||
--exclude='**/SConstruct' \
|
||||
--exclude='**/SConscript' \
|
||||
--exclude='**/.venv/' \
|
||||
--delete-excluded \
|
||||
--chown=comma:comma \
|
||||
${BUILD_DIR}/ ${OUTPUT_DIR}/
|
||||
|
||||
- name: 'Tar.gz files'
|
||||
run: |
|
||||
tar czf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }} .
|
||||
ls -la prebuilt.tar.gz
|
||||
|
||||
- name: 'Upload Artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: prebuilt
|
||||
path: prebuilt.tar.gz
|
||||
|
||||
- name: Re-enable powersave
|
||||
if: always()
|
||||
run: |
|
||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||
|
||||
publish:
|
||||
concurrency:
|
||||
group: publish-${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
||||
needs: [ build ]
|
||||
runs-on: ubuntu-24.04
|
||||
environment: ${{ (contains(fromJSON(vars.AUTO_DEPLOY_PREBUILT_BRANCHES), github.head_ref || github.ref_name) || contains(github.event.pull_request.labels.*.name, 'prebuilt')) && 'auto-deploy' || 'feature-branch' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: prebuilt
|
||||
|
||||
- name: Untar prebuilt
|
||||
run: |
|
||||
mkdir -p ${{ env.OUTPUT_DIR }}
|
||||
tar xzf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }}
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
|
||||
- name: Publish to Public Repository
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo '${{ toJSON(needs.build.outputs) }}'
|
||||
ls -la ${{ env.OUTPUT_DIR }}
|
||||
|
||||
${{ env.CI_DIR }}/publish.sh \
|
||||
"${{ github.workspace }}" \
|
||||
"${{ env.OUTPUT_DIR }}" \
|
||||
"${{ needs.build.outputs.new_branch }}" \
|
||||
"${{ needs.build.outputs.version }}" \
|
||||
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
|
||||
"-${{ needs.build.outputs.extra_version_identifier }}"
|
||||
|
||||
echo ""
|
||||
echo "---- ℹ️ To update the list of branches that auto deploy prebuilts -----"
|
||||
echo ""
|
||||
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES"
|
||||
echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}"
|
||||
echo "3. Update as needed (JSON array with no spaces)"
|
||||
|
||||
notify:
|
||||
needs: [ build, publish ]
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Alpine Linux environment
|
||||
uses: jirutka/setup-alpine@v1.2.0
|
||||
with:
|
||||
packages: 'jq gettext curl'
|
||||
|
||||
- name: Send Discord Notification
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ contains(fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES), env.SOURCE_BRANCH) && secrets.DISCORD_DEV_FEEDBACK_CHANNEL_WEBHOOK || secrets.DISCORD_DEV_PRIVATE_CHANNEL_WEBHOOK }}
|
||||
run: |
|
||||
TEMPLATE='${{ vars.DISCORD_GENERAL_UPDATE_NOTICE }}'
|
||||
export EXTRA_VERSION_IDENTIFIER="${{ needs.build.outputs.extra_version_identifier }}"
|
||||
export VERSION="${{ needs.build.outputs.version }}"
|
||||
export branch_name=${{ env.SOURCE_BRANCH }}
|
||||
export new_branch=${{ needs.build.outputs.new_branch }}
|
||||
export extra_version_identifier=${{ needs.build.outputs.extra_version_identifier || github.run_number}}
|
||||
echo ${TEMPLATE} | envsubst | jq -c '.' | tee payload.json
|
||||
curl -X POST -H "Content-Type: application/json" -d @payload.json $DISCORD_WEBHOOK
|
||||
|
||||
echo ""
|
||||
echo "---- ℹ️ To update the list of branches that notify to dev-feedback -----"
|
||||
echo ""
|
||||
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/DEV_FEEDBACK_NOTIFICATION_BRANCHES"
|
||||
echo "2. Current value: ${{ vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES }}"
|
||||
echo "3. Update as needed (JSON array with no spaces)"
|
||||
shell: alpine.sh {0}
|
||||
|
||||
manage-pr-labels:
|
||||
name: Remove prebuilt label
|
||||
runs-on: ubuntu-latest
|
||||
if: (always() && contains(github.event_name, 'pull_request') && (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
|
||||
env:
|
||||
LABEL: prebuilt
|
||||
steps:
|
||||
- name: Remove trust-fork-pr label if present
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
name: process.env.LABEL
|
||||
});
|
||||
|
||||
console.log(`Removed '${process.env.LABEL}' label from PR #${prNumber}`);
|
||||
@@ -1,223 +0,0 @@
|
||||
name: Build dev-c3-new
|
||||
|
||||
env:
|
||||
DEFAULT_SOURCE_BRANCH: "master"
|
||||
DEFAULT_TARGET_BRANCH: "master-dev-c3-new"
|
||||
PR_LABEL: "dev-c3"
|
||||
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
|
||||
LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
types: [ labeled ]
|
||||
branches:
|
||||
- 'master'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
source_branch:
|
||||
description: 'Source branch to reset from'
|
||||
required: true
|
||||
default: 'master'
|
||||
type: string
|
||||
target_branch:
|
||||
description: 'Target branch to reset and squash into'
|
||||
required: true
|
||||
default: 'master-dev-c3-new'
|
||||
type: string
|
||||
cancel_in_progress:
|
||||
description: 'Cancel any in-progress runs of this workflow'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: ${{ inputs.cancel_in_progress || github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||
|
||||
jobs:
|
||||
reset-and-squash:
|
||||
runs-on: ubuntu-latest
|
||||
if: (
|
||||
(github.event_name == 'workflow_dispatch')
|
||||
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for all branches
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Wait for Tests
|
||||
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
||||
if: (
|
||||
(github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|
||||
)
|
||||
with:
|
||||
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.name 'github-actions[bot]'
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
|
||||
- name: Set up SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Add GitLab public keys
|
||||
run: |
|
||||
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install PyGithub
|
||||
|
||||
- name: Check branches exist
|
||||
run: |
|
||||
# Check if source branch exists
|
||||
if ! git ls-remote --heads origin ${{ inputs.source_branch || env.DEFAULT_SOURCE_BRANCH }} | grep -q "${{ inputs.source_branch || env.DEFAULT_SOURCE_BRANCH }}"; then
|
||||
echo "Source branch ${{ inputs.source_branch || env.DEFAULT_SOURCE_BRANCH }} does not exist!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make sure we have the latest source branch
|
||||
git fetch origin ${{ inputs.source_branch || env.DEFAULT_SOURCE_BRANCH }}
|
||||
|
||||
# Check if target branch exists
|
||||
if ! git ls-remote --heads origin ${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }} | grep -q "${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"; then
|
||||
echo "Target branch ${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }} does not exist, creating it from ${{ inputs.source_branch || env.DEFAULT_SOURCE_BRANCH }}"
|
||||
git checkout -b ${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }} origin/${{ inputs.source_branch || env.DEFAULT_SOURCE_BRANCH }}
|
||||
git push origin ${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}
|
||||
else
|
||||
# Fetch target branch if it exists
|
||||
git fetch origin ${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}
|
||||
fi
|
||||
|
||||
- name: Reset target branch
|
||||
run: |
|
||||
echo "Resetting ${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }} to match ${{ inputs.source_branch || env.DEFAULT_SOURCE_BRANCH }}"
|
||||
# Delete if exists and recreate pointing to source
|
||||
git branch -D ${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }} || true
|
||||
git branch ${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }} origin/${{ inputs.source_branch || env.DEFAULT_SOURCE_BRANCH }}
|
||||
|
||||
- name: Get PRs to squash
|
||||
id: get-prs
|
||||
run: |
|
||||
# Use GitHub API to get PRs with specific label, ordered by creation date
|
||||
PR_LIST=$(gh api graphql -f query='
|
||||
query($label:String!) {
|
||||
search(query: $label, type:ISSUE, first:100) {
|
||||
nodes {
|
||||
... on PullRequest {
|
||||
number
|
||||
headRefName
|
||||
title
|
||||
createdAt
|
||||
labels(last:10) {
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
headRepository {
|
||||
name
|
||||
nameWithOwner
|
||||
url
|
||||
isFork
|
||||
}
|
||||
commits(last: 1) {
|
||||
nodes {
|
||||
commit {
|
||||
statusCheckRollup {
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -F label="is:pr is:open label:${PR_LABEL} draft:false sort:created-asc")
|
||||
|
||||
PR_LIST=${PR_LIST//\'/}
|
||||
echo "PR_LIST=${PR_LIST}" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Process PRs
|
||||
run: |
|
||||
cp ${{ github.workspace }}/release/ci/squash_and_merge.py /tmp/squash_and_merge.py && \
|
||||
chmod +x /tmp/squash_and_merge.py && \
|
||||
python3 ${{ github.workspace }}/release/ci/squash_and_merge_prs.py \
|
||||
--pr-data '${{ steps.get-prs.outputs.PR_LIST }}' \
|
||||
--target-branch ${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }} \
|
||||
--squash-script-path '/tmp/squash_and_merge.py'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update LFS Config
|
||||
run: |
|
||||
echo '[lfs]' > .lfsconfig
|
||||
echo ' url = ${{ env.LFS_URL }}' >> .lfsconfig
|
||||
echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig
|
||||
echo ' locksverify = false' >> .lfsconfig
|
||||
|
||||
- name: Push changes if there are diffs
|
||||
id: push-changes # Add an id so we can reference this step
|
||||
run: |
|
||||
TARGET_BRANCH="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"
|
||||
|
||||
# Fetch the latest from remote
|
||||
git fetch origin $TARGET_BRANCH
|
||||
|
||||
# Check for diffs between local and remote
|
||||
if git diff $TARGET_BRANCH origin/$TARGET_BRANCH --quiet; then
|
||||
echo "No changes to push - local and remote branches are identical"
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# If we get here, there are diffs, so push
|
||||
if ! git push origin $TARGET_BRANCH --force; then
|
||||
echo "Failed to push changes to $TARGET_BRANCH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Branch $TARGET_BRANCH has been reset and updated with squashed PRs"
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Trigger and wait for selfdrive tests
|
||||
if: steps.push-changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
echo "Triggering selfdrive tests..."
|
||||
gh workflow run selfdrive_tests.yaml --ref "${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"
|
||||
|
||||
echo "Sleeping for 120s to give plenty of time for the action to start and then we wait"
|
||||
sleep 120
|
||||
|
||||
echo "Getting latest run ID..."
|
||||
RUN_ID=$(gh run list --workflow=selfdrive_tests.yaml --branch="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}" --limit=1 --json databaseId --jq '.[0].databaseId')
|
||||
|
||||
echo "Watching run ID: $RUN_ID"
|
||||
gh run watch "$RUN_ID"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Trigger prebuilt workflow
|
||||
if: success() && steps.push-changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
gh workflow run sunnypilot-build-prebuilt.yaml --ref "${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,173 +0,0 @@
|
||||
name: "ui preview"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
types: [assigned, opened, synchronize, reopened, edited]
|
||||
branches:
|
||||
- 'master'
|
||||
paths:
|
||||
- 'selfdrive/ui/**'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
UI_JOB_NAME: "Create UI Report"
|
||||
REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||
SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }}
|
||||
BRANCH_NAME: "openpilot/pr-${{ github.event.number }}"
|
||||
|
||||
jobs:
|
||||
preview:
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
name: preview
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
actions: read
|
||||
steps:
|
||||
- name: Waiting for ui generation to start
|
||||
run: sleep 30
|
||||
|
||||
- name: Waiting for ui generation to end
|
||||
uses: lewagon/wait-on-check-action@v1.3.4
|
||||
with:
|
||||
ref: ${{ env.SHA }}
|
||||
check-name: ${{ env.UI_JOB_NAME }}
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
allowed-conclusions: success
|
||||
wait-interval: 20
|
||||
|
||||
- name: Getting workflow run ID
|
||||
id: get_run_id
|
||||
run: |
|
||||
echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?<number>[0-9]+)") | .number')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Getting proposed ui
|
||||
id: download-artifact
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run_id: ${{ steps.get_run_id.outputs.run_id }}
|
||||
search_artifacts: true
|
||||
name: report-1-${{ env.REPORT_NAME }}
|
||||
path: ${{ github.workspace }}/pr_ui
|
||||
|
||||
- name: Getting master ui
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: sunnypilot/ci-artifacts
|
||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||
path: ${{ github.workspace }}/master_ui
|
||||
ref: openpilot_master_ui
|
||||
|
||||
- name: Saving new master ui
|
||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
working-directory: ${{ github.workspace }}/master_ui
|
||||
run: |
|
||||
git checkout --orphan=new_master_ui
|
||||
git rm -rf *
|
||||
git branch -D openpilot_master_ui
|
||||
git branch -m openpilot_master_ui
|
||||
git config user.name "GitHub Actions Bot"
|
||||
git config user.email "<>"
|
||||
mv ${{ github.workspace }}/pr_ui/*.png .
|
||||
git add .
|
||||
git commit -m "screenshots for commit ${{ env.SHA }}"
|
||||
git push origin openpilot_master_ui --force
|
||||
|
||||
- name: Finding diff
|
||||
if: github.event_name == 'pull_request_target'
|
||||
id: find_diff
|
||||
run: >-
|
||||
sudo apt-get update && sudo apt-get install -y imagemagick
|
||||
|
||||
scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device')
|
||||
A=($scenes)
|
||||
|
||||
DIFF=""
|
||||
TABLE="<details><summary>All Screenshots</summary>"
|
||||
TABLE="${TABLE}<table>"
|
||||
|
||||
for ((i=0; i<${#A[*]}; i=i+1));
|
||||
do
|
||||
# Check if the master file exists
|
||||
if [ ! -f "${{ github.workspace }}/master_ui/${A[$i]}.png" ]; then
|
||||
# This is a new file in PR UI that doesn't exist in master
|
||||
DIFF="${DIFF}<details open>"
|
||||
DIFF="${DIFF}<summary>${A[$i]} : \$\${\\color{cyan}\\text{NEW}}\$\$</summary>"
|
||||
DIFF="${DIFF}<table>"
|
||||
|
||||
DIFF="${DIFF}<tr>"
|
||||
DIFF="${DIFF} <td> <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
DIFF="${DIFF}</tr>"
|
||||
|
||||
DIFF="${DIFF}</table>"
|
||||
DIFF="${DIFF}</details>"
|
||||
elif ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then
|
||||
convert ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png -transparent black mask.png
|
||||
composite mask.png ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png
|
||||
convert -delay 100 ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif
|
||||
|
||||
mv ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_master_ref.png
|
||||
|
||||
DIFF="${DIFF}<details open>"
|
||||
DIFF="${DIFF}<summary>${A[$i]} : \$\${\\color{red}\\text{DIFFERENT}}\$\$</summary>"
|
||||
DIFF="${DIFF}<table>"
|
||||
|
||||
DIFF="${DIFF}<tr>"
|
||||
DIFF="${DIFF} <td> master <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_master_ref.png\"> </td>"
|
||||
DIFF="${DIFF} <td> proposed <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
DIFF="${DIFF}</tr>"
|
||||
|
||||
DIFF="${DIFF}<tr>"
|
||||
DIFF="${DIFF} <td> diff <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.png\"> </td>"
|
||||
DIFF="${DIFF} <td> composite diff <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.gif\"> </td>"
|
||||
DIFF="${DIFF}</tr>"
|
||||
|
||||
DIFF="${DIFF}</table>"
|
||||
DIFF="${DIFF}</details>"
|
||||
else
|
||||
rm -f ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png
|
||||
fi
|
||||
|
||||
INDEX=$(($i % 2))
|
||||
if [[ $INDEX -eq 0 ]]; then
|
||||
TABLE="${TABLE}<tr>"
|
||||
fi
|
||||
TABLE="${TABLE} <td> <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then
|
||||
TABLE="${TABLE}</tr>"
|
||||
fi
|
||||
done
|
||||
|
||||
TABLE="${TABLE}</table></details>"
|
||||
|
||||
echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Saving proposed ui
|
||||
if: github.event_name == 'pull_request_target'
|
||||
working-directory: ${{ github.workspace }}/master_ui
|
||||
run: |
|
||||
git config user.name "GitHub Actions Bot"
|
||||
git config user.email "<>"
|
||||
git checkout --orphan=${{ env.BRANCH_NAME }}
|
||||
git rm -rf *
|
||||
mv ${{ github.workspace }}/pr_ui/* .
|
||||
git add .
|
||||
git commit -m "screenshots for PR #${{ github.event.number }}"
|
||||
git push origin ${{ env.BRANCH_NAME }} --force
|
||||
|
||||
- name: Comment Screenshots on PR
|
||||
if: github.event_name == 'pull_request_target'
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
with:
|
||||
message: |
|
||||
<!-- _(run_id_screenshots **${{ github.run_id }}**)_ -->
|
||||
## UI Preview
|
||||
${{ steps.find_diff.outputs.DIFF }}
|
||||
comment_tag: run_id_screenshots
|
||||
pr_number: ${{ github.event.number }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,52 +0,0 @@
|
||||
name: 'Wait for Tests'
|
||||
description: 'Action to wait for workflow tests to start and complete'
|
||||
inputs:
|
||||
workflow:
|
||||
description: 'The workflow file name to monitor'
|
||||
required: true
|
||||
default: 'selfdrive_tests.yaml'
|
||||
branch:
|
||||
description: 'The branch to monitor (defaults to current branch)'
|
||||
required: false
|
||||
default: ''
|
||||
github-token:
|
||||
description: 'GitHub token for API access'
|
||||
required: true
|
||||
wait-time:
|
||||
description: 'Initial sleep time in seconds before monitoring starts'
|
||||
required: false
|
||||
default: '30'
|
||||
should-wait-for-start:
|
||||
description: 'Whether to wait for tests to start'
|
||||
required: false
|
||||
default: false
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Wait for tests to start
|
||||
if: inputs.should-wait-for-start == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Sleeping for ${{ inputs.wait-time }} seconds to give some time for the action to start and then we'll wait"
|
||||
sleep ${{ inputs.wait-time }}
|
||||
|
||||
- name: Wait for tests to finish
|
||||
shell: bash
|
||||
run: |
|
||||
BRANCH="${{ inputs.branch || github.head_ref || github.ref_name }}"
|
||||
|
||||
echo "Looking for workflow runs of ${{ inputs.workflow }} on branch $BRANCH"
|
||||
RUN_ID=$(gh run list --workflow=${{ inputs.workflow }} --branch="$BRANCH" --limit=1 --json databaseId --jq '.[0].databaseId')
|
||||
echo "Watching run ID: $RUN_ID"
|
||||
gh run watch "$RUN_ID"
|
||||
|
||||
CONCLUSION=$(gh run view "$RUN_ID" --json conclusion --jq '.conclusion')
|
||||
echo "Run concluded with: $CONCLUSION"
|
||||
|
||||
if [[ "$CONCLUSION" != "success" ]]; then
|
||||
echo "❌ Workflow run failed with conclusion: $CONCLUSION"
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.github-token }}
|
||||
+6
-89
@@ -1,112 +1,29 @@
|
||||
venv/
|
||||
.venv/
|
||||
.ci_cache
|
||||
.env
|
||||
.clang-format
|
||||
.DS_Store
|
||||
.tags
|
||||
.ipynb_checkpoints
|
||||
.idea
|
||||
.overlay_init
|
||||
.overlay_consistent
|
||||
.sconsign.dblite
|
||||
model2.png
|
||||
a.out
|
||||
.hypothesis
|
||||
.cache/
|
||||
|
||||
/docs_site/
|
||||
|
||||
*.mp4
|
||||
*.dylib
|
||||
*.DSYM
|
||||
*.d
|
||||
*.pyc
|
||||
*.pyo
|
||||
.*.swp
|
||||
.*.swo
|
||||
.*.un~
|
||||
*.tmp
|
||||
*.o
|
||||
*.o-*
|
||||
*.os
|
||||
*.os-*
|
||||
*.so
|
||||
*.a
|
||||
*.clb
|
||||
*.class
|
||||
*.pyxbldc
|
||||
*.vcd
|
||||
*.qm
|
||||
*_pyx.cpp
|
||||
config.json
|
||||
clcache
|
||||
compile_commands.json
|
||||
compare_runtime*.html
|
||||
|
||||
persist
|
||||
selfdrive/pandad/pandad
|
||||
cereal/services.h
|
||||
cereal/gen
|
||||
cereal/messaging/bridge
|
||||
selfdrive/mapd/default_speeds_by_region.json
|
||||
system/proclogd/proclogd
|
||||
selfdrive/ui/translations/tmp
|
||||
selfdrive/test/longitudinal_maneuvers/out
|
||||
selfdrive/car/tests/cars_dump
|
||||
system/camerad/camerad
|
||||
system/camerad/test/ae_gray_test
|
||||
board/obj/
|
||||
selfdrive/boardd/boardd
|
||||
selfdrive/logcatd/logcatd
|
||||
selfdrive/proclogd/proclogd
|
||||
selfdrive/ui/ui
|
||||
/src/
|
||||
|
||||
notebooks
|
||||
hyperthneed
|
||||
provisioning
|
||||
|
||||
.coverage*
|
||||
coverage.xml
|
||||
htmlcov
|
||||
pandaextra
|
||||
|
||||
.mypy_cache/
|
||||
flycheck_*
|
||||
|
||||
cppcheck_report.txt
|
||||
comma*.sh
|
||||
|
||||
selfdrive/modeld/models/*.pkl
|
||||
sunnypilot/modeld*/thneed/compile
|
||||
sunnypilot/modeld*/models/*.thneed
|
||||
sunnypilot/modeld*/models/*.pkl
|
||||
|
||||
*.bz2
|
||||
*.zst
|
||||
|
||||
build/
|
||||
|
||||
!**/.gitkeep
|
||||
|
||||
poetry.toml
|
||||
Pipfile
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
### JetBrains ###
|
||||
!.idea/customTargets.xml
|
||||
!.idea/tools/*
|
||||
!.run/*
|
||||
|
||||
+7
-19
@@ -1,21 +1,9 @@
|
||||
[submodule "panda"]
|
||||
path = panda
|
||||
url = https://github.com/sunnyhaibin/panda.git
|
||||
path = panda
|
||||
url = https://github.com/commaai/panda.git
|
||||
[submodule "opendbc"]
|
||||
path = opendbc_repo
|
||||
url = https://github.com/sunnypilot/opendbc.git
|
||||
[submodule "msgq"]
|
||||
path = msgq_repo
|
||||
url = https://github.com/sunnypilot/msgq.git
|
||||
[submodule "rednose_repo"]
|
||||
path = rednose_repo
|
||||
url = https://github.com/commaai/rednose.git
|
||||
[submodule "teleoprtc_repo"]
|
||||
path = teleoprtc_repo
|
||||
url = https://github.com/commaai/teleoprtc
|
||||
[submodule "tinygrad"]
|
||||
path = tinygrad_repo
|
||||
url = https://github.com/tinygrad/tinygrad.git
|
||||
[submodule "sunnypilot/neural_network_data"]
|
||||
path = sunnypilot/neural_network_data
|
||||
url = https://github.com/sunnypilot/neural-network-data.git
|
||||
path = opendbc
|
||||
url = https://github.com/commaai/opendbc.git
|
||||
[submodule "pyextra"]
|
||||
path = pyextra
|
||||
url = https://github.com/commaai/openpilot-pyextra.git
|
||||
|
||||
Generated
-25
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CLionExternalBuildManager">
|
||||
<target id="a62f99e8-5ec4-434c-8122-49efed5af108" name="uv Scons Build Debug" defaultType="TOOL">
|
||||
<configuration id="b93ec964-16e5-4962-a12e-3ed360ce8f02" name="uv Scons Build Debug">
|
||||
<build type="TOOL">
|
||||
<tool actionId="Tool_External Tools_uv Scons Build Debug" />
|
||||
</build>
|
||||
<clean type="TOOL">
|
||||
<tool actionId="Tool_External Tools_uv Scons Clean" />
|
||||
</clean>
|
||||
</configuration>
|
||||
</target>
|
||||
<target id="edd8ad9d-183b-467c-a355-0d9a0ecab026" name="uv Scons Build Release" defaultType="TOOL">
|
||||
<configuration id="09523339-5ce3-4223-ab9e-904f38ad7752" name="uv Scons Build Release">
|
||||
<build type="TOOL">
|
||||
<tool actionId="Tool_External Tools_uv Scons Build Release" />
|
||||
</build>
|
||||
<clean type="TOOL">
|
||||
<tool actionId="Tool_External Tools_uv Scons Clean" />
|
||||
</clean>
|
||||
</configuration>
|
||||
</target>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-23
@@ -1,23 +0,0 @@
|
||||
<toolSet name="External Tools">
|
||||
<tool name="uv Scons Build Debug" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
|
||||
<exec>
|
||||
<option name="COMMAND" value="bash" />
|
||||
<option name="PARAMETERS" value="-c "source .venv/bin/activate && scons -u -j$(nproc) --ccflags=\"-fno-inline\""" />
|
||||
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
|
||||
</exec>
|
||||
</tool>
|
||||
<tool name="uv Scons Clean" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
|
||||
<exec>
|
||||
<option name="COMMAND" value="bash" />
|
||||
<option name="PARAMETERS" value="-c "source .venv/bin/activate && scons -c" " />
|
||||
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
|
||||
</exec>
|
||||
</tool>
|
||||
<tool name="uv Scons Build Release" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
|
||||
<exec>
|
||||
<option name="COMMAND" value="bash" />
|
||||
<option name="PARAMETERS" value="-c "source .venv/bin/activate && scons -u -j$(nproc)" " />
|
||||
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
|
||||
</exec>
|
||||
</tool>
|
||||
</toolSet>
|
||||
@@ -1,4 +0,0 @@
|
||||
[lfs]
|
||||
url = https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs
|
||||
pushurl = ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git
|
||||
locksverify = false
|
||||
@@ -1,4 +0,0 @@
|
||||
[lfs]
|
||||
url = https://gitlab.com/commaai/openpilot-lfs.git/info/lfs
|
||||
pushurl = ssh://git@gitlab.com/commaai/openpilot-lfs.git
|
||||
locksverify = false
|
||||
@@ -1,10 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="sunnypilot" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
|
||||
<envs>
|
||||
<env name="QT_DBL_CLICK_DIST" value="150" />
|
||||
</envs>
|
||||
<method v="2">
|
||||
<option name="CLION.EXTERNAL.BUILD" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,10 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Build Release" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="sunnypilot" TARGET_NAME="uv Scons Build Release" CONFIG_NAME="uv Scons Build Release" RUN_PATH="ui">
|
||||
<envs>
|
||||
<env name="QT_DBL_CLICK_DIST" value="150" />
|
||||
</envs>
|
||||
<method v="2">
|
||||
<option name="CLION.EXTERNAL.BUILD" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
install:
|
||||
- docker build -t tmppilot -f Dockerfile.openpilot .
|
||||
|
||||
script:
|
||||
- docker run --rm
|
||||
-v "$(pwd)"/selfdrive/test/plant/out:/tmp/openpilot/selfdrive/test/plant/out
|
||||
tmppilot /bin/sh -c 'cd /tmp/openpilot/selfdrive/test/plant && ./runtest.sh'
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-python.python",
|
||||
"ms-vscode.cpptools",
|
||||
"elagil.pre-commit-helper",
|
||||
"charliermarsh.ruff",
|
||||
]
|
||||
}
|
||||
Vendored
-46
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"inputs": [
|
||||
{
|
||||
"id": "python_process",
|
||||
"type": "pickString",
|
||||
"description": "Select the process to debug",
|
||||
"options": [
|
||||
"selfdrive/controls/controlsd.py",
|
||||
"system/timed/timed.py",
|
||||
"tools/sim/run_bridge.py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cpp_process",
|
||||
"type": "pickString",
|
||||
"description": "Select the process to debug",
|
||||
"options": [
|
||||
"selfdrive/ui/ui"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "args",
|
||||
"description": "Arguments to pass to the process",
|
||||
"type": "promptString"
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: openpilot Process",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${input:python_process}",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"args": "${input:args}"
|
||||
},
|
||||
{
|
||||
"name": "C++: openpilot Process",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/${input:cpp_process}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-27
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.renderWhitespace": "trailing",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"search.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.venv": true,
|
||||
"**/__pycache__": true
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.venv": true,
|
||||
"**/__pycache__": true
|
||||
},
|
||||
"python.analysis.exclude": [
|
||||
"**/.git",
|
||||
"**/.venv",
|
||||
"**/__pycache__",
|
||||
// exclude directories that should be using the symlinked version
|
||||
"common/**",
|
||||
"selfdrive/**",
|
||||
"system/**",
|
||||
"third_party/**",
|
||||
"tools/**",
|
||||
]
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
FROM ghcr.io/commaai/openpilot-base:latest
|
||||
FROM ubuntu:16.04
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
RUN apt-get update && apt-get install -y build-essential clang vim screen wget bzip2 git libglib2.0-0 python-pip capnproto libcapnp-dev libzmq5-dev libffi-dev
|
||||
|
||||
ENV OPENPILOT_PATH=/home/batman/openpilot
|
||||
RUN pip install numpy==1.11.2 scipy==0.18.1 matplotlib
|
||||
|
||||
RUN mkdir -p ${OPENPILOT_PATH}
|
||||
WORKDIR ${OPENPILOT_PATH}
|
||||
COPY requirements_openpilot.txt /tmp/
|
||||
RUN pip install -r /tmp/requirements_openpilot.txt
|
||||
|
||||
COPY . ${OPENPILOT_PATH}/
|
||||
ENV PYTHONPATH /tmp/openpilot:$PYTHONPATH
|
||||
|
||||
RUN scons --cache-readonly -j$(nproc)
|
||||
COPY . /tmp/openpilot
|
||||
|
||||
@@ -1,82 +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 ssh pulseaudio xvfb x11-xserver-utils gnome-screenshot python3-tk python3-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LANGUAGE=en_US:en
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
|
||||
COPY tools/install_ubuntu_dependencies.sh /tmp/tools/
|
||||
RUN /tmp/tools/install_ubuntu_dependencies.sh && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||
cd /usr/lib/gcc/arm-none-eabi/* && \
|
||||
rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp
|
||||
|
||||
# Add OpenCL
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
apt-utils \
|
||||
alien \
|
||||
unzip \
|
||||
tar \
|
||||
curl \
|
||||
xz-utils \
|
||||
dbus \
|
||||
gcc-arm-none-eabi \
|
||||
tmux \
|
||||
vim \
|
||||
libx11-6 \
|
||||
wget \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /tmp/opencl-driver-intel && \
|
||||
cd /tmp/opencl-driver-intel && \
|
||||
wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \
|
||||
wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \
|
||||
mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \
|
||||
cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \
|
||||
tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \
|
||||
mkdir -p /etc/OpenCL/vendors && \
|
||||
echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \
|
||||
cd /opt/intel && \
|
||||
tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
mkdir -p /etc/ld.so.conf.d && \
|
||||
echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \
|
||||
ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \
|
||||
cd / && \
|
||||
rm -rf /tmp/opencl-driver-intel
|
||||
|
||||
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute
|
||||
ENV QTWEBENGINE_DISABLE_SANDBOX=1
|
||||
|
||||
RUN dbus-uuidgen > /etc/machine-id
|
||||
RUN apt-get update && apt-get install -y fonts-noto-cjk fonts-noto-color-emoji
|
||||
|
||||
ARG USER=batman
|
||||
ARG USER_UID=1001
|
||||
RUN useradd -m -s /bin/bash -u $USER_UID $USER
|
||||
RUN usermod -aG sudo $USER
|
||||
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||
USER $USER
|
||||
|
||||
COPY --chown=$USER pyproject.toml uv.lock /home/$USER
|
||||
COPY --chown=$USER tools/install_python_dependencies.sh /home/$USER/tools/
|
||||
|
||||
ENV VIRTUAL_ENV=/home/$USER/.venv
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
RUN cd /home/$USER && \
|
||||
tools/install_python_dependencies.sh && \
|
||||
rm -rf tools/ pyproject.toml uv.lock .cache
|
||||
|
||||
USER root
|
||||
RUN sudo git config --global --add safe.directory /tmp/openpilot
|
||||
@@ -1,12 +0,0 @@
|
||||
FROM ghcr.io/sunnypilot/sunnypilot-base:latest
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
ENV OPENPILOT_PATH=/home/batman/openpilot
|
||||
|
||||
RUN mkdir -p ${OPENPILOT_PATH}
|
||||
WORKDIR ${OPENPILOT_PATH}
|
||||
|
||||
COPY . ${OPENPILOT_PATH}/
|
||||
|
||||
RUN scons --cache-readonly -j$(nproc)
|
||||
@@ -1,83 +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 ssh pulseaudio xvfb x11-xserver-utils gnome-screenshot python3-tk python3-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LANGUAGE=en_US:en
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
|
||||
COPY tools/install_ubuntu_dependencies.sh /tmp/tools/
|
||||
RUN /tmp/tools/install_ubuntu_dependencies.sh && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||
cd /usr/lib/gcc/arm-none-eabi/* && \
|
||||
rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp
|
||||
|
||||
# Add OpenCL
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
apt-utils \
|
||||
alien \
|
||||
unzip \
|
||||
tar \
|
||||
curl \
|
||||
xz-utils \
|
||||
dbus \
|
||||
gcc-arm-none-eabi \
|
||||
tmux \
|
||||
vim \
|
||||
libx11-6 \
|
||||
wget \
|
||||
rsync \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /tmp/opencl-driver-intel && \
|
||||
cd /tmp/opencl-driver-intel && \
|
||||
wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \
|
||||
wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \
|
||||
mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \
|
||||
cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \
|
||||
tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \
|
||||
mkdir -p /etc/OpenCL/vendors && \
|
||||
echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \
|
||||
cd /opt/intel && \
|
||||
tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
mkdir -p /etc/ld.so.conf.d && \
|
||||
echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \
|
||||
ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \
|
||||
cd / && \
|
||||
rm -rf /tmp/opencl-driver-intel
|
||||
|
||||
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute
|
||||
ENV QTWEBENGINE_DISABLE_SANDBOX=1
|
||||
|
||||
RUN dbus-uuidgen > /etc/machine-id
|
||||
RUN apt-get update && apt-get install -y fonts-noto-cjk fonts-noto-color-emoji
|
||||
|
||||
ARG USER=batman
|
||||
ARG USER_UID=1001
|
||||
RUN useradd -m -s /bin/bash -u $USER_UID $USER
|
||||
RUN usermod -aG sudo $USER
|
||||
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||
USER $USER
|
||||
|
||||
COPY --chown=$USER pyproject.toml uv.lock /home/$USER
|
||||
COPY --chown=$USER tools/install_python_dependencies.sh /home/$USER/tools/
|
||||
|
||||
ENV VIRTUAL_ENV=/home/$USER/.venv
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
RUN cd /home/$USER && \
|
||||
tools/install_python_dependencies.sh && \
|
||||
rm -rf tools/ pyproject.toml uv.lock .cache
|
||||
|
||||
USER root
|
||||
RUN sudo git config --global --add safe.directory /tmp/openpilot
|
||||
Vendored
-283
@@ -1,283 +0,0 @@
|
||||
def retryWithDelay(int maxRetries, int delay, Closure body) {
|
||||
for (int i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
return body()
|
||||
} catch (Exception e) {
|
||||
sleep(delay)
|
||||
}
|
||||
}
|
||||
throw Exception("Failed after ${maxRetries} retries")
|
||||
}
|
||||
|
||||
def device(String ip, String step_label, String cmd) {
|
||||
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
||||
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'
|
||||
|
||||
set -e
|
||||
|
||||
export TERM=xterm-256color
|
||||
|
||||
shopt -s huponexit # kill all child processes when the shell exits
|
||||
|
||||
export CI=1
|
||||
export PYTHONWARNINGS=error
|
||||
export LOGPRINT=debug
|
||||
export TEST_DIR=${env.TEST_DIR}
|
||||
export SOURCE_DIR=${env.SOURCE_DIR}
|
||||
export GIT_BRANCH=${env.GIT_BRANCH}
|
||||
export GIT_COMMIT=${env.GIT_COMMIT}
|
||||
export CI_ARTIFACTS_TOKEN=${env.CI_ARTIFACTS_TOKEN}
|
||||
export GITHUB_COMMENTS_TOKEN=${env.GITHUB_COMMENTS_TOKEN}
|
||||
export AZURE_TOKEN='${env.AZURE_TOKEN}'
|
||||
# only use 1 thread for tici tests since most require HIL
|
||||
export PYTEST_ADDOPTS="-n0 -s"
|
||||
|
||||
|
||||
export GIT_SSH_COMMAND="ssh -i /data/gitkey"
|
||||
|
||||
source ~/.bash_profile
|
||||
if [ -f /TICI ]; then
|
||||
source /etc/profile
|
||||
|
||||
rm -rf /tmp/tmp*
|
||||
rm -rf ~/.commacache
|
||||
rm -rf /dev/shm/*
|
||||
rm -rf /dev/tmp/tmp*
|
||||
|
||||
if ! systemctl is-active --quiet systemd-resolved; then
|
||||
echo "restarting resolved"
|
||||
sudo systemctl start systemd-resolved
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
# restart aux USB
|
||||
if [ -e /sys/bus/usb/drivers/hub/3-0:1.0 ]; then
|
||||
echo "restarting aux usb"
|
||||
echo "3-0:1.0" | sudo tee /sys/bus/usb/drivers/hub/unbind
|
||||
sleep 0.5
|
||||
echo "3-0:1.0" | sudo tee /sys/bus/usb/drivers/hub/bind
|
||||
fi
|
||||
fi
|
||||
if [ -f /data/openpilot/launch_env.sh ]; then
|
||||
source /data/openpilot/launch_env.sh
|
||||
fi
|
||||
|
||||
ln -snf ${env.TEST_DIR} /data/pythonpath
|
||||
|
||||
cd ${env.TEST_DIR} || true
|
||||
time ${cmd}
|
||||
END"""
|
||||
|
||||
sh script: ssh_cmd, label: step_label
|
||||
}
|
||||
}
|
||||
|
||||
def deviceStage(String stageName, String deviceType, List extra_env, def steps) {
|
||||
stage(stageName) {
|
||||
if (currentBuild.result != null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isReplay()) {
|
||||
error("REPLAYING TESTS IS NOT ALLOWED. FIX THEM INSTEAD.")
|
||||
}
|
||||
|
||||
def extra = extra_env.collect { "export ${it}" }.join('\n');
|
||||
def branch = env.BRANCH_NAME ?: 'master';
|
||||
def gitDiff = sh returnStdout: true, script: 'curl -s -H "Authorization: Bearer ${GITHUB_COMMENTS_TOKEN}" https://api.github.com/repos/commaai/openpilot/compare/master...${GIT_BRANCH} | jq .files[].filename || echo "/"', label: 'Getting changes'
|
||||
|
||||
lock(resource: "", label: deviceType, inversePrecedence: true, variable: 'device_ip', quantity: 1, resourceSelectStrategy: 'random') {
|
||||
docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') {
|
||||
timeout(time: 35, unit: 'MINUTES') {
|
||||
retry (3) {
|
||||
def date = sh(script: 'date', returnStdout: true).trim();
|
||||
device(device_ip, "set time", "date -s '" + date + "'")
|
||||
device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
|
||||
}
|
||||
steps.each { item ->
|
||||
def name = item[0]
|
||||
def cmd = item[1]
|
||||
|
||||
def args = item[2]
|
||||
def diffPaths = args.diffPaths ?: []
|
||||
def cmdTimeout = args.timeout ?: 9999
|
||||
|
||||
if (branch != "master" && !branch.contains("__jenkins_loop_") && diffPaths && !hasPathChanged(gitDiff, diffPaths)) {
|
||||
println "Skipping ${name}: no changes in ${diffPaths}."
|
||||
return
|
||||
} else {
|
||||
timeout(time: cmdTimeout, unit: 'SECONDS') {
|
||||
device(device_ip, name, cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def hasPathChanged(String gitDiff, List<String> paths) {
|
||||
for (path in paths) {
|
||||
if (gitDiff.contains(path)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
def isReplay() {
|
||||
def replayClass = "org.jenkinsci.plugins.workflow.cps.replay.ReplayCause"
|
||||
return currentBuild.rawBuild.getCauses().any{ cause -> cause.toString().contains(replayClass) }
|
||||
}
|
||||
|
||||
def setupCredentials() {
|
||||
withCredentials([
|
||||
string(credentialsId: 'azure_token', variable: 'AZURE_TOKEN'),
|
||||
]) {
|
||||
env.AZURE_TOKEN = "${AZURE_TOKEN}"
|
||||
}
|
||||
|
||||
withCredentials([
|
||||
string(credentialsId: 'ci_artifacts_pat', variable: 'CI_ARTIFACTS_TOKEN'),
|
||||
]) {
|
||||
env.CI_ARTIFACTS_TOKEN = "${CI_ARTIFACTS_TOKEN}"
|
||||
}
|
||||
|
||||
withCredentials([
|
||||
string(credentialsId: 'post_comments_github_pat', variable: 'GITHUB_COMMENTS_TOKEN'),
|
||||
]) {
|
||||
env.GITHUB_COMMENTS_TOKEN = "${GITHUB_COMMENTS_TOKEN}"
|
||||
}
|
||||
}
|
||||
|
||||
def step(String name, String cmd, Map args = [:]) {
|
||||
return [name, cmd, args]
|
||||
}
|
||||
|
||||
node {
|
||||
env.CI = "1"
|
||||
env.PYTHONWARNINGS = "error"
|
||||
env.TEST_DIR = "/data/openpilot"
|
||||
env.SOURCE_DIR = "/data/openpilot_source/"
|
||||
setupCredentials()
|
||||
|
||||
env.GIT_BRANCH = checkout(scm).GIT_BRANCH
|
||||
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
||||
|
||||
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
|
||||
'release-tici', 'testing-closet*', 'hotfix-*']
|
||||
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
||||
|
||||
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
||||
properties([
|
||||
disableConcurrentBuilds(abortPrevious: true)
|
||||
])
|
||||
}
|
||||
|
||||
try {
|
||||
if (env.BRANCH_NAME == 'devel-staging') {
|
||||
deviceStage("build release3-staging", "tici-needs-can", [], [
|
||||
step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"),
|
||||
])
|
||||
}
|
||||
|
||||
if (env.BRANCH_NAME == '__nightly') {
|
||||
parallel (
|
||||
'nightly': {
|
||||
deviceStage("build nightly", "tici-needs-can", [], [
|
||||
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
||||
])
|
||||
},
|
||||
'nightly-dev': {
|
||||
deviceStage("build nightly-dev", "tici-needs-can", [], [
|
||||
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
||||
])
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
||||
parallel (
|
||||
// tici tests
|
||||
'onroad tests': {
|
||||
deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("check dirty", "release/check-dirty.sh"),
|
||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||
])
|
||||
},
|
||||
'HW + Unit Tests': {
|
||||
deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
||||
step("test pigeond", "pytest system/ubloxd/tests/test_pigeond.py", [diffPaths: ["system/ubloxd/"]]),
|
||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
||||
])
|
||||
},
|
||||
'loopback': {
|
||||
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||
])
|
||||
},
|
||||
'camerad AR0231': {
|
||||
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||
])
|
||||
},
|
||||
'camerad OX03C10': {
|
||||
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||
])
|
||||
},
|
||||
'camerad OS04C10': {
|
||||
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||
])
|
||||
},
|
||||
'sensord': {
|
||||
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||
])
|
||||
deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||
])
|
||||
},
|
||||
'replay': {
|
||||
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||
])
|
||||
},
|
||||
'tizi': {
|
||||
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||
// TODO: enable once new AGNOS is available
|
||||
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
|
||||
step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]),
|
||||
])
|
||||
},
|
||||
|
||||
)
|
||||
}
|
||||
} catch (Exception e) {
|
||||
currentBuild.result = 'FAILED'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
# Custom MIT License
|
||||
|
||||
Copyright (c) 2024, Haibin Wen, SUNNYPILOT LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to view and modify the Software, subject to the following conditions:
|
||||
|
||||
1. **Permission Required**: Permission Required for Commercial, For-Profit, or Closed Source Use: Use of the Software, in whole or in part, for any commercial purposes, for-profit projects, or in closed source projects requires explicit written permission from the original author(s).
|
||||
|
||||
2. **Redistribution**: Any redistribution of the Software, modified or unmodified, must retain this license notice and the following acknowledgment:
|
||||
"This software is licensed under a custom license requiring permission for use."
|
||||
|
||||
3. **Visibility**: Any project that uses the Software must visibly mention the following acknowledgment:
|
||||
"This project uses software from Haibin Wen and SUNNYPILOT LLC and is licensed under a custom license requiring permission for use."
|
||||
|
||||
4. **No Warranty**: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Contact sunnypilot Support <support@sunnypilot.ai> for permission requests.
|
||||
|
||||
---
|
||||
|
||||
Haibin Wen, SUNNYPILOT LLC
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018, Comma.ai, Inc.
|
||||
Copyright (c) 2016, Comma.ai, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
.PHONY: all
|
||||
|
||||
# TODO: Add a global build system to openpilot
|
||||
all:
|
||||
cd /data/openpilot/selfdrive && PYTHONPATH=/data/openpilot PREPAREONLY=1 /data/openpilot/selfdrive/manager.py
|
||||
|
||||
@@ -1,119 +1,108 @@
|
||||

|
||||
Welcome to openpilot
|
||||
======
|
||||
|
||||
## 🌞 What is sunnypilot?
|
||||
[sunnypilot](https://github.com/sunnyhaibin/sunnypilot) is a fork of comma.ai's openpilot, an open source driver assistance system. sunnypilot offers the user a unique driving experience for over 300+ supported car makes and models with modified behaviors of driving assist engagements. sunnypilot complies with comma.ai's safety rules as accurately as possible.
|
||||
[openpilot](http://github.com/commaai/openpilot) is an open source driving agent.
|
||||
|
||||
## 💭 Join our Discord
|
||||
Join the official sunnypilot Discord server to stay up to date with all the latest features and be a part of shaping the future of sunnypilot!
|
||||
* https://discord.gg/sunnypilot
|
||||
Currently it performs the functions of Adaptive Cruise Control (ACC) and Lane Keeping Assist System (LKAS) for Hondas and Acuras. It's about on par with Tesla Autopilot at launch, and better than [all other manufacturers](http://www.thedrive.com/tech/5707/the-war-for-autonomous-driving-part-iii-us-vs-germany-vs-japan).
|
||||
|
||||
 
|
||||
The openpilot codebase has been written to be concise and enable rapid prototyping. We look forward to your contributions - improving real vehicle automation has never been easier.
|
||||
|
||||
## Documentation
|
||||
https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot
|
||||
Here are [some](https://www.youtube.com/watch?v=9OwTJFuDI7g) [videos](https://www.youtube.com/watch?v=64Wvt5pYQmE) [of](https://www.youtube.com/watch?v=6IW7Nejsr3A) [it](https://www.youtube.com/watch?v=-VN1YcC83nA) [running](https://www.youtube.com/watch?v=EQJZvVeihZk). And a really cool [tutorial](https://www.youtube.com/watch?v=PwOnsT2UW5o).
|
||||
|
||||
## 🚘 Running on a dedicated device in a car
|
||||
* A supported device to run this software
|
||||
* a [comma three](https://comma.ai/shop/products/three) or a [C3X](https://comma.ai/shop/comma-3x)
|
||||
* This software
|
||||
* One of [the 300+ supported cars](https://github.com/commaai/openpilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot.
|
||||
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car
|
||||
Hardware
|
||||
------
|
||||
|
||||
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
|
||||
Right now openpilot supports the [neo research platform](http://github.com/commaai/neo) for vehicle control. We'd like to support other platforms as well.
|
||||
|
||||
## Installation
|
||||
Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `release-c3` branch.
|
||||
Install openpilot on a neo device by entering ``https://openpilot.comma.ai`` during NEOS setup.
|
||||
|
||||
* sunnypilot not installed or you installed a version before 0.8.17?
|
||||
1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed.
|
||||
2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option.
|
||||
3. Input the installation URL per [Recommended Branches](#-recommended-branches). Example: ```release-c3.sunnypilot.ai```.
|
||||
4. Complete the rest of the installation following the onscreen instructions.
|
||||
Supported Cars
|
||||
------
|
||||
|
||||
* sunnypilot already installed and you installed a version after 0.8.17?
|
||||
1. On the comma three, go to `Settings` ▶️ `Software`.
|
||||
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
|
||||
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
|
||||
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `release-c3`
|
||||
- Acura ILX 2016 with AcuraWatch Plus
|
||||
- Due to use of the cruise control for gas, it can only be enabled above 25 mph
|
||||
|
||||
| Branch | Installation URL |
|
||||
|:------------:|:--------------------------------:|
|
||||
| `release-c3` | https://release-c3.sunnypilot.ai |
|
||||
| `staging-c3` | https://staging-c3.sunnypilot.ai |
|
||||
| `dev-c3` | https://dev-c3.sunnypilot.ai |
|
||||
- Honda Civic 2016 with Honda Sensing
|
||||
- Due to limitations in steering firmware, steering is disabled below 12 mph
|
||||
|
||||
### If you want to use our newest branches (our rewrite)
|
||||
> [!TIP]
|
||||
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
|
||||
- Honda CR-V Touring 2015-2016 (very alpha!)
|
||||
- Can only be enabled above 25 mph
|
||||
|
||||
Directory structure
|
||||
------
|
||||
|
||||
> [!IMPORTANT]
|
||||
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
|
||||
> You can still restore the latest sunnylink backup made on the old branches.
|
||||
- cereal -- The messaging spec used for all logs on the phone
|
||||
- common -- Library like functionality we've developed here
|
||||
- opendbc -- Files showing how to interpret data from cars
|
||||
- panda -- Code used to communicate on CAN and LIN
|
||||
- phonelibs -- Libraries used on the phone
|
||||
- selfdrive -- Code needed to drive the car
|
||||
- assets -- Fonts for ui
|
||||
- boardd -- Daemon to talk to the board
|
||||
- car -- Code that talks to the car and implements CarInterface
|
||||
- common -- Shared C/C++ code for the daemons
|
||||
- controls -- Python controls (PID loops etc) for the car
|
||||
- debug -- Tools to help you debug and do car ports
|
||||
- logcatd -- Android logcat as a service
|
||||
- loggerd -- Logger and uploader of car data
|
||||
- proclogd -- Logs information from proc
|
||||
- radar -- Code that talks to the radar and implements RadarInterface
|
||||
- sensord -- IMU / GPS interface code
|
||||
- test/plant -- Car simulator running code through virtual maneuvers
|
||||
- ui -- The UI
|
||||
- visiond -- embedded vision pipeline
|
||||
|
||||
| Branch | Installation URL |
|
||||
|:----------------:|:---------------------------------------------:|
|
||||
| `staging-c3-new` | `https://staging-c3-new.sunnypilot.ai` |
|
||||
| `dev-c3-new` | `https://dev-c3-new.sunnypilot.ai` |
|
||||
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
|
||||
| `release-c3-new` | **Not yet available**. |
|
||||
To understand how the services interact, see `selfdrive/service_list.yaml`
|
||||
|
||||
> [!TIP]
|
||||
> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
|
||||
Testing on PC
|
||||
------
|
||||
|
||||
## 🎆 Pull Requests
|
||||
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
|
||||
There is rudimentary infrastructure to run a basic simulation and generate a report of openpilot's behavior in different scenarios.
|
||||
|
||||
Pull requests should be against the most current `master` branch.
|
||||
```bash
|
||||
# Requires working docker
|
||||
./run_docker_tests.sh
|
||||
```
|
||||
|
||||
## 📊 User Data
|
||||
The results are written to `selfdrive/test/plant/out/index.html`
|
||||
|
||||
By default, sunnypilot uploads the driving data to comma servers. You can also access your data through [comma connect](https://connect.comma.ai/).
|
||||
More extensive testing infrastructure and simulation environments are coming soon.
|
||||
|
||||
sunnypilot is open source software. The user is free to disable data collection if they wish to do so.
|
||||
Adding Car Support
|
||||
------
|
||||
|
||||
sunnypilot logs the road-facing camera, CAN, GPS, IMU, magnetometer, thermal sensors, crashes, and operating system logs.
|
||||
The driver-facing camera and microphone are only logged if you explicitly opt-in in settings.
|
||||
comma.ai offers [bounties](http://comma.ai/bounties.html) for adding additional car support.
|
||||
|
||||
By using this software, you understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data.
|
||||
CR-V Touring support came in through this program. Chevy Volt is close. Accord is close as well.
|
||||
|
||||
## Licensing
|
||||
User Data / chffr Account / Crash Reporting
|
||||
------
|
||||
|
||||
sunnypilot is released under the [MIT License](LICENSE). This repository includes original work as well as significant portions of code derived from [openpilot by comma.ai](https://github.com/commaai/openpilot), which is also released under the MIT license with additional disclaimers.
|
||||
By default openpilot creates an account and includes a client for chffr, our dashcam app. We use your data to train better models and improve openpilot for everyone.
|
||||
|
||||
The original openpilot license notice, including comma.ai’s indemnification and alpha software disclaimer, is reproduced below as required:
|
||||
It's open source software, so you are free to disable it if you wish.
|
||||
|
||||
> openpilot is released under the MIT license. Some parts of the software are released under other licenses as specified.
|
||||
>
|
||||
> Any user of this software shall indemnify and hold harmless Comma.ai, Inc. and its directors, officers, employees, agents, stockholders, affiliates, subcontractors and customers from and against all allegations, claims, actions, suits, demands, damages, liabilities, obligations, losses, settlements, judgments, costs and expenses (including without limitation attorneys’ fees and costs) which arise out of, relate to or result from any use of this software by user.
|
||||
>
|
||||
> **THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH PURPOSES ONLY. THIS IS NOT A PRODUCT.
|
||||
> YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS.
|
||||
> NO WARRANTY EXPRESSED OR IMPLIED.**
|
||||
It logs the road facing camera, CAN, GPS, IMU, magnetometer, thermal sensors, crashes, and operating system logs.
|
||||
It does not log the user facing camera or the microphone.
|
||||
|
||||
For full license terms, please see the [`LICENSE`](LICENSE) file.
|
||||
By using it, you agree to [our privacy policy](https://beta.comma.ai/privacy.html). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma.ai. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma.ai for the use of this data.
|
||||
|
||||
## 💰 Support sunnypilot
|
||||
If you find any of the features useful, consider becoming a [sponsor on GitHub](https://github.com/sponsors/sunnyhaibin) to support future feature development and improvements.
|
||||
Contributing
|
||||
------
|
||||
|
||||
We welcome both pull requests and issues on
|
||||
[github](http://github.com/commaai/openpilot). See the TODO file for a list of
|
||||
good places to start.
|
||||
|
||||
By becoming a sponsor, you will gain access to exclusive content, early access to new features, and the opportunity to directly influence the project's development.
|
||||
Want to get paid to work on openpilot? [comma.ai is hiring](http://comma.ai/positions.html)
|
||||
|
||||
Licensing
|
||||
------
|
||||
|
||||
<h3>GitHub Sponsor</h3>
|
||||
openpilot is released under the MIT license.
|
||||
|
||||
<a href="https://github.com/sponsors/sunnyhaibin">
|
||||
<img src="https://user-images.githubusercontent.com/47793918/244135584-9800acbd-69fd-4b2b-bec9-e5fa2d85c817.png" alt="Become a Sponsor" width="300" style="max-width: 100%; height: auto;">
|
||||
</a>
|
||||
<br>
|
||||
Any user of this software shall indemnify and hold harmless Comma.ai, Inc. and its directors, officers, employees, agents, stockholders, affiliates, subcontractors and customers from and against all allegations, claims, actions, suits, demands, damages, liabilities, obligations, losses, settlements, judgments, costs and expenses (including without limitation attorneys’ fees and costs) which arise out of, relate to or result from any use of this software by user.
|
||||
|
||||
<h3>PayPal</h3>
|
||||
|
||||
<a href="https://paypal.me/sunnyhaibin0850" target="_blank">
|
||||
<img src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif" alt="PayPal this" title="PayPal - The safer, easier way to pay online!" border="0" />
|
||||
</a>
|
||||
<br></br>
|
||||
|
||||
Your continuous love and support are greatly appreciated! Enjoy 🥰
|
||||
|
||||
<span>-</span> Jason, Founder of sunnypilot
|
||||
**THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH PURPOSES ONLY. THIS IS NOT A PRODUCT.
|
||||
YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS.
|
||||
NO WARRANTY EXPRESSED OR IMPLIED.**
|
||||
|
||||
+52
-1020
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
openpilot Safety
|
||||
======
|
||||
|
||||
openpilot is an Adaptive Cruise Control and Lane Keeping Assist System. Like
|
||||
other ACC and LKAS systems, openpilot requires the driver to be alert and to pay
|
||||
attention at all times. We repeat, **driver alertness is necessary, but not
|
||||
sufficient, for openpilot to be used safely**.
|
||||
|
||||
Even with an attentive driver, we must make further efforts for the system to be
|
||||
safe. We have designed openpilot with two other safety considerations.
|
||||
|
||||
1. The vehicle must always be controllable by the driver.
|
||||
2. The vehicle must not alter its trajectory too quickly for the driver to safely
|
||||
react.
|
||||
|
||||
To address these, we came up with two safety principles.
|
||||
|
||||
1. Enforced disengagements. Step on either pedal or press the cancel button to
|
||||
retake manual control of the car immediately.
|
||||
- These are hard enforced by the board, and soft enforced by the software. The
|
||||
green led on the board signifies if the board is allowing control messages.
|
||||
- Honda CAN uses both a counter and a checksum to ensure integrity and prevent
|
||||
replay of the same message.
|
||||
|
||||
2. Actuation limits. While the system is engaged, the actuators are constrained
|
||||
to operate within reasonable limits; the same limits used by the stock system on
|
||||
the Honda.
|
||||
- Without an interceptor, the gas is controlled by the PCM. The PCM limits
|
||||
acceleration to what is reasonable for a cruise control system. With an
|
||||
interceptor, the gas is clipped to 60% in longcontrol.py
|
||||
- The brake is controlled by the 0x1FA CAN message. This message allows full
|
||||
braking, although the board and the software clip it to 1/4th of the max.
|
||||
This is around .3g of braking.
|
||||
- Steering is controlled by the 0xE4 CAN message. The EPS controller in the
|
||||
car limits the torque to a very small amount, so regardless of the message,
|
||||
the controller cannot jerk the wheel.
|
||||
-385
@@ -1,385 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import platform
|
||||
import numpy as np
|
||||
|
||||
import SCons.Errors
|
||||
|
||||
SCons.Warnings.warningAsException(True)
|
||||
|
||||
# pending upstream fix - https://github.com/SCons/scons/issues/4461
|
||||
#SetOption('warn', 'all')
|
||||
|
||||
TICI = os.path.isfile('/TICI')
|
||||
AGNOS = TICI
|
||||
|
||||
Decider('MD5-timestamp')
|
||||
|
||||
SetOption('num_jobs', max(1, int(os.cpu_count()/2)))
|
||||
|
||||
AddOption('--kaitai',
|
||||
action='store_true',
|
||||
help='Regenerate kaitai struct parsers')
|
||||
|
||||
AddOption('--asan',
|
||||
action='store_true',
|
||||
help='turn on ASAN')
|
||||
|
||||
AddOption('--ubsan',
|
||||
action='store_true',
|
||||
help='turn on UBSan')
|
||||
|
||||
AddOption('--coverage',
|
||||
action='store_true',
|
||||
help='build with test coverage options')
|
||||
|
||||
AddOption('--clazy',
|
||||
action='store_true',
|
||||
help='build with clazy')
|
||||
|
||||
AddOption('--ccflags',
|
||||
action='store',
|
||||
type='string',
|
||||
default='',
|
||||
help='pass arbitrary flags over the command line')
|
||||
|
||||
AddOption('--external-sconscript',
|
||||
action='store',
|
||||
metavar='FILE',
|
||||
dest='external_sconscript',
|
||||
help='add an external SConscript to the build')
|
||||
|
||||
AddOption('--mutation',
|
||||
action='store_true',
|
||||
help='generate mutation-ready code')
|
||||
|
||||
AddOption('--minimal',
|
||||
action='store_false',
|
||||
dest='extras',
|
||||
default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS)
|
||||
help='the minimum build to run openpilot. no tests, tools, etc.')
|
||||
|
||||
AddOption('--stock-ui',
|
||||
action='store_true',
|
||||
dest='stock_ui',
|
||||
default=False,
|
||||
help='Build stock openpilot UI instead of sunnypilot UI')
|
||||
|
||||
## Architecture name breakdown (arch)
|
||||
## - larch64: linux tici aarch64
|
||||
## - aarch64: linux pc aarch64
|
||||
## - x86_64: linux pc x64
|
||||
## - Darwin: mac x64 or arm64
|
||||
real_arch = arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||
if platform.system() == "Darwin":
|
||||
arch = "Darwin"
|
||||
brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip()
|
||||
elif arch == "aarch64" and AGNOS:
|
||||
arch = "larch64"
|
||||
assert arch in ["larch64", "aarch64", "x86_64", "Darwin"]
|
||||
|
||||
lenv = {
|
||||
"PATH": os.environ['PATH'],
|
||||
"PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath,
|
||||
|
||||
"ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath,
|
||||
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath,
|
||||
"TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer"
|
||||
}
|
||||
|
||||
rpath = []
|
||||
|
||||
if arch == "larch64":
|
||||
cpppath = [
|
||||
"#third_party/opencl/include",
|
||||
]
|
||||
|
||||
libpath = [
|
||||
"/usr/local/lib",
|
||||
"/system/vendor/lib64",
|
||||
f"#third_party/acados/{arch}/lib",
|
||||
]
|
||||
|
||||
libpath += [
|
||||
"#third_party/snpe/larch64",
|
||||
"#third_party/libyuv/larch64/lib",
|
||||
"/usr/lib/aarch64-linux-gnu"
|
||||
]
|
||||
cflags = ["-DQCOM2", "-mcpu=cortex-a57"]
|
||||
cxxflags = ["-DQCOM2", "-mcpu=cortex-a57"]
|
||||
rpath += ["/usr/local/lib"]
|
||||
else:
|
||||
cflags = []
|
||||
cxxflags = []
|
||||
cpppath = []
|
||||
rpath += []
|
||||
|
||||
# MacOS
|
||||
if arch == "Darwin":
|
||||
libpath = [
|
||||
f"#third_party/libyuv/{arch}/lib",
|
||||
f"#third_party/acados/{arch}/lib",
|
||||
f"{brew_prefix}/lib",
|
||||
f"{brew_prefix}/opt/openssl@3.0/lib",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Libraries",
|
||||
]
|
||||
|
||||
cflags += ["-DGL_SILENCE_DEPRECATION"]
|
||||
cxxflags += ["-DGL_SILENCE_DEPRECATION"]
|
||||
cpppath += [
|
||||
f"{brew_prefix}/include",
|
||||
f"{brew_prefix}/opt/openssl@3.0/include",
|
||||
]
|
||||
# Linux
|
||||
else:
|
||||
libpath = [
|
||||
f"#third_party/acados/{arch}/lib",
|
||||
f"#third_party/libyuv/{arch}/lib",
|
||||
"/usr/lib",
|
||||
"/usr/local/lib",
|
||||
]
|
||||
|
||||
if arch == "x86_64":
|
||||
libpath += [
|
||||
f"#third_party/snpe/{arch}"
|
||||
]
|
||||
rpath += [
|
||||
Dir(f"#third_party/snpe/{arch}").abspath,
|
||||
]
|
||||
|
||||
if GetOption('asan'):
|
||||
ccflags = ["-fsanitize=address", "-fno-omit-frame-pointer"]
|
||||
ldflags = ["-fsanitize=address"]
|
||||
elif GetOption('ubsan'):
|
||||
ccflags = ["-fsanitize=undefined"]
|
||||
ldflags = ["-fsanitize=undefined"]
|
||||
else:
|
||||
ccflags = []
|
||||
ldflags = []
|
||||
|
||||
# no --as-needed on mac linker
|
||||
if arch != "Darwin":
|
||||
ldflags += ["-Wl,--as-needed", "-Wl,--no-undefined"]
|
||||
|
||||
if not GetOption('stock_ui'):
|
||||
cflags += ["-DSUNNYPILOT"]
|
||||
cxxflags += ["-DSUNNYPILOT"]
|
||||
|
||||
ccflags_option = GetOption('ccflags')
|
||||
if ccflags_option:
|
||||
ccflags += ccflags_option.split(' ')
|
||||
|
||||
env = Environment(
|
||||
ENV=lenv,
|
||||
CCFLAGS=[
|
||||
"-g",
|
||||
"-fPIC",
|
||||
"-O2",
|
||||
"-Wunused",
|
||||
"-Werror",
|
||||
"-Wshadow",
|
||||
"-Wno-unknown-warning-option",
|
||||
"-Wno-inconsistent-missing-override",
|
||||
"-Wno-c99-designator",
|
||||
"-Wno-reorder-init-list",
|
||||
"-Wno-vla-cxx-extension",
|
||||
] + cflags + ccflags,
|
||||
|
||||
CPPPATH=cpppath + [
|
||||
"#",
|
||||
"#third_party/acados/include",
|
||||
"#third_party/acados/include/blasfeo/include",
|
||||
"#third_party/acados/include/hpipm/include",
|
||||
"#third_party/catch2/include",
|
||||
"#third_party/libyuv/include",
|
||||
"#third_party/json11",
|
||||
"#third_party/linux/include",
|
||||
"#third_party/snpe/include",
|
||||
"#third_party",
|
||||
"#msgq",
|
||||
],
|
||||
|
||||
CC='clang',
|
||||
CXX='clang++',
|
||||
LINKFLAGS=ldflags,
|
||||
|
||||
RPATH=rpath,
|
||||
|
||||
CFLAGS=["-std=gnu11"] + cflags,
|
||||
CXXFLAGS=["-std=c++1z"] + cxxflags,
|
||||
LIBPATH=libpath + [
|
||||
"#msgq_repo",
|
||||
"#third_party",
|
||||
"#selfdrive/pandad",
|
||||
"#common",
|
||||
"#rednose/helpers",
|
||||
],
|
||||
CYTHONCFILESUFFIX=".cpp",
|
||||
COMPILATIONDB_USE_ABSPATH=True,
|
||||
REDNOSE_ROOT="#",
|
||||
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
||||
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||
)
|
||||
|
||||
if arch == "Darwin":
|
||||
# RPATH is not supported on macOS, instead use the linker flags
|
||||
darwin_rpath_link_flags = [f"-Wl,-rpath,{path}" for path in env["RPATH"]]
|
||||
env["LINKFLAGS"] += darwin_rpath_link_flags
|
||||
|
||||
env.CompilationDatabase('compile_commands.json')
|
||||
|
||||
# Setup cache dir
|
||||
default_cache_dir = '/data/scons_cache' if AGNOS else '/tmp/scons_cache'
|
||||
cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir)
|
||||
CacheDir(cache_dir)
|
||||
Clean(["."], cache_dir)
|
||||
|
||||
node_interval = 5
|
||||
node_count = 0
|
||||
def progress_function(node):
|
||||
global node_count
|
||||
node_count += node_interval
|
||||
sys.stderr.write("progress: %d\n" % node_count)
|
||||
|
||||
if os.environ.get('SCONS_PROGRESS'):
|
||||
Progress(progress_function, interval=node_interval)
|
||||
|
||||
# Cython build environment
|
||||
py_include = sysconfig.get_paths()['include']
|
||||
envCython = env.Clone()
|
||||
envCython["CPPPATH"] += [py_include, np.get_include()]
|
||||
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-shadow", "-Wno-deprecated-declarations"]
|
||||
envCython["CCFLAGS"].remove("-Werror")
|
||||
|
||||
envCython["LIBS"] = []
|
||||
if arch == "Darwin":
|
||||
envCython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"] + darwin_rpath_link_flags
|
||||
else:
|
||||
envCython["LINKFLAGS"] = ["-pthread", "-shared"]
|
||||
|
||||
np_version = SCons.Script.Value(np.__version__)
|
||||
Export('envCython', 'np_version')
|
||||
|
||||
# Qt build environment
|
||||
qt_env = env.Clone()
|
||||
qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "DBus", "Xml"]
|
||||
|
||||
qt_libs = []
|
||||
if arch == "Darwin":
|
||||
qt_env['QTDIR'] = f"{brew_prefix}/opt/qt@5"
|
||||
qt_dirs = [
|
||||
os.path.join(qt_env['QTDIR'], "include"),
|
||||
]
|
||||
qt_dirs += [f"{qt_env['QTDIR']}/include/Qt{m}" for m in qt_modules]
|
||||
qt_env["LINKFLAGS"] += ["-F" + os.path.join(qt_env['QTDIR'], "lib")]
|
||||
qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"]
|
||||
qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin"))
|
||||
else:
|
||||
qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip()
|
||||
qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip()
|
||||
|
||||
qt_env['QTDIR'] = qt_install_prefix
|
||||
qt_dirs = [
|
||||
f"{qt_install_headers}",
|
||||
]
|
||||
|
||||
qt_gui_path = os.path.join(qt_install_headers, "QtGui")
|
||||
qt_gui_dirs = [d for d in os.listdir(qt_gui_path) if os.path.isdir(os.path.join(qt_gui_path, d))]
|
||||
qt_dirs += [f"{qt_install_headers}/QtGui/{qt_gui_dirs[0]}/QtGui", ] if qt_gui_dirs else []
|
||||
qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules]
|
||||
|
||||
qt_libs = [f"Qt5{m}" for m in qt_modules]
|
||||
if arch == "larch64":
|
||||
qt_libs += ["GLESv2", "wayland-client"]
|
||||
qt_env.PrependENVPath('PATH', Dir("#third_party/qt5/larch64/bin/").abspath)
|
||||
elif arch != "Darwin":
|
||||
qt_libs += ["GL"]
|
||||
qt_env['QT3DIR'] = qt_env['QTDIR']
|
||||
qt_env.Tool('qt3')
|
||||
|
||||
qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"]
|
||||
qt_flags = [
|
||||
"-D_REENTRANT",
|
||||
"-DQT_NO_DEBUG",
|
||||
"-DQT_WIDGETS_LIB",
|
||||
"-DQT_GUI_LIB",
|
||||
"-DQT_CORE_LIB",
|
||||
"-DQT_MESSAGELOGCONTEXT",
|
||||
]
|
||||
qt_env['CXXFLAGS'] += qt_flags
|
||||
qt_env['LIBPATH'] += ['#selfdrive/ui', ]
|
||||
qt_env['LIBS'] = qt_libs
|
||||
|
||||
if GetOption("clazy"):
|
||||
checks = [
|
||||
"level0",
|
||||
"level1",
|
||||
"no-range-loop",
|
||||
"no-non-pod-global-static",
|
||||
]
|
||||
qt_env['CXX'] = 'clazy'
|
||||
qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0]
|
||||
qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks)
|
||||
|
||||
Export('env', 'qt_env', 'arch', 'real_arch')
|
||||
|
||||
# Build common module
|
||||
SConscript(['common/SConscript'])
|
||||
Import('_common', '_gpucommon')
|
||||
|
||||
common = [_common, 'json11', 'zmq']
|
||||
gpucommon = [_gpucommon]
|
||||
|
||||
Export('common', 'gpucommon')
|
||||
|
||||
# Build messaging (cereal + msgq + socketmaster + their dependencies)
|
||||
# Enable swaglog include in submodules
|
||||
env_swaglog = env.Clone()
|
||||
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
|
||||
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
|
||||
SConscript(['opendbc_repo/SConscript'], exports={'env': env_swaglog})
|
||||
|
||||
SConscript(['cereal/SConscript'])
|
||||
|
||||
Import('socketmaster', 'msgq')
|
||||
messaging = [socketmaster, msgq, 'capnp', 'kj',]
|
||||
Export('messaging')
|
||||
|
||||
|
||||
# Build other submodules
|
||||
SConscript(['panda/SConscript'])
|
||||
|
||||
# Build rednose library
|
||||
SConscript(['rednose/SConscript'])
|
||||
|
||||
# Build system services
|
||||
SConscript([
|
||||
'system/ubloxd/SConscript',
|
||||
'system/loggerd/SConscript',
|
||||
])
|
||||
if arch != "Darwin":
|
||||
SConscript([
|
||||
'system/logcatd/SConscript',
|
||||
'system/proclogd/SConscript',
|
||||
])
|
||||
|
||||
if arch == "larch64":
|
||||
SConscript(['system/camerad/SConscript'])
|
||||
|
||||
# Build openpilot
|
||||
SConscript(['third_party/SConscript'])
|
||||
|
||||
SConscript(['selfdrive/SConscript'])
|
||||
|
||||
SConscript(['sunnypilot/SConscript'])
|
||||
|
||||
if Dir('#tools/cabana/').exists() and GetOption('extras'):
|
||||
SConscript(['tools/replay/SConscript'])
|
||||
if arch != "larch64":
|
||||
SConscript(['tools/cabana/SConscript'])
|
||||
|
||||
external_sconscript = GetOption('external_sconscript')
|
||||
if external_sconscript:
|
||||
SConscript([external_sconscript])
|
||||
@@ -1,5 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Suspected vulnerabilities can be reported to both `adeeb@comma.ai` and `security@comma.ai`.
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
gen
|
||||
@@ -0,0 +1,4 @@
|
||||
-include build_from_src.mk
|
||||
|
||||
release:
|
||||
@echo "cereal: this is a release"
|
||||
@@ -1,95 +0,0 @@
|
||||
# What is cereal?
|
||||
|
||||
cereal is the messaging system for openpilot. It uses [msgq](https://github.com/commaai/msgq) as a pub/sub backend, and [Cap'n proto](https://capnproto.org/capnp-tool.html) for serialization of the structs.
|
||||
|
||||
|
||||
## Messaging Spec
|
||||
|
||||
You'll find the message types in [log.capnp](log.capnp). It uses [Cap'n proto](https://capnproto.org/capnp-tool.html) and defines one struct called `Event`.
|
||||
|
||||
All `Events` have a `logMonoTime` and a `valid`. Then a big union defines the packet type.
|
||||
|
||||
### Best Practices
|
||||
|
||||
- **All fields must describe quantities in SI units**, unless otherwise specified in the field name.
|
||||
- In the context of the message they are in, field names should be completely unambiguous.
|
||||
- All values should be easy to plot and be human-readable with minimal parsing.
|
||||
|
||||
### Maintaining backwards-compatibility
|
||||
|
||||
When making changes to the messaging spec you want to maintain backwards-compatibility, such that old logs can
|
||||
be parsed with a new version of cereal. Adding structs and adding members to structs is generally safe, most other
|
||||
things are not. Read more details [here](https://capnproto.org/language.html).
|
||||
|
||||
### Custom forks
|
||||
|
||||
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
|
||||
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
|
||||
fork will remain backwards-compatible with all versions of mainline openpilot and your fork.**
|
||||
|
||||
An example of compatible changes:
|
||||
```diff
|
||||
diff --git a/cereal/custom.capnp b/cereal/custom.capnp
|
||||
index 3348e859e..3365c7b98 100644
|
||||
--- a/cereal/custom.capnp
|
||||
+++ b/cereal/custom.capnp
|
||||
@@ -10,7 +10,11 @@ $Cxx.namespace("cereal");
|
||||
# DO rename the structs
|
||||
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
||||
|
||||
-struct CustomReserved0 @0x81c2f05a394cf4af {
|
||||
+struct SteeringInfo @0x81c2f05a394cf4af {
|
||||
+ active @0 :Bool;
|
||||
+ steeringAngleDeg @1 :Float32;
|
||||
+ steeringRateDeg @2 :Float32;
|
||||
+ steeringAccelDeg @3 :Float32;
|
||||
}
|
||||
|
||||
struct CustomReserved1 @0xaedffd8f31e7b55d {
|
||||
diff --git a/cereal/log.capnp b/cereal/log.capnp
|
||||
index 1209f3fd9..b189f58b6 100644
|
||||
--- a/cereal/log.capnp
|
||||
+++ b/cereal/log.capnp
|
||||
@@ -2558,14 +2558,14 @@ struct Event {
|
||||
|
||||
# DO change the name of the field
|
||||
# DON'T change anything after the "@"
|
||||
- customReservedRawData0 @124 :Data;
|
||||
+ rawCanData @124 :Data;
|
||||
customReservedRawData1 @125 :Data;
|
||||
customReservedRawData2 @126 :Data;
|
||||
|
||||
# DO change the name of the field and struct
|
||||
# DON'T change the ID (e.g. @107)
|
||||
# DON'T change which struct it points to
|
||||
- customReserved0 @107 :Custom.CustomReserved0;
|
||||
+ steeringInfo @107 :Custom.SteeringInfo;
|
||||
customReserved1 @108 :Custom.CustomReserved1;
|
||||
customReserved2 @109 :Custom.CustomReserved2;
|
||||
customReserved3 @110 :Custom.CustomReserved3;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Example
|
||||
---
|
||||
```python
|
||||
import cereal.messaging as messaging
|
||||
|
||||
# in subscriber
|
||||
sm = messaging.SubMaster(['sensorEvents'])
|
||||
while 1:
|
||||
sm.update()
|
||||
print(sm['sensorEvents'])
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
# in publisher
|
||||
pm = messaging.PubMaster(['sensorEvents'])
|
||||
dat = messaging.new_message('sensorEvents', size=1)
|
||||
dat.sensorEvents[0] = {"gyro": {"v": [0.1, -0.1, 0.1]}}
|
||||
pm.send('sensorEvents', dat)
|
||||
```
|
||||
@@ -1,20 +0,0 @@
|
||||
Import('env', 'common', 'msgq')
|
||||
|
||||
cereal_dir = Dir('.')
|
||||
gen_dir = Dir('gen')
|
||||
|
||||
# Build cereal
|
||||
schema_files = ['log.capnp', 'car.capnp', 'legacy.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'], LIBS=[msgq, common, 'pthread'])
|
||||
|
||||
socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc'])
|
||||
|
||||
Export('cereal', 'socketmaster')
|
||||
+13
-4
@@ -1,11 +1,20 @@
|
||||
import os
|
||||
import capnp
|
||||
from importlib.resources import as_file, files
|
||||
|
||||
CEREAL_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
capnp.remove_import_hook()
|
||||
|
||||
with as_file(files("cereal")) as fspath:
|
||||
CEREAL_PATH = fspath.as_posix()
|
||||
if os.getenv("NEWCAPNP"):
|
||||
import tempfile
|
||||
import pyximport
|
||||
|
||||
importers = pyximport.install(build_dir=os.path.join(tempfile.gettempdir(), ".pyxbld"))
|
||||
try:
|
||||
import cereal.gen.cython.log_capnp_cython as log
|
||||
import cereal.gen.cython.car_capnp_cython as car
|
||||
finally:
|
||||
pyximport.uninstall(*importers)
|
||||
del importers
|
||||
else:
|
||||
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"))
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
SRCS := log.capnp car.capnp
|
||||
|
||||
GENS := gen/cpp/car.capnp.c++ gen/cpp/log.capnp.c++
|
||||
|
||||
|
||||
UNAME_M := $(shell uname -m)
|
||||
|
||||
# only generate C++ for docker tests
|
||||
ifneq ($(OPTEST),1)
|
||||
GENS += gen/c/car.capnp.c gen/c/log.capnp.c gen/c/c++.capnp.h gen/c/java.capnp.h
|
||||
|
||||
# Dont build java on the phone...
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
GENS += gen/java/Car.java gen/java/Log.java
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
all: $(GENS)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf gen
|
||||
|
||||
gen/c/%.capnp.c: %.capnp
|
||||
@echo "[ CAPNPC C ] $@"
|
||||
mkdir -p gen/c/
|
||||
capnpc '$<' -o c:gen/c/
|
||||
|
||||
gen/cpp/%.capnp.c++: %.capnp
|
||||
@echo "[ CAPNPC C++ ] $@"
|
||||
mkdir -p gen/cpp/
|
||||
capnpc '$<' -o c++:gen/cpp/
|
||||
|
||||
gen/java/Car.java gen/java/Log.java: $(SRCS)
|
||||
@echo "[ CAPNPC java ] $@"
|
||||
mkdir -p gen/java/
|
||||
capnpc $^ -o java:gen/java
|
||||
|
||||
# c-capnproto needs some empty headers
|
||||
gen/c/c++.capnp.h gen/c/java.capnp.h:
|
||||
mkdir -p gen/c/
|
||||
touch '$@'
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../opendbc_repo/opendbc/car/car.capnp
|
||||
@@ -0,0 +1,203 @@
|
||||
using Cxx = import "c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
using Java = import "java.capnp";
|
||||
$Java.package("ai.comma.openpilot.cereal");
|
||||
$Java.outerClassname("Car");
|
||||
|
||||
@0x8e2af1e708af8b8d;
|
||||
|
||||
# ******* main car state @ 100hz *******
|
||||
# all speeds in m/s
|
||||
|
||||
struct CarState {
|
||||
errors @0: List(Error);
|
||||
|
||||
# car speed
|
||||
vEgo @1 :Float32; # best estimate of speed
|
||||
wheelSpeeds @2 :WheelSpeeds;
|
||||
|
||||
# gas pedal, 0.0-1.0
|
||||
gas @3 :Float32; # this is user + computer
|
||||
gasPressed @4 :Bool; # this is user pedal only
|
||||
|
||||
# brake pedal, 0.0-1.0
|
||||
brake @5 :Float32; # this is user pedal only
|
||||
brakePressed @6 :Bool; # this is user pedal only
|
||||
|
||||
# steering wheel
|
||||
steeringAngle @7 :Float32; # deg
|
||||
steeringTorque @8 :Float32; # TODO: standardize units
|
||||
steeringPressed @9 :Bool; # if the user is using the steering wheel
|
||||
|
||||
# cruise state
|
||||
cruiseState @10 :CruiseState;
|
||||
|
||||
# button presses
|
||||
buttonEvents @11 :List(ButtonEvent);
|
||||
|
||||
# which packets this state came from
|
||||
canMonoTimes @12: List(UInt64);
|
||||
|
||||
struct WheelSpeeds {
|
||||
# optional wheel speeds
|
||||
fl @0 :Float32;
|
||||
fr @1 :Float32;
|
||||
rl @2 :Float32;
|
||||
rr @3 :Float32;
|
||||
}
|
||||
|
||||
struct CruiseState {
|
||||
enabled @0: Bool;
|
||||
speed @1: Float32;
|
||||
available @2: Bool;
|
||||
}
|
||||
|
||||
enum Error {
|
||||
# TODO: copy from error list
|
||||
commIssue @0;
|
||||
steerUnavailable @1;
|
||||
brakeUnavailable @2;
|
||||
gasUnavailable @3;
|
||||
wrongGear @4;
|
||||
doorOpen @5;
|
||||
seatbeltNotLatched @6;
|
||||
espDisabled @7;
|
||||
wrongCarMode @8;
|
||||
steerTempUnavailable @9;
|
||||
reverseGear @10;
|
||||
# ...
|
||||
}
|
||||
|
||||
# send on change
|
||||
struct ButtonEvent {
|
||||
pressed @0: Bool;
|
||||
type @1: Type;
|
||||
|
||||
enum Type {
|
||||
unknown @0;
|
||||
leftBlinker @1;
|
||||
rightBlinker @2;
|
||||
accelCruise @3;
|
||||
decelCruise @4;
|
||||
cancel @5;
|
||||
altButton1 @6;
|
||||
altButton2 @7;
|
||||
altButton3 @8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ******* radar state @ 20hz *******
|
||||
|
||||
struct RadarState {
|
||||
errors @0: List(Error);
|
||||
points @1: List(RadarPoint);
|
||||
|
||||
# which packets this state came from
|
||||
canMonoTimes @2: List(UInt64);
|
||||
|
||||
enum Error {
|
||||
notValid @0;
|
||||
}
|
||||
|
||||
# similar to LiveTracks
|
||||
# is one timestamp valid for all? I think so
|
||||
struct RadarPoint {
|
||||
trackId @0: UInt64; # no trackId reuse
|
||||
|
||||
# these 3 are the minimum required
|
||||
dRel @1: Float32; # m from the front bumper of the car
|
||||
yRel @2: Float32; # m
|
||||
vRel @3: Float32; # m/s
|
||||
|
||||
# these are optional and valid if they are not NaN
|
||||
aRel @4: Float32; # m/s^2
|
||||
yvRel @5: Float32; # m/s
|
||||
}
|
||||
}
|
||||
|
||||
# ******* car controls @ 100hz *******
|
||||
|
||||
struct CarControl {
|
||||
# must be true for any actuator commands to work
|
||||
enabled @0: Bool;
|
||||
|
||||
# range from 0.0 - 1.0
|
||||
gas @1: Float32;
|
||||
brake @2: Float32;
|
||||
|
||||
# range from -1.0 - 1.0
|
||||
steeringTorque @3 :Float32;
|
||||
|
||||
cruiseControl @4: CruiseControl;
|
||||
hudControl @5: HUDControl;
|
||||
|
||||
struct CruiseControl {
|
||||
cancel @0: Bool;
|
||||
override @1: Bool;
|
||||
speedOverride @2: Float32;
|
||||
accelOverride @3: Float32;
|
||||
}
|
||||
|
||||
struct HUDControl {
|
||||
speedVisible @0: Bool;
|
||||
setSpeed @1: Float32;
|
||||
lanesVisible @2: Bool;
|
||||
leadVisible @3: Bool;
|
||||
visualAlert @4: VisualAlert;
|
||||
audibleAlert @5: AudibleAlert;
|
||||
|
||||
enum VisualAlert {
|
||||
# these are the choices from the Honda
|
||||
# map as good as you can for your car
|
||||
none @0;
|
||||
fcw @1;
|
||||
steerRequired @2;
|
||||
brakePressed @3;
|
||||
wrongGear @4;
|
||||
seatbeltUnbuckled @5;
|
||||
speedTooHigh @6;
|
||||
}
|
||||
|
||||
enum AudibleAlert {
|
||||
# these are the choices from the Honda
|
||||
# map as good as you can for your car
|
||||
none @0;
|
||||
beepSingle @1;
|
||||
beepTriple @2;
|
||||
beepRepeated @3;
|
||||
chimeSingle @4;
|
||||
chimeDouble @5;
|
||||
chimeRepeated @6;
|
||||
chimeContinuous @7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ****** car param ******
|
||||
|
||||
struct CarParams {
|
||||
carName @0: Text;
|
||||
radarName @1: Text;
|
||||
carFingerprint @11: Text;
|
||||
|
||||
enableSteer @2: Bool;
|
||||
enableGas @3: Bool;
|
||||
enableBrake @4: Bool;
|
||||
enableCruise @5: Bool;
|
||||
|
||||
# things about the car in the manual
|
||||
wheelBase @6: Float32; # in meters
|
||||
steerRatio @7: Float32;
|
||||
|
||||
# things we can derive
|
||||
slipFactor @8: Float32;
|
||||
|
||||
# Kp and Ki for the lateral control
|
||||
steerKp @9: Float32;
|
||||
steerKi @10: Float32;
|
||||
|
||||
# TODO: Kp and Ki for long control, perhaps not needed?
|
||||
}
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
using Cxx = import "./include/c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
@0xb526ba661d550a59;
|
||||
|
||||
# custom.capnp: a home for empty structs reserved for custom forks
|
||||
# These structs are guaranteed to remain reserved and empty in mainline
|
||||
# cereal, so use these if you want custom events in your fork.
|
||||
|
||||
# DO rename the structs
|
||||
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
||||
|
||||
struct ModularAssistiveDrivingSystem {
|
||||
state @0 :ModularAssistiveDrivingSystemState;
|
||||
enabled @1 :Bool;
|
||||
active @2 :Bool;
|
||||
available @3 :Bool;
|
||||
|
||||
enum ModularAssistiveDrivingSystemState {
|
||||
disabled @0;
|
||||
paused @1;
|
||||
enabled @2;
|
||||
softDisabling @3;
|
||||
overriding @4;
|
||||
}
|
||||
}
|
||||
|
||||
# Same struct as Log.RadarState.LeadData
|
||||
struct LeadData {
|
||||
dRel @0 :Float32;
|
||||
yRel @1 :Float32;
|
||||
vRel @2 :Float32;
|
||||
aRel @3 :Float32;
|
||||
vLead @4 :Float32;
|
||||
dPath @6 :Float32;
|
||||
vLat @7 :Float32;
|
||||
vLeadK @8 :Float32;
|
||||
aLeadK @9 :Float32;
|
||||
fcw @10 :Bool;
|
||||
status @11 :Bool;
|
||||
aLeadTau @12 :Float32;
|
||||
modelProb @13 :Float32;
|
||||
radar @14 :Bool;
|
||||
radarTrackId @15 :Int32 = -1;
|
||||
|
||||
aLeadDEPRECATED @5 :Float32;
|
||||
}
|
||||
|
||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||
mads @0 :ModularAssistiveDrivingSystem;
|
||||
}
|
||||
|
||||
struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
activeBundle @0 :ModelBundle;
|
||||
selectedBundle @1 :ModelBundle;
|
||||
availableBundles @2 :List(ModelBundle);
|
||||
|
||||
struct DownloadUri {
|
||||
uri @0 :Text;
|
||||
sha256 @1 :Text;
|
||||
}
|
||||
|
||||
enum DownloadStatus {
|
||||
notDownloading @0;
|
||||
downloading @1;
|
||||
downloaded @2;
|
||||
cached @3;
|
||||
failed @4;
|
||||
}
|
||||
|
||||
struct DownloadProgress {
|
||||
status @0 :DownloadStatus;
|
||||
progress @1 :Float32;
|
||||
eta @2 :UInt32;
|
||||
}
|
||||
|
||||
struct Artifact {
|
||||
fileName @0 :Text;
|
||||
downloadUri @1 :DownloadUri;
|
||||
downloadProgress @2 :DownloadProgress;
|
||||
}
|
||||
|
||||
struct Model {
|
||||
type @0 :Type;
|
||||
artifact @1 :Artifact; # Main artifact
|
||||
metadata @2 :Artifact; # Metadata artifact
|
||||
|
||||
enum Type {
|
||||
supercombo @0;
|
||||
navigation @1;
|
||||
vision @2;
|
||||
policy @3;
|
||||
}
|
||||
}
|
||||
|
||||
enum Runner {
|
||||
snpe @0;
|
||||
tinygrad @1;
|
||||
stock @2;
|
||||
}
|
||||
|
||||
struct Override {
|
||||
key @0 :Text;
|
||||
value @1 :Text;
|
||||
}
|
||||
|
||||
struct ModelBundle {
|
||||
index @0 :UInt32;
|
||||
internalName @1 :Text;
|
||||
displayName @2 :Text;
|
||||
models @3 :List(Model);
|
||||
status @4 :DownloadStatus;
|
||||
generation @5 :UInt32;
|
||||
environment @6 :Text;
|
||||
runner @7 :Runner;
|
||||
is20hz @8 :Bool;
|
||||
ref @9 :Text;
|
||||
minimumSelectorVersion @10 :UInt32;
|
||||
overrides @11 :List(Override);
|
||||
}
|
||||
}
|
||||
|
||||
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
||||
dec @0 :DynamicExperimentalControl;
|
||||
|
||||
struct DynamicExperimentalControl {
|
||||
state @0 :DynamicExperimentalControlState;
|
||||
enabled @1 :Bool;
|
||||
active @2 :Bool;
|
||||
|
||||
enum DynamicExperimentalControlState {
|
||||
acc @0;
|
||||
blended @1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OnroadEventSP @0xda96579883444c35 {
|
||||
events @0 :List(Event);
|
||||
|
||||
struct Event {
|
||||
name @0 :EventName;
|
||||
|
||||
# event types
|
||||
enable @1 :Bool;
|
||||
noEntry @2 :Bool;
|
||||
warning @3 :Bool; # alerts presented only when enabled or soft disabling
|
||||
userDisable @4 :Bool;
|
||||
softDisable @5 :Bool;
|
||||
immediateDisable @6 :Bool;
|
||||
preEnable @7 :Bool;
|
||||
permanent @8 :Bool; # alerts presented regardless of openpilot state
|
||||
overrideLateral @10 :Bool;
|
||||
overrideLongitudinal @9 :Bool;
|
||||
}
|
||||
|
||||
enum EventName {
|
||||
lkasEnable @0;
|
||||
lkasDisable @1;
|
||||
manualSteeringRequired @2;
|
||||
manualLongitudinalRequired @3;
|
||||
silentLkasEnable @4;
|
||||
silentLkasDisable @5;
|
||||
silentBrakeHold @6;
|
||||
silentWrongGear @7;
|
||||
silentReverseGear @8;
|
||||
silentDoorOpen @9;
|
||||
silentSeatbeltNotLatched @10;
|
||||
silentParkBrake @11;
|
||||
controlsMismatchLateral @12;
|
||||
hyundaiRadarTracksConfirmed @13;
|
||||
experimentalModeSwitched @14;
|
||||
wrongCarModeAlertOnly @15;
|
||||
pedalPressedAlertOnly @16;
|
||||
}
|
||||
}
|
||||
|
||||
struct CarParamsSP @0x80ae746ee2596b11 {
|
||||
flags @0 :UInt32; # flags for car specific quirks in sunnypilot
|
||||
safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags
|
||||
|
||||
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
|
||||
|
||||
struct NeuralNetworkLateralControl {
|
||||
model @0 :Model;
|
||||
fuzzyFingerprint @1 :Bool;
|
||||
|
||||
struct Model {
|
||||
path @0 :Text;
|
||||
name @1 :Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CarControlSP @0xa5cd762cd951a455 {
|
||||
mads @0 :ModularAssistiveDrivingSystem;
|
||||
params @1 :List(Param);
|
||||
leadOne @2 :LeadData;
|
||||
leadTwo @3 :LeadData;
|
||||
|
||||
struct Param {
|
||||
key @0 :Text;
|
||||
value @1 :Text;
|
||||
}
|
||||
}
|
||||
|
||||
struct BackupManagerSP @0xf98d843bfd7004a3 {
|
||||
backupStatus @0 :Status;
|
||||
restoreStatus @1 :Status;
|
||||
backupProgress @2 :Float32;
|
||||
restoreProgress @3 :Float32;
|
||||
lastError @4 :Text;
|
||||
currentBackup @5 :BackupInfo;
|
||||
backupHistory @6 :List(BackupInfo);
|
||||
|
||||
enum Status {
|
||||
idle @0;
|
||||
inProgress @1;
|
||||
completed @2;
|
||||
failed @3;
|
||||
}
|
||||
|
||||
struct Version {
|
||||
major @0 :UInt16;
|
||||
minor @1 :UInt16;
|
||||
patch @2 :UInt16;
|
||||
build @3 :UInt16;
|
||||
branch @4 :Text;
|
||||
}
|
||||
|
||||
struct MetadataEntry {
|
||||
key @0 :Text;
|
||||
value @1 :Text;
|
||||
tags @2 :List(Text);
|
||||
}
|
||||
|
||||
struct BackupInfo {
|
||||
deviceId @0 :Text;
|
||||
version @1 :UInt32;
|
||||
config @2 :Text;
|
||||
isEncrypted @3 :Bool;
|
||||
createdAt @4 :Text; # ISO timestamp
|
||||
updatedAt @5 :Text; # ISO timestamp
|
||||
sunnypilotVersion @6 :Version;
|
||||
backupMetadata @7 :List(MetadataEntry);
|
||||
}
|
||||
}
|
||||
|
||||
struct CarStateSP @0xb86e6369214c01c8 {
|
||||
}
|
||||
|
||||
struct LiveMapDataSP @0xf416ec09499d9d19 {
|
||||
speedLimitValid @0 :Bool;
|
||||
speedLimit @1 :Float32;
|
||||
speedLimitAheadValid @2 :Bool;
|
||||
speedLimitAhead @3 :Float32;
|
||||
speedLimitAheadDistance @4 :Float32;
|
||||
roadName @5 :Text;
|
||||
}
|
||||
|
||||
struct CustomReserved9 @0xa1680744031fdb2d {
|
||||
}
|
||||
|
||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
||||
}
|
||||
|
||||
struct CustomReserved11 @0xc2243c65e0340384 {
|
||||
}
|
||||
|
||||
struct CustomReserved12 @0x9ccdc8676701b412 {
|
||||
}
|
||||
|
||||
struct CustomReserved13 @0xcd96dafb67a082d0 {
|
||||
}
|
||||
|
||||
struct CustomReserved14 @0xb057204d7deadf3f {
|
||||
}
|
||||
|
||||
struct CustomReserved15 @0xbd443b539493bc68 {
|
||||
}
|
||||
|
||||
struct CustomReserved16 @0xfc6241ed8877b611 {
|
||||
}
|
||||
|
||||
struct CustomReserved17 @0xa30662f84033036c {
|
||||
}
|
||||
|
||||
struct CustomReserved18 @0xc86a3d38d13eb3ef {
|
||||
}
|
||||
|
||||
struct CustomReserved19 @0xa4f1eb3323f5f582 {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2013-2015 Sandstorm Development Group, Inc. and contributors
|
||||
# Licensed under the MIT License:
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
@0xc5f1af96651f70ea;
|
||||
|
||||
annotation package @0x9ee4c8f803b3b596 (file) : Text;
|
||||
# Name of the package, such as "org.example.foo", in which the generated code will reside.
|
||||
|
||||
annotation outerClassname @0x9b066bb4881f7cd3 (file) : Text;
|
||||
# Name of the outer class that will wrap the generated code.
|
||||
@@ -1,574 +0,0 @@
|
||||
using Cxx = import "./include/c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
@0x80ef1ec4889c2a63;
|
||||
|
||||
# legacy.capnp: a home for deprecated structs
|
||||
|
||||
struct LogRotate @0x9811e1f38f62f2d1 {
|
||||
segmentNum @0 :Int32;
|
||||
path @1 :Text;
|
||||
}
|
||||
|
||||
struct LiveUI @0xc08240f996aefced {
|
||||
rearViewCam @0 :Bool;
|
||||
alertText1 @1 :Text;
|
||||
alertText2 @2 :Text;
|
||||
awarenessStatus @3 :Float32;
|
||||
}
|
||||
|
||||
struct UiLayoutState @0x88dcce08ad29dda0 {
|
||||
activeApp @0 :App;
|
||||
sidebarCollapsed @1 :Bool;
|
||||
mapEnabled @2 :Bool;
|
||||
mockEngaged @3 :Bool;
|
||||
|
||||
enum App @0x9917470acf94d285 {
|
||||
home @0;
|
||||
music @1;
|
||||
nav @2;
|
||||
settings @3;
|
||||
none @4;
|
||||
}
|
||||
}
|
||||
|
||||
struct OrbslamCorrection @0x8afd33dc9b35e1aa {
|
||||
correctionMonoTime @0 :UInt64;
|
||||
prePositionECEF @1 :List(Float64);
|
||||
postPositionECEF @2 :List(Float64);
|
||||
prePoseQuatECEF @3 :List(Float32);
|
||||
postPoseQuatECEF @4 :List(Float32);
|
||||
numInliers @5 :UInt32;
|
||||
}
|
||||
|
||||
struct EthernetPacket @0xa99a9d5b33cf5859 {
|
||||
pkt @0 :Data;
|
||||
ts @1 :Float32;
|
||||
}
|
||||
|
||||
struct CellInfo @0xcff7566681c277ce {
|
||||
timestamp @0 :UInt64;
|
||||
repr @1 :Text; # android toString() for now
|
||||
}
|
||||
|
||||
struct WifiScan @0xd4df5a192382ba0b {
|
||||
bssid @0 :Text;
|
||||
ssid @1 :Text;
|
||||
capabilities @2 :Text;
|
||||
frequency @3 :Int32;
|
||||
level @4 :Int32;
|
||||
timestamp @5 :Int64;
|
||||
|
||||
centerFreq0 @6 :Int32;
|
||||
centerFreq1 @7 :Int32;
|
||||
channelWidth @8 :ChannelWidth;
|
||||
operatorFriendlyName @9 :Text;
|
||||
venueName @10 :Text;
|
||||
is80211mcResponder @11 :Bool;
|
||||
passpoint @12 :Bool;
|
||||
|
||||
distanceCm @13 :Int32;
|
||||
distanceSdCm @14 :Int32;
|
||||
|
||||
enum ChannelWidth @0xcb6a279f015f6b51 {
|
||||
w20Mhz @0;
|
||||
w40Mhz @1;
|
||||
w80Mhz @2;
|
||||
w160Mhz @3;
|
||||
w80Plus80Mhz @4;
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveEventData @0x94b7baa90c5c321e {
|
||||
name @0 :Text;
|
||||
value @1 :Int32;
|
||||
}
|
||||
|
||||
struct ModelData @0xb8aad62cffef28a9 {
|
||||
frameId @0 :UInt32;
|
||||
frameAge @12 :UInt32;
|
||||
frameDropPerc @13 :Float32;
|
||||
timestampEof @9 :UInt64;
|
||||
modelExecutionTime @14 :Float32;
|
||||
gpuExecutionTime @16 :Float32;
|
||||
rawPred @15 :Data;
|
||||
|
||||
path @1 :PathData;
|
||||
leftLane @2 :PathData;
|
||||
rightLane @3 :PathData;
|
||||
lead @4 :LeadData;
|
||||
freePath @6 :List(Float32);
|
||||
|
||||
settings @5 :ModelSettings;
|
||||
leadFuture @7 :LeadData;
|
||||
speed @8 :List(Float32);
|
||||
meta @10 :MetaData;
|
||||
longitudinal @11 :LongitudinalData;
|
||||
|
||||
struct PathData @0x8817eeea389e9f08 {
|
||||
points @0 :List(Float32);
|
||||
prob @1 :Float32;
|
||||
std @2 :Float32;
|
||||
stds @3 :List(Float32);
|
||||
poly @4 :List(Float32);
|
||||
validLen @5 :Float32;
|
||||
}
|
||||
|
||||
struct LeadData @0xd1c9bef96d26fa91 {
|
||||
dist @0 :Float32;
|
||||
prob @1 :Float32;
|
||||
std @2 :Float32;
|
||||
relVel @3 :Float32;
|
||||
relVelStd @4 :Float32;
|
||||
relY @5 :Float32;
|
||||
relYStd @6 :Float32;
|
||||
relA @7 :Float32;
|
||||
relAStd @8 :Float32;
|
||||
}
|
||||
|
||||
struct ModelSettings @0xa26e3710efd3e914 {
|
||||
bigBoxX @0 :UInt16;
|
||||
bigBoxY @1 :UInt16;
|
||||
bigBoxWidth @2 :UInt16;
|
||||
bigBoxHeight @3 :UInt16;
|
||||
boxProjection @4 :List(Float32);
|
||||
yuvCorrection @5 :List(Float32);
|
||||
inputTransform @6 :List(Float32);
|
||||
}
|
||||
|
||||
struct MetaData @0x9744f25fb60f2bf8 {
|
||||
engagedProb @0 :Float32;
|
||||
desirePrediction @1 :List(Float32);
|
||||
brakeDisengageProb @2 :Float32;
|
||||
gasDisengageProb @3 :Float32;
|
||||
steerOverrideProb @4 :Float32;
|
||||
desireState @5 :List(Float32);
|
||||
}
|
||||
|
||||
struct LongitudinalData @0xf98f999c6a071122 {
|
||||
distances @2 :List(Float32);
|
||||
speeds @0 :List(Float32);
|
||||
accelerations @1 :List(Float32);
|
||||
}
|
||||
}
|
||||
|
||||
struct ECEFPoint @0xc25bbbd524983447 {
|
||||
x @0 :Float64;
|
||||
y @1 :Float64;
|
||||
z @2 :Float64;
|
||||
}
|
||||
|
||||
struct ECEFPointDEPRECATED @0xe10e21168db0c7f7 {
|
||||
x @0 :Float32;
|
||||
y @1 :Float32;
|
||||
z @2 :Float32;
|
||||
}
|
||||
|
||||
struct GPSPlannerPoints @0xab54c59699f8f9f3 {
|
||||
curPosDEPRECATED @0 :ECEFPointDEPRECATED;
|
||||
pointsDEPRECATED @1 :List(ECEFPointDEPRECATED);
|
||||
curPos @6 :ECEFPoint;
|
||||
points @7 :List(ECEFPoint);
|
||||
valid @2 :Bool;
|
||||
trackName @3 :Text;
|
||||
speedLimit @4 :Float32;
|
||||
accelTarget @5 :Float32;
|
||||
}
|
||||
|
||||
struct GPSPlannerPlan @0xf5ad1d90cdc1dd6b {
|
||||
valid @0 :Bool;
|
||||
poly @1 :List(Float32);
|
||||
trackName @2 :Text;
|
||||
speed @3 :Float32;
|
||||
acceleration @4 :Float32;
|
||||
pointsDEPRECATED @5 :List(ECEFPointDEPRECATED);
|
||||
points @6 :List(ECEFPoint);
|
||||
xLookahead @7 :Float32;
|
||||
}
|
||||
|
||||
struct UiNavigationEvent @0x90c8426c3eaddd3b {
|
||||
type @0: Type;
|
||||
status @1: Status;
|
||||
distanceTo @2: Float32;
|
||||
endRoadPointDEPRECATED @3: ECEFPointDEPRECATED;
|
||||
endRoadPoint @4: ECEFPoint;
|
||||
|
||||
enum Type @0xe8db07dcf8fcea05 {
|
||||
none @0;
|
||||
laneChangeLeft @1;
|
||||
laneChangeRight @2;
|
||||
mergeLeft @3;
|
||||
mergeRight @4;
|
||||
turnLeft @5;
|
||||
turnRight @6;
|
||||
}
|
||||
|
||||
enum Status @0xb9aa88c75ef99a1f {
|
||||
none @0;
|
||||
passive @1;
|
||||
approaching @2;
|
||||
active @3;
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveLocationData @0xb99b2bc7a57e8128 {
|
||||
status @0 :UInt8;
|
||||
|
||||
# 3D fix
|
||||
lat @1 :Float64;
|
||||
lon @2 :Float64;
|
||||
alt @3 :Float32; # m
|
||||
|
||||
# speed
|
||||
speed @4 :Float32; # m/s
|
||||
|
||||
# NED velocity components
|
||||
vNED @5 :List(Float32);
|
||||
|
||||
# roll, pitch, heading (x,y,z)
|
||||
roll @6 :Float32; # WRT to center of earth?
|
||||
pitch @7 :Float32; # WRT to center of earth?
|
||||
heading @8 :Float32; # WRT to north?
|
||||
|
||||
# what are these?
|
||||
wanderAngle @9 :Float32;
|
||||
trackAngle @10 :Float32;
|
||||
|
||||
# car frame -- https://upload.wikimedia.org/wikipedia/commons/f/f5/RPY_angles_of_cars.png
|
||||
|
||||
# gyro, in car frame, deg/s
|
||||
gyro @11 :List(Float32);
|
||||
|
||||
# accel, in car frame, m/s^2
|
||||
accel @12 :List(Float32);
|
||||
|
||||
accuracy @13 :Accuracy;
|
||||
|
||||
source @14 :SensorSource;
|
||||
# if we are fixing a location in the past
|
||||
fixMonoTime @15 :UInt64;
|
||||
|
||||
gpsWeek @16 :Int32;
|
||||
timeOfWeek @17 :Float64;
|
||||
|
||||
positionECEF @18 :List(Float64);
|
||||
poseQuatECEF @19 :List(Float32);
|
||||
pitchCalibration @20 :Float32;
|
||||
yawCalibration @21 :Float32;
|
||||
imuFrame @22 :List(Float32);
|
||||
|
||||
struct Accuracy @0x943dc4625473b03f {
|
||||
pNEDError @0 :List(Float32);
|
||||
vNEDError @1 :List(Float32);
|
||||
rollError @2 :Float32;
|
||||
pitchError @3 :Float32;
|
||||
headingError @4 :Float32;
|
||||
ellipsoidSemiMajorError @5 :Float32;
|
||||
ellipsoidSemiMinorError @6 :Float32;
|
||||
ellipsoidOrientationError @7 :Float32;
|
||||
}
|
||||
|
||||
enum SensorSource @0xc871d3cc252af657 {
|
||||
applanix @0;
|
||||
kalman @1;
|
||||
orbslam @2;
|
||||
timing @3;
|
||||
dummy @4;
|
||||
}
|
||||
}
|
||||
|
||||
struct OrbOdometry @0xd7700859ed1f5b76 {
|
||||
# timing first
|
||||
startMonoTime @0 :UInt64;
|
||||
endMonoTime @1 :UInt64;
|
||||
|
||||
# fundamental matrix and error
|
||||
f @2: List(Float64);
|
||||
err @3: Float64;
|
||||
|
||||
# number of inlier points
|
||||
inliers @4: Int32;
|
||||
|
||||
# for debug only
|
||||
# indexed by endMonoTime features
|
||||
# value is startMonoTime feature match
|
||||
# -1 if no match
|
||||
matches @5: List(Int16);
|
||||
}
|
||||
|
||||
struct OrbFeatures @0xcd60164a8a0159ef {
|
||||
timestampEof @0 :UInt64;
|
||||
# transposed arrays of normalized image coordinates
|
||||
# len(xs) == len(ys) == len(descriptors) * 32
|
||||
xs @1 :List(Float32);
|
||||
ys @2 :List(Float32);
|
||||
descriptors @3 :Data;
|
||||
octaves @4 :List(Int8);
|
||||
|
||||
# match index to last OrbFeatures
|
||||
# -1 if no match
|
||||
timestampLastEof @5 :UInt64;
|
||||
matches @6: List(Int16);
|
||||
}
|
||||
|
||||
struct OrbFeaturesSummary @0xd500d30c5803fa4f {
|
||||
timestampEof @0 :UInt64;
|
||||
timestampLastEof @1 :UInt64;
|
||||
|
||||
featureCount @2 :UInt16;
|
||||
matchCount @3 :UInt16;
|
||||
computeNs @4 :UInt64;
|
||||
}
|
||||
|
||||
struct OrbKeyFrame @0xc8233c0345e27e24 {
|
||||
# this is a globally unique id for the KeyFrame
|
||||
id @0: UInt64;
|
||||
|
||||
# this is the location of the KeyFrame
|
||||
pos @1: ECEFPoint;
|
||||
|
||||
# these are the features in the world
|
||||
# len(dpos) == len(descriptors) * 32
|
||||
dpos @2 :List(ECEFPoint);
|
||||
descriptors @3 :Data;
|
||||
}
|
||||
|
||||
struct KalmanOdometry @0x92e21bb7ea38793a {
|
||||
trans @0 :List(Float32); # m/s in device frame
|
||||
rot @1 :List(Float32); # rad/s in device frame
|
||||
transStd @2 :List(Float32); # std m/s in device frame
|
||||
rotStd @3 :List(Float32); # std rad/s in device frame
|
||||
}
|
||||
|
||||
struct OrbObservation @0x9b326d4e436afec7 {
|
||||
observationMonoTime @0 :UInt64;
|
||||
normalizedCoordinates @1 :List(Float32);
|
||||
locationECEF @2 :List(Float64);
|
||||
matchDistance @3: UInt32;
|
||||
}
|
||||
|
||||
struct CalibrationFeatures @0x8fdfadb254ea867a {
|
||||
frameId @0 :UInt32;
|
||||
|
||||
p0 @1 :List(Float32);
|
||||
p1 @2 :List(Float32);
|
||||
status @3 :List(Int8);
|
||||
}
|
||||
|
||||
struct NavStatus @0xbd8822120928120c {
|
||||
isNavigating @0 :Bool;
|
||||
currentAddress @1 :Address;
|
||||
|
||||
struct Address @0xce7cd672cacc7814 {
|
||||
title @0 :Text;
|
||||
lat @1 :Float64;
|
||||
lng @2 :Float64;
|
||||
house @3 :Text;
|
||||
address @4 :Text;
|
||||
street @5 :Text;
|
||||
city @6 :Text;
|
||||
state @7 :Text;
|
||||
country @8 :Text;
|
||||
}
|
||||
}
|
||||
|
||||
struct NavUpdate @0xdb98be6565516acb {
|
||||
isNavigating @0 :Bool;
|
||||
curSegment @1 :Int32;
|
||||
segments @2 :List(Segment);
|
||||
|
||||
struct LatLng @0x9eaef9187cadbb9b {
|
||||
lat @0 :Float64;
|
||||
lng @1 :Float64;
|
||||
}
|
||||
|
||||
struct Segment @0xa5b39b4fc4d7da3f {
|
||||
from @0 :LatLng;
|
||||
to @1 :LatLng;
|
||||
updateTime @2 :Int32;
|
||||
distance @3 :Int32;
|
||||
crossTime @4 :Int32;
|
||||
exitNo @5 :Int32;
|
||||
instruction @6 :Instruction;
|
||||
|
||||
parts @7 :List(LatLng);
|
||||
|
||||
enum Instruction @0xc5417a637451246f {
|
||||
turnLeft @0;
|
||||
turnRight @1;
|
||||
keepLeft @2;
|
||||
keepRight @3;
|
||||
straight @4;
|
||||
roundaboutExitNumber @5;
|
||||
roundaboutExit @6;
|
||||
roundaboutTurnLeft @7;
|
||||
unkn8 @8;
|
||||
roundaboutStraight @9;
|
||||
unkn10 @10;
|
||||
roundaboutTurnRight @11;
|
||||
unkn12 @12;
|
||||
roundaboutUturn @13;
|
||||
unkn14 @14;
|
||||
arrive @15;
|
||||
exitLeft @16;
|
||||
exitRight @17;
|
||||
unkn18 @18;
|
||||
uturn @19;
|
||||
# ...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TrafficEvent @0xacfa74a094e62626 {
|
||||
type @0 :Type;
|
||||
distance @1 :Float32;
|
||||
action @2 :Action;
|
||||
resuming @3 :Bool;
|
||||
|
||||
enum Type @0xd85d75253435bf4b {
|
||||
stopSign @0;
|
||||
lightRed @1;
|
||||
lightYellow @2;
|
||||
lightGreen @3;
|
||||
stopLight @4;
|
||||
}
|
||||
|
||||
enum Action @0xa6f6ce72165ccb49 {
|
||||
none @0;
|
||||
yield @1;
|
||||
stop @2;
|
||||
resumeReady @3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct AndroidGnss @0xdfdf30d03fc485bd {
|
||||
union {
|
||||
measurements @0 :Measurements;
|
||||
navigationMessage @1 :NavigationMessage;
|
||||
}
|
||||
|
||||
struct Measurements @0xa20710d4f428d6cd {
|
||||
clock @0 :Clock;
|
||||
measurements @1 :List(Measurement);
|
||||
|
||||
struct Clock @0xa0e27b453a38f450 {
|
||||
timeNanos @0 :Int64;
|
||||
hardwareClockDiscontinuityCount @1 :Int32;
|
||||
|
||||
hasTimeUncertaintyNanos @2 :Bool;
|
||||
timeUncertaintyNanos @3 :Float64;
|
||||
|
||||
hasLeapSecond @4 :Bool;
|
||||
leapSecond @5 :Int32;
|
||||
|
||||
hasFullBiasNanos @6 :Bool;
|
||||
fullBiasNanos @7 :Int64;
|
||||
|
||||
hasBiasNanos @8 :Bool;
|
||||
biasNanos @9 :Float64;
|
||||
|
||||
hasBiasUncertaintyNanos @10 :Bool;
|
||||
biasUncertaintyNanos @11 :Float64;
|
||||
|
||||
hasDriftNanosPerSecond @12 :Bool;
|
||||
driftNanosPerSecond @13 :Float64;
|
||||
|
||||
hasDriftUncertaintyNanosPerSecond @14 :Bool;
|
||||
driftUncertaintyNanosPerSecond @15 :Float64;
|
||||
}
|
||||
|
||||
struct Measurement @0xd949bf717d77614d {
|
||||
svId @0 :Int32;
|
||||
constellation @1 :Constellation;
|
||||
|
||||
timeOffsetNanos @2 :Float64;
|
||||
state @3 :Int32;
|
||||
receivedSvTimeNanos @4 :Int64;
|
||||
receivedSvTimeUncertaintyNanos @5 :Int64;
|
||||
cn0DbHz @6 :Float64;
|
||||
pseudorangeRateMetersPerSecond @7 :Float64;
|
||||
pseudorangeRateUncertaintyMetersPerSecond @8 :Float64;
|
||||
accumulatedDeltaRangeState @9 :Int32;
|
||||
accumulatedDeltaRangeMeters @10 :Float64;
|
||||
accumulatedDeltaRangeUncertaintyMeters @11 :Float64;
|
||||
|
||||
hasCarrierFrequencyHz @12 :Bool;
|
||||
carrierFrequencyHz @13 :Float32;
|
||||
hasCarrierCycles @14 :Bool;
|
||||
carrierCycles @15 :Int64;
|
||||
hasCarrierPhase @16 :Bool;
|
||||
carrierPhase @17 :Float64;
|
||||
hasCarrierPhaseUncertainty @18 :Bool;
|
||||
carrierPhaseUncertainty @19 :Float64;
|
||||
hasSnrInDb @20 :Bool;
|
||||
snrInDb @21 :Float64;
|
||||
|
||||
multipathIndicator @22 :MultipathIndicator;
|
||||
|
||||
enum Constellation @0x9ef1f3ff0deb5ffb {
|
||||
unknown @0;
|
||||
gps @1;
|
||||
sbas @2;
|
||||
glonass @3;
|
||||
qzss @4;
|
||||
beidou @5;
|
||||
galileo @6;
|
||||
}
|
||||
|
||||
enum State @0xcbb9490adce12d72 {
|
||||
unknown @0;
|
||||
codeLock @1;
|
||||
bitSync @2;
|
||||
subframeSync @3;
|
||||
towDecoded @4;
|
||||
msecAmbiguous @5;
|
||||
symbolSync @6;
|
||||
gloStringSync @7;
|
||||
gloTodDecoded @8;
|
||||
bdsD2BitSync @9;
|
||||
bdsD2SubframeSync @10;
|
||||
galE1bcCodeLock @11;
|
||||
galE1c2ndCodeLock @12;
|
||||
galE1bPageSync @13;
|
||||
sbasSync @14;
|
||||
}
|
||||
|
||||
enum MultipathIndicator @0xc04e7b6231d4caa8 {
|
||||
unknown @0;
|
||||
detected @1;
|
||||
notDetected @2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NavigationMessage @0xe2517b083095fd4e {
|
||||
type @0 :Int32;
|
||||
svId @1 :Int32;
|
||||
messageId @2 :Int32;
|
||||
submessageId @3 :Int32;
|
||||
data @4 :Data;
|
||||
status @5 :Status;
|
||||
|
||||
enum Status @0xec1ff7996b35366f {
|
||||
unknown @0;
|
||||
parityPassed @1;
|
||||
parityRebuilt @2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LidarPts @0xe3d6685d4e9d8f7a {
|
||||
r @0 :List(UInt16); # uint16 m*500.0
|
||||
theta @1 :List(UInt16); # uint16 deg*100.0
|
||||
reflect @2 :List(UInt8); # uint8 0-255
|
||||
|
||||
# For storing out of file.
|
||||
idx @3 :UInt64;
|
||||
|
||||
# For storing in file
|
||||
pkt @4 :Data;
|
||||
}
|
||||
|
||||
|
||||
+598
-2205
File diff suppressed because it is too large
Load Diff
@@ -1,257 +0,0 @@
|
||||
# must be built with scons
|
||||
from msgq.ipc_pyx import Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \
|
||||
set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event
|
||||
from msgq.ipc_pyx import MultiplePublishersError, IpcError
|
||||
from msgq import fake_event_handle, pub_sock, sub_sock, drain_sock_raw
|
||||
import msgq
|
||||
|
||||
import os
|
||||
import capnp
|
||||
import time
|
||||
|
||||
from typing import Optional, List, Union, Dict
|
||||
|
||||
from cereal import log
|
||||
from cereal.services import SERVICE_LIST
|
||||
from openpilot.common.util import MovingAverage
|
||||
|
||||
NO_TRAVERSAL_LIMIT = 2**64-1
|
||||
|
||||
|
||||
def reset_context():
|
||||
msgq.context = Context()
|
||||
|
||||
|
||||
def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event) -> capnp.lib.capnp._DynamicStructReader:
|
||||
with struct.from_bytes(dat, traversal_limit_in_words=NO_TRAVERSAL_LIMIT) as msg:
|
||||
return msg
|
||||
|
||||
|
||||
def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||
args = {
|
||||
'valid': False,
|
||||
'logMonoTime': int(time.monotonic() * 1e9),
|
||||
**kwargs
|
||||
}
|
||||
dat = log.Event.new_message(**args)
|
||||
if service is not None:
|
||||
if size is None:
|
||||
dat.init(service)
|
||||
else:
|
||||
dat.init(service, size)
|
||||
return dat
|
||||
|
||||
|
||||
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]:
|
||||
"""Receive all message currently available on the queue"""
|
||||
msgs = drain_sock_raw(sock, wait_for_one=wait_for_one)
|
||||
return [log_from_bytes(m) for m in msgs]
|
||||
|
||||
|
||||
# TODO: print when we drop packets?
|
||||
def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
||||
"""Same as drain sock, but only returns latest message. Consider using conflate instead."""
|
||||
dat = None
|
||||
|
||||
while 1:
|
||||
if wait and dat is None:
|
||||
recv = sock.receive()
|
||||
else:
|
||||
recv = sock.receive(non_blocking=True)
|
||||
|
||||
if recv is None: # Timeout hit
|
||||
break
|
||||
|
||||
dat = recv
|
||||
|
||||
if dat is not None:
|
||||
dat = log_from_bytes(dat)
|
||||
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
||||
dat = sock.receive()
|
||||
if dat is not None:
|
||||
dat = log_from_bytes(dat)
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one_or_none(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
if dat is not None:
|
||||
dat = log_from_bytes(dat)
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one_retry(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader:
|
||||
"""Keep receiving until we get a message"""
|
||||
while True:
|
||||
dat = sock.receive()
|
||||
if dat is not None:
|
||||
return log_from_bytes(dat)
|
||||
|
||||
|
||||
class FrequencyTracker:
|
||||
def __init__(self, service_freq: float, update_freq: float, is_poll: bool):
|
||||
freq = max(min(service_freq, update_freq), 1.)
|
||||
if is_poll:
|
||||
min_freq = max_freq = freq
|
||||
else:
|
||||
max_freq = min(freq, update_freq)
|
||||
if service_freq >= 2 * update_freq:
|
||||
min_freq = update_freq
|
||||
elif update_freq >= 2* service_freq:
|
||||
min_freq = freq
|
||||
else:
|
||||
min_freq = min(freq, freq / 2.)
|
||||
|
||||
self.min_freq = min_freq * 0.8
|
||||
self.max_freq = max_freq * 1.2
|
||||
self.avg_dt = MovingAverage(int(10 * freq))
|
||||
self.recent_avg_dt = MovingAverage(int(freq))
|
||||
self.prev_time = 0.0
|
||||
|
||||
def record_recv_time(self, cur_time: float) -> None:
|
||||
# TODO: Handle case where cur_time is less than prev_time
|
||||
if self.prev_time > 1e-5:
|
||||
dt = cur_time - self.prev_time
|
||||
|
||||
self.avg_dt.add_value(dt)
|
||||
self.recent_avg_dt.add_value(dt)
|
||||
|
||||
self.prev_time = cur_time
|
||||
|
||||
@property
|
||||
def valid(self) -> bool:
|
||||
if self.avg_dt.count == 0:
|
||||
return False
|
||||
|
||||
avg_freq = 1.0 / self.avg_dt.get_average()
|
||||
if self.min_freq <= avg_freq <= self.max_freq:
|
||||
return True
|
||||
|
||||
avg_freq_recent = 1.0 / self.recent_avg_dt.get_average()
|
||||
return self.min_freq <= avg_freq_recent <= self.max_freq
|
||||
|
||||
|
||||
class SubMaster:
|
||||
def __init__(self, services: List[str], poll: Optional[str] = None,
|
||||
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
|
||||
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
|
||||
self.frame = -1
|
||||
self.services = services
|
||||
self.seen = {s: False for s in services}
|
||||
self.updated = {s: False for s in services}
|
||||
self.recv_time = {s: 0. for s in services}
|
||||
self.recv_frame = {s: 0 for s in services}
|
||||
self.sock = {}
|
||||
self.data = {}
|
||||
self.logMonoTime = {s: 0 for s in services}
|
||||
|
||||
# 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}
|
||||
self.static_freq_services = set(s for s in services if not on_demand[s])
|
||||
self.alive = {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.freq_tracker: Dict[str, FrequencyTracker] = {}
|
||||
self.poller = Poller()
|
||||
polled_services = set([poll, ] if poll is not None else services)
|
||||
self.non_polled_services = set(services) - polled_services
|
||||
|
||||
self.ignore_average_freq = [] if ignore_avg_freq is None else ignore_avg_freq
|
||||
self.ignore_alive = [] if ignore_alive is None else ignore_alive
|
||||
self.ignore_valid = [] if ignore_valid is None else ignore_valid
|
||||
|
||||
self.simulation = bool(int(os.getenv("SIMULATION", "0")))
|
||||
|
||||
# if freq and poll aren't specified, assume the max to be conservative
|
||||
assert frequency is None or poll is None, "Do not specify 'frequency' - frequency of the polled service will be used."
|
||||
self.update_freq = frequency or max([SERVICE_LIST[s].frequency for s in polled_services])
|
||||
|
||||
for s in services:
|
||||
p = self.poller if s not in self.non_polled_services else None
|
||||
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=True)
|
||||
|
||||
try:
|
||||
data = new_message(s)
|
||||
except capnp.lib.capnp.KjException:
|
||||
data = new_message(s, 0) # lists
|
||||
|
||||
self.data[s] = getattr(data.as_reader(), s)
|
||||
self.freq_tracker[s] = FrequencyTracker(SERVICE_LIST[s].frequency, self.update_freq, s == poll)
|
||||
|
||||
def __getitem__(self, s: str) -> capnp.lib.capnp._DynamicStructReader:
|
||||
return self.data[s]
|
||||
|
||||
def _check_avg_freq(self, s: str) -> bool:
|
||||
return SERVICE_LIST[s].frequency > 0.99 and (s not in self.ignore_average_freq) and (s not in self.ignore_alive)
|
||||
|
||||
def update(self, timeout: int = 100) -> None:
|
||||
msgs = []
|
||||
for sock in self.poller.poll(timeout):
|
||||
msgs.append(recv_one_or_none(sock))
|
||||
|
||||
# non-blocking receive for non-polled sockets
|
||||
for s in self.non_polled_services:
|
||||
msgs.append(recv_one_or_none(self.sock[s]))
|
||||
self.update_msgs(time.monotonic(), msgs)
|
||||
|
||||
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
|
||||
self.frame += 1
|
||||
self.updated = dict.fromkeys(self.services, False)
|
||||
for msg in msgs:
|
||||
if msg is None:
|
||||
continue
|
||||
|
||||
s = msg.which()
|
||||
self.seen[s] = True
|
||||
self.updated[s] = True
|
||||
|
||||
self.freq_tracker[s].record_recv_time(cur_time)
|
||||
self.recv_time[s] = cur_time
|
||||
self.recv_frame[s] = self.frame
|
||||
self.data[s] = getattr(msg, s)
|
||||
self.logMonoTime[s] = msg.logMonoTime
|
||||
self.valid[s] = msg.valid
|
||||
|
||||
for s in self.static_freq_services:
|
||||
# alive if delay is within 10x the expected frequency; checks relaxed in simulator
|
||||
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
|
||||
|
||||
def all_alive(self, service_list: Optional[List[str]] = None) -> bool:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list)
|
||||
|
||||
|
||||
class PubMaster:
|
||||
def __init__(self, services: List[str]):
|
||||
self.sock = {}
|
||||
for s in services:
|
||||
self.sock[s] = pub_sock(s)
|
||||
|
||||
def send(self, s: str, dat: Union[bytes, capnp.lib.capnp._DynamicStructBuilder]) -> None:
|
||||
if not isinstance(dat, bytes):
|
||||
dat = dat.to_bytes()
|
||||
self.sock[s].send(dat)
|
||||
|
||||
def wait_for_readers_to_update(self, s: str, timeout: int, dt: float = 0.05) -> bool:
|
||||
for _ in range(int(timeout*(1./dt))):
|
||||
if self.sock[s].all_readers_updated():
|
||||
return True
|
||||
time.sleep(dt)
|
||||
return False
|
||||
|
||||
def all_readers_updated(self, s: str) -> bool:
|
||||
return self.sock[s].all_readers_updated() # type: ignore
|
||||
@@ -1,71 +0,0 @@
|
||||
#include <cassert>
|
||||
|
||||
#include "cereal/messaging/msgq_to_zmq.h"
|
||||
#include "cereal/services.h"
|
||||
#include "common/util.h"
|
||||
|
||||
ExitHandler do_exit;
|
||||
|
||||
static std::vector<std::string> get_services(const std::string &whitelist_str, bool zmq_to_msgq) {
|
||||
std::vector<std::string> service_list;
|
||||
for (const auto& it : services) {
|
||||
std::string name = it.second.name;
|
||||
bool in_whitelist = whitelist_str.find(name) != std::string::npos;
|
||||
if (zmq_to_msgq && !in_whitelist) {
|
||||
continue;
|
||||
}
|
||||
service_list.push_back(name);
|
||||
}
|
||||
return service_list;
|
||||
}
|
||||
|
||||
void msgq_to_zmq(const std::vector<std::string> &endpoints, const std::string &ip) {
|
||||
MsgqToZmq bridge;
|
||||
bridge.run(endpoints, ip);
|
||||
}
|
||||
|
||||
void zmq_to_msgq(const std::vector<std::string> &endpoints, const std::string &ip) {
|
||||
auto poller = std::make_unique<ZMQPoller>();
|
||||
auto pub_context = std::make_unique<MSGQContext>();
|
||||
auto sub_context = std::make_unique<ZMQContext>();
|
||||
std::map<SubSocket *, PubSocket *> sub2pub;
|
||||
|
||||
for (auto endpoint : endpoints) {
|
||||
auto pub_sock = new MSGQPubSocket();
|
||||
auto sub_sock = new ZMQSubSocket();
|
||||
pub_sock->connect(pub_context.get(), endpoint);
|
||||
sub_sock->connect(sub_context.get(), endpoint, ip, false);
|
||||
|
||||
poller->registerSocket(sub_sock);
|
||||
sub2pub[sub_sock] = pub_sock;
|
||||
}
|
||||
|
||||
while (!do_exit) {
|
||||
for (auto sub_sock : poller->poll(100)) {
|
||||
std::unique_ptr<Message> msg(sub_sock->receive(true));
|
||||
if (msg) {
|
||||
sub2pub[sub_sock]->sendMessage(msg.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up allocated sockets
|
||||
for (auto &[sub_sock, pub_sock] : sub2pub) {
|
||||
delete sub_sock;
|
||||
delete pub_sock;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
bool is_zmq_to_msgq = argc > 2;
|
||||
std::string ip = is_zmq_to_msgq ? argv[1] : "127.0.0.1";
|
||||
std::string whitelist_str = is_zmq_to_msgq ? std::string(argv[2]) : "";
|
||||
std::vector<std::string> endpoints = get_services(whitelist_str, is_zmq_to_msgq);
|
||||
|
||||
if (is_zmq_to_msgq) {
|
||||
zmq_to_msgq(endpoints, ip);
|
||||
} else {
|
||||
msgq_to_zmq(endpoints, ip);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include <capnp/serialize.h>
|
||||
|
||||
#include "cereal/gen/cpp/log.capnp.h"
|
||||
#include "common/timing.h"
|
||||
#include "msgq/ipc.h"
|
||||
|
||||
class SubMaster {
|
||||
public:
|
||||
SubMaster(const std::vector<const char *> &service_list, const std::vector<const char *> &poll = {},
|
||||
const char *address = nullptr, const std::vector<const char *> &ignore_alive = {});
|
||||
void update(int timeout = 1000);
|
||||
void update_msgs(uint64_t current_time, const std::vector<std::pair<std::string, cereal::Event::Reader>> &messages);
|
||||
inline bool allAlive(const std::vector<const char *> &service_list = {}) { return all_(service_list, false, true); }
|
||||
inline bool allValid(const std::vector<const char *> &service_list = {}) { return all_(service_list, true, false); }
|
||||
inline bool allAliveAndValid(const std::vector<const char *> &service_list = {}) { return all_(service_list, true, true); }
|
||||
void drain();
|
||||
~SubMaster();
|
||||
|
||||
uint64_t frame = 0;
|
||||
bool updated(const char *name) const;
|
||||
bool alive(const char *name) const;
|
||||
bool valid(const char *name) const;
|
||||
uint64_t rcv_frame(const char *name) const;
|
||||
uint64_t rcv_time(const char *name) const;
|
||||
cereal::Event::Reader &operator[](const char *name) const;
|
||||
|
||||
private:
|
||||
bool all_(const std::vector<const char *> &service_list, bool valid, bool alive);
|
||||
Poller *poller_ = nullptr;
|
||||
struct SubMessage;
|
||||
std::map<SubSocket *, SubMessage *> messages_;
|
||||
std::map<std::string, SubMessage *> services_;
|
||||
};
|
||||
|
||||
class MessageBuilder : public capnp::MallocMessageBuilder {
|
||||
public:
|
||||
MessageBuilder() = default;
|
||||
|
||||
cereal::Event::Builder initEvent(bool valid = true) {
|
||||
cereal::Event::Builder event = initRoot<cereal::Event>();
|
||||
event.setLogMonoTime(nanos_since_boot());
|
||||
event.setValid(valid);
|
||||
return event;
|
||||
}
|
||||
|
||||
kj::ArrayPtr<capnp::byte> toBytes() {
|
||||
heapArray_ = capnp::messageToFlatArray(*this);
|
||||
return heapArray_.asBytes();
|
||||
}
|
||||
|
||||
size_t getSerializedSize() {
|
||||
return capnp::computeSerializedSizeInWords(*this) * sizeof(capnp::word);
|
||||
}
|
||||
|
||||
int serializeToBuffer(unsigned char *buffer, size_t buffer_size) {
|
||||
size_t serialized_size = getSerializedSize();
|
||||
if (serialized_size > buffer_size) { return -1; }
|
||||
kj::ArrayOutputStream out(kj::ArrayPtr<capnp::byte>(buffer, buffer_size));
|
||||
capnp::writeMessage(out, *this);
|
||||
return serialized_size;
|
||||
}
|
||||
|
||||
private:
|
||||
kj::Array<capnp::word> heapArray_;
|
||||
};
|
||||
|
||||
class PubMaster {
|
||||
public:
|
||||
PubMaster(const std::vector<const char *> &service_list);
|
||||
inline int send(const char *name, capnp::byte *data, size_t size) { return sockets_.at(name)->send((char *)data, size); }
|
||||
int send(const char *name, MessageBuilder &msg);
|
||||
~PubMaster();
|
||||
|
||||
private:
|
||||
std::map<std::string, PubSocket *> sockets_;
|
||||
};
|
||||
|
||||
class AlignedBuffer {
|
||||
public:
|
||||
kj::ArrayPtr<const capnp::word> align(const char *data, const size_t size) {
|
||||
words_size = size / sizeof(capnp::word) + 1;
|
||||
if (aligned_buf.size() < words_size) {
|
||||
aligned_buf = kj::heapArray<capnp::word>(words_size < 512 ? 512 : words_size);
|
||||
}
|
||||
memcpy(aligned_buf.begin(), data, size);
|
||||
return aligned_buf.slice(0, words_size);
|
||||
}
|
||||
inline kj::ArrayPtr<const capnp::word> align(Message *m) {
|
||||
return align(m->getData(), m->getSize());
|
||||
}
|
||||
private:
|
||||
kj::Array<capnp::word> aligned_buf;
|
||||
size_t words_size;
|
||||
};
|
||||
@@ -1,144 +0,0 @@
|
||||
#include "cereal/messaging/msgq_to_zmq.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "common/util.h"
|
||||
|
||||
extern ExitHandler do_exit;
|
||||
|
||||
// Max messages to process per socket per poll
|
||||
constexpr int MAX_MESSAGES_PER_SOCKET = 50;
|
||||
|
||||
static std::string recv_zmq_msg(void *sock) {
|
||||
zmq_msg_t msg;
|
||||
zmq_msg_init(&msg);
|
||||
std::string ret;
|
||||
if (zmq_msg_recv(&msg, sock, 0) > 0) {
|
||||
ret.assign((char *)zmq_msg_data(&msg), zmq_msg_size(&msg));
|
||||
}
|
||||
zmq_msg_close(&msg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MsgqToZmq::run(const std::vector<std::string> &endpoints, const std::string &ip) {
|
||||
zmq_context = std::make_unique<ZMQContext>();
|
||||
msgq_context = std::make_unique<MSGQContext>();
|
||||
|
||||
// Create ZMQPubSockets for each endpoint
|
||||
for (const auto &endpoint : endpoints) {
|
||||
auto &socket_pair = socket_pairs.emplace_back();
|
||||
socket_pair.endpoint = endpoint;
|
||||
socket_pair.pub_sock = std::make_unique<ZMQPubSocket>();
|
||||
int ret = socket_pair.pub_sock->connect(zmq_context.get(), endpoint);
|
||||
if (ret != 0) {
|
||||
printf("Failed to create ZMQ publisher for [%s]: %s\n", endpoint.c_str(), zmq_strerror(zmq_errno()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Start ZMQ monitoring thread to monitor socket events
|
||||
std::thread thread(&MsgqToZmq::zmqMonitorThread, this);
|
||||
|
||||
// Main loop for processing messages
|
||||
while (!do_exit) {
|
||||
{
|
||||
std::unique_lock lk(mutex);
|
||||
cv.wait(lk, [this]() { return do_exit || !sub2pub.empty(); });
|
||||
if (do_exit) break;
|
||||
|
||||
for (auto sub_sock : msgq_poller->poll(100)) {
|
||||
// Process messages for each socket
|
||||
ZMQPubSocket *pub_sock = sub2pub.at(sub_sock);
|
||||
for (int i = 0; i < MAX_MESSAGES_PER_SOCKET; ++i) {
|
||||
auto msg = std::unique_ptr<Message>(sub_sock->receive(true));
|
||||
if (!msg) break;
|
||||
|
||||
while (pub_sock->sendMessage(msg.get()) == -1) {
|
||||
if (errno != EINTR) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
util::sleep_for(1); // Give zmqMonitorThread a chance to acquire the mutex
|
||||
}
|
||||
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void MsgqToZmq::zmqMonitorThread() {
|
||||
std::vector<zmq_pollitem_t> pollitems;
|
||||
|
||||
// Set up ZMQ monitor for each pub socket
|
||||
for (int i = 0; i < socket_pairs.size(); ++i) {
|
||||
std::string addr = "inproc://op-bridge-monitor-" + std::to_string(i);
|
||||
zmq_socket_monitor(socket_pairs[i].pub_sock->sock, addr.c_str(), ZMQ_EVENT_ACCEPTED | ZMQ_EVENT_DISCONNECTED);
|
||||
|
||||
void *monitor_socket = zmq_socket(zmq_context->getRawContext(), ZMQ_PAIR);
|
||||
zmq_connect(monitor_socket, addr.c_str());
|
||||
pollitems.emplace_back(zmq_pollitem_t{.socket = monitor_socket, .events = ZMQ_POLLIN});
|
||||
}
|
||||
|
||||
while (!do_exit) {
|
||||
int ret = zmq_poll(pollitems.data(), pollitems.size(), 1000);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR) {
|
||||
// Due to frequent EINTR signals from msgq, introduce a brief delay (200 ms)
|
||||
// to reduce CPU usage during retry attempts.
|
||||
util::sleep_for(200);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < pollitems.size(); ++i) {
|
||||
if (pollitems[i].revents & ZMQ_POLLIN) {
|
||||
// First frame in message contains event number and value
|
||||
std::string frame = recv_zmq_msg(pollitems[i].socket);
|
||||
if (frame.empty()) continue;
|
||||
|
||||
uint16_t event_type = *(uint16_t *)(frame.data());
|
||||
|
||||
// Second frame in message contains event address
|
||||
frame = recv_zmq_msg(pollitems[i].socket);
|
||||
if (frame.empty()) continue;
|
||||
|
||||
std::unique_lock lk(mutex);
|
||||
auto &pair = socket_pairs[i];
|
||||
if (event_type & ZMQ_EVENT_ACCEPTED) {
|
||||
printf("socket [%s] connected\n", pair.endpoint.c_str());
|
||||
if (++pair.connected_clients == 1) {
|
||||
// Create new MSGQ subscriber socket and map to ZMQ publisher
|
||||
pair.sub_sock = std::make_unique<MSGQSubSocket>();
|
||||
pair.sub_sock->connect(msgq_context.get(), pair.endpoint, "127.0.0.1");
|
||||
sub2pub[pair.sub_sock.get()] = pair.pub_sock.get();
|
||||
registerSockets();
|
||||
}
|
||||
} else if (event_type & ZMQ_EVENT_DISCONNECTED) {
|
||||
printf("socket [%s] disconnected\n", pair.endpoint.c_str());
|
||||
if (pair.connected_clients == 0 || --pair.connected_clients == 0) {
|
||||
// Remove MSGQ subscriber socket from mapping and reset it
|
||||
sub2pub.erase(pair.sub_sock.get());
|
||||
pair.sub_sock.reset(nullptr);
|
||||
registerSockets();
|
||||
}
|
||||
}
|
||||
cv.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up monitor sockets
|
||||
for (int i = 0; i < pollitems.size(); ++i) {
|
||||
zmq_socket_monitor(socket_pairs[i].pub_sock->sock, nullptr, 0);
|
||||
zmq_close(pollitems[i].socket);
|
||||
}
|
||||
cv.notify_one();
|
||||
}
|
||||
|
||||
void MsgqToZmq::registerSockets() {
|
||||
msgq_poller = std::make_unique<MSGQPoller>();
|
||||
for (const auto &socket_pair : socket_pairs) {
|
||||
if (socket_pair.sub_sock) {
|
||||
msgq_poller->registerSocket(socket_pair.sub_sock.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define private public
|
||||
#include "msgq/impl_msgq.h"
|
||||
#include "msgq/impl_zmq.h"
|
||||
|
||||
class MsgqToZmq {
|
||||
public:
|
||||
MsgqToZmq() {}
|
||||
void run(const std::vector<std::string> &endpoints, const std::string &ip);
|
||||
|
||||
protected:
|
||||
void registerSockets();
|
||||
void zmqMonitorThread();
|
||||
|
||||
struct SocketPair {
|
||||
std::string endpoint;
|
||||
std::unique_ptr<ZMQPubSocket> pub_sock;
|
||||
std::unique_ptr<MSGQSubSocket> sub_sock;
|
||||
int connected_clients = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<MSGQContext> msgq_context;
|
||||
std::unique_ptr<ZMQContext> zmq_context;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
std::unique_ptr<MSGQPoller> msgq_poller;
|
||||
std::map<SubSocket *, ZMQPubSocket *> sub2pub;
|
||||
std::vector<SocketPair> socket_pairs;
|
||||
};
|
||||
@@ -1,203 +0,0 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
#include "cereal/services.h"
|
||||
#include "cereal/messaging/messaging.h"
|
||||
|
||||
const bool SIMULATION = (getenv("SIMULATION") != nullptr) && (std::string(getenv("SIMULATION")) == "1");
|
||||
|
||||
static inline bool inList(const std::vector<const char *> &list, const char *value) {
|
||||
for (auto &v : list) {
|
||||
if (strcmp(value, v) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class MessageContext {
|
||||
public:
|
||||
MessageContext() : ctx_(nullptr) {}
|
||||
~MessageContext() { delete ctx_; }
|
||||
inline Context *context() {
|
||||
std::call_once(init_flag, [=]() { ctx_ = Context::create(); });
|
||||
return ctx_;
|
||||
}
|
||||
private:
|
||||
Context *ctx_;
|
||||
std::once_flag init_flag;
|
||||
};
|
||||
|
||||
MessageContext message_context;
|
||||
|
||||
struct SubMaster::SubMessage {
|
||||
std::string name;
|
||||
SubSocket *socket = nullptr;
|
||||
int freq = 0;
|
||||
bool updated = false, alive = false, valid = false, ignore_alive;
|
||||
uint64_t rcv_time = 0, rcv_frame = 0;
|
||||
void *allocated_msg_reader = nullptr;
|
||||
bool is_polled = false;
|
||||
capnp::FlatArrayMessageReader *msg_reader = nullptr;
|
||||
AlignedBuffer aligned_buf;
|
||||
cereal::Event::Reader event;
|
||||
};
|
||||
|
||||
SubMaster::SubMaster(const std::vector<const char *> &service_list, const std::vector<const char *> &poll,
|
||||
const char *address, const std::vector<const char *> &ignore_alive) {
|
||||
poller_ = Poller::create();
|
||||
for (auto name : service_list) {
|
||||
assert(services.count(std::string(name)) > 0);
|
||||
|
||||
service serv = services.at(std::string(name));
|
||||
SubSocket *socket = SubSocket::create(message_context.context(), name, address ? address : "127.0.0.1", true);
|
||||
assert(socket != 0);
|
||||
bool is_polled = inList(poll, name) || poll.empty();
|
||||
if (is_polled) poller_->registerSocket(socket);
|
||||
SubMessage *m = new SubMessage{
|
||||
.name = name,
|
||||
.socket = socket,
|
||||
.freq = serv.frequency,
|
||||
.ignore_alive = inList(ignore_alive, name),
|
||||
.allocated_msg_reader = malloc(sizeof(capnp::FlatArrayMessageReader)),
|
||||
.is_polled = is_polled};
|
||||
m->msg_reader = new (m->allocated_msg_reader) capnp::FlatArrayMessageReader({});
|
||||
messages_[socket] = m;
|
||||
services_[name] = m;
|
||||
}
|
||||
}
|
||||
|
||||
void SubMaster::update(int timeout) {
|
||||
for (auto &kv : messages_) kv.second->updated = false;
|
||||
|
||||
auto sockets = poller_->poll(timeout);
|
||||
|
||||
// add non-polled sockets for non-blocking receive
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
SubSocket *s = kv.first;
|
||||
if (!m->is_polled) sockets.push_back(s);
|
||||
}
|
||||
|
||||
uint64_t current_time = nanos_since_boot();
|
||||
|
||||
std::vector<std::pair<std::string, cereal::Event::Reader>> messages;
|
||||
|
||||
for (auto s : sockets) {
|
||||
Message *msg = s->receive(true);
|
||||
if (msg == nullptr) continue;
|
||||
|
||||
SubMessage *m = messages_.at(s);
|
||||
|
||||
m->msg_reader->~FlatArrayMessageReader();
|
||||
capnp::ReaderOptions options;
|
||||
options.traversalLimitInWords = kj::maxValue; // Don't limit
|
||||
m->msg_reader = new (m->allocated_msg_reader) capnp::FlatArrayMessageReader(m->aligned_buf.align(msg), options);
|
||||
delete msg;
|
||||
messages.push_back({m->name, m->msg_reader->getRoot<cereal::Event>()});
|
||||
}
|
||||
|
||||
update_msgs(current_time, messages);
|
||||
}
|
||||
|
||||
void SubMaster::update_msgs(uint64_t current_time, const std::vector<std::pair<std::string, cereal::Event::Reader>> &messages){
|
||||
if (++frame == UINT64_MAX) frame = 1;
|
||||
|
||||
for (auto &kv : messages) {
|
||||
auto m_find = services_.find(kv.first);
|
||||
if (m_find == services_.end()){
|
||||
continue;
|
||||
}
|
||||
SubMessage *m = m_find->second;
|
||||
m->event = kv.second;
|
||||
m->updated = true;
|
||||
m->rcv_time = current_time;
|
||||
m->rcv_frame = frame;
|
||||
m->valid = m->event.getValid();
|
||||
if (SIMULATION) m->alive = true;
|
||||
}
|
||||
|
||||
if (!SIMULATION) {
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
m->alive = (m->freq <= (1e-5) || ((current_time - m->rcv_time) * (1e-9)) < (10.0 / m->freq));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SubMaster::all_(const std::vector<const char *> &service_list, bool valid, bool alive) {
|
||||
int found = 0;
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
if (service_list.size() == 0 || inList(service_list, m->name.c_str())) {
|
||||
found += (!valid || m->valid) && (!alive || (m->alive || m->ignore_alive));
|
||||
}
|
||||
}
|
||||
return service_list.size() == 0 ? found == messages_.size() : found == service_list.size();
|
||||
}
|
||||
|
||||
void SubMaster::drain() {
|
||||
while (true) {
|
||||
auto polls = poller_->poll(0);
|
||||
if (polls.size() == 0)
|
||||
break;
|
||||
|
||||
for (auto sock : polls) {
|
||||
Message *msg = sock->receive(true);
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SubMaster::updated(const char *name) const {
|
||||
return services_.at(name)->updated;
|
||||
}
|
||||
|
||||
bool SubMaster::alive(const char *name) const {
|
||||
return services_.at(name)->alive;
|
||||
}
|
||||
|
||||
bool SubMaster::valid(const char *name) const {
|
||||
return services_.at(name)->valid;
|
||||
}
|
||||
|
||||
uint64_t SubMaster::rcv_frame(const char *name) const {
|
||||
return services_.at(name)->rcv_frame;
|
||||
}
|
||||
|
||||
uint64_t SubMaster::rcv_time(const char *name) const {
|
||||
return services_.at(name)->rcv_time;
|
||||
}
|
||||
|
||||
cereal::Event::Reader &SubMaster::operator[](const char *name) const {
|
||||
return services_.at(name)->event;
|
||||
}
|
||||
|
||||
SubMaster::~SubMaster() {
|
||||
delete poller_;
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
m->msg_reader->~FlatArrayMessageReader();
|
||||
free(m->allocated_msg_reader);
|
||||
delete m->socket;
|
||||
delete m;
|
||||
}
|
||||
}
|
||||
|
||||
PubMaster::PubMaster(const std::vector<const char *> &service_list) {
|
||||
for (auto name : service_list) {
|
||||
assert(services.count(name) > 0);
|
||||
PubSocket *socket = PubSocket::create(message_context.context(), name);
|
||||
assert(socket);
|
||||
sockets_[name] = socket;
|
||||
}
|
||||
}
|
||||
|
||||
int PubMaster::send(const char *name, MessageBuilder &msg) {
|
||||
auto bytes = msg.toBytes();
|
||||
return send(name, bytes.begin(), bytes.size());
|
||||
}
|
||||
|
||||
PubMaster::~PubMaster() {
|
||||
for (auto s : sockets_) delete s.second;
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
import os
|
||||
import capnp
|
||||
import multiprocessing
|
||||
import numbers
|
||||
import random
|
||||
import threading
|
||||
import time
|
||||
from parameterized import parameterized
|
||||
import pytest
|
||||
|
||||
from cereal import log, car
|
||||
import cereal.messaging as messaging
|
||||
from cereal.services import SERVICE_LIST
|
||||
|
||||
events = [evt for evt in log.Event.schema.union_fields if evt in SERVICE_LIST.keys()]
|
||||
|
||||
def random_sock():
|
||||
return random.choice(events)
|
||||
|
||||
def random_socks(num_socks=10):
|
||||
return list({random_sock() for _ in range(num_socks)})
|
||||
|
||||
def random_bytes(length=1000):
|
||||
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
|
||||
def random_carstate():
|
||||
fields = ["vEgo", "aEgo", "brake", "steeringAngleDeg"]
|
||||
msg = messaging.new_message("carState")
|
||||
cs = msg.carState
|
||||
for f in fields:
|
||||
setattr(cs, f, random.random() * 10)
|
||||
return msg
|
||||
|
||||
# TODO: this should compare any capnp structs
|
||||
def assert_carstate(cs1, cs2):
|
||||
for f in car.CarState.schema.non_union_fields:
|
||||
# TODO: check all types
|
||||
val1, val2 = getattr(cs1, f), getattr(cs2, f)
|
||||
if isinstance(val1, numbers.Number):
|
||||
assert val1 == val2, f"{f}: sent '{val1}' vs recvd '{val2}'"
|
||||
|
||||
def delayed_send(delay, sock, dat):
|
||||
def send_func():
|
||||
sock.send(dat)
|
||||
threading.Timer(delay, send_func).start()
|
||||
|
||||
|
||||
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)
|
||||
def test_new_message(self, evt):
|
||||
try:
|
||||
msg = messaging.new_message(evt)
|
||||
except capnp.lib.capnp.KjException:
|
||||
msg = messaging.new_message(evt, random.randrange(200))
|
||||
assert (time.monotonic() - msg.logMonoTime) < 0.1
|
||||
assert not msg.valid
|
||||
assert evt == msg.which()
|
||||
|
||||
@parameterized.expand(events)
|
||||
def test_pub_sock(self, evt):
|
||||
messaging.pub_sock(evt)
|
||||
|
||||
@parameterized.expand(events)
|
||||
def test_sub_sock(self, evt):
|
||||
messaging.sub_sock(evt)
|
||||
|
||||
@parameterized.expand([
|
||||
(messaging.drain_sock, capnp._DynamicStructReader),
|
||||
(messaging.drain_sock_raw, bytes),
|
||||
])
|
||||
def test_drain_sock(self, func, expected_type):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
||||
zmq_sleep()
|
||||
|
||||
# no wait and no msgs in queue
|
||||
msgs = func(sub_sock)
|
||||
assert isinstance(msgs, list)
|
||||
assert len(msgs) == 0
|
||||
|
||||
# no wait but msgs are queued up
|
||||
num_msgs = random.randrange(3, 10)
|
||||
for _ in range(num_msgs):
|
||||
pub_sock.send(messaging.new_message(sock).to_bytes())
|
||||
time.sleep(0.1)
|
||||
msgs = func(sub_sock)
|
||||
assert isinstance(msgs, list)
|
||||
assert all(isinstance(msg, expected_type) for msg in msgs)
|
||||
assert len(msgs) == num_msgs
|
||||
|
||||
def test_recv_sock(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=100)
|
||||
zmq_sleep()
|
||||
|
||||
# no wait and no msg in queue, socket should timeout
|
||||
recvd = messaging.recv_sock(sub_sock)
|
||||
assert recvd is None
|
||||
|
||||
# no wait and one msg in queue
|
||||
msg = random_carstate()
|
||||
pub_sock.send(msg.to_bytes())
|
||||
time.sleep(0.01)
|
||||
recvd = messaging.recv_sock(sub_sock)
|
||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||
# https://github.com/python/mypy/issues/13038
|
||||
assert_carstate(msg.carState, recvd.carState)
|
||||
|
||||
def test_recv_one(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
||||
zmq_sleep()
|
||||
|
||||
# no msg in queue, socket should timeout
|
||||
recvd = messaging.recv_one(sub_sock)
|
||||
assert recvd is None
|
||||
|
||||
# one msg in queue
|
||||
msg = random_carstate()
|
||||
pub_sock.send(msg.to_bytes())
|
||||
recvd = messaging.recv_one(sub_sock)
|
||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||
assert_carstate(msg.carState, recvd.carState)
|
||||
|
||||
@pytest.mark.xfail(condition="ZMQ" in os.environ, reason='ZMQ detected')
|
||||
def test_recv_one_or_none(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock)
|
||||
zmq_sleep()
|
||||
|
||||
# no msg in queue, socket shouldn't block
|
||||
recvd = messaging.recv_one_or_none(sub_sock)
|
||||
assert recvd is None
|
||||
|
||||
# one msg in queue
|
||||
msg = random_carstate()
|
||||
pub_sock.send(msg.to_bytes())
|
||||
recvd = messaging.recv_one_or_none(sub_sock)
|
||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||
assert_carstate(msg.carState, recvd.carState)
|
||||
|
||||
def test_recv_one_retry(self):
|
||||
sock = "carState"
|
||||
sock_timeout = 0.1
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000))
|
||||
zmq_sleep()
|
||||
|
||||
# this test doesn't work with ZMQ since multiprocessing interrupts it
|
||||
if "ZMQ" not in os.environ:
|
||||
# wait 5 socket timeouts and make sure it's still retrying
|
||||
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
|
||||
p.start()
|
||||
time.sleep(sock_timeout*5)
|
||||
assert p.is_alive()
|
||||
p.terminate()
|
||||
|
||||
# wait 5 socket timeouts before sending
|
||||
msg = random_carstate()
|
||||
start_time = time.monotonic()
|
||||
delayed_send(sock_timeout*5, pub_sock, msg.to_bytes())
|
||||
recvd = messaging.recv_one_retry(sub_sock)
|
||||
assert (time.monotonic() - start_time) >= sock_timeout*5
|
||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||
assert_carstate(msg.carState, recvd.carState)
|
||||
@@ -1,160 +0,0 @@
|
||||
import random
|
||||
import time
|
||||
from typing import Sized, cast
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
|
||||
random_bytes, random_carstate, assert_carstate, \
|
||||
zmq_sleep
|
||||
from cereal.services import SERVICE_LIST
|
||||
|
||||
|
||||
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):
|
||||
sm = messaging.SubMaster(events)
|
||||
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
||||
sm.sock, sm.data, sm.logMonoTime, sm.valid]:
|
||||
assert len(cast(Sized, p)) == len(events)
|
||||
|
||||
def test_init_state(self):
|
||||
socks = random_socks()
|
||||
sm = messaging.SubMaster(socks)
|
||||
assert sm.frame == -1
|
||||
assert not any(sm.updated.values())
|
||||
assert not any(sm.seen.values())
|
||||
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in sm.services}
|
||||
assert all(sm.alive[s] == sm.valid[s] == sm.freq_ok[s] == on_demand[s] for s in sm.services)
|
||||
assert all(t == 0. for t in sm.recv_time.values())
|
||||
assert all(f == 0 for f in sm.recv_frame.values())
|
||||
assert all(t == 0 for t in sm.logMonoTime.values())
|
||||
|
||||
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
||||
sm.sock, sm.data, sm.logMonoTime, sm.valid]:
|
||||
assert len(cast(Sized, p)) == len(socks)
|
||||
|
||||
def test_getitem(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sm = messaging.SubMaster([sock,])
|
||||
zmq_sleep()
|
||||
|
||||
msg = random_carstate()
|
||||
pub_sock.send(msg.to_bytes())
|
||||
sm.update(1000)
|
||||
assert_carstate(msg.carState, sm[sock])
|
||||
|
||||
# TODO: break this test up to individually test SubMaster.update and SubMaster.update_msgs
|
||||
def test_update(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sm = messaging.SubMaster([sock,])
|
||||
zmq_sleep()
|
||||
|
||||
for i in range(10):
|
||||
msg = messaging.new_message(sock)
|
||||
pub_sock.send(msg.to_bytes())
|
||||
sm.update(1000)
|
||||
assert sm.frame == i
|
||||
assert all(sm.updated.values())
|
||||
|
||||
def test_update_timeout(self):
|
||||
sock = random_sock()
|
||||
sm = messaging.SubMaster([sock,])
|
||||
timeout = random.randrange(1000, 3000)
|
||||
start_time = time.monotonic()
|
||||
sm.update(timeout)
|
||||
t = time.monotonic() - start_time
|
||||
assert t >= timeout/1000.
|
||||
assert t < 3
|
||||
assert not any(sm.updated.values())
|
||||
|
||||
def test_avg_frequency_checks(self):
|
||||
for poll in (True, False):
|
||||
sm = messaging.SubMaster(["modelV2", "carParams", "carState", "cameraOdometry", "liveCalibration"],
|
||||
poll=("modelV2" if poll else None),
|
||||
frequency=(20. if not poll else None))
|
||||
|
||||
checks = {
|
||||
"carState": (20, 20),
|
||||
"modelV2": (20, 20 if poll else 10),
|
||||
"cameraOdometry": (20, 10),
|
||||
"liveCalibration": (4, 4),
|
||||
"carParams": (None, None),
|
||||
"userBookmark": (None, None),
|
||||
}
|
||||
|
||||
for service, (max_freq, min_freq) in checks.items():
|
||||
if max_freq is not None:
|
||||
assert sm._check_avg_freq(service)
|
||||
assert sm.freq_tracker[service].max_freq == max_freq*1.2
|
||||
assert sm.freq_tracker[service].min_freq == min_freq*0.8
|
||||
else:
|
||||
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
|
||||
def test_conflate(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sm = messaging.SubMaster([sock,])
|
||||
|
||||
n = 10
|
||||
for i in range(n+1):
|
||||
msg = messaging.new_message(sock)
|
||||
msg.carState.vEgo = i
|
||||
pub_sock.send(msg.to_bytes())
|
||||
time.sleep(0.01)
|
||||
sm.update(1000)
|
||||
assert sm[sock].vEgo == n
|
||||
|
||||
|
||||
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):
|
||||
messaging.PubMaster(events)
|
||||
|
||||
def test_send(self):
|
||||
socks = random_socks()
|
||||
pm = messaging.PubMaster(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
|
||||
for capnp in [True, False]:
|
||||
for i in range(100):
|
||||
sock = socks[i % len(socks)]
|
||||
|
||||
if capnp:
|
||||
try:
|
||||
msg = messaging.new_message(sock)
|
||||
except Exception:
|
||||
msg = messaging.new_message(sock, random.randrange(50))
|
||||
else:
|
||||
msg = random_bytes()
|
||||
|
||||
pm.send(sock, msg)
|
||||
recvd = sub_socks[sock].receive()
|
||||
|
||||
if capnp:
|
||||
msg.clear_write_flag()
|
||||
msg = msg.to_bytes()
|
||||
assert msg == recvd, i
|
||||
@@ -1,21 +0,0 @@
|
||||
import os
|
||||
import tempfile
|
||||
from typing import Dict
|
||||
from parameterized import parameterized
|
||||
|
||||
import cereal.services as services
|
||||
from cereal.services import SERVICE_LIST
|
||||
|
||||
|
||||
class TestServices:
|
||||
|
||||
@parameterized.expand(SERVICE_LIST.keys())
|
||||
def test_services(self, s):
|
||||
service = SERVICE_LIST[s]
|
||||
assert service.frequency <= 104
|
||||
assert service.decimation != 0
|
||||
|
||||
def test_generated_header(self):
|
||||
with tempfile.NamedTemporaryFile(suffix=".h") as f:
|
||||
ret = os.system(f"python3 {services.__file__} > {f.name} && clang++ {f.name} -std=c++11")
|
||||
assert ret == 0, "generated services header is not valid C"
|
||||
@@ -1,222 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
from typing import Any, List, Tuple
|
||||
|
||||
DEBUG = False
|
||||
|
||||
|
||||
def print_debug(string: str) -> None:
|
||||
if DEBUG:
|
||||
print(string)
|
||||
|
||||
|
||||
def create_schema_instance(struct: Any, prop: Tuple[str, Any]) -> Any:
|
||||
"""
|
||||
Create a new instance of a schema type, handling different field types.
|
||||
|
||||
Args:
|
||||
struct: The Cap'n Proto schema structure
|
||||
prop: A tuple containing the field name and field metadata
|
||||
|
||||
Returns:
|
||||
A new initialized schema instance
|
||||
"""
|
||||
struct_instance = struct.new_message()
|
||||
field_name, field_metadata = prop
|
||||
|
||||
try:
|
||||
field_type = field_metadata.proto.slot.type.which()
|
||||
|
||||
# Initialize different types of fields
|
||||
if field_type in ('list', 'text', 'data'):
|
||||
struct_instance.init(field_name, 1)
|
||||
print_debug(f"Initialized list/text/data field: {field_name}")
|
||||
elif field_type in ('struct', 'object'):
|
||||
struct_instance.init(field_name)
|
||||
print_debug(f"Initialized struct/object field: {field_name}")
|
||||
|
||||
return struct_instance
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error creating instance for {field_name}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def get_schema_fields(schema_struct: Any) -> List[Tuple[str, Any]]:
|
||||
"""
|
||||
Retrieve all fields from a given schema structure.
|
||||
|
||||
Args:
|
||||
schema_struct: The Cap'n Proto schema structure
|
||||
|
||||
Returns:
|
||||
A list of field names and their metadata
|
||||
"""
|
||||
try:
|
||||
# Get all fields from the schema
|
||||
schema_fields = list(schema_struct.schema.fields.items())
|
||||
|
||||
print_debug("Discovered schema fields:")
|
||||
for field_name, field_metadata in schema_fields:
|
||||
print_debug(f"- {field_name}")
|
||||
|
||||
return schema_fields
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error retrieving schema fields: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def generate_schema_instances(schema_struct: Any) -> List[Any]:
|
||||
"""
|
||||
Generate instances for all fields in a given schema.
|
||||
|
||||
Args:
|
||||
schema_struct: The Cap'n Proto schema structure
|
||||
|
||||
Returns:
|
||||
A list of schema instances
|
||||
"""
|
||||
schema_fields = get_schema_fields(schema_struct)
|
||||
instances = []
|
||||
|
||||
for field_prop in schema_fields:
|
||||
try:
|
||||
instance = create_schema_instance(schema_struct, field_prop)
|
||||
if instance is not None:
|
||||
instances.append(instance)
|
||||
except Exception as e:
|
||||
print(f"Skipping field due to error: {e}")
|
||||
|
||||
print(f"Generated {len(instances)} schema instances")
|
||||
return instances
|
||||
|
||||
|
||||
def persist_instances(instances: List[Any], filename: str) -> None:
|
||||
"""
|
||||
Write schema instances to a binary file.
|
||||
|
||||
Args:
|
||||
instances: List of schema instances
|
||||
filename: Output file path
|
||||
"""
|
||||
try:
|
||||
with open(filename, 'wb') as f:
|
||||
for instance in instances:
|
||||
f.write(instance.to_bytes())
|
||||
|
||||
print(f"Successfully wrote {len(instances)} instances to {filename}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error persisting instances: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def read_instances(filename: str, schema_type: Any) -> List[Any]:
|
||||
"""
|
||||
Read schema instances from a binary file.
|
||||
|
||||
Args:
|
||||
filename: Input file path
|
||||
schema_type: The schema type to use for reading
|
||||
|
||||
Returns:
|
||||
A list of read schema instances
|
||||
"""
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
instances = list(schema_type.read_multiple_bytes(data))
|
||||
|
||||
print(f"Read {len(instances)} instances from {filename}")
|
||||
return instances
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error reading instances: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def compare_schemas(original_instances: List[Any], read_instances: List[Any]) -> bool:
|
||||
"""
|
||||
Compare original and read-back instances to detect potential breaking changes.
|
||||
|
||||
Args:
|
||||
original_instances: List of originally generated instances
|
||||
read_instances: List of instances read back from file
|
||||
|
||||
Returns:
|
||||
Boolean indicating whether schemas appear compatible
|
||||
"""
|
||||
if len(original_instances) != len(read_instances):
|
||||
print("❌ Schema Compatibility Warning: Instance count mismatch")
|
||||
return False
|
||||
|
||||
compatible = True
|
||||
for struct in read_instances:
|
||||
try:
|
||||
getattr(struct, struct.which()) # Attempting to access the field to validate readability
|
||||
except Exception as e:
|
||||
print(f"❌ Structural change detected: {struct.which()} is not readable.\nFull error: {e}")
|
||||
compatible = False
|
||||
|
||||
return compatible
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
CLI entry point for schema compatibility testing.
|
||||
"""
|
||||
# Setup argument parser
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Cap\'n Proto Schema Compatibility Testing Tool',
|
||||
epilog='Test schema compatibility by generating and reading back instances.'
|
||||
)
|
||||
|
||||
# Add mutually exclusive group for generation or reading mode
|
||||
mode_group = parser.add_mutually_exclusive_group(required=True)
|
||||
mode_group.add_argument('-g', '--generate', action='store_true',
|
||||
help='Generate schema instances')
|
||||
mode_group.add_argument('-r', '--read', action='store_true',
|
||||
help='Read and validate schema instances')
|
||||
|
||||
# Common arguments
|
||||
parser.add_argument('-f', '--file',
|
||||
default='schema_instances.bin',
|
||||
help='Output/input binary file (default: schema_instances.bin)')
|
||||
|
||||
# Parse arguments
|
||||
args = parser.parse_args()
|
||||
|
||||
# Import the schema dynamically
|
||||
try:
|
||||
from cereal import log
|
||||
schema_type = log.Event
|
||||
except ImportError:
|
||||
print("Error: Unable to import schema. Ensure 'cereal' is installed.")
|
||||
sys.exit(1)
|
||||
|
||||
# Execute based on mode
|
||||
if args.generate:
|
||||
print("🔧 Generating Schema Instances")
|
||||
instances = generate_schema_instances(schema_type)
|
||||
persist_instances(instances, args.file)
|
||||
print("✅ Instance generation complete")
|
||||
|
||||
elif args.read:
|
||||
print("🔍 Reading and Validating Schema Instances")
|
||||
generated_instances = generate_schema_instances(schema_type)
|
||||
read_back_instances = read_instances(args.file, schema_type)
|
||||
|
||||
# Compare schemas
|
||||
if compare_schemas(generated_instances, read_back_instances):
|
||||
print("✅ Schema Compatibility: No breaking changes detected")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("❌ Potential Schema Breaking Changes Detected")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,137 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Service:
|
||||
def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None):
|
||||
self.should_log = should_log
|
||||
self.frequency = frequency
|
||||
self.decimation = decimation
|
||||
|
||||
|
||||
_services: dict[str, tuple] = {
|
||||
# service: (should_log, frequency, qlog decimation (optional))
|
||||
# note: the "EncodeIdx" packets will still be in the log
|
||||
"gyroscope": (True, 104., 104),
|
||||
"gyroscope2": (True, 100., 100),
|
||||
"accelerometer": (True, 104., 104),
|
||||
"accelerometer2": (True, 100., 100),
|
||||
"magnetometer": (True, 25.),
|
||||
"lightSensor": (True, 100., 100),
|
||||
"temperatureSensor": (True, 2., 200),
|
||||
"temperatureSensor2": (True, 2., 200),
|
||||
"gpsNMEA": (True, 9.),
|
||||
"deviceState": (True, 2., 1),
|
||||
"touch": (True, 20., 1),
|
||||
"can": (True, 100., 2053), # decimation gives ~3 msgs in a full segment
|
||||
"controlsState": (True, 100., 10),
|
||||
"selfdriveState": (True, 100., 10),
|
||||
"pandaStates": (True, 10., 1),
|
||||
"peripheralState": (True, 2., 1),
|
||||
"radarState": (True, 20., 5),
|
||||
"roadEncodeIdx": (False, 20., 1),
|
||||
"liveTracks": (True, 20.),
|
||||
"sendcan": (True, 100., 139),
|
||||
"logMessage": (True, 0.),
|
||||
"errorLogMessage": (True, 0., 1),
|
||||
"liveCalibration": (True, 4., 4),
|
||||
"liveTorqueParameters": (True, 4., 1),
|
||||
"liveDelay": (True, 4., 1),
|
||||
"androidLog": (True, 0.),
|
||||
"carState": (True, 100., 10),
|
||||
"carControl": (True, 100., 10),
|
||||
"carOutput": (True, 100., 10),
|
||||
"longitudinalPlan": (True, 20., 10),
|
||||
"driverAssistance": (True, 20., 20),
|
||||
"procLog": (True, 0.5, 15),
|
||||
"gpsLocationExternal": (True, 10., 10),
|
||||
"gpsLocation": (True, 1., 1),
|
||||
"ubloxGnss": (True, 10.),
|
||||
"qcomGnss": (True, 2.),
|
||||
"gnssMeasurements": (True, 10., 10),
|
||||
"clocks": (True, 0.1, 1),
|
||||
"ubloxRaw": (True, 20.),
|
||||
"livePose": (True, 20., 4),
|
||||
"liveParameters": (True, 20., 5),
|
||||
"cameraOdometry": (True, 20., 10),
|
||||
"thumbnail": (True, 1 / 60., 1),
|
||||
"onroadEvents": (True, 1., 1),
|
||||
"carParams": (True, 0.02, 1),
|
||||
"roadCameraState": (True, 20., 20),
|
||||
"driverCameraState": (True, 20., 20),
|
||||
"driverEncodeIdx": (False, 20., 1),
|
||||
"driverStateV2": (True, 20., 10),
|
||||
"driverMonitoringState": (True, 20., 10),
|
||||
"wideRoadEncodeIdx": (False, 20., 1),
|
||||
"wideRoadCameraState": (True, 20., 20),
|
||||
"drivingModelData": (True, 20., 10),
|
||||
"modelV2": (True, 20.),
|
||||
"managerState": (True, 2., 1),
|
||||
"uploaderState": (True, 0., 1),
|
||||
"navInstruction": (True, 1., 10),
|
||||
"navRoute": (True, 0.),
|
||||
"navThumbnail": (True, 0.),
|
||||
"qRoadEncodeIdx": (False, 20.),
|
||||
"userBookmark": (True, 0., 1),
|
||||
"soundPressure": (True, 10., 10),
|
||||
"rawAudioData": (False, 20.),
|
||||
"bookmarkButton": (True, 0., 1),
|
||||
"audioFeedback": (True, 0., 1),
|
||||
|
||||
# sunnypilot
|
||||
"modelManagerSP": (False, 1., 1),
|
||||
"backupManagerSP": (False, 1., 1),
|
||||
"selfdriveStateSP": (True, 100., 10),
|
||||
"longitudinalPlanSP": (True, 20., 10),
|
||||
"onroadEventsSP": (True, 1., 1),
|
||||
"carParamsSP": (True, 0.02, 1),
|
||||
"carControlSP": (True, 100., 10),
|
||||
"carStateSP": (True, 100., 10),
|
||||
"liveMapDataSP": (True, 1., 1),
|
||||
|
||||
# debug
|
||||
"uiDebug": (True, 0., 1),
|
||||
"testJoystick": (True, 0.),
|
||||
"alertDebug": (True, 20., 5),
|
||||
"roadEncodeData": (False, 20.),
|
||||
"driverEncodeData": (False, 20.),
|
||||
"wideRoadEncodeData": (False, 20.),
|
||||
"qRoadEncodeData": (False, 20.),
|
||||
"livestreamWideRoadEncodeIdx": (False, 20.),
|
||||
"livestreamRoadEncodeIdx": (False, 20.),
|
||||
"livestreamDriverEncodeIdx": (False, 20.),
|
||||
"livestreamWideRoadEncodeData": (False, 20.),
|
||||
"livestreamRoadEncodeData": (False, 20.),
|
||||
"livestreamDriverEncodeData": (False, 20.),
|
||||
"customReservedRawData0": (True, 0.),
|
||||
"customReservedRawData1": (True, 0.),
|
||||
"customReservedRawData2": (True, 0.),
|
||||
}
|
||||
SERVICE_LIST = {name: Service(*vals) for
|
||||
idx, (name, vals) in enumerate(_services.items())}
|
||||
|
||||
|
||||
def build_header():
|
||||
h = ""
|
||||
h += "/* THIS IS AN AUTOGENERATED FILE, PLEASE EDIT services.py */\n"
|
||||
h += "#ifndef __SERVICES_H\n"
|
||||
h += "#define __SERVICES_H\n"
|
||||
|
||||
h += "#include <map>\n"
|
||||
h += "#include <string>\n"
|
||||
|
||||
h += "struct service { std::string name; bool should_log; int frequency; int decimation; };\n"
|
||||
h += "static std::map<std::string, service> services = {\n"
|
||||
for k, v in SERVICE_LIST.items():
|
||||
should_log = "true" if v.should_log else "false"
|
||||
decimation = -1 if v.decimation is None else v.decimation
|
||||
h += ' { "%s", {"%s", %s, %d, %d}},\n' % \
|
||||
(k, k, should_log, v.frequency, decimation)
|
||||
h += "};\n"
|
||||
|
||||
h += "#endif\n"
|
||||
return h
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(build_header())
|
||||
@@ -1 +0,0 @@
|
||||
*.cpp
|
||||
@@ -1,35 +0,0 @@
|
||||
Import('env', 'envCython', 'arch')
|
||||
|
||||
common_libs = [
|
||||
'params.cc',
|
||||
'swaglog.cc',
|
||||
'util.cc',
|
||||
'watchdog.cc',
|
||||
'ratekeeper.cc'
|
||||
]
|
||||
|
||||
_common = env.Library('common', common_libs, LIBS="json11")
|
||||
|
||||
files = [
|
||||
'clutil.cc',
|
||||
]
|
||||
|
||||
_gpucommon = env.Library('gpucommon', files)
|
||||
Export('_common', '_gpucommon')
|
||||
|
||||
if GetOption('extras'):
|
||||
env.Program('tests/test_common',
|
||||
['tests/test_runner.cc', 'tests/test_params.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'],
|
||||
LIBS=[_common, 'json11', 'zmq', 'pthread'])
|
||||
|
||||
# Cython bindings
|
||||
params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11'])
|
||||
|
||||
SConscript([
|
||||
'transformations/SConscript',
|
||||
])
|
||||
|
||||
Import('transformations_python')
|
||||
common_python = [params_python, transformations_python]
|
||||
|
||||
Export('common_python')
|
||||
+12
-19
@@ -1,22 +1,15 @@
|
||||
from openpilot.common.api.comma_connect import CommaConnectApi
|
||||
|
||||
|
||||
class Api:
|
||||
def __init__(self, dongle_id):
|
||||
self.service = CommaConnectApi(dongle_id)
|
||||
|
||||
def request(self, method, endpoint, **params):
|
||||
return self.service.request(method, endpoint, **params)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return self.service.get(*args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
return self.service.post(*args, **kwargs)
|
||||
|
||||
def get_token(self, expiry_hours=1):
|
||||
return self.service.get_token(expiry_hours)
|
||||
import requests
|
||||
|
||||
from selfdrive.version import version
|
||||
|
||||
def api_get(endpoint, method='GET', timeout=None, access_token=None, **params):
|
||||
return CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, **params)
|
||||
backend = "https://api.commadotai.com/"
|
||||
|
||||
headers = {}
|
||||
if access_token is not None:
|
||||
headers['Authorization'] = "JWT "+access_token
|
||||
|
||||
headers['User-Agent'] = "openpilot-" + version
|
||||
|
||||
return requests.request(method, backend+endpoint, timeout=timeout, headers = headers, params=params)
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import jwt
|
||||
import requests
|
||||
import unicodedata
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from openpilot.system.version import get_version
|
||||
|
||||
|
||||
class BaseApi:
|
||||
def __init__(self, dongle_id, api_host, user_agent="openpilot-"):
|
||||
self.dongle_id = dongle_id
|
||||
self.api_host = api_host
|
||||
self.user_agent = user_agent
|
||||
with open(f'{Paths.persist_root()}/comma/id_rsa') as f:
|
||||
self.private_key = f.read()
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return self.request('GET', *args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
return self.request('POST', *args, **kwargs)
|
||||
|
||||
def request(self, method, endpoint, timeout=None, access_token=None, **params):
|
||||
return self.api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params)
|
||||
|
||||
def _get_token(self, expiry_hours=1, **extra_payload):
|
||||
now = datetime.now(UTC).replace(tzinfo=None)
|
||||
payload = {
|
||||
'identity': self.dongle_id,
|
||||
'nbf': now,
|
||||
'iat': now,
|
||||
'exp': now + timedelta(hours=expiry_hours),
|
||||
**extra_payload
|
||||
}
|
||||
token = jwt.encode(payload, self.private_key, algorithm='RS256')
|
||||
if isinstance(token, bytes):
|
||||
token = token.decode('utf8')
|
||||
return token
|
||||
|
||||
def get_token(self, expiry_hours=1):
|
||||
return self._get_token(expiry_hours)
|
||||
|
||||
def remove_non_ascii_chars(self, text):
|
||||
normalized_text = unicodedata.normalize('NFD', text)
|
||||
ascii_encoded_text = normalized_text.encode('ascii', 'ignore')
|
||||
return ascii_encoded_text.decode()
|
||||
|
||||
def api_get(self, endpoint, method='GET', timeout=None, access_token=None, json=None, **params):
|
||||
headers = {}
|
||||
if access_token is not None:
|
||||
headers['Authorization'] = "JWT " + access_token
|
||||
|
||||
version = self.remove_non_ascii_chars(get_version())
|
||||
headers['User-Agent'] = self.user_agent + version
|
||||
|
||||
return requests.request(method, f"{self.api_host}/{endpoint}", timeout=timeout, headers=headers, json=json, params=params)
|
||||
@@ -1,11 +0,0 @@
|
||||
import os
|
||||
|
||||
from openpilot.common.api.base import BaseApi
|
||||
|
||||
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')
|
||||
|
||||
|
||||
class CommaConnectApi(BaseApi):
|
||||
def __init__(self, dongle_id):
|
||||
super().__init__(dongle_id, API_HOST)
|
||||
self.user_agent = "openpilot-"
|
||||
@@ -1,4 +0,0 @@
|
||||
import os
|
||||
|
||||
|
||||
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../"))
|
||||
@@ -1,98 +0,0 @@
|
||||
#include "common/clutil.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "common/util.h"
|
||||
#include "common/swaglog.h"
|
||||
|
||||
namespace { // helper functions
|
||||
|
||||
template <typename Func, typename Id, typename Name>
|
||||
std::string get_info(Func get_info_func, Id id, Name param_name) {
|
||||
size_t size = 0;
|
||||
CL_CHECK(get_info_func(id, param_name, 0, NULL, &size));
|
||||
std::string info(size, '\0');
|
||||
CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL));
|
||||
return info;
|
||||
}
|
||||
inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); }
|
||||
inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); }
|
||||
|
||||
void cl_print_info(cl_platform_id platform, cl_device_id device) {
|
||||
size_t work_group_size = 0;
|
||||
cl_device_type device_type = 0;
|
||||
clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL);
|
||||
clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL);
|
||||
const char *type_str = "Other...";
|
||||
switch (device_type) {
|
||||
case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break;
|
||||
case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break;
|
||||
case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break;
|
||||
}
|
||||
|
||||
LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str());
|
||||
LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str());
|
||||
LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str());
|
||||
LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str());
|
||||
LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str());
|
||||
LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str());
|
||||
LOGD("max work group size: %zu", work_group_size);
|
||||
LOGD("type = %d, %s", (int)device_type, type_str);
|
||||
}
|
||||
|
||||
void cl_print_build_errors(cl_program program, cl_device_id device) {
|
||||
cl_build_status status;
|
||||
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL);
|
||||
size_t log_size;
|
||||
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size);
|
||||
std::string log(log_size, '\0');
|
||||
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL);
|
||||
|
||||
LOGE("build failed; status=%d, log: %s", status, log.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
cl_device_id cl_get_device_id(cl_device_type device_type) {
|
||||
cl_uint num_platforms = 0;
|
||||
CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms));
|
||||
std::unique_ptr<cl_platform_id[]> platform_ids = std::make_unique<cl_platform_id[]>(num_platforms);
|
||||
CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL));
|
||||
|
||||
for (size_t i = 0; i < num_platforms; ++i) {
|
||||
LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str());
|
||||
|
||||
// Get first device
|
||||
if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) {
|
||||
cl_print_info(platform_ids[i], device_id);
|
||||
return device_id;
|
||||
}
|
||||
}
|
||||
LOGE("No valid openCL platform found");
|
||||
assert(0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cl_context cl_create_context(cl_device_id device_id) {
|
||||
return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err));
|
||||
}
|
||||
|
||||
void cl_release_context(cl_context context) {
|
||||
clReleaseContext(context);
|
||||
}
|
||||
|
||||
cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) {
|
||||
return cl_program_from_source(ctx, device_id, util::read_file(path), args);
|
||||
}
|
||||
|
||||
cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) {
|
||||
const char *csrc = src.c_str();
|
||||
cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err));
|
||||
if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) {
|
||||
cl_print_build_errors(prg, device_id);
|
||||
assert(0);
|
||||
}
|
||||
return prg;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenCL/cl.h>
|
||||
#else
|
||||
#include <CL/cl.h>
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
#define CL_CHECK(_expr) \
|
||||
do { \
|
||||
assert(CL_SUCCESS == (_expr)); \
|
||||
} while (0)
|
||||
|
||||
#define CL_CHECK_ERR(_expr) \
|
||||
({ \
|
||||
cl_int err = CL_INVALID_VALUE; \
|
||||
__typeof__(_expr) _ret = _expr; \
|
||||
assert(_ret&& err == CL_SUCCESS); \
|
||||
_ret; \
|
||||
})
|
||||
|
||||
cl_device_id cl_get_device_id(cl_device_type device_type);
|
||||
cl_context cl_create_context(cl_device_id device_id);
|
||||
void cl_release_context(cl_context context);
|
||||
cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr);
|
||||
cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args);
|
||||
@@ -1,23 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
# conversions
|
||||
class CV:
|
||||
# Speed
|
||||
MPH_TO_KPH = 1.609344
|
||||
KPH_TO_MPH = 1. / MPH_TO_KPH
|
||||
MS_TO_KPH = 3.6
|
||||
KPH_TO_MS = 1. / MS_TO_KPH
|
||||
MS_TO_MPH = MS_TO_KPH * KPH_TO_MPH
|
||||
MPH_TO_MS = MPH_TO_KPH * KPH_TO_MS
|
||||
MS_TO_KNOTS = 1.9438
|
||||
KNOTS_TO_MS = 1. / MS_TO_KNOTS
|
||||
|
||||
# Angle
|
||||
DEG_TO_RAD = np.pi / 180.
|
||||
RAD_TO_DEG = 1. / DEG_TO_RAD
|
||||
|
||||
# Mass
|
||||
LB_TO_KG = 0.453592
|
||||
|
||||
|
||||
ACCELERATION_DUE_TO_GRAVITY = 9.81 # m/s^2
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Install exception handler for process crash."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
if os.getenv("NOLOG"):
|
||||
def capture_exception(*exc_info):
|
||||
pass
|
||||
def bind_user(**kwargs):
|
||||
pass
|
||||
def bind_extra(**kwargs):
|
||||
pass
|
||||
def install():
|
||||
pass
|
||||
else:
|
||||
from raven import Client
|
||||
from raven.transport.http import HTTPTransport
|
||||
|
||||
client = Client('https://1994756b5e6f41cf939a4c65de45f4f2:cefebaf3a8aa40d182609785f7189bd7@app.getsentry.com/77924',
|
||||
install_sys_hook=False, transport=HTTPTransport)
|
||||
|
||||
capture_exception = client.captureException
|
||||
|
||||
def bind_user(**kwargs):
|
||||
client.user_context(kwargs)
|
||||
|
||||
def bind_extra(**kwargs):
|
||||
client.extra_context(kwargs)
|
||||
|
||||
def install():
|
||||
# installs a sys.excepthook
|
||||
__excepthook__ = sys.excepthook
|
||||
def handle_exception(*exc_info):
|
||||
if exc_info[0] not in (KeyboardInterrupt, SystemExit):
|
||||
client.captureException(exc_info=exc_info)
|
||||
__excepthook__(*exc_info)
|
||||
sys.excepthook = handle_exception
|
||||
Executable
+195
@@ -0,0 +1,195 @@
|
||||
import re
|
||||
import os
|
||||
import struct
|
||||
import bitstring
|
||||
from collections import namedtuple
|
||||
|
||||
def int_or_float(s):
|
||||
# return number, trying to maintain int format
|
||||
try:
|
||||
return int(s)
|
||||
except ValueError:
|
||||
return float(s)
|
||||
|
||||
DBCSignal = namedtuple(
|
||||
"DBCSignal", ["name", "start_bit", "size", "is_little_endian", "is_signed",
|
||||
"factor", "offset", "tmin", "tmax", "units"])
|
||||
|
||||
class dbc(object):
|
||||
def __init__(self, fn):
|
||||
self.name, _ = os.path.splitext(os.path.basename(fn))
|
||||
with open(fn) as f:
|
||||
self.txt = f.read().split("\n")
|
||||
self._warned_addresses = set()
|
||||
|
||||
# regexps from https://github.com/ebroecker/canmatrix/blob/master/canmatrix/importdbc.py
|
||||
bo_regexp = re.compile("^BO\_ (\w+) (\w+) *: (\w+) (\w+)")
|
||||
sg_regexp = re.compile("^SG\_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*)")
|
||||
sgm_regexp = re.compile("^SG\_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*)")
|
||||
|
||||
# A dictionary which maps message ids to tuples ((name, size), signals).
|
||||
# name is the ASCII name of the message.
|
||||
# size is the size of the message in bytes.
|
||||
# signals is a list signals contained in the message.
|
||||
# signals is a list of DBCSignal in order of increasing start_bit.
|
||||
self.msgs = {}
|
||||
|
||||
# lookup to bit reverse each byte
|
||||
self.bits_index = [(i & ~0b111) + ((-i-1) & 0b111) for i in xrange(64)]
|
||||
|
||||
for l in self.txt:
|
||||
l = l.strip()
|
||||
|
||||
if l.startswith("BO_ "):
|
||||
# new group
|
||||
dat = bo_regexp.match(l)
|
||||
|
||||
if dat is None:
|
||||
print "bad BO", l
|
||||
name = dat.group(2)
|
||||
size = int(dat.group(3))
|
||||
ids = int(dat.group(1), 0) # could be hex
|
||||
|
||||
self.msgs[ids] = ((name, size), [])
|
||||
|
||||
if l.startswith("SG_ "):
|
||||
# new signal
|
||||
dat = sg_regexp.match(l)
|
||||
go = 0
|
||||
if dat is None:
|
||||
dat = sgm_regexp.match(l)
|
||||
go = 1
|
||||
if dat is None:
|
||||
print "bad SG", l
|
||||
|
||||
sgname = dat.group(1)
|
||||
start_bit = int(dat.group(go+2))
|
||||
signal_size = int(dat.group(go+3))
|
||||
is_little_endian = int(dat.group(go+4))==1
|
||||
is_signed = dat.group(go+5)=='-'
|
||||
factor = int_or_float(dat.group(go+6))
|
||||
offset = int_or_float(dat.group(go+7))
|
||||
tmin = int_or_float(dat.group(go+8))
|
||||
tmax = int_or_float(dat.group(go+9))
|
||||
units = dat.group(go+10)
|
||||
|
||||
self.msgs[ids][1].append(
|
||||
DBCSignal(sgname, start_bit, signal_size, is_little_endian,
|
||||
is_signed, factor, offset, tmin, tmax, units))
|
||||
|
||||
for msg in self.msgs.viewvalues():
|
||||
msg[1].sort(key=lambda x: x.start_bit)
|
||||
|
||||
def encode(self, msg_id, dd):
|
||||
"""Encode a CAN message using the dbc.
|
||||
|
||||
Inputs:
|
||||
msg_id: The message ID.
|
||||
dd: A dictionary mapping signal name to signal data.
|
||||
"""
|
||||
# TODO: Stop using bitstring, which is super slow.
|
||||
msg_def = self.msgs[msg_id]
|
||||
size = msg_def[0][1]
|
||||
|
||||
bsf = bitstring.Bits(hex="00"*size)
|
||||
for s in msg_def[1]:
|
||||
ival = dd.get(s.name)
|
||||
if ival is not None:
|
||||
ival = (ival / s.factor) - s.offset
|
||||
ival = int(round(ival))
|
||||
|
||||
# should pack this
|
||||
if s.is_little_endian:
|
||||
ss = s.start_bit
|
||||
else:
|
||||
ss = self.bits_index[s.start_bit]
|
||||
|
||||
|
||||
if s.is_signed:
|
||||
tbs = bitstring.Bits(int=ival, length=s.size)
|
||||
else:
|
||||
tbs = bitstring.Bits(uint=ival, length=s.size)
|
||||
|
||||
lpad = bitstring.Bits(bin="0b"+"0"*ss)
|
||||
rpad = bitstring.Bits(bin="0b"+"0"*(8*size-(ss+s.size)))
|
||||
tbs = lpad+tbs+rpad
|
||||
|
||||
bsf |= tbs
|
||||
return bsf.tobytes()
|
||||
|
||||
def decode(self, x, arr=None, debug=False):
|
||||
"""Decode a CAN message using the dbc.
|
||||
|
||||
Inputs:
|
||||
x: A collection with elements (address, time, data), where address is
|
||||
the CAN address, time is the bus time, and data is the CAN data as a
|
||||
hex string.
|
||||
arr: Optional list of signals which should be decoded and returned.
|
||||
debug: True to print debugging statements.
|
||||
|
||||
Returns:
|
||||
A tuple (name, data), where name is the name of the CAN message and data
|
||||
is the decoded result. If arr is None, data is a dict of properties.
|
||||
Otherwise data is a list of the same length as arr.
|
||||
|
||||
Returns (None, None) if the message could not be decoded.
|
||||
"""
|
||||
|
||||
if arr is None:
|
||||
out = {}
|
||||
else:
|
||||
out = [None]*len(arr)
|
||||
|
||||
msg = self.msgs.get(x[0])
|
||||
if msg is None:
|
||||
if x[0] not in self._warned_addresses:
|
||||
print("WARNING: Unknown message address {}".format(x[0]))
|
||||
self._warned_addresses.add(x[0])
|
||||
return None, None
|
||||
|
||||
name = msg[0][0]
|
||||
if debug:
|
||||
print name
|
||||
|
||||
blen = 8*len(x[2])
|
||||
|
||||
st = x[2].rjust(8, '\x00')
|
||||
le, be = None, None
|
||||
|
||||
for s in msg[1]:
|
||||
if arr is not None and s[0] not in arr:
|
||||
continue
|
||||
|
||||
# big or little endian?
|
||||
# see http://vi-firmware.openxcplatform.com/en/master/config/bit-numbering.html
|
||||
if s[3] is False:
|
||||
ss = self.bits_index[s[1]]
|
||||
if be is None:
|
||||
be = struct.unpack(">Q", st)[0]
|
||||
x2_int = be
|
||||
data_bit_pos = (blen - (ss + s[2]))
|
||||
else:
|
||||
if le is None:
|
||||
le = struct.unpack("<Q", st)[0]
|
||||
x2_int = le
|
||||
ss = s[1]
|
||||
data_bit_pos = ss
|
||||
|
||||
if data_bit_pos < 0:
|
||||
continue
|
||||
ival = (x2_int >> data_bit_pos) & ((1 << (s[2])) - 1)
|
||||
|
||||
if s[4] and (ival & (1<<(s[2]-1))): # signed
|
||||
ival -= (1<<s[2])
|
||||
|
||||
# control the offset
|
||||
ival = (ival * s[5]) + s[6]
|
||||
#if debug:
|
||||
# print "%40s %2d %2d %7.2f %s" % (s[0], s[1], s[2], ival, s[-1])
|
||||
|
||||
if arr is None:
|
||||
out[s[0]] = ival
|
||||
else:
|
||||
out[arr.index(s[0])] = ival
|
||||
return name, out
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user