mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-07-23 23:02:04 +08:00
Merge branch 'upstream/openpilot/master' into sync-20240608
# Conflicts: # cereal # opendbc # panda # release/files_common # selfdrive/monitoring/dmonitoringd.py # selfdrive/ui/translations/main_zh-CHS.ts # selfdrive/ui/translations/main_zh-CHT.ts
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
FROM ghcr.io/commaai/openpilot-base:latest
|
||||
|
||||
RUN apt update && apt install -y vim net-tools usbutils htop ripgrep tmux wget mesa-utils xvfb libxtst6 libxv1 libglu1-mesa libegl1-mesa gdb
|
||||
RUN apt update && apt install -y vim net-tools usbutils htop ripgrep tmux wget mesa-utils xvfb libxtst6 libxv1 libglu1-mesa libegl1-mesa gdb bash-completion
|
||||
RUN pip install ipython jupyter jupyterlab
|
||||
|
||||
RUN cd /tmp && \
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"force_color_prompt": "1"
|
||||
},
|
||||
"runArgs": [
|
||||
"--volume=/dev:/dev",
|
||||
"--volume=/tmp/.X11-unix:/tmp/.X11-unix",
|
||||
"--volume=${localWorkspaceFolder}/.devcontainer/.host/.Xauthority:/home/batman/.Xauthority",
|
||||
"--volume=${localEnv:HOME}/.comma:/home/batman/.comma",
|
||||
@@ -49,4 +50,4 @@
|
||||
"mounts": [
|
||||
"type=volume,source=scons_cache,target=/tmp/scons_cache"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: apply labels
|
||||
name: review
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
@@ -14,17 +14,17 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
# Label PRs
|
||||
- uses: actions/labeler@v5.0.0
|
||||
with:
|
||||
dot: true
|
||||
configuration-path: .github/labeler.yaml
|
||||
|
||||
pr_branch_check:
|
||||
name: check branch
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
steps:
|
||||
- uses: Vankka/pr-target-branch-action@def32ec9d93514138d6ac0132ee62e120a72aed5
|
||||
# Check PR target branch
|
||||
- name: check branch
|
||||
uses: Vankka/pr-target-branch-action@def32ec9d93514138d6ac0132ee62e120a72aed5
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -34,194 +34,20 @@ jobs:
|
||||
already-exists-action: close_this
|
||||
already-exists-comment: "Your PR should be made against the `master` branch"
|
||||
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: comment
|
||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6
|
||||
if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot'
|
||||
with:
|
||||
message: |
|
||||
<!-- _(run_id **${{ github.run_id }}**)_ -->
|
||||
Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following:
|
||||
* Convert your PR to a draft unless it's ready to review
|
||||
* Read the [contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md)
|
||||
* Before marking as "ready for review", ensure:
|
||||
* the goal is clearly stated in the description
|
||||
* all the tests are passing
|
||||
* the change is [something we merge](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md#what-gets-merged)
|
||||
* include a route or your device' dongle ID if relevant
|
||||
comment_tag: run_id
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
check-pr-template:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
if: false && github.event.pull_request.head.repo.full_name != 'commaai/openpilot'
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// Comment to add to the PR if no template has been used
|
||||
const NO_TEMPLATE_MESSAGE =
|
||||
"It looks like you didn't use one of the Pull Request templates. Please check [the contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md). \
|
||||
Also make sure that you didn't modify any of the checkboxes or headings within the template.";
|
||||
// body data for future requests
|
||||
const body_data = {
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
};
|
||||
|
||||
// Utility function to extract all headings
|
||||
const extractHeadings = (markdown) => {
|
||||
const headingRegex = /^(#{1,6})\s+(.+)$/gm;
|
||||
const boldTextRegex = /^(?:\*\*|__)(.+?)(?:\*\*|__)\s*$/gm;
|
||||
const headings = [];
|
||||
let headingMatch;
|
||||
while ((headingMatch = headingRegex.exec(markdown))) {
|
||||
headings.push(headingMatch[2].trim());
|
||||
}
|
||||
let boldMatch;
|
||||
while ((boldMatch = boldTextRegex.exec(markdown))) {
|
||||
headings.push(boldMatch[1].trim());
|
||||
}
|
||||
return headings;
|
||||
};
|
||||
|
||||
// Utility function to extract all check box descriptions
|
||||
const extractCheckBoxTexts = (markdown) => {
|
||||
const checkboxRegex = /^\s*-\s*\[( |x)\]\s+(.+)$/gm;
|
||||
const checkboxes = [];
|
||||
let match;
|
||||
while ((match = checkboxRegex.exec(markdown))) {
|
||||
checkboxes.push(match[2].trim());
|
||||
}
|
||||
return checkboxes;
|
||||
};
|
||||
|
||||
// Utility function to check if a list is a subset of another list
|
||||
isSubset = (subset, superset) => {
|
||||
return subset.every((item) => superset.includes(item));
|
||||
};
|
||||
|
||||
// Utility function to check if a list of checkboxes is a subset of another list of checkboxes
|
||||
isCheckboxSubset = (templateCheckBoxTexts, prTextCheckBoxTexts) => {
|
||||
// Check if each template checkbox text is a substring of at least one PR checkbox text
|
||||
// (user should be allowed to add additional text)
|
||||
return templateCheckBoxTexts.every((item) => prTextCheckBoxTexts.some((element) => element.includes(item)))
|
||||
}
|
||||
|
||||
// Get filenames of all currently checked-in PR templates
|
||||
const template_contents = await github.rest.repos.getContent({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: ".github/PULL_REQUEST_TEMPLATE",
|
||||
});
|
||||
var template_filenames = [];
|
||||
for (const content of template_contents.data) {
|
||||
template_filenames.push(content.path);
|
||||
}
|
||||
console.debug("Received template filenames: " + template_filenames);
|
||||
// Retrieve templates
|
||||
var templates = [];
|
||||
for (const template_filename of template_filenames) {
|
||||
const template_response = await github.rest.repos.getContent({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: template_filename,
|
||||
});
|
||||
// Convert Base64 content back
|
||||
const decoded_template = atob(template_response.data.content);
|
||||
const headings = extractHeadings(decoded_template);
|
||||
const checkboxes = extractCheckBoxTexts(decoded_template);
|
||||
if (!headings.length && !checkboxes.length) {
|
||||
console.warn(
|
||||
"Invalid template! Contains neither headings nor checkboxes, ignoring it: \n" +
|
||||
decoded_template
|
||||
);
|
||||
} else {
|
||||
templates.push({ headings: headings, checkboxes: checkboxes });
|
||||
}
|
||||
}
|
||||
// Retrieve the PR Body
|
||||
const pull_request = await github.rest.issues.get({
|
||||
...body_data,
|
||||
});
|
||||
const pull_request_text = pull_request.data.body;
|
||||
console.debug("Received Pull Request body: \n" + pull_request_text);
|
||||
|
||||
/* Check if the PR Body matches one of the templates
|
||||
A template is defined by all headings and checkboxes it contains
|
||||
We extract all Headings and Checkboxes from the PR text and check if any of the templates is a subset of that
|
||||
*/
|
||||
const pr_headings = extractHeadings(pull_request_text);
|
||||
const pr_checkboxes = extractCheckBoxTexts(pull_request_text);
|
||||
console.debug("Found Headings in PR body:\n" + pr_headings);
|
||||
console.debug("Found Checkboxes in PR body:\n" + pr_checkboxes);
|
||||
var template_found = false;
|
||||
// Iterate over each template to check if it applies
|
||||
for (const template of templates) {
|
||||
console.log(
|
||||
"Checking for headings: [" +
|
||||
template.headings +
|
||||
"] and checkboxes: [" +
|
||||
template.checkboxes + "]"
|
||||
);
|
||||
if (
|
||||
isCheckboxSubset(template.checkboxes, pr_checkboxes) &&
|
||||
isSubset(template.headings, pr_headings)
|
||||
) {
|
||||
console.debug("Found matching template!");
|
||||
template_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// List comments from previous runs
|
||||
var existing_comments = [];
|
||||
const comments = await github.rest.issues.listComments({
|
||||
...body_data,
|
||||
});
|
||||
for (const comment of comments.data) {
|
||||
if (comment.body === NO_TEMPLATE_MESSAGE) {
|
||||
existing_comments.push(comment);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a comment to the PR that it is not using a the template (but only if this comment does not exist already)
|
||||
if (!template_found) {
|
||||
var comment_already_sent = false;
|
||||
|
||||
// Add an 'in-bot-review' label since this PR doesn't have the template
|
||||
github.rest.issues.addLabels({
|
||||
...body_data,
|
||||
labels: ["in-bot-review"],
|
||||
});
|
||||
|
||||
if (existing_comments.length < 1) {
|
||||
github.rest.issues.createComment({
|
||||
...body_data,
|
||||
body: NO_TEMPLATE_MESSAGE,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// If template has been found, delete any old comment about missing template
|
||||
for (const existing_comment of existing_comments) {
|
||||
github.rest.issues.deleteComment({
|
||||
...body_data,
|
||||
comment_id: existing_comment.id,
|
||||
});
|
||||
}
|
||||
// Remove the 'in-bot-review' label after the review is done and the PR has passed
|
||||
github.rest.issues.removeLabel({
|
||||
...body_data,
|
||||
name: "in-bot-review",
|
||||
}).catch((error) => {
|
||||
console.log("Label 'in-bot-review' not found, ignoring");
|
||||
});
|
||||
}
|
||||
|
||||
# Welcome comment
|
||||
- name: comment
|
||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6
|
||||
if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot'
|
||||
with:
|
||||
message: |
|
||||
<!-- _(run_id **${{ github.run_id }}**)_ -->
|
||||
Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following:
|
||||
* Convert your PR to a draft unless it's ready to review
|
||||
* Read the [contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md)
|
||||
* Before marking as "ready for review", ensure:
|
||||
* the goal is clearly stated in the description
|
||||
* all the tests are passing
|
||||
* the change is [something we merge](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md#what-gets-merged)
|
||||
* include a route or your device' dongle ID if relevant
|
||||
comment_tag: run_id
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -47,13 +47,13 @@ jobs:
|
||||
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 }} "python selfdrive/manager/build.py"
|
||||
${{ env.RUN }} "python system/manager/build.py"
|
||||
- name: Run tests
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
cd $STRIPPED_DIR
|
||||
${{ env.RUN }} "release/check-dirty.sh && \
|
||||
MAX_EXAMPLES=5 $PYTEST selfdrive/car"
|
||||
MAX_EXAMPLES=5 $PYTEST -m 'not slow' selfdrive/car"
|
||||
- name: pre-commit
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
cp pyproject.toml $STRIPPED_DIR
|
||||
cp poetry.lock $STRIPPED_DIR
|
||||
cd $STRIPPED_DIR
|
||||
${{ env.RUN }} "unset PYTHONWARNINGS && SKIP=check-added-large-files pre-commit run --all && chmod -R 777 /tmp/pre-commit"
|
||||
${{ env.RUN }} "unset PYTHONWARNINGS && SKIP=check-added-large-files,check-hooks-apply,check-useless-excludes pre-commit run --all && chmod -R 777 /tmp/pre-commit"
|
||||
|
||||
build:
|
||||
strategy:
|
||||
@@ -76,24 +76,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
with:
|
||||
docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }}
|
||||
- uses: ./.github/workflows/compile-openpilot
|
||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache
|
||||
|
||||
docker_push:
|
||||
name: docker push
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }}
|
||||
runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-latest' }}
|
||||
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup to push to repo
|
||||
- name: Setup docker push
|
||||
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
|
||||
run: |
|
||||
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
|
||||
echo "TARGET_ARCHITECTURE=${{ matrix.arch }}" >> "$GITHUB_ENV"
|
||||
@@ -101,12 +85,14 @@ jobs:
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
with:
|
||||
docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }}
|
||||
- uses: ./.github/workflows/compile-openpilot
|
||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache
|
||||
|
||||
docker_push_multiarch:
|
||||
name: docker push multiarch tag
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
|
||||
needs: [docker_push]
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -134,24 +120,6 @@ jobs:
|
||||
timeout-minutes: 4
|
||||
run: ${{ env.RUN }} "unset PYTHONWARNINGS && pre-commit run --all && chmod -R 777 /tmp/pre-commit"
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run valgrind
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
${{ env.RUN }} "python selfdrive/test/test_valgrind_replay.py"
|
||||
- name: Print logs
|
||||
if: always()
|
||||
run: cat selfdrive/test/valgrind_logs.txt
|
||||
|
||||
unit_tests:
|
||||
name: unit tests
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
@@ -200,7 +168,7 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .ci_cache/comma_download_cache
|
||||
key: proc-replay-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/ref_commit') }}
|
||||
key: proc-replay-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_regen.py') }}
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
@@ -225,64 +193,18 @@ jobs:
|
||||
if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }}
|
||||
run: |
|
||||
${{ env.RUN }} "unset PYTHONWARNINGS && AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
|
||||
- name: "Upload coverage to Codecov"
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
name: ${{ github.job }}
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
regen:
|
||||
name: regen
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Cache test routes
|
||||
id: dependency-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .ci_cache/comma_download_cache
|
||||
key: regen-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/test_regen.py') }}
|
||||
- name: Build base Docker image
|
||||
run: eval "$BUILD"
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run regen
|
||||
timeout-minutes: 30
|
||||
run: |
|
||||
${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
|
||||
test_modeld:
|
||||
name: model tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build base Docker image
|
||||
run: eval "$BUILD"
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
# PYTHONWARNINGS triggers a SyntaxError in onnxruntime
|
||||
- name: Run model replay with ONNX
|
||||
timeout-minutes: 4
|
||||
run: |
|
||||
${{ env.RUN }} "unset PYTHONWARNINGS && \
|
||||
ONNXCPU=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \
|
||||
coverage combine && \
|
||||
coverage xml"
|
||||
- name: Run unit tests
|
||||
ONNXCPU=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \
|
||||
coverage combine && coverage xml"
|
||||
- name: Run regen
|
||||
timeout-minutes: 4
|
||||
run: |
|
||||
${{ env.RUN }} "unset PYTHONWARNINGS && \
|
||||
$PYTEST selfdrive/modeld"
|
||||
${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
- name: "Upload coverage to Codecov"
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
@@ -298,7 +220,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job: [0, 1, 2, 3, 4]
|
||||
job: [0, 1]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -313,12 +235,12 @@ jobs:
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Test car models
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
${{ env.RUN }} "$PYTEST selfdrive/car/tests/test_models.py && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
env:
|
||||
NUM_JOBS: 5
|
||||
NUM_JOBS: 2
|
||||
JOB_ID: ${{ matrix.job }}
|
||||
- name: "Upload coverage to Codecov"
|
||||
uses: codecov/codecov-action@v4
|
||||
|
||||
@@ -5,8 +5,8 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DAYS_BEFORE_PR_CLOSE: 7
|
||||
DAYS_BEFORE_PR_STALE: 30
|
||||
DAYS_BEFORE_PR_CLOSE: 3
|
||||
DAYS_BEFORE_PR_STALE: 14
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
@@ -20,23 +20,6 @@ env:
|
||||
|
||||
|
||||
jobs:
|
||||
plotjuggler:
|
||||
name: plotjuggler
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot
|
||||
timeout-minutes: 5
|
||||
run: ${{ env.RUN }} "scons -j$(nproc) cereal/ common/ opendbc/ --minimal"
|
||||
- name: Test PlotJuggler
|
||||
timeout-minutes: 2
|
||||
run: |
|
||||
${{ env.RUN }} "pytest tools/plotjuggler/"
|
||||
|
||||
simulator_build:
|
||||
name: simulator docker build
|
||||
runs-on: ubuntu-latest
|
||||
@@ -66,8 +49,6 @@ jobs:
|
||||
submodules: true
|
||||
- run: git lfs pull
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build base docker image
|
||||
run: eval "$BUILD"
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
@@ -76,7 +57,7 @@ jobs:
|
||||
${{ env.RUN }} "export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \
|
||||
source selfdrive/test/setup_xvfb.sh && \
|
||||
source selfdrive/test/setup_vsound.sh && \
|
||||
CI=1 tools/sim/tests/test_metadrive_bridge.py"
|
||||
CI=1 pytest tools/sim/tests/test_metadrive_bridge.py"
|
||||
|
||||
devcontainer:
|
||||
name: devcontainer
|
||||
@@ -92,7 +73,7 @@ jobs:
|
||||
- name: Setup Dev Container CLI
|
||||
run: npm install -g @devcontainers/cli
|
||||
- name: Build dev container image
|
||||
run: devcontainer build --workspace-folder .
|
||||
run: ./scripts/retry.sh devcontainer build --workspace-folder .
|
||||
- name: Run dev container
|
||||
run: |
|
||||
mkdir -p /tmp/devcontainer_scons_cache/
|
||||
@@ -104,21 +85,3 @@ jobs:
|
||||
devcontainer exec --workspace-folder . pip install pip-install-test
|
||||
devcontainer exec --workspace-folder . touch /home/batman/.comma/auth.json
|
||||
devcontainer exec --workspace-folder . sudo touch /root/test.txt
|
||||
|
||||
notebooks:
|
||||
name: notebooks
|
||||
runs-on: ubuntu-latest
|
||||
if: false && github.repository == 'commaai/openpilot'
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot
|
||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Test notebooks
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
${{ env.RUN }} "pip install nbmake && pytest --nbmake tools/car_porting/examples/"
|
||||
|
||||
@@ -55,12 +55,8 @@ selfdrive/modeld/_modeld
|
||||
selfdrive/modeld/_dmonitoringmodeld
|
||||
/src/
|
||||
|
||||
one
|
||||
notebooks
|
||||
xx
|
||||
yy
|
||||
hyperthneed
|
||||
panda_jungle
|
||||
provisioning
|
||||
|
||||
.coverage*
|
||||
|
||||
@@ -33,7 +33,7 @@ repos:
|
||||
- -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints
|
||||
- --builtins clear,rare,informal,usage,code,names,en-GB_to_en-US
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.4.3
|
||||
rev: v0.4.4
|
||||
hooks:
|
||||
- id: ruff
|
||||
exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)'
|
||||
@@ -47,7 +47,7 @@ repos:
|
||||
args:
|
||||
- --local-partial-types
|
||||
- --explicit-package-bases
|
||||
exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(xx/)'
|
||||
exclude: '^(third_party/)|(body/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)'
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: cppcheck
|
||||
@@ -89,7 +89,7 @@ repos:
|
||||
entry: selfdrive/ui/tests/test_translations.py
|
||||
language: script
|
||||
pass_filenames: false
|
||||
files: 'selfdrive/ui/translations/*'
|
||||
files: '^selfdrive/ui/translations/'
|
||||
- repo: https://github.com/python-poetry/poetry
|
||||
rev: '1.8.0'
|
||||
hooks:
|
||||
@@ -98,6 +98,6 @@ repos:
|
||||
args:
|
||||
- --lock
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.28.2
|
||||
rev: 0.28.3
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
|
||||
@@ -13,14 +13,10 @@ ENV LANGUAGE en_US:en
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
|
||||
COPY tools/install_ubuntu_dependencies.sh /tmp/tools/
|
||||
RUN cd /tmp && \
|
||||
tools/install_ubuntu_dependencies.sh && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
rm -rf /tmp/* && \
|
||||
# remove unused architectures from gcc for panda
|
||||
RUN INSTALL_EXTRA_PACKAGES=no /tmp/tools/install_ubuntu_dependencies.sh && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||
cd /usr/lib/gcc/arm-none-eabi/9.2.1 && \
|
||||
rm -rf arm/ && \
|
||||
rm -rf thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp
|
||||
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 \
|
||||
@@ -83,4 +79,4 @@ RUN cd /tmp && \
|
||||
rm -rf /home/$USER/pyenv/versions/3.11.4/lib/python3.11/test
|
||||
|
||||
USER root
|
||||
RUN sudo git config --global --add safe.directory /tmp/openpilot
|
||||
RUN sudo git config --global --add safe.directory /tmp/openpilot
|
||||
|
||||
Vendored
+11
-10
@@ -183,7 +183,7 @@ node {
|
||||
deviceStage("onroad", "tici-needs-can", [], [
|
||||
// TODO: ideally, this test runs in master-ci, but it takes 5+m to build it
|
||||
//["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"],
|
||||
["build openpilot", "cd selfdrive/manager && ./build.py"],
|
||||
["build openpilot", "cd system/manager && ./build.py"],
|
||||
["check dirty", "release/check-dirty.sh"],
|
||||
["onroad tests", "pytest selfdrive/test/test_onroad.py -s"],
|
||||
["time to onroad", "pytest selfdrive/test/test_time_to_onroad.py"],
|
||||
@@ -191,52 +191,53 @@ node {
|
||||
},
|
||||
'HW + Unit Tests': {
|
||||
deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"],
|
||||
["test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"],
|
||||
["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"],
|
||||
["test pigeond", "pytest system/ubloxd/tests/test_pigeond.py"],
|
||||
["test manager", "pytest selfdrive/manager/test/test_manager.py"],
|
||||
["test manager", "pytest system/manager/test/test_manager.py"],
|
||||
])
|
||||
},
|
||||
'loopback': {
|
||||
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
|
||||
["build openpilot", "cd selfdrive/manager && ./build.py"],
|
||||
["build openpilot", "cd system/manager && ./build.py"],
|
||||
["test boardd loopback", "pytest selfdrive/boardd/tests/test_boardd_loopback.py"],
|
||||
])
|
||||
},
|
||||
'camerad': {
|
||||
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["test camerad", "pytest system/camerad/test/test_camerad.py"],
|
||||
["test exposure", "pytest system/camerad/test/test_exposure.py"],
|
||||
])
|
||||
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["test camerad", "pytest system/camerad/test/test_camerad.py"],
|
||||
["test exposure", "pytest system/camerad/test/test_exposure.py"],
|
||||
])
|
||||
},
|
||||
'sensord': {
|
||||
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["test sensord", "pytest system/sensord/tests/test_sensord.py"],
|
||||
])
|
||||
deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["test sensord", "pytest system/sensord/tests/test_sensord.py"],
|
||||
])
|
||||
},
|
||||
'replay': {
|
||||
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["model replay", "selfdrive/test/process_replay/model_replay.py"],
|
||||
])
|
||||
},
|
||||
'tizi': {
|
||||
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
||||
["build openpilot", "cd selfdrive/manager && ./build.py"],
|
||||
["build openpilot", "cd system/manager && ./build.py"],
|
||||
["test boardd loopback", "SINGLE_PANDA=1 pytest selfdrive/boardd/tests/test_boardd_loopback.py"],
|
||||
["test boardd spi", "pytest selfdrive/boardd/tests/test_boardd_spi.py"],
|
||||
["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"],
|
||||
["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"],
|
||||
["test hw", "pytest system/hardware/tici/tests/test_hardware.py"],
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
Version 0.9.7 (2024-05-XX)
|
||||
Version 0.9.7 (2024-06-11)
|
||||
========================
|
||||
* New driving model
|
||||
* Adjust driving personality with the follow distance button
|
||||
* Support for hybrid variants of supported Ford models
|
||||
* Added toggle to enable driver monitoring even when openpilot is not engaged
|
||||
* Support for hybrid variants of supported Ford models
|
||||
* Fingerprinting without the OBD-II port on all cars
|
||||
|
||||
Version 0.9.6 (2024-02-27)
|
||||
|
||||
+1
-1
Submodule cereal updated: 5ce28549ba...53cdd80fa8
@@ -7,7 +7,7 @@ from openpilot.system.version import get_version
|
||||
|
||||
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')
|
||||
|
||||
class Api():
|
||||
class Api:
|
||||
def __init__(self, dongle_id):
|
||||
self.dongle_id = dongle_id
|
||||
with open(Paths.persist_root()+'/comma/id_rsa') as f:
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from functools import cache
|
||||
|
||||
def gpio_init(pin: int, output: bool) -> None:
|
||||
try:
|
||||
@@ -35,7 +35,7 @@ def gpio_export(pin: int) -> None:
|
||||
except Exception:
|
||||
print(f"Failed to export gpio {pin}")
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
@cache
|
||||
def get_irq_action(irq: int) -> list[str]:
|
||||
try:
|
||||
with open(f"/sys/kernel/irq/{irq}/actions") as f:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "common/params.h"
|
||||
#include "common/util.h"
|
||||
#include "system/hardware/hw.h"
|
||||
|
||||
class OpenpilotPrefix {
|
||||
public:
|
||||
@@ -25,6 +26,10 @@ public:
|
||||
system(util::string_format("rm %s -rf", real_path.c_str()).c_str());
|
||||
unlink(param_path.c_str());
|
||||
}
|
||||
if (getenv("COMMA_CACHE") == nullptr) {
|
||||
system(util::string_format("rm %s -rf", Path::download_cache_root().c_str()).c_str());
|
||||
}
|
||||
system(util::string_format("rm %s -rf", Path::comma_home().c_str()).c_str());
|
||||
system(util::string_format("rm %s -rf", msgq_path.c_str()).c_str());
|
||||
unsetenv("OPENPILOT_PREFIX");
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import subprocess
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
|
||||
|
||||
class Spinner():
|
||||
class Spinner:
|
||||
def __init__(self):
|
||||
try:
|
||||
self.spinner_proc = subprocess.Popen(["./spinner"],
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import numpy as np
|
||||
|
||||
class RunningStat():
|
||||
class RunningStat:
|
||||
# tracks realtime mean and standard deviation without storing any data
|
||||
def __init__(self, priors=None, max_trackable=-1):
|
||||
self.max_trackable = max_trackable
|
||||
@@ -51,7 +51,7 @@ class RunningStat():
|
||||
def params_to_save(self):
|
||||
return [self.M, self.S, self.n]
|
||||
|
||||
class RunningStatFilter():
|
||||
class RunningStatFilter:
|
||||
def __init__(self, raw_priors=None, filtered_priors=None, max_trackable=-1):
|
||||
self.raw_stat = RunningStat(raw_priors, -1)
|
||||
self.filtered_stat = RunningStat(filtered_priors, max_trackable)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import os
|
||||
import unittest
|
||||
from uuid import uuid4
|
||||
|
||||
from openpilot.common.file_helpers import atomic_write_in_dir
|
||||
|
||||
|
||||
class TestFileHelpers(unittest.TestCase):
|
||||
class TestFileHelpers:
|
||||
def run_atomic_write_func(self, atomic_write_func):
|
||||
path = f"/tmp/tmp{uuid4()}"
|
||||
with atomic_write_func(path) as f:
|
||||
@@ -13,12 +12,8 @@ class TestFileHelpers(unittest.TestCase):
|
||||
assert not os.path.exists(path)
|
||||
|
||||
with open(path) as f:
|
||||
self.assertEqual(f.read(), "test")
|
||||
assert f.read() == "test"
|
||||
os.remove(path)
|
||||
|
||||
def test_atomic_write_in_dir(self):
|
||||
self.run_atomic_write_func(atomic_write_in_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import numpy as np
|
||||
import unittest
|
||||
|
||||
from openpilot.common.numpy_fast import interp
|
||||
|
||||
|
||||
class InterpTest(unittest.TestCase):
|
||||
class TestInterp:
|
||||
def test_correctness_controls(self):
|
||||
_A_CRUISE_MIN_BP = np.asarray([0., 5., 10., 20., 40.])
|
||||
_A_CRUISE_MIN_V = np.asarray([-1.0, -.8, -.67, -.5, -.30])
|
||||
@@ -20,7 +19,3 @@ class InterpTest(unittest.TestCase):
|
||||
expected = np.interp(v_ego, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V)
|
||||
actual = interp(v_ego, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V)
|
||||
np.testing.assert_equal(actual, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+12
-16
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
import unittest
|
||||
|
||||
from openpilot.common.params import Params, ParamKeyType, UnknownKeyName
|
||||
|
||||
class TestParams(unittest.TestCase):
|
||||
def setUp(self):
|
||||
class TestParams:
|
||||
def setup_method(self):
|
||||
self.params = Params()
|
||||
|
||||
def test_params_put_and_get(self):
|
||||
@@ -49,16 +49,16 @@ class TestParams(unittest.TestCase):
|
||||
assert self.params.get("CarParams", True) == b"test"
|
||||
|
||||
def test_params_unknown_key_fails(self):
|
||||
with self.assertRaises(UnknownKeyName):
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.get("swag")
|
||||
|
||||
with self.assertRaises(UnknownKeyName):
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.get_bool("swag")
|
||||
|
||||
with self.assertRaises(UnknownKeyName):
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.put("swag", "abc")
|
||||
|
||||
with self.assertRaises(UnknownKeyName):
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.put_bool("swag", True)
|
||||
|
||||
def test_remove_not_there(self):
|
||||
@@ -68,19 +68,19 @@ class TestParams(unittest.TestCase):
|
||||
|
||||
def test_get_bool(self):
|
||||
self.params.remove("IsMetric")
|
||||
self.assertFalse(self.params.get_bool("IsMetric"))
|
||||
assert not self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put_bool("IsMetric", True)
|
||||
self.assertTrue(self.params.get_bool("IsMetric"))
|
||||
assert self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put_bool("IsMetric", False)
|
||||
self.assertFalse(self.params.get_bool("IsMetric"))
|
||||
assert not self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put("IsMetric", "1")
|
||||
self.assertTrue(self.params.get_bool("IsMetric"))
|
||||
assert self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put("IsMetric", "0")
|
||||
self.assertFalse(self.params.get_bool("IsMetric"))
|
||||
assert not self.params.get_bool("IsMetric")
|
||||
|
||||
def test_put_non_blocking_with_get_block(self):
|
||||
q = Params()
|
||||
@@ -107,7 +107,3 @@ class TestParams(unittest.TestCase):
|
||||
assert len(keys) > 20
|
||||
assert len(keys) == len(set(keys))
|
||||
assert b"CarParams" in keys
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import unittest
|
||||
|
||||
from openpilot.common.simple_kalman import KF1D
|
||||
|
||||
|
||||
class TestSimpleKalman(unittest.TestCase):
|
||||
def setUp(self):
|
||||
class TestSimpleKalman:
|
||||
def setup_method(self):
|
||||
dt = 0.01
|
||||
x0_0 = 0.0
|
||||
x1_0 = 0.0
|
||||
@@ -24,12 +22,8 @@ class TestSimpleKalman(unittest.TestCase):
|
||||
|
||||
def test_getter_setter(self):
|
||||
self.kf.set_x([[1.0], [1.0]])
|
||||
self.assertEqual(self.kf.x, [[1.0], [1.0]])
|
||||
assert self.kf.x == [[1.0], [1.0]]
|
||||
|
||||
def update_returns_state(self):
|
||||
x = self.kf.update(100)
|
||||
self.assertEqual(x, self.kf.x)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert x == self.kf.x
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
|
||||
import openpilot.common.transformations.coordinates as coord
|
||||
|
||||
@@ -44,7 +43,7 @@ ned_offsets_batch = np.array([[ 53.88103168, 43.83445935, -46.27488057],
|
||||
[ 78.56272609, 18.53100158, -43.25290759]])
|
||||
|
||||
|
||||
class TestNED(unittest.TestCase):
|
||||
class TestNED:
|
||||
def test_small_distances(self):
|
||||
start_geodetic = np.array([33.8042184, -117.888593, 0.0])
|
||||
local_coord = coord.LocalCoord.from_geodetic(start_geodetic)
|
||||
@@ -54,13 +53,13 @@ class TestNED(unittest.TestCase):
|
||||
|
||||
west_geodetic = start_geodetic + [0, -0.0005, 0]
|
||||
west_ned = local_coord.geodetic2ned(west_geodetic)
|
||||
self.assertLess(np.abs(west_ned[0]), 1e-3)
|
||||
self.assertLess(west_ned[1], 0)
|
||||
assert np.abs(west_ned[0]) < 1e-3
|
||||
assert west_ned[1] < 0
|
||||
|
||||
southwest_geodetic = start_geodetic + [-0.0005, -0.002, 0]
|
||||
southwest_ned = local_coord.geodetic2ned(southwest_geodetic)
|
||||
self.assertLess(southwest_ned[0], 0)
|
||||
self.assertLess(southwest_ned[1], 0)
|
||||
assert southwest_ned[0] < 0
|
||||
assert southwest_ned[1] < 0
|
||||
|
||||
def test_ecef_geodetic(self):
|
||||
# testing single
|
||||
@@ -105,5 +104,3 @@ class TestNED(unittest.TestCase):
|
||||
np.testing.assert_allclose(converter.ned2ecef(ned_offsets_batch),
|
||||
ecef_positions_offset_batch,
|
||||
rtol=1e-9, atol=1e-7)
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
|
||||
from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \
|
||||
rot2quat, quat2rot, \
|
||||
@@ -32,7 +31,7 @@ ned_eulers = np.array([[ 0.46806039, -0.4881889 , 1.65697808],
|
||||
[ 2.50450101, 0.36304151, 0.33136365]])
|
||||
|
||||
|
||||
class TestOrientation(unittest.TestCase):
|
||||
class TestOrientation:
|
||||
def test_quat_euler(self):
|
||||
for i, eul in enumerate(eulers):
|
||||
np.testing.assert_allclose(quats[i], euler2quat(eul), rtol=1e-7)
|
||||
@@ -62,7 +61,3 @@ class TestOrientation(unittest.TestCase):
|
||||
np.testing.assert_allclose(ned_eulers[i], ned_euler_from_ecef(ecef_positions[i], eulers[i]), rtol=1e-7)
|
||||
#np.testing.assert_allclose(eulers[i], ecef_euler_from_ned(ecef_positions[i], ned_eulers[i]), rtol=1e-7)
|
||||
# np.testing.assert_allclose(ned_eulers, ned_euler_from_ecef(ecef_positions, eulers), rtol=1e-7)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import pytest
|
||||
import random
|
||||
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.manager import manager
|
||||
from openpilot.system.manager import manager
|
||||
from openpilot.system.hardware import TICI, HARDWARE
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ Get paid to improve openpilot!
|
||||
* once you open a PR, the bounty is locked to you until you stop working on it
|
||||
* open a ticket at [comma.ai/support](https://comma.ai/support/shop-order) with links to your PRs to claim
|
||||
* get an extra 20% if you redeem your bounty in [comma shop](https://comma.ai/shop) credit (including refunds on previous orders)
|
||||
* for bounties >$100, the first PR gets a lock, which times out after a week of no progress
|
||||
|
||||
We put up each bounty with the intention that it'll get merged, but occasionally the right resolution is to close the bounty, which only becomes clear once some effort is put in.
|
||||
This is still valuable work, so we'll pay out $100 for getting any bounty closed with a good explanation.
|
||||
|
||||
+26
-24
@@ -4,7 +4,7 @@
|
||||
|
||||
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
|
||||
|
||||
# 286 Supported Cars
|
||||
# 288 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -49,7 +49,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Genesis|G70 2018|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2018">Buy Here</a></sub></details>||
|
||||
|Genesis|G70 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2019-21">Buy Here</a></sub></details>||
|
||||
|Genesis|G70 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2022-23">Buy Here</a></sub></details>||
|
||||
|Genesis|G80 2017|All|Stock|19 mph|37 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G80 2017">Buy Here</a></sub></details>||
|
||||
|Genesis|G80 2017|All|Stock|19 mph|37 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G80 2017">Buy Here</a></sub></details>||
|
||||
|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G80 2018-19">Buy Here</a></sub></details>||
|
||||
|Genesis|G90 2017-20|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G90 2017-20">Buy Here</a></sub></details>||
|
||||
|Genesis|GV60 (Advanced Trim) 2023[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV60 (Advanced Trim) 2023">Buy Here</a></sub></details>||
|
||||
@@ -83,12 +83,12 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Azera Hybrid 2019">Buy Here</a></sub></details>||
|
||||
|Hyundai|Azera Hybrid 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Azera Hybrid 2020">Buy Here</a></sub></details>||
|
||||
|Hyundai|Custin 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Custin 2023">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2017-18">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2019">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2017-18">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2019">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra GT 2017-19">Buy Here</a></sub></details>||
|
||||
|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra Hybrid 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Genesis 2015-16">Buy Here</a></sub></details>||
|
||||
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Genesis 2015-16">Buy Here</a></sub></details>||
|
||||
|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=i30 2017-19">Buy Here</a></sub></details>||
|
||||
|Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Ioniq 5 (Southeast Asia only) 2022-23">Buy Here</a></sub></details>||
|
||||
|Hyundai|Ioniq 5 (with HDA II) 2022-23[<sup>5</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Ioniq 5 (with HDA II) 2022-23">Buy Here</a></sub></details>||
|
||||
@@ -100,7 +100,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Ioniq Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>||
|
||||
|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Ioniq Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona 2020">Buy Here</a></sub></details>||
|
||||
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona 2020">Buy Here</a></sub></details>||
|
||||
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric 2018-21">Buy Here</a></sub></details>||
|
||||
|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric 2022-23">Buy Here</a></sub></details>||
|
||||
|Hyundai|Kona Electric (with HDA II, Korea only) 2023[<sup>5</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
@@ -115,22 +115,23 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Hyundai|Sonata 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Sonata 2020-23">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=ix63r9kE3Fw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Sonata Hybrid 2020-23">Buy Here</a></sub></details>||
|
||||
|Hyundai|Staria 2023[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Staria 2023">Buy Here</a></sub></details>||
|
||||
|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson 2021">Buy Here</a></sub></details>||
|
||||
|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson 2021">Buy Here</a></sub></details>||
|
||||
|Hyundai|Tucson 2022[<sup>5</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson 2022">Buy Here</a></sub></details>||
|
||||
|Hyundai|Tucson 2023-24[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson 2023-24">Buy Here</a></sub></details>||
|
||||
|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson Diesel 2019">Buy Here</a></sub></details>||
|
||||
|Hyundai|Tucson Hybrid 2022-24[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson Hybrid 2022-24">Buy Here</a></sub></details>||
|
||||
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Veloster 2019-20">Buy Here</a></sub></details>||
|
||||
|Hyundai|Tucson Plug-in Hybrid 2024[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson Plug-in Hybrid 2024">Buy Here</a></sub></details>||
|
||||
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Veloster 2019-20">Buy Here</a></sub></details>||
|
||||
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Jeep&model=Grand Cherokee 2016-18">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=eLR9o2JkuRk" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Jeep&model=Grand Cherokee 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=jBe4lWnRSu4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Kia|Carnival 2022-24[<sup>5</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Carnival 2022-24">Buy Here</a></sub></details>||
|
||||
|Kia|Carnival (China only) 2023[<sup>5</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Carnival (China only) 2023">Buy Here</a></sub></details>||
|
||||
|Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Ceed 2019">Buy Here</a></sub></details>||
|
||||
|Kia|EV6 (Southeast Asia only) 2022-23[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (Southeast Asia only) 2022-23">Buy Here</a></sub></details>||
|
||||
|Kia|EV6 (with HDA II) 2022-23[<sup>5</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (with HDA II) 2022-23">Buy Here</a></sub></details>||
|
||||
|Kia|EV6 (without HDA II) 2022-23[<sup>5</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (without HDA II) 2022-23">Buy Here</a></sub></details>||
|
||||
|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Forte 2019-21">Buy Here</a></sub></details>||
|
||||
|Kia|Forte 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Forte 2023">Buy Here</a></sub></details>||
|
||||
|Kia|EV6 (Southeast Asia only) 2022-24[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (Southeast Asia only) 2022-24">Buy Here</a></sub></details>||
|
||||
|Kia|EV6 (with HDA II) 2022-24[<sup>5</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (with HDA II) 2022-24">Buy Here</a></sub></details>||
|
||||
|Kia|EV6 (without HDA II) 2022-24[<sup>5</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (without HDA II) 2022-24">Buy Here</a></sub></details>||
|
||||
|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Forte 2019-21">Buy Here</a></sub></details>||
|
||||
|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Forte 2022-23">Buy Here</a></sub></details>||
|
||||
|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=K5 2021-24">Buy Here</a></sub></details>||
|
||||
|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=K5 Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Kia|K8 Hybrid (with HDA II) 2023[<sup>5</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=K8 Hybrid (with HDA II) 2023">Buy Here</a></sub></details>||
|
||||
@@ -139,11 +140,11 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Kia|Niro EV 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro EV 2021">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Kia|Niro EV 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro EV 2022">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Kia|Niro EV 2023[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro EV 2023">Buy Here</a></sub></details>||
|
||||
|Kia|Niro Hybrid 2018|All|Stock|10 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro Hybrid 2018">Buy Here</a></sub></details>||
|
||||
|Kia|Niro Hybrid 2018|All|Stock|10 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro Hybrid 2018">Buy Here</a></sub></details>||
|
||||
|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro Hybrid 2021">Buy Here</a></sub></details>||
|
||||
|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro Hybrid 2022">Buy Here</a></sub></details>||
|
||||
|Kia|Niro Hybrid 2023[<sup>5</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro Hybrid 2023">Buy Here</a></sub></details>||
|
||||
|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro Plug-in Hybrid 2018-19">Buy Here</a></sub></details>||
|
||||
|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro Plug-in Hybrid 2018-19">Buy Here</a></sub></details>||
|
||||
|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro Plug-in Hybrid 2020">Buy Here</a></sub></details>||
|
||||
|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro Plug-in Hybrid 2021">Buy Here</a></sub></details>||
|
||||
|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Niro Plug-in Hybrid 2022">Buy Here</a></sub></details>||
|
||||
@@ -232,14 +233,14 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Camry Hybrid 2021-24">Buy Here</a></sub></details>||
|
||||
|Toyota|Corolla 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Corolla 2017-19">Buy Here</a></sub></details>||
|
||||
|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Corolla 2020-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=_66pXk0CBYA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Corolla Cross (Non-US only) 2020-23">Buy Here</a></sub></details>||
|
||||
|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Corolla Cross Hybrid (Non-US only) 2020-22">Buy Here</a></sub></details>||
|
||||
|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Corolla Cross (Non-US only) 2020-23">Buy Here</a></sub></details>||
|
||||
|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Corolla Cross Hybrid (Non-US only) 2020-22">Buy Here</a></sub></details>||
|
||||
|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Corolla Hatchback 2019-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=_66pXk0CBYA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Corolla Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Toyota|Corolla Hybrid (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Corolla Hybrid (Non-US only) 2020-23">Buy Here</a></sub></details>||
|
||||
|Toyota|Highlander 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Highlander 2017-19">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=0wS0wXSLzoo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Corolla Hybrid (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Corolla Hybrid (Non-US only) 2020-23">Buy Here</a></sub></details>||
|
||||
|Toyota|Highlander 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Highlander 2017-19">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=0wS0wXSLzoo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Highlander 2020-23">Buy Here</a></sub></details>||
|
||||
|Toyota|Highlander Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Highlander Hybrid 2017-19">Buy Here</a></sub></details>||
|
||||
|Toyota|Highlander Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Highlander Hybrid 2017-19">Buy Here</a></sub></details>||
|
||||
|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Highlander Hybrid 2020-23">Buy Here</a></sub></details>||
|
||||
|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Mirai 2021">Buy Here</a></sub></details>||
|
||||
|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Prius 2016">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=8zopPJI8XQ0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
@@ -247,21 +248,22 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Prius 2021-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=J58TvCpUd4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Prius Prime 2017-20|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Prius Prime 2017-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=8zopPJI8XQ0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Prius Prime 2021-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=J58TvCpUd4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Prius v 2017">Buy Here</a></sub></details>||
|
||||
|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Prius v 2017">Buy Here</a></sub></details>||
|
||||
|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2016">Buy Here</a></sub></details>||
|
||||
|Toyota|RAV4 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2017-18">Buy Here</a></sub></details>||
|
||||
|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=wJxjDd42gGA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|RAV4 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2022">Buy Here</a></sub></details>||
|
||||
|Toyota|RAV4 2023-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2023-24">Buy Here</a></sub></details>||
|
||||
|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2016">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2017-18">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2016">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2017-18">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2019-21">Buy Here</a></sub></details>||
|
||||
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|RAV4 Hybrid 2023-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2023-24">Buy Here</a></sub></details>||
|
||||
|Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Arteon Shooting Brake 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Atlas 2018-23">Buy Here</a></sub></details>||
|
||||
|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Atlas Cross Sport 2020-22">Buy Here</a></sub></details>||
|
||||
|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=California 2021-23">Buy Here</a></sub></details>||
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ function launch {
|
||||
tmux capture-pane -pq -S-1000 > /tmp/launch_log
|
||||
|
||||
# start manager
|
||||
cd selfdrive/manager
|
||||
cd system/manager
|
||||
if [ ! -f $DIR/prebuilt ]; then
|
||||
./build.py
|
||||
fi
|
||||
|
||||
+1
-1
Submodule opendbc updated: 9876e5827d...0c00eae7a5
+1
-1
Submodule panda updated: a6eb8dd7be...3d77f11ca1
Generated
+539
-500
File diff suppressed because it is too large
Load Diff
+36
-8
@@ -1,3 +1,12 @@
|
||||
[project]
|
||||
name = "openpilot"
|
||||
requires-python = ">= 3.11"
|
||||
readme = "README.md"
|
||||
license = {text = "MIT License"}
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://comma.ai"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "6.0"
|
||||
addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup"
|
||||
@@ -11,17 +20,17 @@ markers = [
|
||||
]
|
||||
testpaths = [
|
||||
"common",
|
||||
"selfdrive/athena",
|
||||
"selfdrive/boardd",
|
||||
"selfdrive/car",
|
||||
"selfdrive/controls",
|
||||
"selfdrive/locationd",
|
||||
"selfdrive/monitoring",
|
||||
"selfdrive/navd/tests",
|
||||
"selfdrive/thermald",
|
||||
"selfdrive/test/longitudinal_maneuvers",
|
||||
"selfdrive/test/process_replay/test_fuzzy.py",
|
||||
"selfdrive/updated",
|
||||
"system/updated",
|
||||
"system/thermald",
|
||||
"system/athena",
|
||||
"system/camerad",
|
||||
"system/hardware/tici",
|
||||
"system/loggerd",
|
||||
@@ -125,14 +134,14 @@ breathe = "*"
|
||||
control = "*"
|
||||
coverage = "*"
|
||||
dictdiffer = "*"
|
||||
ft4222 = "*"
|
||||
flaky = "*"
|
||||
hypothesis = "~6.47"
|
||||
inputs = "*"
|
||||
Jinja2 = "*"
|
||||
lru-dict = "*"
|
||||
matplotlib = "*"
|
||||
metadrive-simulator = { version = "0.4.2.3", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies
|
||||
# No release for this fix https://github.com/metadriverse/metadrive/issues/632. Pinned to this commit until next release
|
||||
metadrive-simulator = {git = "https://github.com/metadriverse/metadrive.git", rev ="233a3a1698be7038ec3dd050ca10b547b4b3324c", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies
|
||||
mpld3 = "*"
|
||||
mypy = "*"
|
||||
myst-parser = "*"
|
||||
@@ -154,12 +163,14 @@ pytest-subtests = "*"
|
||||
pytest-xdist = "*"
|
||||
pytest-timeout = "*"
|
||||
pytest-randomly = "*"
|
||||
pytest-asyncio = "*"
|
||||
pytest-mock = "*"
|
||||
rerun-sdk = "*"
|
||||
ruff = "*"
|
||||
sphinx = "*"
|
||||
sphinx-rtd-theme = "*"
|
||||
sphinx-sitemap = "*"
|
||||
tabulate = "*"
|
||||
tenacity = "*"
|
||||
types-requests = "*"
|
||||
types-tabulate = "*"
|
||||
tqdm = "*"
|
||||
@@ -174,8 +185,24 @@ build-backend = "poetry.core.masonry.api"
|
||||
# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml
|
||||
[tool.ruff]
|
||||
indent-width = 2
|
||||
lint.select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF008", "RUF100", "A", "B", "TID251"]
|
||||
lint.ignore = ["E741", "E402", "C408", "ISC003", "B027", "B024"]
|
||||
lint.select = [
|
||||
"E", "F", "W", "PIE", "C4", "ISC", "A", "B",
|
||||
"NPY", # numpy
|
||||
"UP", # pyupgrade
|
||||
"TRY302", "TRY400", "TRY401", # try/excepts
|
||||
"RUF008", "RUF100",
|
||||
"TID251"
|
||||
]
|
||||
lint.ignore = [
|
||||
"E741",
|
||||
"E402",
|
||||
"C408",
|
||||
"ISC003",
|
||||
"B027",
|
||||
"B024",
|
||||
"NPY002", # new numpy random syntax is worse
|
||||
"UP038", # (x, y) -> x|y for isinstance
|
||||
]
|
||||
line-length = 160
|
||||
target-version="py311"
|
||||
exclude = [
|
||||
@@ -197,6 +224,7 @@ lint.flake8-implicit-str-concat.allow-multiline=false
|
||||
"third_party".msg = "Use openpilot.third_party"
|
||||
"tools".msg = "Use openpilot.tools"
|
||||
"pytest.main".msg = "pytest.main requires special handling that is easy to mess up!"
|
||||
"unittest".msg = "Use pytest"
|
||||
|
||||
[tool.coverage.run]
|
||||
concurrency = ["multiprocessing", "thread"]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
@@ -44,7 +43,7 @@ git clean -xdff
|
||||
# do the files copy
|
||||
echo "[-] copying files T=$SECONDS"
|
||||
cd $SOURCE_DIR
|
||||
cp -pR --parents $(cat release/files_*) $TARGET_DIR/
|
||||
cp -pR --parents $(./release/release_files.py) $TARGET_DIR/
|
||||
|
||||
# in the directory
|
||||
cd $TARGET_DIR
|
||||
@@ -64,6 +63,13 @@ date: $DATETIME
|
||||
master commit: $GIT_HASH
|
||||
"
|
||||
|
||||
# should be no submodules or LFS files
|
||||
git submodule status
|
||||
if [ ! -z "$(git lfs ls-files)" ]; then
|
||||
echo "LFS files detected!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ensure files are within GitHub's limit
|
||||
BIG_FILES="$(find . -type f -not -path './.git/*' -size +95M)"
|
||||
if [ ! -z "$BIG_FILES" ]; then
|
||||
|
||||
+10
-12
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/bash -e
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# git diff --name-status origin/release3-staging | grep "^A" | less
|
||||
|
||||
@@ -9,13 +11,6 @@ cd $DIR
|
||||
BUILD_DIR=/data/openpilot
|
||||
SOURCE_DIR="$(git rev-parse --show-toplevel)"
|
||||
|
||||
if [ -f /TICI ]; then
|
||||
FILES_SRC="release/files_tici"
|
||||
else
|
||||
echo "no release files set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$RELEASE_BRANCH" ]; then
|
||||
echo "RELEASE_BRANCH is not set"
|
||||
exit 1
|
||||
@@ -36,8 +31,7 @@ git checkout --orphan $RELEASE_BRANCH
|
||||
# do the files copy
|
||||
echo "[-] copying files T=$SECONDS"
|
||||
cd $SOURCE_DIR
|
||||
cp -pR --parents $(cat release/files_common) $BUILD_DIR/
|
||||
cp -pR --parents $(cat $FILES_SRC) $BUILD_DIR/
|
||||
cp -pR --parents $(./release/release_files.py) $BUILD_DIR/
|
||||
|
||||
# in the directory
|
||||
cd $BUILD_DIR
|
||||
@@ -54,7 +48,7 @@ git commit -a -m "openpilot v$VERSION release"
|
||||
|
||||
# Build
|
||||
export PYTHONPATH="$BUILD_DIR"
|
||||
scons -j$(nproc)
|
||||
scons -j$(nproc) --minimal
|
||||
|
||||
# release panda fw
|
||||
CERT=/data/pandaextra/certs/release RELEASE=1 scons -j$(nproc) panda/
|
||||
@@ -77,6 +71,10 @@ find . -name '__pycache__' -delete
|
||||
rm -rf .sconsign.dblite Jenkinsfile release/
|
||||
rm selfdrive/modeld/models/supercombo.onnx
|
||||
|
||||
find third_party/ -name '*x86*' -exec rm -r {} +
|
||||
find third_party/ -name '*Darwin*' -exec rm -r {} +
|
||||
|
||||
|
||||
# Restore third_party
|
||||
git checkout third_party/
|
||||
|
||||
@@ -93,7 +91,7 @@ cd $SOURCE_DIR
|
||||
cp -pR -n --parents $TEST_FILES $BUILD_DIR/
|
||||
cd $BUILD_DIR
|
||||
RELEASE=1 selfdrive/test/test_onroad.py
|
||||
#selfdrive/manager/test/test_manager.py
|
||||
#system/manager/test/test_manager.py
|
||||
selfdrive/car/tests/test_car_interfaces.py
|
||||
rm -rf $TEST_FILES
|
||||
|
||||
|
||||
@@ -1,570 +0,0 @@
|
||||
.gitignore
|
||||
LICENSE
|
||||
launch_env.sh
|
||||
launch_chffrplus.sh
|
||||
launch_openpilot.sh
|
||||
|
||||
Jenkinsfile
|
||||
SConstruct
|
||||
pyproject.toml
|
||||
|
||||
README.md
|
||||
RELEASES.md
|
||||
docs/CARS.md
|
||||
docs/CONTRIBUTING.md
|
||||
docs/INTEGRATION.md
|
||||
docs/LIMITATIONS.md
|
||||
site_scons/site_tools/cython.py
|
||||
|
||||
openpilot/__init__.py
|
||||
openpilot/**
|
||||
|
||||
common/.gitignore
|
||||
common/__init__.py
|
||||
common/*.py
|
||||
common/*.pyx
|
||||
common/mock/*
|
||||
|
||||
common/transformations/__init__.py
|
||||
common/transformations/camera.py
|
||||
common/transformations/model.py
|
||||
|
||||
common/transformations/SConscript
|
||||
common/transformations/coordinates.py
|
||||
common/transformations/coordinates.cc
|
||||
common/transformations/coordinates.hpp
|
||||
common/transformations/orientation.py
|
||||
common/transformations/orientation.cc
|
||||
common/transformations/orientation.hpp
|
||||
common/transformations/transformations.pxd
|
||||
common/transformations/transformations.pyx
|
||||
|
||||
common/api/__init__.py
|
||||
|
||||
release/*
|
||||
|
||||
tools/__init__.py
|
||||
tools/lib/*
|
||||
tools/bodyteleop/.gitignore
|
||||
tools/bodyteleop/web.py
|
||||
tools/bodyteleop/static/*
|
||||
tools/joystick/*
|
||||
tools/replay/*.cc
|
||||
tools/replay/*.h
|
||||
|
||||
selfdrive/__init__.py
|
||||
selfdrive/sentry.py
|
||||
selfdrive/tombstoned.py
|
||||
selfdrive/statsd.py
|
||||
|
||||
selfdrive/updated/**
|
||||
|
||||
system/logmessaged.py
|
||||
system/micd.py
|
||||
system/version.py
|
||||
|
||||
selfdrive/SConscript
|
||||
|
||||
selfdrive/athena/__init__.py
|
||||
selfdrive/athena/athenad.py
|
||||
selfdrive/athena/manage_athenad.py
|
||||
selfdrive/athena/registration.py
|
||||
|
||||
selfdrive/boardd/.gitignore
|
||||
selfdrive/boardd/SConscript
|
||||
selfdrive/boardd/__init__.py
|
||||
selfdrive/boardd/boardd.cc
|
||||
selfdrive/boardd/boardd.h
|
||||
selfdrive/boardd/main.cc
|
||||
selfdrive/boardd/boardd.py
|
||||
selfdrive/boardd/boardd_api_impl.pyx
|
||||
selfdrive/boardd/can_list_to_can_capnp.cc
|
||||
selfdrive/boardd/panda.cc
|
||||
selfdrive/boardd/panda.h
|
||||
selfdrive/boardd/spi.cc
|
||||
selfdrive/boardd/panda_comms.h
|
||||
selfdrive/boardd/panda_comms.cc
|
||||
selfdrive/boardd/pandad.py
|
||||
selfdrive/boardd/tests/test_boardd_loopback.py
|
||||
|
||||
selfdrive/car/__init__.py
|
||||
selfdrive/car/card.py
|
||||
selfdrive/car/docs_definitions.py
|
||||
selfdrive/car/car_helpers.py
|
||||
selfdrive/car/fingerprints.py
|
||||
selfdrive/car/interfaces.py
|
||||
selfdrive/car/values.py
|
||||
selfdrive/car/vin.py
|
||||
selfdrive/car/disable_ecu.py
|
||||
selfdrive/car/fw_versions.py
|
||||
selfdrive/car/fw_query_definitions.py
|
||||
selfdrive/car/ecu_addrs.py
|
||||
selfdrive/car/isotp_parallel_query.py
|
||||
selfdrive/car/tests/__init__.py
|
||||
selfdrive/car/tests/test_car_interfaces.py
|
||||
selfdrive/car/torque_data/*
|
||||
|
||||
selfdrive/car/body/*.py
|
||||
selfdrive/car/chrysler/*.py
|
||||
selfdrive/car/ford/*.py
|
||||
selfdrive/car/gm/*.py
|
||||
selfdrive/car/honda/*.py
|
||||
selfdrive/car/hyundai/*.py
|
||||
selfdrive/car/mazda/*.py
|
||||
selfdrive/car/mock/*.py
|
||||
selfdrive/car/nissan/*.py
|
||||
selfdrive/car/subaru/*.py
|
||||
selfdrive/car/tesla/*.py
|
||||
selfdrive/car/toyota/*.py
|
||||
selfdrive/car/volkswagen/*.py
|
||||
|
||||
selfdrive/debug/can_printer.py
|
||||
selfdrive/debug/check_freq.py
|
||||
selfdrive/debug/dump.py
|
||||
selfdrive/debug/filter_log_message.py
|
||||
selfdrive/debug/format_fingerprints.py
|
||||
selfdrive/debug/get_fingerprint.py
|
||||
selfdrive/debug/uiview.py
|
||||
|
||||
selfdrive/debug/hyundai_enable_radar_points.py
|
||||
selfdrive/debug/vw_mqb_config.py
|
||||
|
||||
selfdrive/gpxd/gpx_uploader.py
|
||||
selfdrive/gpxd/gpxd.py
|
||||
|
||||
common/SConscript
|
||||
common/version.h
|
||||
|
||||
common/*.h
|
||||
common/*.cc
|
||||
|
||||
selfdrive/controls/__init__.py
|
||||
selfdrive/controls/controlsd.py
|
||||
selfdrive/controls/plannerd.py
|
||||
selfdrive/controls/radard.py
|
||||
selfdrive/controls/lib/__init__.py
|
||||
selfdrive/controls/lib/alertmanager.py
|
||||
selfdrive/controls/lib/alerts_offroad.json
|
||||
selfdrive/controls/lib/desire_helper.py
|
||||
selfdrive/controls/lib/drive_helpers.py
|
||||
selfdrive/controls/lib/events.py
|
||||
selfdrive/controls/lib/latcontrol_angle.py
|
||||
selfdrive/controls/lib/latcontrol_torque.py
|
||||
selfdrive/controls/lib/latcontrol_pid.py
|
||||
selfdrive/controls/lib/latcontrol.py
|
||||
selfdrive/controls/lib/longcontrol.py
|
||||
selfdrive/controls/lib/longitudinal_planner.py
|
||||
selfdrive/controls/lib/pid.py
|
||||
selfdrive/controls/lib/vehicle_model.py
|
||||
|
||||
selfdrive/controls/lib/lateral_mpc_lib/.gitignore
|
||||
selfdrive/controls/lib/longitudinal_mpc_lib/.gitignore
|
||||
selfdrive/controls/lib/lateral_mpc_lib/*
|
||||
selfdrive/controls/lib/longitudinal_mpc_lib/*
|
||||
|
||||
system/__init__.py
|
||||
system/*.py
|
||||
|
||||
system/hardware/__init__.py
|
||||
system/hardware/base.h
|
||||
system/hardware/base.py
|
||||
system/hardware/hw.h
|
||||
system/hardware/hw.py
|
||||
system/hardware/tici/__init__.py
|
||||
system/hardware/tici/hardware.h
|
||||
system/hardware/tici/hardware.py
|
||||
system/hardware/tici/pins.py
|
||||
system/hardware/tici/agnos.py
|
||||
system/hardware/tici/agnos.json
|
||||
system/hardware/tici/amplifier.py
|
||||
system/hardware/tici/updater
|
||||
system/hardware/tici/iwlist.py
|
||||
system/hardware/tici/esim.nmconnection
|
||||
system/hardware/pc/__init__.py
|
||||
system/hardware/pc/hardware.h
|
||||
system/hardware/pc/hardware.py
|
||||
|
||||
system/ubloxd/.gitignore
|
||||
system/ubloxd/SConscript
|
||||
system/ubloxd/pigeond.py
|
||||
system/ubloxd/generated/*
|
||||
system/ubloxd/*.h
|
||||
system/ubloxd/*.cc
|
||||
|
||||
system/updated/*
|
||||
|
||||
selfdrive/locationd/__init__.py
|
||||
selfdrive/locationd/SConscript
|
||||
selfdrive/locationd/.gitignore
|
||||
selfdrive/locationd/locationd.h
|
||||
selfdrive/locationd/locationd.cc
|
||||
selfdrive/locationd/paramsd.py
|
||||
selfdrive/locationd/models/__init__.py
|
||||
selfdrive/locationd/models/.gitignore
|
||||
selfdrive/locationd/models/car_kf.py
|
||||
selfdrive/locationd/models/live_kf.py
|
||||
selfdrive/locationd/models/live_kf.h
|
||||
selfdrive/locationd/models/live_kf.cc
|
||||
selfdrive/locationd/models/constants.py
|
||||
|
||||
selfdrive/locationd/torqued.py
|
||||
selfdrive/locationd/calibrationd.py
|
||||
selfdrive/locationd/helpers.py
|
||||
|
||||
system/logcatd/.gitignore
|
||||
system/logcatd/SConscript
|
||||
system/logcatd/logcatd_systemd.cc
|
||||
|
||||
system/proclogd/SConscript
|
||||
system/proclogd/main.cc
|
||||
system/proclogd/proclog.cc
|
||||
system/proclogd/proclog.h
|
||||
|
||||
system/loggerd/.gitignore
|
||||
system/loggerd/SConscript
|
||||
system/loggerd/encoder/encoder.cc
|
||||
system/loggerd/encoder/encoder.h
|
||||
system/loggerd/encoder/v4l_encoder.cc
|
||||
system/loggerd/encoder/v4l_encoder.h
|
||||
system/loggerd/video_writer.cc
|
||||
system/loggerd/video_writer.h
|
||||
system/loggerd/logger.cc
|
||||
system/loggerd/logger.h
|
||||
system/loggerd/loggerd.cc
|
||||
system/loggerd/loggerd.h
|
||||
system/loggerd/encoderd.cc
|
||||
system/loggerd/bootlog.cc
|
||||
system/loggerd/encoder/ffmpeg_encoder.cc
|
||||
system/loggerd/encoder/ffmpeg_encoder.h
|
||||
|
||||
system/loggerd/__init__.py
|
||||
system/loggerd/config.py
|
||||
system/loggerd/uploader.py
|
||||
system/loggerd/deleter.py
|
||||
system/loggerd/xattr_cache.py
|
||||
|
||||
system/sensord/.gitignore
|
||||
system/sensord/SConscript
|
||||
system/sensord/sensors_qcom2.cc
|
||||
system/sensord/sensors/*.cc
|
||||
system/sensord/sensors/*.h
|
||||
|
||||
system/webrtc/__init__.py
|
||||
system/webrtc/webrtcd.py
|
||||
system/webrtc/schema.py
|
||||
system/webrtc/device/audio.py
|
||||
system/webrtc/device/video.py
|
||||
|
||||
selfdrive/thermald/thermald.py
|
||||
selfdrive/thermald/power_monitoring.py
|
||||
selfdrive/thermald/fan_controller.py
|
||||
|
||||
selfdrive/test/__init__.py
|
||||
selfdrive/test/fuzzy_generation.py
|
||||
selfdrive/test/helpers.py
|
||||
selfdrive/test/setup_device_ci.sh
|
||||
selfdrive/test/test_onroad.py
|
||||
selfdrive/test/test_time_to_onroad.py
|
||||
|
||||
selfdrive/ui/.gitignore
|
||||
selfdrive/ui/SConscript
|
||||
selfdrive/ui/*.cc
|
||||
selfdrive/ui/*.h
|
||||
selfdrive/ui/text
|
||||
selfdrive/ui/spinner
|
||||
selfdrive/ui/soundd.py
|
||||
selfdrive/ui/translations/*.ts
|
||||
selfdrive/ui/translations/languages.json
|
||||
selfdrive/ui/update_translations.py
|
||||
selfdrive/ui/tests/test_translations.py
|
||||
|
||||
selfdrive/ui/qt/*.cc
|
||||
selfdrive/ui/qt/*.h
|
||||
selfdrive/ui/qt/network/*.cc
|
||||
selfdrive/ui/qt/network/*.h
|
||||
selfdrive/ui/qt/offroad/*.cc
|
||||
selfdrive/ui/qt/offroad/*.h
|
||||
selfdrive/ui/qt/offroad/*.qml
|
||||
selfdrive/ui/qt/onroad/*.cc
|
||||
selfdrive/ui/qt/onroad/*.h
|
||||
selfdrive/ui/qt/widgets/*.cc
|
||||
selfdrive/ui/qt/widgets/*.h
|
||||
selfdrive/ui/qt/maps/*.cc
|
||||
selfdrive/ui/qt/maps/*.h
|
||||
selfdrive/ui/qt/setup/*.cc
|
||||
selfdrive/ui/qt/setup/*.h
|
||||
|
||||
selfdrive/ui/installer/*.cc
|
||||
selfdrive/ui/installer/*.h
|
||||
selfdrive/ui/installer/*.cc
|
||||
|
||||
system/camerad/SConscript
|
||||
system/camerad/main.cc
|
||||
|
||||
system/camerad/snapshot/*
|
||||
system/camerad/cameras/camera_common.h
|
||||
system/camerad/cameras/camera_common.cc
|
||||
system/camerad/sensors/*.h
|
||||
system/camerad/sensors/*.cc
|
||||
|
||||
selfdrive/manager/__init__.py
|
||||
selfdrive/manager/build.py
|
||||
selfdrive/manager/helpers.py
|
||||
selfdrive/manager/manager.py
|
||||
selfdrive/manager/process_config.py
|
||||
selfdrive/manager/process.py
|
||||
selfdrive/manager/test/__init__.py
|
||||
selfdrive/manager/test/test_manager.py
|
||||
|
||||
selfdrive/modeld/.gitignore
|
||||
selfdrive/modeld/__init__.py
|
||||
selfdrive/modeld/SConscript
|
||||
selfdrive/modeld/modeld.py
|
||||
selfdrive/modeld/parse_model_outputs.py
|
||||
selfdrive/modeld/fill_model_msg.py
|
||||
selfdrive/modeld/get_model_metadata.py
|
||||
selfdrive/modeld/dmonitoringmodeld.py
|
||||
selfdrive/modeld/constants.py
|
||||
selfdrive/modeld/modeld
|
||||
|
||||
selfdrive/modeld/models/__init__.py
|
||||
selfdrive/modeld/models/*.pxd
|
||||
selfdrive/modeld/models/*.pyx
|
||||
|
||||
selfdrive/modeld/models/commonmodel.cc
|
||||
selfdrive/modeld/models/commonmodel.h
|
||||
|
||||
selfdrive/modeld/models/supercombo.onnx
|
||||
|
||||
selfdrive/modeld/models/dmonitoring_model_q.dlc
|
||||
|
||||
selfdrive/modeld/transforms/loadyuv.cc
|
||||
selfdrive/modeld/transforms/loadyuv.h
|
||||
selfdrive/modeld/transforms/loadyuv.cl
|
||||
selfdrive/modeld/transforms/transform.cc
|
||||
selfdrive/modeld/transforms/transform.h
|
||||
selfdrive/modeld/transforms/transform.cl
|
||||
|
||||
selfdrive/modeld/thneed/*.py
|
||||
selfdrive/modeld/thneed/thneed.h
|
||||
selfdrive/modeld/thneed/thneed_common.cc
|
||||
selfdrive/modeld/thneed/thneed_qcom2.cc
|
||||
selfdrive/modeld/thneed/serialize.cc
|
||||
|
||||
selfdrive/modeld/runners/__init__.py
|
||||
selfdrive/modeld/runners/*.pxd
|
||||
selfdrive/modeld/runners/*.pyx
|
||||
selfdrive/modeld/runners/*.cc
|
||||
selfdrive/modeld/runners/*.h
|
||||
selfdrive/modeld/runners/*.py
|
||||
|
||||
selfdrive/monitoring/dmonitoringd.py
|
||||
selfdrive/monitoring/driver_monitor.py
|
||||
|
||||
selfdrive/navd/.gitignore
|
||||
selfdrive/navd/__init__.py
|
||||
selfdrive/navd/**
|
||||
|
||||
selfdrive/assets/.gitignore
|
||||
selfdrive/assets/assets.qrc
|
||||
selfdrive/assets/*.png
|
||||
selfdrive/assets/*.svg
|
||||
selfdrive/assets/body/*
|
||||
selfdrive/assets/fonts/*.ttf
|
||||
selfdrive/assets/icons/*
|
||||
selfdrive/assets/images/*
|
||||
selfdrive/assets/offroad/*
|
||||
selfdrive/assets/sounds/*
|
||||
selfdrive/assets/training/*
|
||||
selfdrive/assets/navigation/*
|
||||
|
||||
third_party/.gitignore
|
||||
third_party/SConscript
|
||||
|
||||
third_party/linux/**
|
||||
third_party/opencl/**
|
||||
|
||||
third_party/json11/json11.cpp
|
||||
third_party/json11/json11.hpp
|
||||
|
||||
third_party/qrcode/*.cc
|
||||
third_party/qrcode/*.hpp
|
||||
|
||||
third_party/kaitai/*.h
|
||||
third_party/kaitai/*.cpp
|
||||
|
||||
third_party/libyuv/include/**
|
||||
|
||||
third_party/snpe/include/**
|
||||
third_party/snpe/dsp**
|
||||
|
||||
third_party/acados/.gitignore
|
||||
third_party/acados/include/**
|
||||
third_party/acados/acados_template/**
|
||||
|
||||
third_party/bootstrap/**
|
||||
third_party/qt5/larch64/bin/**
|
||||
third_party/maplibre-native-qt/**
|
||||
|
||||
scripts/update_now.sh
|
||||
scripts/stop_updater.sh
|
||||
|
||||
teleoprtc/**
|
||||
|
||||
rednose_repo/site_scons/site_tools/rednose_filter.py
|
||||
rednose/.gitignore
|
||||
rednose/**
|
||||
|
||||
body/.gitignore
|
||||
body/board/SConscript
|
||||
body/board/*.h
|
||||
body/board/*.c
|
||||
body/board/*.s
|
||||
body/board/*.ld
|
||||
body/board/inc/**
|
||||
body/board/obj/
|
||||
body/board/bldc/**
|
||||
body/board/drivers/**
|
||||
body/certs/**
|
||||
body/crypto/**
|
||||
|
||||
cereal/.gitignore
|
||||
cereal/__init__.py
|
||||
cereal/car.capnp
|
||||
cereal/custom.capnp
|
||||
cereal/legacy.capnp
|
||||
cereal/log.capnp
|
||||
cereal/services.py
|
||||
cereal/SConscript
|
||||
cereal/include/**
|
||||
cereal/logger/logger.h
|
||||
cereal/messaging/.gitignore
|
||||
cereal/messaging/__init__.py
|
||||
cereal/messaging/bridge.cc
|
||||
cereal/messaging/event.cc
|
||||
cereal/messaging/event.h
|
||||
cereal/messaging/impl_fake.cc
|
||||
cereal/messaging/impl_fake.h
|
||||
cereal/messaging/impl_msgq.cc
|
||||
cereal/messaging/impl_msgq.h
|
||||
cereal/messaging/impl_zmq.cc
|
||||
cereal/messaging/impl_zmq.h
|
||||
cereal/messaging/messaging.cc
|
||||
cereal/messaging/messaging.h
|
||||
cereal/messaging/messaging.pxd
|
||||
cereal/messaging/messaging_pyx.pyx
|
||||
cereal/messaging/msgq.cc
|
||||
cereal/messaging/msgq.h
|
||||
cereal/messaging/socketmaster.cc
|
||||
cereal/visionipc/.gitignore
|
||||
cereal/visionipc/__init__.py
|
||||
cereal/visionipc/*.cc
|
||||
cereal/visionipc/*.h
|
||||
cereal/visionipc/*.pyx
|
||||
cereal/visionipc/*.pxd
|
||||
|
||||
panda/.gitignore
|
||||
panda/__init__.py
|
||||
panda/SConscript
|
||||
panda/board/**
|
||||
panda/certs/**
|
||||
panda/crypto/**
|
||||
panda/examples/query_fw_versions.py
|
||||
panda/python/**
|
||||
|
||||
opendbc/.gitignore
|
||||
opendbc/__init__.py
|
||||
opendbc/can/__init__.py
|
||||
opendbc/can/SConscript
|
||||
opendbc/can/can_define.py
|
||||
opendbc/can/common.cc
|
||||
opendbc/can/common.h
|
||||
opendbc/can/common.pxd
|
||||
opendbc/can/common_dbc.h
|
||||
opendbc/can/dbc.cc
|
||||
opendbc/can/packer.cc
|
||||
opendbc/can/packer.py
|
||||
opendbc/can/packer_pyx.pyx
|
||||
opendbc/can/parser.cc
|
||||
opendbc/can/parser.py
|
||||
opendbc/can/parser_pyx.pyx
|
||||
|
||||
opendbc/comma_body.dbc
|
||||
|
||||
opendbc/chrysler_ram_hd_generated.dbc
|
||||
opendbc/chrysler_ram_dt_generated.dbc
|
||||
opendbc/chrysler_pacifica_2017_hybrid_generated.dbc
|
||||
opendbc/chrysler_pacifica_2017_hybrid_private_fusion.dbc
|
||||
|
||||
opendbc/gm_global_a_powertrain_generated.dbc
|
||||
opendbc/gm_global_a_object.dbc
|
||||
opendbc/gm_global_a_chassis.dbc
|
||||
|
||||
opendbc/FORD_CADS.dbc
|
||||
opendbc/ford_fusion_2018_adas.dbc
|
||||
opendbc/ford_lincoln_base_pt.dbc
|
||||
|
||||
opendbc/honda_accord_2018_can_generated.dbc
|
||||
opendbc/acura_ilx_2016_can_generated.dbc
|
||||
opendbc/acura_rdx_2018_can_generated.dbc
|
||||
opendbc/acura_rdx_2020_can_generated.dbc
|
||||
opendbc/honda_civic_touring_2016_can_generated.dbc
|
||||
opendbc/honda_civic_hatchback_ex_2017_can_generated.dbc
|
||||
opendbc/honda_crv_touring_2016_can_generated.dbc
|
||||
opendbc/honda_crv_ex_2017_can_generated.dbc
|
||||
opendbc/honda_crv_ex_2017_body_generated.dbc
|
||||
opendbc/honda_crv_executive_2016_can_generated.dbc
|
||||
opendbc/honda_fit_ex_2018_can_generated.dbc
|
||||
opendbc/honda_odyssey_exl_2018_generated.dbc
|
||||
opendbc/honda_odyssey_extreme_edition_2018_china_can_generated.dbc
|
||||
opendbc/honda_insight_ex_2019_can_generated.dbc
|
||||
opendbc/acura_ilx_2016_nidec.dbc
|
||||
opendbc/honda_civic_ex_2022_can_generated.dbc
|
||||
|
||||
opendbc/hyundai_canfd.dbc
|
||||
opendbc/hyundai_kia_generic.dbc
|
||||
opendbc/hyundai_kia_mando_front_radar_generated.dbc
|
||||
|
||||
opendbc/mazda_2017.dbc
|
||||
|
||||
opendbc/nissan_x_trail_2017_generated.dbc
|
||||
opendbc/nissan_leaf_2018_generated.dbc
|
||||
|
||||
opendbc/subaru_global_2017_generated.dbc
|
||||
opendbc/subaru_global_2020_hybrid_generated.dbc
|
||||
opendbc/subaru_outback_2015_generated.dbc
|
||||
opendbc/subaru_outback_2019_generated.dbc
|
||||
opendbc/subaru_forester_2017_generated.dbc
|
||||
|
||||
opendbc/toyota_tnga_k_pt_generated.dbc
|
||||
opendbc/toyota_new_mc_pt_generated.dbc
|
||||
opendbc/toyota_nodsu_pt_generated.dbc
|
||||
opendbc/toyota_adas.dbc
|
||||
opendbc/toyota_tss2_adas.dbc
|
||||
|
||||
opendbc/vw_golf_mk4.dbc
|
||||
opendbc/vw_mqb_2010.dbc
|
||||
|
||||
opendbc/tesla_can.dbc
|
||||
opendbc/tesla_radar_bosch_generated.dbc
|
||||
opendbc/tesla_radar_continental_generated.dbc
|
||||
opendbc/tesla_powertrain.dbc
|
||||
|
||||
tinygrad_repo/openpilot/compile2.py
|
||||
tinygrad_repo/extra/onnx.py
|
||||
tinygrad_repo/extra/onnx_ops.py
|
||||
tinygrad_repo/extra/thneed.py
|
||||
tinygrad_repo/extra/utils.py
|
||||
tinygrad_repo/tinygrad/codegen/kernel.py
|
||||
tinygrad_repo/tinygrad/codegen/linearizer.py
|
||||
tinygrad_repo/tinygrad/features/image.py
|
||||
tinygrad_repo/tinygrad/features/search.py
|
||||
tinygrad_repo/tinygrad/nn/*
|
||||
tinygrad_repo/tinygrad/renderer/cstyle.py
|
||||
tinygrad_repo/tinygrad/renderer/opencl.py
|
||||
tinygrad_repo/tinygrad/runtime/lib.py
|
||||
tinygrad_repo/tinygrad/runtime/ops_cpu.py
|
||||
tinygrad_repo/tinygrad/runtime/ops_disk.py
|
||||
tinygrad_repo/tinygrad/runtime/ops_gpu.py
|
||||
tinygrad_repo/tinygrad/shape/*
|
||||
tinygrad_repo/tinygrad/*.py
|
||||
@@ -1,4 +0,0 @@
|
||||
third_party/libyuv/x86_64/**
|
||||
third_party/snpe/x86_64/**
|
||||
third_party/snpe/x86_64-linux-clang/**
|
||||
third_party/acados/x86_64/**
|
||||
@@ -1,15 +0,0 @@
|
||||
third_party/libyuv/larch64/**
|
||||
third_party/snpe/larch64**
|
||||
third_party/snpe/aarch64-ubuntu-gcc7.5/*
|
||||
third_party/acados/larch64/**
|
||||
|
||||
system/camerad/cameras/camera_qcom2.cc
|
||||
system/camerad/cameras/camera_qcom2.h
|
||||
system/camerad/cameras/camera_util.cc
|
||||
system/camerad/cameras/camera_util.h
|
||||
system/camerad/cameras/process_raw.cl
|
||||
|
||||
system/qcomgpsd/*
|
||||
|
||||
selfdrive/ui/qt/spinner_larch64
|
||||
selfdrive/ui/qt/text_larch64
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
ROOT = HERE + "/.."
|
||||
|
||||
# blacklisting is for two purposes:
|
||||
# - minimizing release download size
|
||||
# - keeping the diff readable
|
||||
blacklist = [
|
||||
"^scripts/",
|
||||
"body/STL/",
|
||||
"tools/cabana/",
|
||||
"panda/examples/",
|
||||
"opendbc/generator/",
|
||||
|
||||
"^tools/",
|
||||
"^tinygrad_repo/",
|
||||
|
||||
"matlab.*.md",
|
||||
|
||||
".git$", # for submodules
|
||||
".git/",
|
||||
".github/",
|
||||
".devcontainer/",
|
||||
"Darwin/",
|
||||
".vscode",
|
||||
|
||||
# no LFS
|
||||
".lfsconfig",
|
||||
".gitattributes",
|
||||
]
|
||||
|
||||
# gets you through the blacklist
|
||||
whitelist = [
|
||||
"tools/lib/",
|
||||
"tools/bodyteleop/",
|
||||
|
||||
"tinygrad_repo/openpilot/compile2.py",
|
||||
"tinygrad_repo/extra/onnx.py",
|
||||
"tinygrad_repo/extra/onnx_ops.py",
|
||||
"tinygrad_repo/extra/thneed.py",
|
||||
"tinygrad_repo/extra/utils.py",
|
||||
"tinygrad_repo/tinygrad/codegen/kernel.py",
|
||||
"tinygrad_repo/tinygrad/codegen/linearizer.py",
|
||||
"tinygrad_repo/tinygrad/features/image.py",
|
||||
"tinygrad_repo/tinygrad/features/search.py",
|
||||
"tinygrad_repo/tinygrad/nn/*",
|
||||
"tinygrad_repo/tinygrad/renderer/cstyle.py",
|
||||
"tinygrad_repo/tinygrad/renderer/opencl.py",
|
||||
"tinygrad_repo/tinygrad/runtime/lib.py",
|
||||
"tinygrad_repo/tinygrad/runtime/ops_cpu.py",
|
||||
"tinygrad_repo/tinygrad/runtime/ops_disk.py",
|
||||
"tinygrad_repo/tinygrad/runtime/ops_gpu.py",
|
||||
"tinygrad_repo/tinygrad/shape/*",
|
||||
"tinygrad_repo/tinygrad/.*.py",
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
for f in Path(ROOT).rglob("**/*"):
|
||||
if not (f.is_file() or f.is_symlink()):
|
||||
continue
|
||||
|
||||
rf = str(f.relative_to(ROOT))
|
||||
blacklisted = any(re.search(p, rf) for p in blacklist)
|
||||
whitelisted = any(re.search(p, rf) for p in whitelist)
|
||||
if blacklisted and not whitelisted:
|
||||
continue
|
||||
|
||||
print(rf)
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Stop updater
|
||||
pkill -2 -f selfdrive.updated.updated
|
||||
pkill -2 -f system.updated.updated
|
||||
|
||||
# Remove pending update
|
||||
rm -f /data/safe_staging/finalized/.overlay_consistent
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Send SIGHUP to updater
|
||||
pkill -1 -f selfdrive.updated
|
||||
pkill -1 -f system.updated
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import unittest
|
||||
from Crypto.PublicKey import RSA
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID
|
||||
from openpilot.selfdrive.athena.tests.helpers import MockResponse
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
|
||||
|
||||
class TestRegistration(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# clear params and setup key paths
|
||||
self.params = Params()
|
||||
self.params.clear_all()
|
||||
|
||||
persist_dir = Path(Paths.persist_root()) / "comma"
|
||||
persist_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
self.priv_key = persist_dir / "id_rsa"
|
||||
self.pub_key = persist_dir / "id_rsa.pub"
|
||||
|
||||
def _generate_keys(self):
|
||||
self.pub_key.touch()
|
||||
k = RSA.generate(2048)
|
||||
with open(self.priv_key, "wb") as f:
|
||||
f.write(k.export_key())
|
||||
with open(self.pub_key, "wb") as f:
|
||||
f.write(k.publickey().export_key())
|
||||
|
||||
def test_valid_cache(self):
|
||||
# if all params are written, return the cached dongle id
|
||||
self.params.put("IMEI", "imei")
|
||||
self.params.put("HardwareSerial", "serial")
|
||||
self._generate_keys()
|
||||
|
||||
with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m:
|
||||
dongle = "DONGLE_ID_123"
|
||||
self.params.put("DongleId", dongle)
|
||||
self.assertEqual(register(), dongle)
|
||||
self.assertFalse(m.called)
|
||||
|
||||
def test_no_keys(self):
|
||||
# missing pubkey
|
||||
with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m:
|
||||
dongle = register()
|
||||
self.assertEqual(m.call_count, 0)
|
||||
self.assertEqual(dongle, UNREGISTERED_DONGLE_ID)
|
||||
self.assertEqual(self.params.get("DongleId", encoding='utf-8'), dongle)
|
||||
|
||||
def test_missing_cache(self):
|
||||
# keys exist but no dongle id
|
||||
self._generate_keys()
|
||||
with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m:
|
||||
dongle = "DONGLE_ID_123"
|
||||
m.return_value = MockResponse(json.dumps({'dongle_id': dongle}), 200)
|
||||
self.assertEqual(register(), dongle)
|
||||
self.assertEqual(m.call_count, 1)
|
||||
|
||||
# call again, shouldn't hit the API this time
|
||||
self.assertEqual(register(), dongle)
|
||||
self.assertEqual(m.call_count, 1)
|
||||
self.assertEqual(self.params.get("DongleId", encoding='utf-8'), dongle)
|
||||
|
||||
def test_unregistered(self):
|
||||
# keys exist, but unregistered
|
||||
self._generate_keys()
|
||||
with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m:
|
||||
m.return_value = MockResponse(None, 402)
|
||||
dongle = register()
|
||||
self.assertEqual(m.call_count, 1)
|
||||
self.assertEqual(dongle, UNREGISTERED_DONGLE_ID)
|
||||
self.assertEqual(self.params.get("DongleId", encoding='utf-8'), dongle)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -15,16 +15,17 @@ cdef extern from "can_list_to_can_capnp.cc":
|
||||
void can_list_to_can_capnp_cpp(const vector[can_frame] &can_list, string &out, bool sendCan, bool valid)
|
||||
|
||||
def can_list_to_can_capnp(can_msgs, msgtype='can', valid=True):
|
||||
cdef can_frame *f
|
||||
cdef vector[can_frame] can_list
|
||||
can_list.reserve(len(can_msgs))
|
||||
|
||||
cdef can_frame f
|
||||
can_list.reserve(len(can_msgs))
|
||||
for can_msg in can_msgs:
|
||||
f = &(can_list.emplace_back())
|
||||
f.address = can_msg[0]
|
||||
f.busTime = can_msg[1]
|
||||
f.dat = can_msg[2]
|
||||
f.src = can_msg[3]
|
||||
can_list.push_back(f)
|
||||
|
||||
cdef string out
|
||||
can_list_to_can_capnp_cpp(can_list, out, msgtype == 'sendcan', valid)
|
||||
return out
|
||||
|
||||
+17
-12
@@ -301,7 +301,11 @@ int PandaSpiHandle::lltransfer(spi_ioc_transfer &t) {
|
||||
}
|
||||
if ((static_cast<double>(rand()) / RAND_MAX) < err_prob && t.tx_buf != (uint64_t)NULL) {
|
||||
printf("corrupting TX\n");
|
||||
memset((uint8_t*)t.tx_buf, (uint8_t)(rand() % 256), rand() % (t.len+1));
|
||||
for (int i = 0; i < t.len; i++) {
|
||||
if ((static_cast<double>(rand()) / RAND_MAX) > 0.9) {
|
||||
((uint8_t*)t.tx_buf)[i] = (uint8_t)(rand() % 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +314,11 @@ int PandaSpiHandle::lltransfer(spi_ioc_transfer &t) {
|
||||
if (err_prob > 0) {
|
||||
if ((static_cast<double>(rand()) / RAND_MAX) < err_prob && t.rx_buf != (uint64_t)NULL) {
|
||||
printf("corrupting RX\n");
|
||||
memset((uint8_t*)t.rx_buf, (uint8_t)(rand() % 256), rand() % (t.len+1));
|
||||
for (int i = 0; i < t.len; i++) {
|
||||
if ((static_cast<double>(rand()) / RAND_MAX) > 0.9) {
|
||||
((uint8_t*)t.rx_buf)[i] = (uint8_t)(rand() % 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,13 +353,13 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
||||
ret = lltransfer(transfer);
|
||||
if (ret < 0) {
|
||||
LOGE("SPI: failed to send header");
|
||||
goto transfer_fail;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Wait for (N)ACK
|
||||
ret = wait_for_ack(SPI_HACK, 0x11, timeout, 1);
|
||||
if (ret < 0) {
|
||||
goto transfer_fail;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Send data
|
||||
@@ -363,20 +371,20 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
||||
ret = lltransfer(transfer);
|
||||
if (ret < 0) {
|
||||
LOGE("SPI: failed to send data");
|
||||
goto transfer_fail;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Wait for (N)ACK
|
||||
ret = wait_for_ack(SPI_DACK, 0x13, timeout, 3);
|
||||
if (ret < 0) {
|
||||
goto transfer_fail;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Read data
|
||||
rx_data_len = *(uint16_t *)(rx_buf+1);
|
||||
if (rx_data_len >= SPI_BUF_SIZE) {
|
||||
LOGE("SPI: RX data len larger than buf size %d", rx_data_len);
|
||||
goto transfer_fail;
|
||||
return -1;
|
||||
}
|
||||
|
||||
transfer.len = rx_data_len + 1;
|
||||
@@ -384,11 +392,11 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
||||
ret = lltransfer(transfer);
|
||||
if (ret < 0) {
|
||||
LOGE("SPI: failed to read rx data");
|
||||
goto transfer_fail;
|
||||
return ret;
|
||||
}
|
||||
if (!check_checksum(rx_buf, rx_data_len + 4)) {
|
||||
LOGE("SPI: bad checksum");
|
||||
goto transfer_fail;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rx_data != NULL) {
|
||||
@@ -396,8 +404,5 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
||||
}
|
||||
|
||||
return rx_data_len;
|
||||
|
||||
transfer_fail:
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3,7 +3,7 @@ import os
|
||||
import copy
|
||||
import random
|
||||
import time
|
||||
import unittest
|
||||
import pytest
|
||||
from collections import defaultdict
|
||||
from pprint import pprint
|
||||
|
||||
@@ -17,42 +17,61 @@ from openpilot.system.hardware import TICI
|
||||
from openpilot.selfdrive.test.helpers import phone_only, with_processes
|
||||
|
||||
|
||||
class TestBoardd(unittest.TestCase):
|
||||
def setup_boardd(num_pandas):
|
||||
params = Params()
|
||||
params.put_bool("IsOnroad", False)
|
||||
|
||||
with Timeout(90, "boardd didn't start"):
|
||||
sm = messaging.SubMaster(['pandaStates'])
|
||||
while sm.recv_frame['pandaStates'] < 1 or len(sm['pandaStates']) == 0 or \
|
||||
any(ps.pandaType == log.PandaState.PandaType.unknown for ps in sm['pandaStates']):
|
||||
sm.update(1000)
|
||||
|
||||
found_pandas = len(sm['pandaStates'])
|
||||
assert num_pandas == found_pandas, "connected pandas ({found_pandas}) doesn't match expected panda count ({num_pandas}). \
|
||||
connect another panda for multipanda tests."
|
||||
|
||||
# boardd safety setting relies on these params
|
||||
cp = car.CarParams.new_message()
|
||||
|
||||
safety_config = car.CarParams.SafetyConfig.new_message()
|
||||
safety_config.safetyModel = car.CarParams.SafetyModel.allOutput
|
||||
cp.safetyConfigs = [safety_config]*num_pandas
|
||||
|
||||
params.put_bool("IsOnroad", True)
|
||||
params.put_bool("FirmwareQueryDone", True)
|
||||
params.put_bool("ControlsReady", True)
|
||||
params.put("CarParams", cp.to_bytes())
|
||||
|
||||
|
||||
def send_random_can_messages(sendcan, count, num_pandas=1):
|
||||
sent_msgs = defaultdict(set)
|
||||
for _ in range(count):
|
||||
to_send = []
|
||||
for __ in range(random.randrange(20)):
|
||||
bus = random.choice([b for b in range(3*num_pandas) if b % 4 != 3])
|
||||
addr = random.randrange(1, 1<<29)
|
||||
dat = bytes(random.getrandbits(8) for _ in range(random.randrange(1, 9)))
|
||||
if (addr, dat) in sent_msgs[bus]:
|
||||
continue
|
||||
sent_msgs[bus].add((addr, dat))
|
||||
to_send.append(make_can_msg(addr, dat, bus))
|
||||
sendcan.send(can_list_to_can_capnp(to_send, msgtype='sendcan'))
|
||||
return sent_msgs
|
||||
|
||||
|
||||
@pytest.mark.tici
|
||||
class TestBoarddLoopback:
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
def setup_class(cls):
|
||||
os.environ['STARTED'] = '1'
|
||||
os.environ['BOARDD_LOOPBACK'] = '1'
|
||||
|
||||
@phone_only
|
||||
@with_processes(['pandad'])
|
||||
def test_loopback(self):
|
||||
params = Params()
|
||||
params.put_bool("IsOnroad", False)
|
||||
|
||||
with Timeout(90, "boardd didn't start"):
|
||||
sm = messaging.SubMaster(['pandaStates'])
|
||||
while sm.recv_frame['pandaStates'] < 1 or len(sm['pandaStates']) == 0 or \
|
||||
any(ps.pandaType == log.PandaState.PandaType.unknown for ps in sm['pandaStates']):
|
||||
sm.update(1000)
|
||||
|
||||
num_pandas = len(sm['pandaStates'])
|
||||
expected_pandas = 2 if TICI and "SINGLE_PANDA" not in os.environ else 1
|
||||
self.assertEqual(num_pandas, expected_pandas, "connected pandas ({num_pandas}) doesn't match expected panda count ({expected_pandas}). \
|
||||
connect another panda for multipanda tests.")
|
||||
|
||||
# boardd safety setting relies on these params
|
||||
cp = car.CarParams.new_message()
|
||||
|
||||
safety_config = car.CarParams.SafetyConfig.new_message()
|
||||
safety_config.safetyModel = car.CarParams.SafetyModel.allOutput
|
||||
cp.safetyConfigs = [safety_config]*num_pandas
|
||||
|
||||
params.put_bool("IsOnroad", True)
|
||||
params.put_bool("FirmwareQueryDone", True)
|
||||
params.put_bool("ControlsReady", True)
|
||||
params.put("CarParams", cp.to_bytes())
|
||||
|
||||
num_pandas = 2 if TICI and "SINGLE_PANDA" not in os.environ else 1
|
||||
setup_boardd(num_pandas)
|
||||
sendcan = messaging.pub_sock('sendcan')
|
||||
can = messaging.sub_sock('can', conflate=False, timeout=100)
|
||||
sm = messaging.SubMaster(['pandaStates'])
|
||||
@@ -62,16 +81,7 @@ class TestBoardd(unittest.TestCase):
|
||||
for i in range(n):
|
||||
print(f"boardd loopback {i}/{n}")
|
||||
|
||||
sent_msgs = defaultdict(set)
|
||||
for _ in range(random.randrange(20, 100)):
|
||||
to_send = []
|
||||
for __ in range(random.randrange(20)):
|
||||
bus = random.choice([b for b in range(3*num_pandas) if b % 4 != 3])
|
||||
addr = random.randrange(1, 1<<29)
|
||||
dat = bytes(random.getrandbits(8) for _ in range(random.randrange(1, 9)))
|
||||
sent_msgs[bus].add((addr, dat))
|
||||
to_send.append(make_can_msg(addr, dat, bus))
|
||||
sendcan.send(can_list_to_can_capnp(to_send, msgtype='sendcan'))
|
||||
sent_msgs = send_random_can_messages(sendcan, random.randrange(20, 100), num_pandas)
|
||||
|
||||
sent_loopback = copy.deepcopy(sent_msgs)
|
||||
sent_loopback.update({k+128: copy.deepcopy(v) for k, v in sent_msgs.items()})
|
||||
@@ -96,7 +106,3 @@ class TestBoardd(unittest.TestCase):
|
||||
pprint(sm['pandaStates']) # may drop messages due to RX buffer overflow
|
||||
for bus in sent_loopback.keys():
|
||||
assert not len(sent_loopback[bus]), f"loop {i}: bus {bus} missing {len(sent_loopback[bus])} out of {sent_total[bus]} messages"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from cereal.services import SERVICE_LIST
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.selfdrive.test.helpers import phone_only, with_processes
|
||||
from openpilot.selfdrive.boardd.tests.test_boardd_loopback import setup_boardd
|
||||
|
||||
|
||||
@pytest.mark.tici
|
||||
class TestBoarddSpi:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
if HARDWARE.get_device_type() == 'tici':
|
||||
pytest.skip("only for spi pandas")
|
||||
os.environ['STARTED'] = '1'
|
||||
os.environ['BOARDD_LOOPBACK'] = '1'
|
||||
os.environ['SPI_ERR_PROB'] = '0.001'
|
||||
|
||||
@phone_only
|
||||
@with_processes(['pandad'])
|
||||
def test_spi_corruption(self, subtests):
|
||||
setup_boardd(1)
|
||||
|
||||
socks = {s: messaging.sub_sock(s, conflate=False, timeout=100) for s in ('can', 'pandaStates', 'peripheralState')}
|
||||
time.sleep(2)
|
||||
for s in socks.values():
|
||||
messaging.drain_sock_raw(s)
|
||||
|
||||
st = time.monotonic()
|
||||
ts = {s: list() for s in socks.keys()}
|
||||
for _ in range(20):
|
||||
for service, sock in socks.items():
|
||||
for m in messaging.drain_sock(sock):
|
||||
ts[service].append(m.logMonoTime)
|
||||
|
||||
# sanity check for corruption
|
||||
assert m.valid
|
||||
if service == "can":
|
||||
assert len(m.can) == 0
|
||||
elif service == "pandaStates":
|
||||
assert len(m.pandaStates) == 1
|
||||
ps = m.pandaStates[0]
|
||||
assert ps.uptime < 100
|
||||
assert ps.pandaType == "tres"
|
||||
assert ps.ignitionLine
|
||||
assert not ps.ignitionCan
|
||||
assert ps.voltage < 14000
|
||||
elif service == "peripheralState":
|
||||
ps = m.peripheralState
|
||||
assert ps.pandaType == "tres"
|
||||
assert 4000 < ps.voltage < 14000
|
||||
assert 100 < ps.current < 1000
|
||||
assert ps.fanSpeedRpm < 8000
|
||||
|
||||
time.sleep(0.5)
|
||||
et = time.monotonic() - st
|
||||
|
||||
print("\n======== timing report ========")
|
||||
for service, times in ts.items():
|
||||
dts = np.diff(times)/1e6
|
||||
print(service.ljust(17), f"{np.mean(dts):7.2f} {np.min(dts):7.2f} {np.max(dts):7.2f}")
|
||||
with subtests.test(msg="timing check", service=service):
|
||||
edt = 1e3 / SERVICE_LIST[service].frequency
|
||||
assert edt*0.9 < np.mean(dts) < edt*1.1
|
||||
assert np.max(dts) < edt*3
|
||||
assert np.min(dts) < edt
|
||||
assert len(dts) >= ((et-0.5)*SERVICE_LIST[service].frequency*0.8)
|
||||
@@ -2,13 +2,12 @@
|
||||
import os
|
||||
import pytest
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from cereal import log
|
||||
from openpilot.common.gpio import gpio_set, gpio_init
|
||||
from panda import Panda, PandaDFU, PandaProtocolMismatch
|
||||
from openpilot.selfdrive.manager.process_config import managed_processes
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.hardware.tici.pins import GPIO
|
||||
|
||||
@@ -16,16 +15,16 @@ HERE = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
@pytest.mark.tici
|
||||
class TestPandad(unittest.TestCase):
|
||||
class TestPandad:
|
||||
|
||||
def setUp(self):
|
||||
def setup_method(self):
|
||||
# ensure panda is up
|
||||
if len(Panda.list()) == 0:
|
||||
self._run_test(60)
|
||||
|
||||
self.spi = HARDWARE.get_device_type() != 'tici'
|
||||
|
||||
def tearDown(self):
|
||||
def teardown_method(self):
|
||||
managed_processes['pandad'].stop()
|
||||
|
||||
def _run_test(self, timeout=30) -> float:
|
||||
@@ -65,7 +64,7 @@ class TestPandad(unittest.TestCase):
|
||||
|
||||
assert Panda.wait_for_panda(None, 10)
|
||||
if expect_mismatch:
|
||||
with self.assertRaises(PandaProtocolMismatch):
|
||||
with pytest.raises(PandaProtocolMismatch):
|
||||
Panda()
|
||||
else:
|
||||
with Panda() as p:
|
||||
@@ -108,9 +107,10 @@ class TestPandad(unittest.TestCase):
|
||||
assert 0.1 < (sum(ts)/len(ts)) < (0.5 if self.spi else 5.0)
|
||||
print("startup times", ts, sum(ts) / len(ts))
|
||||
|
||||
|
||||
def test_protocol_version_check(self):
|
||||
if not self.spi:
|
||||
raise unittest.SkipTest("SPI test")
|
||||
pytest.skip("SPI test")
|
||||
# flash old fw
|
||||
fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin")
|
||||
self._flash_bootstub_and_test(fn, expect_mismatch=True)
|
||||
@@ -127,7 +127,3 @@ class TestPandad(unittest.TestCase):
|
||||
self._assert_no_panda()
|
||||
|
||||
self._run_test(60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -75,7 +75,7 @@ class CarController(CarControllerBase):
|
||||
can_sends = []
|
||||
can_sends.append(bodycan.create_control(self.packer, torque_l, torque_r))
|
||||
|
||||
new_actuators = CC.actuators.copy()
|
||||
new_actuators = CC.actuators.as_builder()
|
||||
new_actuators.accel = torque_l
|
||||
new_actuators.steer = torque_r
|
||||
new_actuators.steerOutputCan = torque_r
|
||||
|
||||
+87
-31
@@ -9,30 +9,34 @@ from cereal import car
|
||||
from panda import ALTERNATIVE_EXPERIENCE
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL
|
||||
|
||||
from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp
|
||||
from openpilot.selfdrive.car.car_helpers import get_car, get_one_can
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
from openpilot.selfdrive.controls.lib.events import Events
|
||||
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
|
||||
EventName = car.CarEvent.EventName
|
||||
|
||||
class CarD:
|
||||
|
||||
class Car:
|
||||
CI: CarInterfaceBase
|
||||
CS: car.CarState
|
||||
|
||||
def __init__(self, CI=None):
|
||||
self.can_sock = messaging.sub_sock('can', timeout=20)
|
||||
self.sm = messaging.SubMaster(['pandaStates'])
|
||||
self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents'])
|
||||
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput'])
|
||||
|
||||
self.can_rcv_timeout_counter = 0 # consecutive timeout count
|
||||
self.can_rcv_cum_timeout_counter = 0 # cumulative timeout count
|
||||
|
||||
self.CC_prev = car.CarControl.new_message()
|
||||
self.CS_prev = car.CarState.new_message()
|
||||
self.initialized_prev = False
|
||||
|
||||
self.last_actuators = None
|
||||
self.last_actuators_output = car.CarControl.Actuators.new_message()
|
||||
|
||||
self.params = Params()
|
||||
|
||||
@@ -48,9 +52,9 @@ class CarD:
|
||||
self.CI, self.CP = CI, CI.CP
|
||||
|
||||
# set alternative experiences from parameters
|
||||
disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
|
||||
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
|
||||
self.CP.alternativeExperience = 0
|
||||
if not disengage_on_accelerator:
|
||||
if not self.disengage_on_accelerator:
|
||||
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
|
||||
|
||||
openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle")
|
||||
@@ -74,16 +78,17 @@ class CarD:
|
||||
self.params.put_nonblocking("CarParamsCache", cp_bytes)
|
||||
self.params.put_nonblocking("CarParamsPersistent", cp_bytes)
|
||||
|
||||
def initialize(self):
|
||||
"""Initialize CarInterface, once controls are ready"""
|
||||
self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan'])
|
||||
self.events = Events()
|
||||
|
||||
def state_update(self):
|
||||
# card is driven by can recv, expected at 100Hz
|
||||
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
||||
|
||||
def state_update(self) -> car.CarState:
|
||||
"""carState update loop, driven by can"""
|
||||
|
||||
# Update carState from CAN
|
||||
can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True)
|
||||
self.CS = self.CI.update(self.CC_prev, can_strs)
|
||||
CS = self.CI.update(self.CC_prev, can_strs)
|
||||
|
||||
self.sm.update(0)
|
||||
|
||||
@@ -101,21 +106,26 @@ class CarD:
|
||||
if can_rcv_valid and REPLAY:
|
||||
self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime
|
||||
|
||||
self.state_publish()
|
||||
return CS
|
||||
|
||||
return self.CS
|
||||
def update_events(self, CS: car.CarState) -> car.CarState:
|
||||
self.events.clear()
|
||||
|
||||
def state_publish(self):
|
||||
self.events.add_from_msg(CS.events)
|
||||
|
||||
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
|
||||
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
|
||||
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
|
||||
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
|
||||
self.events.add(EventName.pedalPressed)
|
||||
|
||||
CS.events = self.events.to_msg()
|
||||
|
||||
def state_publish(self, CS: car.CarState):
|
||||
"""carState and carParams publish loop"""
|
||||
|
||||
# carState
|
||||
cs_send = messaging.new_message('carState')
|
||||
cs_send.valid = self.CS.canValid
|
||||
cs_send.carState = self.CS
|
||||
self.pm.send('carState', cs_send)
|
||||
|
||||
# carParams - logged every 50 seconds (> 1 per segment)
|
||||
if (self.sm.frame % int(50. / DT_CTRL) == 0):
|
||||
if self.sm.frame % int(50. / DT_CTRL) == 0:
|
||||
cp_send = messaging.new_message('carParams')
|
||||
cp_send.valid = True
|
||||
cp_send.carParams = self.CP
|
||||
@@ -123,17 +133,63 @@ class CarD:
|
||||
|
||||
# publish new carOutput
|
||||
co_send = messaging.new_message('carOutput')
|
||||
co_send.valid = True
|
||||
if self.last_actuators is not None:
|
||||
co_send.carOutput.actuatorsOutput = self.last_actuators
|
||||
co_send.valid = self.sm.all_checks(['carControl'])
|
||||
co_send.carOutput.actuatorsOutput = self.last_actuators_output
|
||||
self.pm.send('carOutput', co_send)
|
||||
|
||||
def controls_update(self, CC: car.CarControl):
|
||||
# kick off controlsd step while we actuate the latest carControl packet
|
||||
cs_send = messaging.new_message('carState')
|
||||
cs_send.valid = CS.canValid
|
||||
cs_send.carState = CS
|
||||
cs_send.carState.canRcvTimeout = self.can_rcv_timeout
|
||||
cs_send.carState.canErrorCounter = self.can_rcv_cum_timeout_counter
|
||||
cs_send.carState.cumLagMs = -self.rk.remaining * 1000.
|
||||
self.pm.send('carState', cs_send)
|
||||
|
||||
def controls_update(self, CS: car.CarState, CC: car.CarControl):
|
||||
"""control update loop, driven by carControl"""
|
||||
|
||||
# send car controls over can
|
||||
now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9)
|
||||
self.last_actuators, can_sends = self.CI.apply(CC, now_nanos)
|
||||
self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=self.CS.canValid))
|
||||
if not self.initialized_prev:
|
||||
# Initialize CarInterface, once controls are ready
|
||||
# TODO: this can make us miss at least a few cycles when doing an ECU knockout
|
||||
self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan'])
|
||||
# signal boardd to switch to car safety mode
|
||||
self.params.put_bool_nonblocking("ControlsReady", True)
|
||||
|
||||
self.CC_prev = CC
|
||||
if self.sm.all_alive(['carControl']):
|
||||
# send car controls over can
|
||||
now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9)
|
||||
self.last_actuators_output, can_sends = self.CI.apply(CC, now_nanos)
|
||||
self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid))
|
||||
|
||||
self.CC_prev = CC
|
||||
|
||||
def step(self):
|
||||
CS = self.state_update()
|
||||
|
||||
self.update_events(CS)
|
||||
|
||||
self.state_publish(CS)
|
||||
|
||||
initialized = (not any(e.name == EventName.controlsInitializing for e in self.sm['onroadEvents']) and
|
||||
self.sm.seen['onroadEvents'])
|
||||
if not self.CP.passive and initialized:
|
||||
self.controls_update(CS, self.sm['carControl'])
|
||||
|
||||
self.initialized_prev = initialized
|
||||
self.CS_prev = CS.as_reader()
|
||||
|
||||
def card_thread(self):
|
||||
while True:
|
||||
self.step()
|
||||
self.rk.monitor_time()
|
||||
|
||||
|
||||
def main():
|
||||
config_realtime_process(4, Priority.CTRL_HIGH)
|
||||
car = Car()
|
||||
car.card_thread()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -83,7 +83,7 @@ class CarController(CarControllerBase):
|
||||
|
||||
self.frame += 1
|
||||
|
||||
new_actuators = CC.actuators.copy()
|
||||
new_actuators = CC.actuators.as_builder()
|
||||
new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX
|
||||
new_actuators.steerOutputCan = self.apply_steer_last
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ FW_VERSIONS = {
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'68267018AO ',
|
||||
b'68267020AJ ',
|
||||
b'68303534AG ',
|
||||
b'68303534AJ ',
|
||||
b'68340762AD ',
|
||||
b'68340764AD ',
|
||||
@@ -106,6 +107,7 @@ FW_VERSIONS = {
|
||||
b'68405565AB',
|
||||
b'68405565AC',
|
||||
b'68444299AC',
|
||||
b'68480707AC',
|
||||
b'68480708AC',
|
||||
b'68526663AB',
|
||||
],
|
||||
@@ -143,13 +145,16 @@ FW_VERSIONS = {
|
||||
b'68413871AE ',
|
||||
b'68413871AH ',
|
||||
b'68413871AI ',
|
||||
b'68413873AH ',
|
||||
b'68413873AI ',
|
||||
b'68443120AE ',
|
||||
b'68443123AC ',
|
||||
b'68443125AC ',
|
||||
b'68496647AI ',
|
||||
b'68496647AJ ',
|
||||
b'68496650AH ',
|
||||
b'68496650AI ',
|
||||
b'68496652AH ',
|
||||
b'68526752AD ',
|
||||
b'68526752AE ',
|
||||
b'68526754AE ',
|
||||
@@ -161,10 +166,12 @@ FW_VERSIONS = {
|
||||
b'68414271AC',
|
||||
b'68414271AD',
|
||||
b'68414275AC',
|
||||
b'68414275AD',
|
||||
b'68443154AB',
|
||||
b'68443155AC',
|
||||
b'68443158AB',
|
||||
b'68501050AD',
|
||||
b'68501051AD',
|
||||
b'68501055AD',
|
||||
b'68527221AB',
|
||||
b'68527223AB',
|
||||
@@ -264,8 +271,10 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.JEEP_GRAND_CHEROKEE: {
|
||||
(Ecu.combinationMeter, 0x742, None): [
|
||||
b'68243549AG',
|
||||
b'68302211AC',
|
||||
b'68302212AD',
|
||||
b'68302223AC',
|
||||
b'68302246AC',
|
||||
b'68331511AC',
|
||||
b'68331574AC',
|
||||
@@ -274,18 +283,22 @@ FW_VERSIONS = {
|
||||
b'68340272AD',
|
||||
],
|
||||
(Ecu.srs, 0x744, None): [
|
||||
b'68309533AA',
|
||||
b'68316742AB',
|
||||
b'68355363AB',
|
||||
],
|
||||
(Ecu.abs, 0x747, None): [
|
||||
b'68252642AG',
|
||||
b'68306178AD',
|
||||
b'68336276AB',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x753, None): [
|
||||
b'04672627AB',
|
||||
b'68251506AF',
|
||||
b'68332015AB',
|
||||
],
|
||||
(Ecu.eps, 0x75a, None): [
|
||||
b'68276201AG',
|
||||
b'68321644AB',
|
||||
b'68321644AC',
|
||||
b'68321646AC',
|
||||
@@ -293,6 +306,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'05035920AE ',
|
||||
b'68252272AG ',
|
||||
b'68284455AI ',
|
||||
b'68284456AI ',
|
||||
b'68284477AF ',
|
||||
@@ -303,6 +317,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'05035517AH',
|
||||
b'68253222AF',
|
||||
b'68311218AC',
|
||||
b'68311223AF',
|
||||
b'68311223AG',
|
||||
@@ -422,7 +437,9 @@ FW_VERSIONS = {
|
||||
b'68527403AD',
|
||||
b'68546047AF',
|
||||
b'68631938AA',
|
||||
b'68631939AA',
|
||||
b'68631940AA',
|
||||
b'68631940AB',
|
||||
b'68631942AA',
|
||||
b'68631943AB',
|
||||
],
|
||||
@@ -478,6 +495,7 @@ FW_VERSIONS = {
|
||||
b'68440789AC',
|
||||
b'68466110AA',
|
||||
b'68466110AB',
|
||||
b'68466113AA',
|
||||
b'68469901AA',
|
||||
b'68469907AA',
|
||||
b'68522583AA',
|
||||
@@ -552,6 +570,7 @@ FW_VERSIONS = {
|
||||
b'68539651AD',
|
||||
b'68586101AA ',
|
||||
b'68586105AB ',
|
||||
b'68629919AC ',
|
||||
b'68629922AC ',
|
||||
b'68629925AC ',
|
||||
b'68629926AC ',
|
||||
@@ -589,6 +608,8 @@ FW_VERSIONS = {
|
||||
b'68520870AC',
|
||||
b'68540431AB',
|
||||
b'68540433AB',
|
||||
b'68551676AA',
|
||||
b'68629935AB',
|
||||
b'68629936AC',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -275,7 +275,7 @@ class CarDocs:
|
||||
self.min_enable_speed = CP.minEnableSpeed
|
||||
|
||||
if self.auto_resume is None:
|
||||
self.auto_resume = CP.autoResumeSng
|
||||
self.auto_resume = CP.autoResumeSng and self.min_enable_speed <= 0
|
||||
|
||||
# hardware column
|
||||
hardware_col = "None"
|
||||
|
||||
@@ -113,7 +113,7 @@ class CarController(CarControllerBase):
|
||||
self.steer_alert_last = steer_alert
|
||||
self.lead_distance_bars_last = hud_control.leadDistanceBars
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.curvature = self.apply_curvature_last
|
||||
|
||||
self.frame += 1
|
||||
|
||||
@@ -39,6 +39,18 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
if ret.flags & FordFlags.CANFD:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_CANFD
|
||||
else:
|
||||
# Lock out if the car does not have needed lateral and longitudinal control APIs.
|
||||
# Note that we also check CAN for adaptive cruise, but no known signal for LCA exists
|
||||
pscm_config = next((fw for fw in car_fw if fw.ecu == Ecu.eps and b'\x22\xDE\x01' in fw.request), None)
|
||||
if pscm_config:
|
||||
if len(pscm_config.fwVersion) != 24:
|
||||
ret.dashcamOnly = True
|
||||
else:
|
||||
config_tja = pscm_config.fwVersion[7] # Traffic Jam Assist
|
||||
config_lca = pscm_config.fwVersion[8] # Lane Centering Assist
|
||||
if config_tja != 0xFF or config_lca != 0xFF:
|
||||
ret.dashcamOnly = True
|
||||
|
||||
# Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1
|
||||
found_ecus = [fw.ecu for fw in car_fw]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import unittest
|
||||
from collections.abc import Iterable
|
||||
|
||||
import capnp
|
||||
@@ -43,31 +42,31 @@ ECU_PART_NUMBER = {
|
||||
}
|
||||
|
||||
|
||||
class TestFordFW(unittest.TestCase):
|
||||
class TestFordFW:
|
||||
def test_fw_query_config(self):
|
||||
for (ecu, addr, subaddr) in FW_QUERY_CONFIG.extra_ecus:
|
||||
self.assertIn(ecu, ECU_ADDRESSES, "Unknown ECU")
|
||||
self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch")
|
||||
self.assertIsNone(subaddr, "Unexpected ECU subaddress")
|
||||
assert ecu in ECU_ADDRESSES, "Unknown ECU"
|
||||
assert addr == ECU_ADDRESSES[ecu], "ECU address mismatch"
|
||||
assert subaddr is None, "Unexpected ECU subaddress"
|
||||
|
||||
@parameterized.expand(FW_VERSIONS.items())
|
||||
def test_fw_versions(self, car_model: str, fw_versions: dict[tuple[capnp.lib.capnp._EnumModule, int, int | None], Iterable[bytes]]):
|
||||
for (ecu, addr, subaddr), fws in fw_versions.items():
|
||||
self.assertIn(ecu, ECU_PART_NUMBER, "Unexpected ECU")
|
||||
self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch")
|
||||
self.assertIsNone(subaddr, "Unexpected ECU subaddress")
|
||||
assert ecu in ECU_PART_NUMBER, "Unexpected ECU"
|
||||
assert addr == ECU_ADDRESSES[ecu], "ECU address mismatch"
|
||||
assert subaddr is None, "Unexpected ECU subaddress"
|
||||
|
||||
for fw in fws:
|
||||
self.assertEqual(len(fw), 24, "Expected ECU response to be 24 bytes")
|
||||
assert len(fw) == 24, "Expected ECU response to be 24 bytes"
|
||||
|
||||
match = FW_PATTERN.match(fw)
|
||||
self.assertIsNotNone(match, f"Unable to parse FW: {fw!r}")
|
||||
assert match is not None, f"Unable to parse FW: {fw!r}"
|
||||
if match:
|
||||
part_number = match.group("part_number")
|
||||
self.assertIn(part_number, ECU_PART_NUMBER[ecu], f"Unexpected part number for {fw!r}")
|
||||
assert part_number in ECU_PART_NUMBER[ecu], f"Unexpected part number for {fw!r}"
|
||||
|
||||
codes = get_platform_codes([fw])
|
||||
self.assertEqual(1, len(codes), f"Unable to parse FW: {fw!r}")
|
||||
assert 1 == len(codes), f"Unable to parse FW: {fw!r}"
|
||||
|
||||
@settings(max_examples=100)
|
||||
@given(data=st.data())
|
||||
@@ -85,7 +84,7 @@ class TestFordFW(unittest.TestCase):
|
||||
b"PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
b"LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
])
|
||||
self.assertEqual(results, {(b"X6A", b"J"), (b"Z6T", b"N"), (b"J6T", b"P"), (b"B5A", b"L")})
|
||||
assert results == {(b"X6A", b"J"), (b"Z6T", b"N"), (b"J6T", b"P"), (b"B5A", b"L")}
|
||||
|
||||
def test_fuzzy_match(self):
|
||||
for platform, fw_by_addr in FW_VERSIONS.items():
|
||||
@@ -100,7 +99,7 @@ class TestFordFW(unittest.TestCase):
|
||||
|
||||
CP = car.CarParams.new_message(carFw=car_fw)
|
||||
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS)
|
||||
self.assertEqual(matches, {platform})
|
||||
assert matches == {platform}
|
||||
|
||||
def test_match_fw_fuzzy(self):
|
||||
offline_fw = {
|
||||
@@ -132,18 +131,14 @@ class TestFordFW(unittest.TestCase):
|
||||
(0x706, None): {b"LB5T-14F397-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
|
||||
}
|
||||
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw})
|
||||
self.assertEqual(candidates, {expected_fingerprint})
|
||||
assert candidates == {expected_fingerprint}
|
||||
|
||||
# model year hint in between the range should match
|
||||
live_fw[(0x706, None)] = {b"MB5T-14F397-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}
|
||||
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw,})
|
||||
self.assertEqual(candidates, {expected_fingerprint})
|
||||
assert candidates == {expected_fingerprint}
|
||||
|
||||
# unseen model year hint should not match
|
||||
live_fw[(0x760, None)] = {b"M1MC-2D053-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}
|
||||
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw})
|
||||
self.assertEqual(len(candidates), 0, "Should not match new model year hint")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert len(candidates) == 0, "Should not match new model year hint"
|
||||
|
||||
@@ -374,7 +374,7 @@ if __name__ == "__main__":
|
||||
t = time.time()
|
||||
print("Getting vin...")
|
||||
set_obd_multiplexing(params, True)
|
||||
vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (0, 1), retry=10, debug=args.debug)
|
||||
vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (0, 1), debug=args.debug)
|
||||
print(f'RX: {hex(vin_rx_addr)}, BUS: {vin_rx_bus}, VIN: {vin}')
|
||||
print(f"Getting VIN took {time.time() - t:.3f} s")
|
||||
print()
|
||||
|
||||
@@ -155,7 +155,7 @@ class CarController(CarControllerBase):
|
||||
if self.frame % 10 == 0:
|
||||
can_sends.append(gmcan.create_pscm_status(self.packer_pt, CanBus.CAMERA, CS.pscm_status))
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX
|
||||
new_actuators.steerOutputCan = self.apply_steer_last
|
||||
new_actuators.gas = self.apply_gas
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
from parameterized import parameterized
|
||||
import unittest
|
||||
|
||||
from openpilot.selfdrive.car.gm.fingerprints import FINGERPRINTS
|
||||
from openpilot.selfdrive.car.gm.values import CAMERA_ACC_CAR, GM_RX_OFFSET
|
||||
@@ -8,19 +7,15 @@ from openpilot.selfdrive.car.gm.values import CAMERA_ACC_CAR, GM_RX_OFFSET
|
||||
CAMERA_DIAGNOSTIC_ADDRESS = 0x24b
|
||||
|
||||
|
||||
class TestGMFingerprint(unittest.TestCase):
|
||||
class TestGMFingerprint:
|
||||
@parameterized.expand(FINGERPRINTS.items())
|
||||
def test_can_fingerprints(self, car_model, fingerprints):
|
||||
self.assertGreater(len(fingerprints), 0)
|
||||
assert len(fingerprints) > 0
|
||||
|
||||
self.assertTrue(all(len(finger) for finger in fingerprints))
|
||||
assert all(len(finger) for finger in fingerprints)
|
||||
|
||||
# The camera can sometimes be communicating on startup
|
||||
if car_model in CAMERA_ACC_CAR:
|
||||
for finger in fingerprints:
|
||||
for required_addr in (CAMERA_DIAGNOSTIC_ADDRESS, CAMERA_DIAGNOSTIC_ADDRESS + GM_RX_OFFSET):
|
||||
self.assertEqual(finger.get(required_addr), 8, required_addr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert finger.get(required_addr) == 8, required_addr
|
||||
|
||||
@@ -260,7 +260,7 @@ class CarController(CarControllerBase):
|
||||
if not self.CP.enableGasInterceptorDEPRECATED:
|
||||
self.gas = pcm_accel / self.params.NIDEC_GAS_MAX
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.speed = self.speed
|
||||
new_actuators.accel = self.accel
|
||||
new_actuators.gas = self.gas
|
||||
|
||||
@@ -28,7 +28,7 @@ def get_can_messages(CP, gearbox_msg):
|
||||
("STEER_MOTOR_TORQUE", 0), # TODO: not on every car
|
||||
]
|
||||
|
||||
if CP.carFingerprint in (SERIAL_STEERING | (CAR.HONDA_ODYSSEY_CHN, )):
|
||||
if CP.carFingerprint in (SERIAL_STEERING | {CAR.HONDA_ODYSSEY_CHN, }):
|
||||
messages += [
|
||||
("SCM_FEEDBACK", 25),
|
||||
("SCM_BUTTONS", 50),
|
||||
@@ -58,7 +58,7 @@ def get_can_messages(CP, gearbox_msg):
|
||||
("ACC_CONTROL", 50),
|
||||
]
|
||||
else: # Nidec signals
|
||||
if CP.carFingerprint in (SERIAL_STEERING | (CAR.HONDA_ODYSSEY_CHN, )):
|
||||
if CP.carFingerprint in (SERIAL_STEERING | {CAR.HONDA_ODYSSEY_CHN, }):
|
||||
messages.append(("CRUISE_PARAMS", 10))
|
||||
else:
|
||||
messages.append(("CRUISE_PARAMS", 50))
|
||||
@@ -78,7 +78,7 @@ def get_can_messages(CP, gearbox_msg):
|
||||
|
||||
if CP.carFingerprint in HONDA_BOSCH_RADARLESS:
|
||||
messages.append(("CRUISE_FAULT_STATUS", 50))
|
||||
elif CP.carFingerprint == CAR.CLARITY:
|
||||
elif CP.carFingerprint == CAR.HONDA_CLARITY:
|
||||
messages.append(("BRAKE_ERROR", 100)),
|
||||
elif CP.openpilotLongitudinalControl:
|
||||
messages.append(("STANDSTILL", 50))
|
||||
@@ -149,7 +149,7 @@ class CarState(CarStateBase):
|
||||
|
||||
if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS:
|
||||
ret.accFaulted = bool(cp.vl["CRUISE_FAULT_STATUS"]["CRUISE_FAULT"])
|
||||
elif self.CP.carFingerprint == CAR.CLARITY:
|
||||
elif self.CP.carFingerprint == CAR.HONDA_CLARITY:
|
||||
ret.accFaulted = bool(cp.vl["BRAKE_ERROR"]["BRAKE_ERROR_1"] or cp.vl["BRAKE_ERROR"]["BRAKE_ERROR_2"])
|
||||
else:
|
||||
# On some cars, these two signals are always 1, this flag is masking a bug in release
|
||||
@@ -249,12 +249,12 @@ class CarState(CarStateBase):
|
||||
if ret.brake > 0.1:
|
||||
ret.brakePressed = True
|
||||
|
||||
if self.CP.carFingerprint in (CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN, CAR.CRV_5G, CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH,
|
||||
CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E):
|
||||
if self.CP.carFingerprint in (CAR.HONDA_CIVIC, CAR.HONDA_ODYSSEY, CAR.HONDA_ODYSSEY_CHN, CAR.HONDA_CRV_5G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH,
|
||||
CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CRV_HYBRID, CAR.ACURA_RDX_3G, CAR.HONDA_E):
|
||||
ret.brakeLightsDEPRECATED = bool(cp.vl["ACC_CONTROL"]['BRAKE_LIGHTS'] != 0 or ret.brake > 0.4) if not self.CP.openpilotLongitudinalControl else \
|
||||
bool(ret.brake > 0.4)
|
||||
elif self.CP.carFingerprint in HONDA_BOSCH and self.CP.carFingerprint not in (CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN, CAR.CRV_5G, CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH,
|
||||
CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E) and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS:
|
||||
elif self.CP.carFingerprint in HONDA_BOSCH and self.CP.carFingerprint not in (CAR.HONDA_CIVIC, CAR.HONDA_ODYSSEY, CAR.HONDA_ODYSSEY_CHN, CAR.HONDA_CRV_5G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH,
|
||||
CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CRV_HYBRID, CAR.ACURA_RDX_3G, CAR.HONDA_E) and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS:
|
||||
ret.brakeLightsDEPRECATED = bool(cp.vl["ACC_CONTROL"]['BRAKE_LIGHTS'] != 0 or ret.brake > 0.4) if not self.CP.openpilotLongitudinalControl else \
|
||||
bool(ret.brake > 0.4)
|
||||
|
||||
@@ -263,7 +263,7 @@ class CarState(CarStateBase):
|
||||
if self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS:
|
||||
ret.stockAeb = (not self.CP.openpilotLongitudinalControl) and bool(cp.vl["ACC_CONTROL"]["AEB_STATUS"] and cp.vl["ACC_CONTROL"]["ACCEL_COMMAND"] < -1e-5)
|
||||
else:
|
||||
aeb_sig = "COMPUTER_BRAKE_ALT" if self.CP.carFingerprint == CAR.CLARITY else "COMPUTER_BRAKE"
|
||||
aeb_sig = "COMPUTER_BRAKE_ALT" if self.CP.carFingerprint == CAR.HONDA_CLARITY else "COMPUTER_BRAKE"
|
||||
ret.stockAeb = bool(cp_cam.vl["BRAKE_COMMAND"]["AEB_REQ_1"] and cp_cam.vl["BRAKE_COMMAND"][aeb_sig] > 1e-5)
|
||||
|
||||
self.acc_hud = False
|
||||
|
||||
@@ -809,6 +809,7 @@ FW_VERSIONS = {
|
||||
b'37805-RLV-4070\x00\x00',
|
||||
b'37805-RLV-5140\x00\x00',
|
||||
b'37805-RLV-5230\x00\x00',
|
||||
b'37805-RLV-5250\x00\x00',
|
||||
b'37805-RLV-A630\x00\x00',
|
||||
b'37805-RLV-A830\x00\x00',
|
||||
b'37805-RLV-A840\x00\x00',
|
||||
@@ -833,6 +834,7 @@ FW_VERSIONS = {
|
||||
b'37805-RLV-L350\x00\x00',
|
||||
b'37805-RLV-L410\x00\x00',
|
||||
b'37805-RLV-L430\x00\x00',
|
||||
b'37805-RLV-L830\x00\x00',
|
||||
b'37805-RLV-L850\x00\x00',
|
||||
],
|
||||
(Ecu.gateway, 0x18daeff1, None): [
|
||||
@@ -921,6 +923,7 @@ FW_VERSIONS = {
|
||||
b'37805-5YF-A430\x00\x00',
|
||||
b'37805-5YF-A750\x00\x00',
|
||||
b'37805-5YF-A760\x00\x00',
|
||||
b'37805-5YF-A770\x00\x00',
|
||||
b'37805-5YF-A850\x00\x00',
|
||||
b'37805-5YF-A860\x00\x00',
|
||||
b'37805-5YF-A870\x00\x00',
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from openpilot.selfdrive.car.honda.fingerprints import FW_VERSIONS
|
||||
|
||||
HONDA_FW_VERSION_RE = br"[A-Z0-9]{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$"
|
||||
|
||||
|
||||
class TestHondaFingerprint(unittest.TestCase):
|
||||
class TestHondaFingerprint:
|
||||
def test_fw_version_format(self):
|
||||
# Asserts all FW versions follow an expected format
|
||||
for fw_by_ecu in FW_VERSIONS.values():
|
||||
for fws in fw_by_ecu.values():
|
||||
for fw in fws:
|
||||
self.assertTrue(re.match(HONDA_FW_VERSION_RE, fw) is not None, fw)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert re.match(HONDA_FW_VERSION_RE, fw) is not None, fw
|
||||
|
||||
@@ -165,7 +165,7 @@ class CarController(CarControllerBase):
|
||||
if self.frame % 50 == 0 and self.CP.openpilotLongitudinalControl and not escc:
|
||||
can_sends.append(hyundaican.create_frt_radar_opt(self.packer))
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steer = apply_steer / self.params.STEER_MAX
|
||||
new_actuators.steerOutputCan = apply_steer
|
||||
new_actuators.accel = accel
|
||||
|
||||
@@ -117,6 +117,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2210 4APHC101',
|
||||
b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2310 4APHC101',
|
||||
b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2510 4APHC101',
|
||||
b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2560 4APHC101',
|
||||
@@ -864,6 +865,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00CN7_ SCC FNCUP 1.00 1.01 99110-AA000 ',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA050 4CNDC106',
|
||||
b'\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106',
|
||||
b'\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106',
|
||||
b'\xf1\x00CN7 MDPS C 1.00 1.07 56310AA050\x00 4CNDC107',
|
||||
@@ -965,6 +967,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.05 99210-CV000 211027',
|
||||
b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.06 99210-CV000 220328',
|
||||
b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV100 220630',
|
||||
b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV200 230510',
|
||||
b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027',
|
||||
b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328',
|
||||
],
|
||||
@@ -974,6 +977,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NE1 MFC AT CAN LHD 1.00 1.01 99211-GI010 211007',
|
||||
b'\xf1\x00NE1 MFC AT CAN LHD 1.00 1.05 99211-GI010 220614',
|
||||
b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.01 99211-GI010 211007',
|
||||
b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813',
|
||||
@@ -1002,6 +1006,7 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.HYUNDAI_TUCSON_4TH_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.00 99211-N9260 14Y',
|
||||
b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.01 99211-N9100 14A',
|
||||
b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 1.00 99211-N9220 14K',
|
||||
b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 2.02 99211-N9000 14E',
|
||||
@@ -1037,6 +1042,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00NQ5 FR_CMR AT GEN LHD 1.00 1.00 99211-P1060 665',
|
||||
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662',
|
||||
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663',
|
||||
b'\xf1\x00NQ5 FR_CMR AT EUR LHD 1.00 1.00 99211-P1040 663',
|
||||
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665',
|
||||
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1070 690',
|
||||
],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
from hypothesis import settings, given, strategies as st
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car.fw_versions import build_fw_dict
|
||||
@@ -39,19 +40,19 @@ NO_DATES_PLATFORMS = {
|
||||
CANFD_EXPECTED_ECUS = {Ecu.fwdCamera, Ecu.fwdRadar}
|
||||
|
||||
|
||||
class TestHyundaiFingerprint(unittest.TestCase):
|
||||
class TestHyundaiFingerprint:
|
||||
def test_can_features(self):
|
||||
# Test no EV/HEV in any gear lists (should all use ELECT_GEAR)
|
||||
self.assertEqual(set.union(*CAN_GEARS.values()) & (HYBRID_CAR | EV_CAR), set())
|
||||
assert set.union(*CAN_GEARS.values()) & (HYBRID_CAR | EV_CAR) == set()
|
||||
|
||||
# Test CAN FD car not in CAN feature lists
|
||||
can_specific_feature_list = set.union(*CAN_GEARS.values(), *CHECKSUM.values(), LEGACY_SAFETY_MODE_CAR, UNSUPPORTED_LONGITUDINAL_CAR, CAMERA_SCC_CAR)
|
||||
for car_model in CANFD_CAR:
|
||||
self.assertNotIn(car_model, can_specific_feature_list, "CAN FD car unexpectedly found in a CAN feature list")
|
||||
assert car_model not in can_specific_feature_list, "CAN FD car unexpectedly found in a CAN feature list"
|
||||
|
||||
def test_hybrid_ev_sets(self):
|
||||
self.assertEqual(HYBRID_CAR & EV_CAR, set(), "Shared cars between hybrid and EV")
|
||||
self.assertEqual(CANFD_CAR & HYBRID_CAR, set(), "Hard coding CAN FD cars as hybrid is no longer supported")
|
||||
assert HYBRID_CAR & EV_CAR == set(), "Shared cars between hybrid and EV"
|
||||
assert CANFD_CAR & HYBRID_CAR == set(), "Hard coding CAN FD cars as hybrid is no longer supported"
|
||||
|
||||
def test_canfd_ecu_whitelist(self):
|
||||
# Asserts only expected Ecus can exist in database for CAN-FD cars
|
||||
@@ -59,34 +60,34 @@ class TestHyundaiFingerprint(unittest.TestCase):
|
||||
ecus = {fw[0] for fw in FW_VERSIONS[car_model].keys()}
|
||||
ecus_not_in_whitelist = ecus - CANFD_EXPECTED_ECUS
|
||||
ecu_strings = ", ".join([f"Ecu.{ECU_NAME[ecu]}" for ecu in ecus_not_in_whitelist])
|
||||
self.assertEqual(len(ecus_not_in_whitelist), 0,
|
||||
f"{car_model}: Car model has unexpected ECUs: {ecu_strings}")
|
||||
assert len(ecus_not_in_whitelist) == 0, \
|
||||
f"{car_model}: Car model has unexpected ECUs: {ecu_strings}"
|
||||
|
||||
def test_blacklisted_parts(self):
|
||||
def test_blacklisted_parts(self, subtests):
|
||||
# Asserts no ECUs known to be shared across platforms exist in the database.
|
||||
# Tucson having Santa Cruz camera and EPS for example
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
if car_model == CAR.HYUNDAI_SANTA_CRUZ_1ST_GEN:
|
||||
raise unittest.SkipTest("Skip checking Santa Cruz for its parts")
|
||||
pytest.skip("Skip checking Santa Cruz for its parts")
|
||||
|
||||
for code, _ in get_platform_codes(ecus[(Ecu.fwdCamera, 0x7c4, None)]):
|
||||
if b"-" not in code:
|
||||
continue
|
||||
part = code.split(b"-")[1]
|
||||
self.assertFalse(part.startswith(b'CW'), "Car has bad part number")
|
||||
assert not part.startswith(b'CW'), "Car has bad part number"
|
||||
|
||||
def test_correct_ecu_response_database(self):
|
||||
def test_correct_ecu_response_database(self, subtests):
|
||||
"""
|
||||
Assert standard responses for certain ECUs, since they can
|
||||
respond to multiple queries with different data
|
||||
"""
|
||||
expected_fw_prefix = HYUNDAI_VERSION_REQUEST_LONG[1:]
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
self.assertTrue(all(fw.startswith(expected_fw_prefix) for fw in fws),
|
||||
f"FW from unexpected request in database: {(ecu, fws)}")
|
||||
assert all(fw.startswith(expected_fw_prefix) for fw in fws), \
|
||||
f"FW from unexpected request in database: {(ecu, fws)}"
|
||||
|
||||
@settings(max_examples=100)
|
||||
@given(data=st.data())
|
||||
@@ -96,10 +97,10 @@ class TestHyundaiFingerprint(unittest.TestCase):
|
||||
fws = data.draw(fw_strategy)
|
||||
get_platform_codes(fws)
|
||||
|
||||
def test_expected_platform_codes(self):
|
||||
def test_expected_platform_codes(self, subtests):
|
||||
# Ensures we don't accidentally add multiple platform codes for a car unless it is intentional
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
@@ -107,37 +108,37 @@ class TestHyundaiFingerprint(unittest.TestCase):
|
||||
# Third and fourth character are usually EV/hybrid identifiers
|
||||
codes = {code.split(b"-")[0][:2] for code, _ in get_platform_codes(fws)}
|
||||
if car_model == CAR.HYUNDAI_PALISADE:
|
||||
self.assertEqual(codes, {b"LX", b"ON"}, f"Car has unexpected platform codes: {car_model} {codes}")
|
||||
assert codes == {b"LX", b"ON"}, f"Car has unexpected platform codes: {car_model} {codes}"
|
||||
elif car_model == CAR.HYUNDAI_KONA_EV and ecu[0] == Ecu.fwdCamera:
|
||||
self.assertEqual(codes, {b"OE", b"OS"}, f"Car has unexpected platform codes: {car_model} {codes}")
|
||||
assert codes == {b"OE", b"OS"}, f"Car has unexpected platform codes: {car_model} {codes}"
|
||||
else:
|
||||
self.assertEqual(len(codes), 1, f"Car has multiple platform codes: {car_model} {codes}")
|
||||
assert len(codes) == 1, f"Car has multiple platform codes: {car_model} {codes}"
|
||||
|
||||
# Tests for platform codes, part numbers, and FW dates which Hyundai will use to fuzzy
|
||||
# fingerprint in the absence of full FW matches:
|
||||
def test_platform_code_ecus_available(self):
|
||||
def test_platform_code_ecus_available(self, subtests):
|
||||
# TODO: add queries for these non-CAN FD cars to get EPS
|
||||
no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H,
|
||||
CAR.KIA_OPTIMA_H_G4_FL, CAR.HYUNDAI_SONATA_LF, CAR.HYUNDAI_TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80, CAR.HYUNDAI_ELANTRA}
|
||||
|
||||
# Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for platform_code_ecu in PLATFORM_CODE_ECUS:
|
||||
if platform_code_ecu in (Ecu.fwdRadar, Ecu.eps) and car_model == CAR.HYUNDAI_GENESIS:
|
||||
continue
|
||||
if platform_code_ecu == Ecu.eps and car_model in no_eps_platforms:
|
||||
continue
|
||||
self.assertIn(platform_code_ecu, [e[0] for e in ecus])
|
||||
assert platform_code_ecu in [e[0] for e in ecus]
|
||||
|
||||
def test_fw_format(self):
|
||||
def test_fw_format(self, subtests):
|
||||
# Asserts:
|
||||
# - every supported ECU FW version returns one platform code
|
||||
# - every supported ECU FW version has a part number
|
||||
# - expected parsing of ECU FW dates
|
||||
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
@@ -145,40 +146,40 @@ class TestHyundaiFingerprint(unittest.TestCase):
|
||||
codes = set()
|
||||
for fw in fws:
|
||||
result = get_platform_codes([fw])
|
||||
self.assertEqual(1, len(result), f"Unable to parse FW: {fw}")
|
||||
assert 1 == len(result), f"Unable to parse FW: {fw}"
|
||||
codes |= result
|
||||
|
||||
if ecu[0] not in DATE_FW_ECUS or car_model in NO_DATES_PLATFORMS:
|
||||
self.assertTrue(all(date is None for _, date in codes))
|
||||
assert all(date is None for _, date in codes)
|
||||
else:
|
||||
self.assertTrue(all(date is not None for _, date in codes))
|
||||
assert all(date is not None for _, date in codes)
|
||||
|
||||
if car_model == CAR.HYUNDAI_GENESIS:
|
||||
raise unittest.SkipTest("No part numbers for car model")
|
||||
pytest.skip("No part numbers for car model")
|
||||
|
||||
# Hyundai places the ECU part number in their FW versions, assert all parsable
|
||||
# Some examples of valid formats: b"56310-L0010", b"56310L0010", b"56310/M6300"
|
||||
self.assertTrue(all(b"-" in code for code, _ in codes),
|
||||
f"FW does not have part number: {fw}")
|
||||
assert all(b"-" in code for code, _ in codes), \
|
||||
f"FW does not have part number: {fw}"
|
||||
|
||||
def test_platform_codes_spot_check(self):
|
||||
# Asserts basic platform code parsing behavior for a few cases
|
||||
results = get_platform_codes([b"\xf1\x00DH LKAS 1.1 -150210"])
|
||||
self.assertEqual(results, {(b"DH", b"150210")})
|
||||
assert results == {(b"DH", b"150210")}
|
||||
|
||||
# Some cameras and all radars do not have dates
|
||||
results = get_platform_codes([b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 "])
|
||||
self.assertEqual(results, {(b"AEhe-G2000", None)})
|
||||
assert results == {(b"AEhe-G2000", None)}
|
||||
|
||||
results = get_platform_codes([b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 "])
|
||||
self.assertEqual(results, {(b"CV1-CV000", None)})
|
||||
assert results == {(b"CV1-CV000", None)}
|
||||
|
||||
results = get_platform_codes([
|
||||
b"\xf1\x00DH LKAS 1.1 -150210",
|
||||
b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ",
|
||||
b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ",
|
||||
])
|
||||
self.assertEqual(results, {(b"DH", b"150210"), (b"AEhe-G2000", None), (b"CV1-CV000", None)})
|
||||
assert results == {(b"DH", b"150210"), (b"AEhe-G2000", None), (b"CV1-CV000", None)}
|
||||
|
||||
results = get_platform_codes([
|
||||
b"\xf1\x00LX2 MFC AT USA LHD 1.00 1.07 99211-S8100 220222",
|
||||
@@ -186,8 +187,8 @@ class TestHyundaiFingerprint(unittest.TestCase):
|
||||
b"\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 190405",
|
||||
b"\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 190720",
|
||||
])
|
||||
self.assertEqual(results, {(b"LX2-S8100", b"220222"), (b"LX2-S8100", b"211103"),
|
||||
(b"ON-S9100", b"190405"), (b"ON-S9100", b"190720")})
|
||||
assert results == {(b"LX2-S8100", b"220222"), (b"LX2-S8100", b"211103"),
|
||||
(b"ON-S9100", b"190405"), (b"ON-S9100", b"190720")}
|
||||
|
||||
def test_fuzzy_excluded_platforms(self):
|
||||
# Asserts a list of platforms that will not fuzzy fingerprint with platform codes due to them being shared.
|
||||
@@ -211,12 +212,8 @@ class TestHyundaiFingerprint(unittest.TestCase):
|
||||
CP = car.CarParams.new_message(carFw=car_fw)
|
||||
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS)
|
||||
if len(matches) == 1:
|
||||
self.assertEqual(list(matches)[0], platform)
|
||||
assert list(matches)[0] == platform
|
||||
else:
|
||||
platforms_with_shared_codes.add(platform)
|
||||
|
||||
self.assertEqual(platforms_with_shared_codes, excluded_platforms)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert platforms_with_shared_codes == excluded_platforms
|
||||
|
||||
@@ -225,7 +225,7 @@ class CAR(Platforms):
|
||||
flags=HyundaiFlags.HYBRID,
|
||||
)
|
||||
HYUNDAI_KONA = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Kona 2020", car_parts=CarParts.common([CarHarness.hyundai_b]))],
|
||||
[HyundaiCarDocs("Hyundai Kona 2020", min_enable_speed=6 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b]))],
|
||||
CarSpecs(mass=1275, wheelbase=2.6, steerRatio=13.42, tireStiffnessFactor=0.385),
|
||||
flags=HyundaiFlags.CLUSTER_GEARS,
|
||||
)
|
||||
@@ -333,6 +333,7 @@ class CAR(Platforms):
|
||||
HyundaiCarDocs("Hyundai Tucson 2022", car_parts=CarParts.common([CarHarness.hyundai_n])),
|
||||
HyundaiCarDocs("Hyundai Tucson 2023-24", "All", car_parts=CarParts.common([CarHarness.hyundai_n])),
|
||||
HyundaiCarDocs("Hyundai Tucson Hybrid 2022-24", "All", car_parts=CarParts.common([CarHarness.hyundai_n])),
|
||||
HyundaiCarDocs("Hyundai Tucson Plug-in Hybrid 2024", "All", car_parts=CarParts.common([CarHarness.hyundai_n])),
|
||||
],
|
||||
CarSpecs(mass=1630, wheelbase=2.756, steerRatio=13.7, tireStiffnessFactor=0.385),
|
||||
)
|
||||
@@ -350,8 +351,8 @@ class CAR(Platforms):
|
||||
# Kia
|
||||
KIA_FORTE = HyundaiPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Kia Forte 2019-21", car_parts=CarParts.common([CarHarness.hyundai_g])),
|
||||
HyundaiCarDocs("Kia Forte 2023", car_parts=CarParts.common([CarHarness.hyundai_e])),
|
||||
HyundaiCarDocs("Kia Forte 2019-21", min_enable_speed=6 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_g])),
|
||||
HyundaiCarDocs("Kia Forte 2022-23", car_parts=CarParts.common([CarHarness.hyundai_e])),
|
||||
],
|
||||
CarSpecs(mass=2878 * CV.LB_TO_KG, wheelbase=2.8, steerRatio=13.75, tireStiffnessFactor=0.5)
|
||||
)
|
||||
@@ -487,9 +488,9 @@ class CAR(Platforms):
|
||||
)
|
||||
KIA_EV6 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Kia EV6 (Southeast Asia only) 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_p])),
|
||||
HyundaiCarDocs("Kia EV6 (without HDA II) 2022-23", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_l])),
|
||||
HyundaiCarDocs("Kia EV6 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p]))
|
||||
HyundaiCarDocs("Kia EV6 (Southeast Asia only) 2022-24", "All", car_parts=CarParts.common([CarHarness.hyundai_p])),
|
||||
HyundaiCarDocs("Kia EV6 (without HDA II) 2022-24", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_l])),
|
||||
HyundaiCarDocs("Kia EV6 (with HDA II) 2022-24", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p]))
|
||||
],
|
||||
CarSpecs(mass=2055, wheelbase=2.9, steerRatio=16, tireStiffnessFactor=0.65),
|
||||
flags=HyundaiFlags.EV,
|
||||
@@ -692,6 +693,8 @@ PLATFORM_CODE_ECUS = [Ecu.fwdRadar, Ecu.fwdCamera, Ecu.eps]
|
||||
# TODO: there are date codes in the ABS firmware versions in hex
|
||||
DATE_FW_ECUS = [Ecu.fwdCamera]
|
||||
|
||||
# Note: an ECU on CAN FD cars may sometimes send 0x30080aaaaaaaaaaa (flow control continue) while we
|
||||
# are attempting to query ECUs. This currently does not seem to affect fingerprinting from the camera
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
requests=[
|
||||
# TODO: add back whitelists
|
||||
|
||||
@@ -6,6 +6,7 @@ from abc import abstractmethod, ABC
|
||||
from enum import StrEnum
|
||||
from typing import Any, NamedTuple
|
||||
from collections.abc import Callable
|
||||
from functools import cache
|
||||
|
||||
from cereal import car
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
@@ -43,6 +44,7 @@ class LatControlInputs(NamedTuple):
|
||||
TorqueFromLateralAccelCallbackType = Callable[[LatControlInputs, car.CarParams.LateralTorqueTuning, float, float, bool, bool], float]
|
||||
|
||||
|
||||
@cache
|
||||
def get_torque_params(candidate):
|
||||
with open(TORQUE_SUBSTITUTE_PATH, 'rb') as f:
|
||||
sub = tomllib.load(f)
|
||||
@@ -243,11 +245,10 @@ class CarInterfaceBase(ABC):
|
||||
ret.cruiseState.speedCluster = ret.cruiseState.speed
|
||||
|
||||
# copy back for next iteration
|
||||
reader = ret.as_reader()
|
||||
if self.CS is not None:
|
||||
self.CS.out = reader
|
||||
self.CS.out = ret.as_reader()
|
||||
|
||||
return reader
|
||||
return ret
|
||||
|
||||
|
||||
def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True, allow_enable=True,
|
||||
@@ -283,6 +284,10 @@ class CarInterfaceBase(ABC):
|
||||
events.add(EventName.accFaulted)
|
||||
if cs_out.steeringPressed:
|
||||
events.add(EventName.steerOverride)
|
||||
if cs_out.brakePressed and cs_out.standstill:
|
||||
events.add(EventName.preEnableStandstill)
|
||||
if cs_out.gasPressed:
|
||||
events.add(EventName.gasPressedOverride)
|
||||
|
||||
# Handle button presses
|
||||
for b in cs_out.buttonEvents:
|
||||
|
||||
@@ -58,7 +58,7 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(mazdacan.create_steering_control(self.packer, self.CP,
|
||||
self.frame, apply_steer, CS.cam_lkas))
|
||||
|
||||
new_actuators = CC.actuators.copy()
|
||||
new_actuators = CC.actuators.as_builder()
|
||||
new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX
|
||||
new_actuators.steerOutputCan = apply_steer
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@ from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
|
||||
class CarController(CarControllerBase):
|
||||
def update(self, CC, CS, now_nanos):
|
||||
return CC.actuators.copy(), []
|
||||
return CC.actuators.as_builder(), []
|
||||
|
||||
@@ -75,7 +75,7 @@ class CarController(CarControllerBase):
|
||||
self.packer, CS.lkas_hud_info_msg, steer_hud_alert
|
||||
))
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steeringAngleDeg = apply_angle
|
||||
|
||||
self.frame += 1
|
||||
|
||||
@@ -136,7 +136,7 @@ class CarController(CarControllerBase):
|
||||
if self.frame % 2 == 0:
|
||||
can_sends.append(subarucan.create_es_static_2(self.packer))
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX
|
||||
new_actuators.steerOutputCan = self.apply_steer_last
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ FW_VERSIONS = {
|
||||
b'\xa2 \x194\x00',
|
||||
b'\xa2 `\x00',
|
||||
b'\xa2 !3\x00',
|
||||
b'\xa2 !6\x00',
|
||||
b'\xa2 !`\x00',
|
||||
b'\xa2 !i\x00',
|
||||
],
|
||||
@@ -172,6 +173,7 @@ FW_VERSIONS = {
|
||||
b'\n\xc0\x04\x01',
|
||||
b'\x9a\xc0\x00\x00',
|
||||
b'\x9a\xc0\x04\x00',
|
||||
b'\x9a\xc0\n\x01',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x787, None): [
|
||||
b'\x00\x00eb\x1f@ "',
|
||||
@@ -181,6 +183,7 @@ FW_VERSIONS = {
|
||||
b'\x00\x00e\x8f\x1f@ )',
|
||||
b'\x00\x00e\x92\x00\x00\x00\x00',
|
||||
b'\x00\x00e\xa4\x00\x00\x00\x00',
|
||||
b'\x00\x00e\xa4\x1f@ (',
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xca!`0\x07',
|
||||
@@ -188,6 +191,7 @@ FW_VERSIONS = {
|
||||
b'\xca!ap\x07',
|
||||
b'\xca!f@\x07',
|
||||
b'\xca!fp\x07',
|
||||
b'\xcaacp\x07',
|
||||
b'\xcc!`p\x07',
|
||||
b'\xcc!fp\x07',
|
||||
b'\xcc"f0\x07',
|
||||
@@ -200,6 +204,7 @@ FW_VERSIONS = {
|
||||
b'\xf3"fr\x07',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xe6\x15\x042\x00',
|
||||
b'\xe6\xf5\x04\x00\x00',
|
||||
b'\xe6\xf5$\x00\x00',
|
||||
b'\xe6\xf5D0\x00',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from cereal import car
|
||||
import unittest
|
||||
from openpilot.selfdrive.car.subaru.fingerprints import FW_VERSIONS
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
@@ -7,14 +6,11 @@ Ecu = car.CarParams.Ecu
|
||||
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
|
||||
|
||||
|
||||
class TestSubaruFingerprint(unittest.TestCase):
|
||||
class TestSubaruFingerprint:
|
||||
def test_fw_version_format(self):
|
||||
for platform, fws_per_ecu in FW_VERSIONS.items():
|
||||
for (ecu, _, _), fws in fws_per_ecu.items():
|
||||
fw_size = len(fws[0])
|
||||
for fw in fws:
|
||||
self.assertEqual(len(fw), fw_size, f"{platform} {ecu}: {len(fw)} {fw_size}")
|
||||
assert len(fw) == fw_size, f"{platform} {ecu}: {len(fw)} {fw_size}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -60,7 +60,7 @@ class CarController(CarControllerBase):
|
||||
|
||||
# TODO: HUD control
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steeringAngleDeg = self.apply_angle_last
|
||||
|
||||
self.frame += 1
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
from parameterized import parameterized
|
||||
import unittest
|
||||
|
||||
from cereal import log, messaging
|
||||
from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, can_fingerprint
|
||||
from openpilot.selfdrive.car.fingerprints import _FINGERPRINTS as FINGERPRINTS
|
||||
|
||||
|
||||
class TestCanFingerprint(unittest.TestCase):
|
||||
class TestCanFingerprint:
|
||||
@parameterized.expand(list(FINGERPRINTS.items()))
|
||||
def test_can_fingerprint(self, car_model, fingerprints):
|
||||
"""Tests online fingerprinting function on offline fingerprints"""
|
||||
@@ -21,12 +20,12 @@ class TestCanFingerprint(unittest.TestCase):
|
||||
empty_can = messaging.new_message('can', 0)
|
||||
car_fingerprint, finger = can_fingerprint(lambda: next(fingerprint_iter, empty_can)) # noqa: B023
|
||||
|
||||
self.assertEqual(car_fingerprint, car_model)
|
||||
self.assertEqual(finger[0], fingerprint)
|
||||
self.assertEqual(finger[1], fingerprint)
|
||||
self.assertEqual(finger[2], {})
|
||||
assert car_fingerprint == car_model
|
||||
assert finger[0] == fingerprint
|
||||
assert finger[1] == fingerprint
|
||||
assert finger[2] == {}
|
||||
|
||||
def test_timing(self):
|
||||
def test_timing(self, subtests):
|
||||
# just pick any CAN fingerprinting car
|
||||
car_model = "CHEVROLET_BOLT_EUV"
|
||||
fingerprint = FINGERPRINTS[car_model][0]
|
||||
@@ -50,7 +49,7 @@ class TestCanFingerprint(unittest.TestCase):
|
||||
cases.append((FRAME_FINGERPRINT * 2, None, can))
|
||||
|
||||
for expected_frames, car_model, can in cases:
|
||||
with self.subTest(expected_frames=expected_frames, car_model=car_model):
|
||||
with subtests.test(expected_frames=expected_frames, car_model=car_model):
|
||||
frames = 0
|
||||
|
||||
def test():
|
||||
@@ -59,9 +58,5 @@ class TestCanFingerprint(unittest.TestCase):
|
||||
return can # noqa: B023
|
||||
|
||||
car_fingerprint, _ = can_fingerprint(test)
|
||||
self.assertEqual(car_fingerprint, car_model)
|
||||
self.assertEqual(frames, expected_frames + 2) # TODO: fix extra frames
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert car_fingerprint == car_model
|
||||
assert frames == expected_frames + 2# TODO: fix extra frames
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import math
|
||||
import unittest
|
||||
import hypothesis.strategies as st
|
||||
from hypothesis import Phase, given, settings
|
||||
import importlib
|
||||
@@ -12,7 +11,7 @@ from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.car import gen_empty_fingerprint
|
||||
from openpilot.selfdrive.car.car_helpers import interfaces
|
||||
from openpilot.selfdrive.car.fingerprints import all_known_cars
|
||||
from openpilot.selfdrive.car.fw_versions import FW_VERSIONS
|
||||
from openpilot.selfdrive.car.fw_versions import FW_VERSIONS, FW_QUERY_CONFIGS
|
||||
from openpilot.selfdrive.car.interfaces import get_interface_attr
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
|
||||
@@ -20,7 +19,10 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongControl
|
||||
from openpilot.selfdrive.test.fuzzy_generation import DrawType, FuzzyGenerator
|
||||
|
||||
ALL_ECUS = list({ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()})
|
||||
ALL_ECUS = {ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()}
|
||||
ALL_ECUS |= {ecu for config in FW_QUERY_CONFIGS.values() for ecu in config.extra_ecus}
|
||||
|
||||
ALL_REQUESTS = {tuple(r.request) for config in FW_QUERY_CONFIGS.values() for r in config.requests}
|
||||
|
||||
MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '40'))
|
||||
|
||||
@@ -32,7 +34,7 @@ def get_fuzzy_car_interface_args(draw: DrawType) -> dict:
|
||||
gen_empty_fingerprint()})
|
||||
|
||||
# only pick from possible ecus to reduce search space
|
||||
car_fw_strategy = st.lists(st.sampled_from(ALL_ECUS))
|
||||
car_fw_strategy = st.lists(st.sampled_from(sorted(ALL_ECUS)))
|
||||
|
||||
params_strategy = st.fixed_dictionaries({
|
||||
'fingerprints': fingerprint_strategy,
|
||||
@@ -41,11 +43,13 @@ def get_fuzzy_car_interface_args(draw: DrawType) -> dict:
|
||||
})
|
||||
|
||||
params: dict = draw(params_strategy)
|
||||
params['car_fw'] = [car.CarParams.CarFw(ecu=fw[0], address=fw[1], subAddress=fw[2] or 0) for fw in params['car_fw']]
|
||||
params['car_fw'] = [car.CarParams.CarFw(ecu=fw[0], address=fw[1], subAddress=fw[2] or 0,
|
||||
request=draw(st.sampled_from(sorted(ALL_REQUESTS))))
|
||||
for fw in params['car_fw']]
|
||||
return params
|
||||
|
||||
|
||||
class TestCarInterfaces(unittest.TestCase):
|
||||
class TestCarInterfaces:
|
||||
# FIXME: Due to the lists used in carParams, Phase.target is very slow and will cause
|
||||
# many generated examples to overrun when max_examples > ~20, don't use it
|
||||
@parameterized.expand([(car,) for car in sorted(all_known_cars())])
|
||||
@@ -63,16 +67,16 @@ class TestCarInterfaces(unittest.TestCase):
|
||||
assert car_params
|
||||
assert car_interface
|
||||
|
||||
self.assertGreater(car_params.mass, 1)
|
||||
self.assertGreater(car_params.wheelbase, 0)
|
||||
assert car_params.mass > 1
|
||||
assert car_params.wheelbase > 0
|
||||
# centerToFront is center of gravity to front wheels, assert a reasonable range
|
||||
self.assertTrue(car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7)
|
||||
self.assertGreater(car_params.maxLateralAccel, 0)
|
||||
assert car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7
|
||||
assert car_params.maxLateralAccel > 0
|
||||
|
||||
# Longitudinal sanity checks
|
||||
self.assertEqual(len(car_params.longitudinalTuning.kpV), len(car_params.longitudinalTuning.kpBP))
|
||||
self.assertEqual(len(car_params.longitudinalTuning.kiV), len(car_params.longitudinalTuning.kiBP))
|
||||
self.assertEqual(len(car_params.longitudinalTuning.deadzoneV), len(car_params.longitudinalTuning.deadzoneBP))
|
||||
assert len(car_params.longitudinalTuning.kpV) == len(car_params.longitudinalTuning.kpBP)
|
||||
assert len(car_params.longitudinalTuning.kiV) == len(car_params.longitudinalTuning.kiBP)
|
||||
assert len(car_params.longitudinalTuning.deadzoneV) == len(car_params.longitudinalTuning.deadzoneBP)
|
||||
|
||||
# If we're using the interceptor for gasPressed, we should be commanding gas with it
|
||||
if car_params.enableGasInterceptorDEPRECATED:
|
||||
@@ -82,13 +86,13 @@ class TestCarInterfaces(unittest.TestCase):
|
||||
if car_params.steerControlType != car.CarParams.SteerControlType.angle:
|
||||
tune = car_params.lateralTuning
|
||||
if tune.which() == 'pid':
|
||||
self.assertTrue(not math.isnan(tune.pid.kf) and tune.pid.kf > 0)
|
||||
self.assertTrue(len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP))
|
||||
self.assertTrue(len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP))
|
||||
assert not math.isnan(tune.pid.kf) and tune.pid.kf > 0
|
||||
assert len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP)
|
||||
assert len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP)
|
||||
|
||||
elif tune.which() == 'torque':
|
||||
self.assertTrue(not math.isnan(tune.torque.kf) and tune.torque.kf > 0)
|
||||
self.assertTrue(not math.isnan(tune.torque.friction) and tune.torque.friction > 0)
|
||||
assert not math.isnan(tune.torque.kf) and tune.torque.kf > 0
|
||||
assert not math.isnan(tune.torque.friction) and tune.torque.friction > 0
|
||||
|
||||
cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True)
|
||||
# Run car interface
|
||||
@@ -96,16 +100,14 @@ class TestCarInterfaces(unittest.TestCase):
|
||||
CC = car.CarControl.new_message(**cc_msg)
|
||||
for _ in range(10):
|
||||
car_interface.update(CC, [])
|
||||
car_interface.apply(CC, now_nanos)
|
||||
car_interface.apply(CC, now_nanos)
|
||||
car_interface.apply(CC.as_reader(), now_nanos)
|
||||
now_nanos += DT_CTRL * 1e9 # 10 ms
|
||||
|
||||
CC = car.CarControl.new_message(**cc_msg)
|
||||
CC.enabled = True
|
||||
for _ in range(10):
|
||||
car_interface.update(CC, [])
|
||||
car_interface.apply(CC, now_nanos)
|
||||
car_interface.apply(CC, now_nanos)
|
||||
car_interface.apply(CC.as_reader(), now_nanos)
|
||||
now_nanos += DT_CTRL * 1e9 # 10ms
|
||||
|
||||
# Test controller initialization
|
||||
@@ -134,33 +136,29 @@ class TestCarInterfaces(unittest.TestCase):
|
||||
if not car_params.radarUnavailable and radar_interface.rcp is not None:
|
||||
cans = [messaging.new_message('can', 1).to_bytes() for _ in range(5)]
|
||||
rr = radar_interface.update(cans)
|
||||
self.assertTrue(rr is None or len(rr.errors) > 0)
|
||||
assert rr is None or len(rr.errors) > 0
|
||||
|
||||
def test_interface_attrs(self):
|
||||
"""Asserts basic behavior of interface attribute getter"""
|
||||
num_brands = len(get_interface_attr('CAR'))
|
||||
self.assertGreaterEqual(num_brands, 13)
|
||||
assert num_brands >= 13
|
||||
|
||||
# Should return value for all brands when not combining, even if attribute doesn't exist
|
||||
ret = get_interface_attr('FAKE_ATTR')
|
||||
self.assertEqual(len(ret), num_brands)
|
||||
assert len(ret) == num_brands
|
||||
|
||||
# Make sure we can combine dicts
|
||||
ret = get_interface_attr('DBC', combine_brands=True)
|
||||
self.assertGreaterEqual(len(ret), 160)
|
||||
assert len(ret) >= 160
|
||||
|
||||
# We don't support combining non-dicts
|
||||
ret = get_interface_attr('CAR', combine_brands=True)
|
||||
self.assertEqual(len(ret), 0)
|
||||
assert len(ret) == 0
|
||||
|
||||
# If brand has None value, it shouldn't return when ignore_none=True is specified
|
||||
none_brands = {b for b, v in get_interface_attr('FINGERPRINTS').items() if v is None}
|
||||
self.assertGreaterEqual(len(none_brands), 1)
|
||||
assert len(none_brands) >= 1
|
||||
|
||||
ret = get_interface_attr('FINGERPRINTS', ignore_none=True)
|
||||
none_brands_in_ret = none_brands.intersection(ret)
|
||||
self.assertEqual(len(none_brands_in_ret), 0, f'Brands with None values in ignore_none=True result: {none_brands_in_ret}')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert len(none_brands_in_ret) == 0, f'Brands with None values in ignore_none=True result: {none_brands_in_ret}'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
from collections import defaultdict
|
||||
import os
|
||||
import pytest
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.selfdrive.car.car_helpers import interfaces
|
||||
@@ -14,9 +14,9 @@ from openpilot.selfdrive.debug.dump_car_docs import dump_car_docs
|
||||
from openpilot.selfdrive.debug.print_docs_diff import print_car_docs_diff
|
||||
|
||||
|
||||
class TestCarDocs(unittest.TestCase):
|
||||
class TestCarDocs:
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
def setup_class(cls):
|
||||
cls.all_cars = get_all_car_docs()
|
||||
|
||||
def test_generator(self):
|
||||
@@ -24,8 +24,7 @@ class TestCarDocs(unittest.TestCase):
|
||||
with open(CARS_MD_OUT) as f:
|
||||
current_cars_md = f.read()
|
||||
|
||||
self.assertEqual(generated_cars_md, current_cars_md,
|
||||
"Run selfdrive/car/docs.py to update the compatibility documentation")
|
||||
assert generated_cars_md == current_cars_md, "Run selfdrive/car/docs.py to update the compatibility documentation"
|
||||
|
||||
def test_docs_diff(self):
|
||||
dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump")
|
||||
@@ -33,65 +32,61 @@ class TestCarDocs(unittest.TestCase):
|
||||
print_car_docs_diff(dump_path)
|
||||
os.remove(dump_path)
|
||||
|
||||
def test_duplicate_years(self):
|
||||
def test_duplicate_years(self, subtests):
|
||||
make_model_years = defaultdict(list)
|
||||
for car in self.all_cars:
|
||||
with self.subTest(car_docs_name=car.name):
|
||||
with subtests.test(car_docs_name=car.name):
|
||||
make_model = (car.make, car.model)
|
||||
for year in car.year_list:
|
||||
self.assertNotIn(year, make_model_years[make_model], f"{car.name}: Duplicate model year")
|
||||
assert year not in make_model_years[make_model], f"{car.name}: Duplicate model year"
|
||||
make_model_years[make_model].append(year)
|
||||
|
||||
def test_missing_car_docs(self):
|
||||
def test_missing_car_docs(self, subtests):
|
||||
all_car_docs_platforms = [name for name, config in PLATFORMS.items()]
|
||||
for platform in sorted(interfaces.keys()):
|
||||
with self.subTest(platform=platform):
|
||||
self.assertTrue(platform in all_car_docs_platforms, f"Platform: {platform} doesn't have a CarDocs entry")
|
||||
with subtests.test(platform=platform):
|
||||
assert platform in all_car_docs_platforms, f"Platform: {platform} doesn't have a CarDocs entry"
|
||||
|
||||
def test_naming_conventions(self):
|
||||
def test_naming_conventions(self, subtests):
|
||||
# Asserts market-standard car naming conventions by brand
|
||||
for car in self.all_cars:
|
||||
with self.subTest(car=car):
|
||||
with subtests.test(car=car.name):
|
||||
tokens = car.model.lower().split(" ")
|
||||
if car.car_name == "hyundai":
|
||||
self.assertNotIn("phev", tokens, "Use `Plug-in Hybrid`")
|
||||
self.assertNotIn("hev", tokens, "Use `Hybrid`")
|
||||
assert "phev" not in tokens, "Use `Plug-in Hybrid`"
|
||||
assert "hev" not in tokens, "Use `Hybrid`"
|
||||
if "plug-in hybrid" in car.model.lower():
|
||||
self.assertIn("Plug-in Hybrid", car.model, "Use correct capitalization")
|
||||
assert "Plug-in Hybrid" in car.model, "Use correct capitalization"
|
||||
if car.make != "Kia":
|
||||
self.assertNotIn("ev", tokens, "Use `Electric`")
|
||||
assert "ev" not in tokens, "Use `Electric`"
|
||||
elif car.car_name == "toyota":
|
||||
if "rav4" in tokens:
|
||||
self.assertIn("RAV4", car.model, "Use correct capitalization")
|
||||
assert "RAV4" in car.model, "Use correct capitalization"
|
||||
|
||||
def test_torque_star(self):
|
||||
def test_torque_star(self, subtests):
|
||||
# Asserts brand-specific assumptions around steering torque star
|
||||
for car in self.all_cars:
|
||||
with self.subTest(car=car):
|
||||
with subtests.test(car=car.name):
|
||||
# honda sanity check, it's the definition of a no torque star
|
||||
if car.car_fingerprint in (HONDA.HONDA_ACCORD, HONDA.HONDA_CIVIC, HONDA.HONDA_CRV, HONDA.HONDA_ODYSSEY, HONDA.HONDA_PILOT):
|
||||
self.assertEqual(car.row[Column.STEERING_TORQUE], Star.EMPTY, f"{car.name} has full torque star")
|
||||
assert car.row[Column.STEERING_TORQUE] == Star.EMPTY, f"{car.name} has full torque star"
|
||||
elif car.car_name in ("toyota", "hyundai"):
|
||||
self.assertNotEqual(car.row[Column.STEERING_TORQUE], Star.EMPTY, f"{car.name} has no torque star")
|
||||
assert car.row[Column.STEERING_TORQUE] != Star.EMPTY, f"{car.name} has no torque star"
|
||||
|
||||
def test_year_format(self):
|
||||
def test_year_format(self, subtests):
|
||||
for car in self.all_cars:
|
||||
with self.subTest(car=car):
|
||||
self.assertIsNone(re.search(r"\d{4}-\d{4}", car.name), f"Format years correctly: {car.name}")
|
||||
with subtests.test(car=car.name):
|
||||
assert re.search(r"\d{4}-\d{4}", car.name) is None, f"Format years correctly: {car.name}"
|
||||
|
||||
def test_harnesses(self):
|
||||
def test_harnesses(self, subtests):
|
||||
for car in self.all_cars:
|
||||
with self.subTest(car=car):
|
||||
with subtests.test(car=car.name):
|
||||
if car.name == "comma body":
|
||||
raise unittest.SkipTest
|
||||
pytest.skip()
|
||||
|
||||
car_part_type = [p.part_type for p in car.car_parts.all_parts()]
|
||||
car_parts = list(car.car_parts.all_parts())
|
||||
self.assertTrue(len(car_parts) > 0, f"Need to specify car parts: {car.name}")
|
||||
self.assertTrue(car_part_type.count(PartType.connector) == 1, f"Need to specify one harness connector: {car.name}")
|
||||
self.assertTrue(car_part_type.count(PartType.mount) == 1, f"Need to specify one mount: {car.name}")
|
||||
self.assertTrue(Cable.right_angle_obd_c_cable_1_5ft in car_parts, f"Need to specify a right angle OBD-C cable (1.5ft): {car.name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert len(car_parts) > 0, f"Need to specify car parts: {car.name}"
|
||||
assert car_part_type.count(PartType.connector) == 1, f"Need to specify one harness connector: {car.name}"
|
||||
assert car_part_type.count(PartType.mount) == 1, f"Need to specify one mount: {car.name}"
|
||||
assert Cable.right_angle_obd_c_cable_1_5ft in car_parts, f"Need to specify a right angle OBD-C cable (1.5ft): {car.name}"
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import random
|
||||
import time
|
||||
import unittest
|
||||
from collections import defaultdict
|
||||
from parameterized import parameterized
|
||||
from unittest import mock
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car.car_helpers import interfaces
|
||||
@@ -27,11 +26,11 @@ class FakeSocket:
|
||||
pass
|
||||
|
||||
|
||||
class TestFwFingerprint(unittest.TestCase):
|
||||
class TestFwFingerprint:
|
||||
def assertFingerprints(self, candidates, expected):
|
||||
candidates = list(candidates)
|
||||
self.assertEqual(len(candidates), 1, f"got more than one candidate: {candidates}")
|
||||
self.assertEqual(candidates[0], expected)
|
||||
assert len(candidates) == 1, f"got more than one candidate: {candidates}"
|
||||
assert candidates[0] == expected
|
||||
|
||||
@parameterized.expand([(b, c, e[c], n) for b, e in VERSIONS.items() for c in e for n in (True, False)])
|
||||
def test_exact_match(self, brand, car_model, ecus, test_non_essential):
|
||||
@@ -62,7 +61,7 @@ class TestFwFingerprint(unittest.TestCase):
|
||||
# Assert brand-specific fuzzy fingerprinting function doesn't disagree with standard fuzzy function
|
||||
config = FW_QUERY_CONFIGS[brand]
|
||||
if config.match_fw_to_car_fuzzy is None:
|
||||
raise unittest.SkipTest("Brand does not implement custom fuzzy fingerprinting function")
|
||||
pytest.skip("Brand does not implement custom fuzzy fingerprinting function")
|
||||
|
||||
CP = car.CarParams.new_message()
|
||||
for _ in range(5):
|
||||
@@ -77,14 +76,14 @@ class TestFwFingerprint(unittest.TestCase):
|
||||
|
||||
# If both have matches, they must agree
|
||||
if len(matches) == 1 and len(brand_matches) == 1:
|
||||
self.assertEqual(matches, brand_matches)
|
||||
assert matches == brand_matches
|
||||
|
||||
@parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e])
|
||||
def test_fuzzy_match_ecu_count(self, brand, car_model, ecus):
|
||||
# Asserts that fuzzy matching does not count matching FW, but ECU address keys
|
||||
valid_ecus = [e for e in ecus if e[0] not in FUZZY_EXCLUDE_ECUS]
|
||||
if not len(valid_ecus):
|
||||
raise unittest.SkipTest("Car model has no compatible ECUs for fuzzy matching")
|
||||
pytest.skip("Car model has no compatible ECUs for fuzzy matching")
|
||||
|
||||
fw = []
|
||||
for ecu in valid_ecus:
|
||||
@@ -99,19 +98,19 @@ class TestFwFingerprint(unittest.TestCase):
|
||||
# Assert no match if there are not enough unique ECUs
|
||||
unique_ecus = {(f['address'], f['subAddress']) for f in fw}
|
||||
if len(unique_ecus) < 2:
|
||||
self.assertEqual(len(matches), 0, car_model)
|
||||
assert len(matches) == 0, car_model
|
||||
# There won't always be a match due to shared FW, but if there is it should be correct
|
||||
elif len(matches):
|
||||
self.assertFingerprints(matches, car_model)
|
||||
|
||||
def test_fw_version_lists(self):
|
||||
def test_fw_version_lists(self, subtests):
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for ecu, ecu_fw in ecus.items():
|
||||
with self.subTest(ecu):
|
||||
with subtests.test(ecu):
|
||||
duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1}
|
||||
self.assertFalse(len(duplicates), f'{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}')
|
||||
self.assertGreater(len(ecu_fw), 0, f'{car_model}: No FW versions: Ecu.{ECU_NAME[ecu[0]]}')
|
||||
assert not len(duplicates), f'{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}'
|
||||
assert len(ecu_fw) > 0, f'{car_model}: No FW versions: Ecu.{ECU_NAME[ecu[0]]}'
|
||||
|
||||
def test_all_addrs_map_to_one_ecu(self):
|
||||
for brand, cars in VERSIONS.items():
|
||||
@@ -121,59 +120,59 @@ class TestFwFingerprint(unittest.TestCase):
|
||||
addr_to_ecu[(addr, sub_addr)].add(ecu_type)
|
||||
ecus_for_addr = addr_to_ecu[(addr, sub_addr)]
|
||||
ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_for_addr])
|
||||
self.assertLessEqual(len(ecus_for_addr), 1, f"{brand} has multiple ECUs that map to one address: {ecu_strings} -> ({hex(addr)}, {sub_addr})")
|
||||
assert len(ecus_for_addr) <= 1, f"{brand} has multiple ECUs that map to one address: {ecu_strings} -> ({hex(addr)}, {sub_addr})"
|
||||
|
||||
def test_data_collection_ecus(self):
|
||||
def test_data_collection_ecus(self, subtests):
|
||||
# Asserts no extra ECUs are in the fingerprinting database
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
for car_model, ecus in VERSIONS[brand].items():
|
||||
bad_ecus = set(ecus).intersection(config.extra_ecus)
|
||||
with self.subTest(car_model=car_model.value):
|
||||
self.assertFalse(len(bad_ecus), f'{car_model}: Fingerprints contain ECUs added for data collection: {bad_ecus}')
|
||||
with subtests.test(car_model=car_model.value):
|
||||
assert not len(bad_ecus), f'{car_model}: Fingerprints contain ECUs added for data collection: {bad_ecus}'
|
||||
|
||||
def test_blacklisted_ecus(self):
|
||||
def test_blacklisted_ecus(self, subtests):
|
||||
blacklisted_addrs = (0x7c4, 0x7d0) # includes A/C ecu and an unknown ecu
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
CP = interfaces[car_model][0].get_non_essential_params(car_model)
|
||||
if CP.carName == 'subaru':
|
||||
for ecu in ecus.keys():
|
||||
self.assertNotIn(ecu[1], blacklisted_addrs, f'{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})')
|
||||
assert ecu[1] not in blacklisted_addrs, f'{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})'
|
||||
|
||||
elif CP.carName == "chrysler":
|
||||
# Some HD trucks have a combined TCM and ECM
|
||||
if CP.carFingerprint.startswith("RAM HD"):
|
||||
for ecu in ecus.keys():
|
||||
self.assertNotEqual(ecu[0], Ecu.transmission, f"{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})")
|
||||
assert ecu[0] != Ecu.transmission, f"{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})"
|
||||
|
||||
def test_non_essential_ecus(self):
|
||||
def test_non_essential_ecus(self, subtests):
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
with self.subTest(brand):
|
||||
with subtests.test(brand):
|
||||
# These ECUs are already not in ESSENTIAL_ECUS which the fingerprint functions give a pass if missing
|
||||
unnecessary_non_essential_ecus = set(config.non_essential_ecus) - set(ESSENTIAL_ECUS)
|
||||
self.assertEqual(unnecessary_non_essential_ecus, set(), "Declaring non-essential ECUs non-essential is not required: " +
|
||||
f"{', '.join([f'Ecu.{ECU_NAME[ecu]}' for ecu in unnecessary_non_essential_ecus])}")
|
||||
assert unnecessary_non_essential_ecus == set(), "Declaring non-essential ECUs non-essential is not required: " + \
|
||||
f"{', '.join([f'Ecu.{ECU_NAME[ecu]}' for ecu in unnecessary_non_essential_ecus])}"
|
||||
|
||||
def test_missing_versions_and_configs(self):
|
||||
def test_missing_versions_and_configs(self, subtests):
|
||||
brand_versions = set(VERSIONS.keys())
|
||||
brand_configs = set(FW_QUERY_CONFIGS.keys())
|
||||
if len(brand_configs - brand_versions):
|
||||
with self.subTest():
|
||||
self.fail(f"Brands do not implement FW_VERSIONS: {brand_configs - brand_versions}")
|
||||
with subtests.test():
|
||||
pytest.fail(f"Brands do not implement FW_VERSIONS: {brand_configs - brand_versions}")
|
||||
|
||||
if len(brand_versions - brand_configs):
|
||||
with self.subTest():
|
||||
self.fail(f"Brands do not implement FW_QUERY_CONFIG: {brand_versions - brand_configs}")
|
||||
with subtests.test():
|
||||
pytest.fail(f"Brands do not implement FW_QUERY_CONFIG: {brand_versions - brand_configs}")
|
||||
|
||||
# Ensure each brand has at least 1 ECU to query, and extra ECU retrieval
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
self.assertEqual(len(config.get_all_ecus({}, include_extra_ecus=False)), 0)
|
||||
self.assertEqual(config.get_all_ecus({}), set(config.extra_ecus))
|
||||
self.assertGreater(len(config.get_all_ecus(VERSIONS[brand])), 0)
|
||||
assert len(config.get_all_ecus({}, include_extra_ecus=False)) == 0
|
||||
assert config.get_all_ecus({}) == set(config.extra_ecus)
|
||||
assert len(config.get_all_ecus(VERSIONS[brand])) > 0
|
||||
|
||||
def test_fw_request_ecu_whitelist(self):
|
||||
def test_fw_request_ecu_whitelist(self, subtests):
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
with self.subTest(brand=brand):
|
||||
with subtests.test(brand=brand):
|
||||
whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus}
|
||||
brand_ecus = {fw[0] for car_fw in VERSIONS[brand].values() for fw in car_fw}
|
||||
brand_ecus |= {ecu[0] for ecu in config.extra_ecus}
|
||||
@@ -182,30 +181,30 @@ class TestFwFingerprint(unittest.TestCase):
|
||||
ecus_not_whitelisted = brand_ecus - whitelisted_ecus
|
||||
|
||||
ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_whitelisted])
|
||||
self.assertFalse(len(whitelisted_ecus) and len(ecus_not_whitelisted),
|
||||
f'{brand.title()}: ECUs not in any FW query whitelists: {ecu_strings}')
|
||||
assert not (len(whitelisted_ecus) and len(ecus_not_whitelisted)), \
|
||||
f'{brand.title()}: ECUs not in any FW query whitelists: {ecu_strings}'
|
||||
|
||||
def test_fw_requests(self):
|
||||
def test_fw_requests(self, subtests):
|
||||
# Asserts equal length request and response lists
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
with self.subTest(brand=brand):
|
||||
with subtests.test(brand=brand):
|
||||
for request_obj in config.requests:
|
||||
self.assertEqual(len(request_obj.request), len(request_obj.response))
|
||||
assert len(request_obj.request) == len(request_obj.response)
|
||||
|
||||
# No request on the OBD port (bus 1, multiplexed) should be run on an aux panda
|
||||
self.assertFalse(request_obj.auxiliary and request_obj.bus == 1 and request_obj.obd_multiplexing,
|
||||
f"{brand.title()}: OBD multiplexed request is marked auxiliary: {request_obj}")
|
||||
assert not (request_obj.auxiliary and request_obj.bus == 1 and request_obj.obd_multiplexing), \
|
||||
f"{brand.title()}: OBD multiplexed request is marked auxiliary: {request_obj}"
|
||||
|
||||
def test_brand_ecu_matches(self):
|
||||
empty_response = {brand: set() for brand in FW_QUERY_CONFIGS}
|
||||
self.assertEqual(get_brand_ecu_matches(set()), empty_response)
|
||||
assert get_brand_ecu_matches(set()) == empty_response
|
||||
|
||||
# we ignore bus
|
||||
expected_response = empty_response | {'toyota': {(0x750, 0xf)}}
|
||||
self.assertEqual(get_brand_ecu_matches({(0x758, 0xf, 99)}), expected_response)
|
||||
assert get_brand_ecu_matches({(0x758, 0xf, 99)}) == expected_response
|
||||
|
||||
|
||||
class TestFwFingerprintTiming(unittest.TestCase):
|
||||
class TestFwFingerprintTiming:
|
||||
N: int = 5
|
||||
TOL: float = 0.05
|
||||
|
||||
@@ -223,26 +222,26 @@ class TestFwFingerprintTiming(unittest.TestCase):
|
||||
self.total_time += timeout
|
||||
return {}
|
||||
|
||||
def _benchmark_brand(self, brand, num_pandas):
|
||||
def _benchmark_brand(self, brand, num_pandas, mocker):
|
||||
fake_socket = FakeSocket()
|
||||
self.total_time = 0
|
||||
with (mock.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing),
|
||||
mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)):
|
||||
for _ in range(self.N):
|
||||
# Treat each brand as the most likely (aka, the first) brand with OBD multiplexing initially on
|
||||
self.current_obd_multiplexing = True
|
||||
mocker.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing)
|
||||
mocker.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)
|
||||
for _ in range(self.N):
|
||||
# Treat each brand as the most likely (aka, the first) brand with OBD multiplexing initially on
|
||||
self.current_obd_multiplexing = True
|
||||
|
||||
t = time.perf_counter()
|
||||
get_fw_versions(fake_socket, fake_socket, brand, num_pandas=num_pandas)
|
||||
self.total_time += time.perf_counter() - t
|
||||
t = time.perf_counter()
|
||||
get_fw_versions(fake_socket, fake_socket, brand, num_pandas=num_pandas)
|
||||
self.total_time += time.perf_counter() - t
|
||||
|
||||
return self.total_time / self.N
|
||||
|
||||
def _assert_timing(self, avg_time, ref_time):
|
||||
self.assertLess(avg_time, ref_time + self.TOL)
|
||||
self.assertGreater(avg_time, ref_time - self.TOL, "Performance seems to have improved, update test refs.")
|
||||
assert avg_time < ref_time + self.TOL
|
||||
assert avg_time > ref_time - self.TOL, "Performance seems to have improved, update test refs."
|
||||
|
||||
def test_startup_timing(self):
|
||||
def test_startup_timing(self, subtests, mocker):
|
||||
# Tests worse-case VIN query time and typical present ECU query time
|
||||
vin_ref_times = {'worst': 1.4, 'best': 0.7} # best assumes we go through all queries to get a match
|
||||
present_ecu_ref_time = 0.45
|
||||
@@ -253,24 +252,24 @@ class TestFwFingerprintTiming(unittest.TestCase):
|
||||
|
||||
fake_socket = FakeSocket()
|
||||
self.total_time = 0.0
|
||||
with (mock.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing),
|
||||
mock.patch("openpilot.selfdrive.car.fw_versions.get_ecu_addrs", fake_get_ecu_addrs)):
|
||||
for _ in range(self.N):
|
||||
self.current_obd_multiplexing = True
|
||||
get_present_ecus(fake_socket, fake_socket, num_pandas=2)
|
||||
mocker.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing)
|
||||
mocker.patch("openpilot.selfdrive.car.fw_versions.get_ecu_addrs", fake_get_ecu_addrs)
|
||||
for _ in range(self.N):
|
||||
self.current_obd_multiplexing = True
|
||||
get_present_ecus(fake_socket, fake_socket, num_pandas=2)
|
||||
self._assert_timing(self.total_time / self.N, present_ecu_ref_time)
|
||||
print(f'get_present_ecus, query time={self.total_time / self.N} seconds')
|
||||
|
||||
for name, args in (('worst', {}), ('best', {'retry': 1})):
|
||||
with self.subTest(name=name):
|
||||
with subtests.test(name=name):
|
||||
self.total_time = 0.0
|
||||
with (mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)):
|
||||
for _ in range(self.N):
|
||||
get_vin(fake_socket, fake_socket, (0, 1), **args)
|
||||
mocker.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)
|
||||
for _ in range(self.N):
|
||||
get_vin(fake_socket, fake_socket, (0, 1), **args)
|
||||
self._assert_timing(self.total_time / self.N, vin_ref_times[name])
|
||||
print(f'get_vin {name} case, query time={self.total_time / self.N} seconds')
|
||||
|
||||
def test_fw_query_timing(self):
|
||||
def test_fw_query_timing(self, subtests, mocker):
|
||||
total_ref_time = {1: 7.2, 2: 7.8}
|
||||
brand_ref_times = {
|
||||
1: {
|
||||
@@ -297,8 +296,8 @@ class TestFwFingerprintTiming(unittest.TestCase):
|
||||
total_times = {1: 0.0, 2: 0.0}
|
||||
for num_pandas in (1, 2):
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
with self.subTest(brand=brand, num_pandas=num_pandas):
|
||||
avg_time = self._benchmark_brand(brand, num_pandas)
|
||||
with subtests.test(brand=brand, num_pandas=num_pandas):
|
||||
avg_time = self._benchmark_brand(brand, num_pandas, mocker)
|
||||
total_times[num_pandas] += avg_time
|
||||
avg_time = round(avg_time, 2)
|
||||
|
||||
@@ -311,11 +310,7 @@ class TestFwFingerprintTiming(unittest.TestCase):
|
||||
print(f'{brand=}, {num_pandas=}, {len(config.requests)=}, avg FW query time={avg_time} seconds')
|
||||
|
||||
for num_pandas in (1, 2):
|
||||
with self.subTest(brand='all_brands', num_pandas=num_pandas):
|
||||
with subtests.test(brand='all_brands', num_pandas=num_pandas):
|
||||
total_time = round(total_times[num_pandas], 2)
|
||||
self._assert_timing(total_time, total_ref_time[num_pandas])
|
||||
print(f'all brands, total FW query time={total_time} seconds')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
from collections import defaultdict
|
||||
import importlib
|
||||
from parameterized import parameterized_class
|
||||
import sys
|
||||
import unittest
|
||||
import pytest
|
||||
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.car.car_helpers import interfaces
|
||||
@@ -25,23 +24,23 @@ car_model_jerks: defaultdict[str, dict[str, float]] = defaultdict(dict)
|
||||
|
||||
|
||||
@parameterized_class('car_model', [(c,) for c in sorted(CAR_MODELS)])
|
||||
class TestLateralLimits(unittest.TestCase):
|
||||
class TestLateralLimits:
|
||||
car_model: str
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
def setup_class(cls):
|
||||
CarInterface, _, _ = interfaces[cls.car_model]
|
||||
CP = CarInterface.get_non_essential_params(cls.car_model)
|
||||
|
||||
if CP.dashcamOnly:
|
||||
raise unittest.SkipTest("Platform is behind dashcamOnly")
|
||||
pytest.skip("Platform is behind dashcamOnly")
|
||||
|
||||
# TODO: test all platforms
|
||||
if CP.lateralTuning.which() != 'torque':
|
||||
raise unittest.SkipTest
|
||||
pytest.skip()
|
||||
|
||||
if CP.notCar:
|
||||
raise unittest.SkipTest
|
||||
pytest.skip()
|
||||
|
||||
CarControllerParams = importlib.import_module(f'selfdrive.car.{CP.carName}.values').CarControllerParams
|
||||
cls.control_params = CarControllerParams(CP)
|
||||
@@ -66,26 +65,8 @@ class TestLateralLimits(unittest.TestCase):
|
||||
def test_jerk_limits(self):
|
||||
up_jerk, down_jerk = self.calculate_0_5s_jerk(self.control_params, self.torque_params)
|
||||
car_model_jerks[self.car_model] = {"up_jerk": up_jerk, "down_jerk": down_jerk}
|
||||
self.assertLessEqual(up_jerk, MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE)
|
||||
self.assertLessEqual(down_jerk, MAX_LAT_JERK_DOWN)
|
||||
assert up_jerk <= MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE
|
||||
assert down_jerk <= MAX_LAT_JERK_DOWN
|
||||
|
||||
def test_max_lateral_accel(self):
|
||||
self.assertLessEqual(self.torque_params["MAX_LAT_ACCEL_MEASURED"], MAX_LAT_ACCEL)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = unittest.main(exit=False)
|
||||
|
||||
print(f"\n\n---- Lateral limit report ({len(CAR_MODELS)} cars) ----\n")
|
||||
|
||||
max_car_model_len = max([len(car_model) for car_model in car_model_jerks])
|
||||
for car_model, _jerks in sorted(car_model_jerks.items(), key=lambda i: i[1]['up_jerk'], reverse=True):
|
||||
violation = _jerks["up_jerk"] > MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE or \
|
||||
_jerks["down_jerk"] > MAX_LAT_JERK_DOWN
|
||||
violation_str = " - VIOLATION" if violation else ""
|
||||
|
||||
print(f"{car_model:{max_car_model_len}} - up jerk: {round(_jerks['up_jerk'], 2):5} " +
|
||||
f"m/s^3, down jerk: {round(_jerks['down_jerk'], 2):5} m/s^3{violation_str}")
|
||||
|
||||
# exit with test result
|
||||
sys.exit(not result.result.wasSuccessful())
|
||||
assert self.torque_params["MAX_LAT_ACCEL_MEASURED"] <= MAX_LAT_ACCEL
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
import importlib
|
||||
import pytest
|
||||
import random
|
||||
import unittest
|
||||
import unittest # noqa: TID251
|
||||
from collections import defaultdict, Counter
|
||||
import hypothesis.strategies as st
|
||||
from hypothesis import Phase, given, settings
|
||||
@@ -15,12 +15,12 @@ from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.car import gen_empty_fingerprint
|
||||
from openpilot.selfdrive.car.card import Car
|
||||
from openpilot.selfdrive.car.fingerprints import all_known_cars, MIGRATION
|
||||
from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces
|
||||
from openpilot.selfdrive.car.honda.values import CAR as HONDA, HondaFlags
|
||||
from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute
|
||||
from openpilot.selfdrive.car.values import Platform
|
||||
from openpilot.selfdrive.controls.controlsd import Controls
|
||||
from openpilot.selfdrive.test.helpers import read_segment_list
|
||||
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||
from openpilot.tools.lib.logreader import LogReader, internal_source, openpilotci_source
|
||||
@@ -215,7 +215,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
# TODO: also check for checksum violations from can parser
|
||||
can_invalid_cnt = 0
|
||||
can_valid = False
|
||||
CC = car.CarControl.new_message()
|
||||
CC = car.CarControl.new_message().as_reader()
|
||||
|
||||
for i, msg in enumerate(self.can_msgs):
|
||||
CS = self.CI.update(CC, (msg.as_builder().to_bytes(),))
|
||||
@@ -308,17 +308,17 @@ class TestCarModelBase(unittest.TestCase):
|
||||
|
||||
# Make sure we can send all messages while inactive
|
||||
CC = car.CarControl.new_message()
|
||||
test_car_controller(CC)
|
||||
test_car_controller(CC.as_reader())
|
||||
|
||||
# Test cancel + general messages (controls_allowed=False & cruise_engaged=True)
|
||||
self.safety.set_cruise_engaged_prev(True)
|
||||
CC = car.CarControl.new_message(cruiseControl={'cancel': True})
|
||||
test_car_controller(CC)
|
||||
test_car_controller(CC.as_reader())
|
||||
|
||||
# Test resume + general messages (controls_allowed=True & cruise_engaged=True)
|
||||
self.safety.set_controls_allowed(True)
|
||||
CC = car.CarControl.new_message(cruiseControl={'resume': True})
|
||||
test_car_controller(CC)
|
||||
test_car_controller(CC.as_reader())
|
||||
|
||||
# Skip stdout/stderr capture with pytest, causes elevated memory usage
|
||||
@pytest.mark.nocapture
|
||||
@@ -406,8 +406,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
controls_allowed_prev = False
|
||||
CS_prev = car.CarState.new_message()
|
||||
checks = defaultdict(int)
|
||||
controlsd = Controls(CI=self.CI)
|
||||
controlsd.initialized = True
|
||||
card = Car(CI=self.CI)
|
||||
for idx, can in enumerate(self.can_msgs):
|
||||
CS = self.CI.update(CC, (can.as_builder().to_bytes(), ))
|
||||
for msg in filter(lambda m: m.src in range(64), can.can):
|
||||
@@ -452,10 +451,10 @@ class TestCarModelBase(unittest.TestCase):
|
||||
checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev()
|
||||
else:
|
||||
# Check for enable events on rising edge of controls allowed
|
||||
controlsd.update_events(CS)
|
||||
controlsd.CS_prev = CS
|
||||
card.update_events(CS)
|
||||
card.CS_prev = CS
|
||||
button_enable = (any(evt.enable for evt in CS.events) and
|
||||
not any(evt == EventName.pedalPressed for evt in controlsd.events.names))
|
||||
not any(evt == EventName.pedalPressed for evt in card.events.names))
|
||||
mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev)
|
||||
checks['controlsAllowed'] += mismatch
|
||||
controls_allowed_prev = self.safety.get_controls_allowed()
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
|
||||
from openpilot.selfdrive.car.values import PLATFORMS
|
||||
|
||||
|
||||
class TestPlatformConfigs(unittest.TestCase):
|
||||
def test_configs(self):
|
||||
class TestPlatformConfigs:
|
||||
def test_configs(self, subtests):
|
||||
|
||||
for name, platform in PLATFORMS.items():
|
||||
with self.subTest(platform=str(platform)):
|
||||
self.assertTrue(platform.config._frozen)
|
||||
with subtests.test(platform=str(platform)):
|
||||
assert platform.config._frozen
|
||||
|
||||
if platform != "MOCK":
|
||||
self.assertIn("pt", platform.config.dbc_dict)
|
||||
self.assertTrue(len(platform.config.platform_str) > 0)
|
||||
assert "pt" in platform.config.dbc_dict
|
||||
assert len(platform.config.platform_str) > 0
|
||||
|
||||
self.assertEqual(name, platform.config.platform_str)
|
||||
assert name == platform.config.platform_str
|
||||
|
||||
self.assertIsNotNone(platform.config.specs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert platform.config.specs is not None
|
||||
|
||||
@@ -122,11 +122,6 @@ class CarController(CarControllerBase):
|
||||
interceptor_gas_cmd = 0.
|
||||
pcm_accel_cmd = clip(actuators.accel, self.params.ACCEL_MIN, self.params.ACCEL_MAX)
|
||||
|
||||
# TODO: probably can delete this. CS.pcm_acc_status uses a different signal
|
||||
# than CS.cruiseState.enabled. confirm they're not meaningfully different
|
||||
if not CC.enabled and CS.pcm_acc_status:
|
||||
pcm_cancel_cmd = 1
|
||||
|
||||
# on entering standstill, send standstill request
|
||||
if CS.out.standstill and not self.last_standstill and (self.CP.carFingerprint not in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptorDEPRECATED):
|
||||
self.standstill_req = True
|
||||
@@ -200,7 +195,7 @@ class CarController(CarControllerBase):
|
||||
if self.frame % 20 == 0 and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value:
|
||||
can_sends.append([0x750, 0, b"\x0F\x02\x3E\x00\x00\x00\x00\x00", 0])
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steer = apply_steer / self.params.STEER_MAX
|
||||
new_actuators.steerOutputCan = apply_steer
|
||||
new_actuators.steeringAngleDeg = self.last_angle
|
||||
|
||||
@@ -521,6 +521,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.abs, 0x7b0, None): [
|
||||
b'\x01F152602280\x00\x00\x00\x00\x00\x00',
|
||||
b'\x01F152602281\x00\x00\x00\x00\x00\x00',
|
||||
b'\x01F152602470\x00\x00\x00\x00\x00\x00',
|
||||
b'\x01F152602560\x00\x00\x00\x00\x00\x00',
|
||||
b'\x01F152602590\x00\x00\x00\x00\x00\x00',
|
||||
@@ -1141,6 +1142,7 @@ FW_VERSIONS = {
|
||||
CAR.TOYOTA_RAV4_TSS2_2023: {
|
||||
(Ecu.abs, 0x7b0, None): [
|
||||
b'\x01F15260R450\x00\x00\x00\x00\x00\x00',
|
||||
b'\x01F15260R50000\x00\x00\x00\x00',
|
||||
b'\x01F15260R51000\x00\x00\x00\x00',
|
||||
b'\x01F15264283200\x00\x00\x00\x00',
|
||||
b'\x01F15264283300\x00\x00\x00\x00',
|
||||
@@ -1162,6 +1164,7 @@ FW_VERSIONS = {
|
||||
b'\x01896634AJ2000\x00\x00\x00\x00',
|
||||
b'\x01896634AL5000\x00\x00\x00\x00',
|
||||
b'\x01896634AL6000\x00\x00\x00\x00',
|
||||
b'\x01896634AL8000\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x750, 0xf): [
|
||||
b'\x018821F0R03100\x00\x00\x00\x00',
|
||||
@@ -1474,6 +1477,7 @@ FW_VERSIONS = {
|
||||
b'\x01896630E41200\x00\x00\x00\x00',
|
||||
b'\x01896630E41500\x00\x00\x00\x00',
|
||||
b'\x01896630EA3100\x00\x00\x00\x00',
|
||||
b'\x01896630EA3300\x00\x00\x00\x00',
|
||||
b'\x01896630EA3400\x00\x00\x00\x00',
|
||||
b'\x01896630EA4100\x00\x00\x00\x00',
|
||||
b'\x01896630EA4300\x00\x00\x00\x00',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
from hypothesis import given, settings, strategies as st
|
||||
import unittest
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car.fw_versions import build_fw_dict
|
||||
@@ -17,59 +16,58 @@ def check_fw_version(fw_version: bytes) -> bool:
|
||||
return b'?' not in fw_version
|
||||
|
||||
|
||||
class TestToyotaInterfaces(unittest.TestCase):
|
||||
class TestToyotaInterfaces:
|
||||
def test_car_sets(self):
|
||||
self.assertTrue(len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0)
|
||||
self.assertTrue(len(RADAR_ACC_CAR - TSS2_CAR) == 0)
|
||||
assert len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0
|
||||
assert len(RADAR_ACC_CAR - TSS2_CAR) == 0
|
||||
|
||||
def test_lta_platforms(self):
|
||||
# At this time, only RAV4 2023 is expected to use LTA/angle control
|
||||
self.assertEqual(ANGLE_CONTROL_CAR, {CAR.TOYOTA_RAV4_TSS2_2023})
|
||||
assert ANGLE_CONTROL_CAR == {CAR.TOYOTA_RAV4_TSS2_2023}
|
||||
|
||||
def test_tss2_dbc(self):
|
||||
# We make some assumptions about TSS2 platforms,
|
||||
# like looking up certain signals only in this DBC
|
||||
for car_model, dbc in DBC.items():
|
||||
if car_model in TSS2_CAR:
|
||||
self.assertEqual(dbc["pt"], "toyota_nodsu_pt_generated")
|
||||
assert dbc["pt"] == "toyota_nodsu_pt_generated"
|
||||
|
||||
def test_essential_ecus(self):
|
||||
def test_essential_ecus(self, subtests):
|
||||
# Asserts standard ECUs exist for each platform
|
||||
common_ecus = {Ecu.fwdRadar, Ecu.fwdCamera}
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
present_ecus = {ecu[0] for ecu in ecus}
|
||||
missing_ecus = common_ecus - present_ecus
|
||||
self.assertEqual(len(missing_ecus), 0)
|
||||
assert len(missing_ecus) == 0
|
||||
|
||||
# Some exceptions for other common ECUs
|
||||
if car_model not in (CAR.TOYOTA_ALPHARD_TSS2,):
|
||||
self.assertIn(Ecu.abs, present_ecus)
|
||||
assert Ecu.abs in present_ecus
|
||||
|
||||
if car_model not in (CAR.TOYOTA_MIRAI,):
|
||||
self.assertIn(Ecu.engine, present_ecus)
|
||||
assert Ecu.engine in present_ecus
|
||||
|
||||
if car_model not in (CAR.TOYOTA_PRIUS_V, CAR.LEXUS_CTH):
|
||||
self.assertIn(Ecu.eps, present_ecus)
|
||||
assert Ecu.eps in present_ecus
|
||||
|
||||
|
||||
class TestToyotaFingerprint(unittest.TestCase):
|
||||
def test_non_essential_ecus(self):
|
||||
class TestToyotaFingerprint:
|
||||
def test_non_essential_ecus(self, subtests):
|
||||
# Ensures only the cars that have multiple engine ECUs are in the engine non-essential ECU list
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
engine_ecus = {ecu for ecu in ecus if ecu[0] == Ecu.engine}
|
||||
self.assertEqual(len(engine_ecus) > 1,
|
||||
car_model in FW_QUERY_CONFIG.non_essential_ecus[Ecu.engine],
|
||||
f"Car model unexpectedly {'not ' if len(engine_ecus) > 1 else ''}in non-essential list")
|
||||
assert (len(engine_ecus) > 1) == (car_model in FW_QUERY_CONFIG.non_essential_ecus[Ecu.engine]), \
|
||||
f"Car model unexpectedly {'not ' if len(engine_ecus) > 1 else ''}in non-essential list"
|
||||
|
||||
def test_valid_fw_versions(self):
|
||||
def test_valid_fw_versions(self, subtests):
|
||||
# Asserts all FW versions are valid
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for fws in ecus.values():
|
||||
for fw in fws:
|
||||
self.assertTrue(check_fw_version(fw), fw)
|
||||
assert check_fw_version(fw), fw
|
||||
|
||||
# Tests for part numbers, platform codes, and sub-versions which Toyota will use to fuzzy
|
||||
# fingerprint in the absence of full FW matches:
|
||||
@@ -80,25 +78,25 @@ class TestToyotaFingerprint(unittest.TestCase):
|
||||
fws = data.draw(fw_strategy)
|
||||
get_platform_codes(fws)
|
||||
|
||||
def test_platform_code_ecus_available(self):
|
||||
def test_platform_code_ecus_available(self, subtests):
|
||||
# Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for platform_code_ecu in PLATFORM_CODE_ECUS:
|
||||
if platform_code_ecu == Ecu.eps and car_model in (CAR.TOYOTA_PRIUS_V, CAR.LEXUS_CTH,):
|
||||
continue
|
||||
if platform_code_ecu == Ecu.abs and car_model in (CAR.TOYOTA_ALPHARD_TSS2,):
|
||||
continue
|
||||
self.assertIn(platform_code_ecu, [e[0] for e in ecus])
|
||||
assert platform_code_ecu in [e[0] for e in ecus]
|
||||
|
||||
def test_fw_format(self):
|
||||
def test_fw_format(self, subtests):
|
||||
# Asserts:
|
||||
# - every supported ECU FW version returns one platform code
|
||||
# - every supported ECU FW version has a part number
|
||||
# - expected parsing of ECU sub-versions
|
||||
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
@@ -107,15 +105,14 @@ class TestToyotaFingerprint(unittest.TestCase):
|
||||
for fw in fws:
|
||||
result = get_platform_codes([fw])
|
||||
# Check only one platform code and sub-version
|
||||
self.assertEqual(1, len(result), f"Unable to parse FW: {fw}")
|
||||
self.assertEqual(1, len(list(result.values())[0]), f"Unable to parse FW: {fw}")
|
||||
assert 1 == len(result), f"Unable to parse FW: {fw}"
|
||||
assert 1 == len(list(result.values())[0]), f"Unable to parse FW: {fw}"
|
||||
codes |= result
|
||||
|
||||
# Toyota places the ECU part number in their FW versions, assert all parsable
|
||||
# Note that there is only one unique part number per ECU across the fleet, so this
|
||||
# is not important for identification, just a sanity check.
|
||||
self.assertTrue(all(code.count(b"-") > 1 for code in codes),
|
||||
f"FW does not have part number: {fw} {codes}")
|
||||
assert all(code.count(b"-") > 1 for code in codes), f"FW does not have part number: {fw} {codes}"
|
||||
|
||||
def test_platform_codes_spot_check(self):
|
||||
# Asserts basic platform code parsing behavior for a few cases
|
||||
@@ -125,20 +122,20 @@ class TestToyotaFingerprint(unittest.TestCase):
|
||||
b"F152607110\x00\x00\x00\x00\x00\x00",
|
||||
b"F152607180\x00\x00\x00\x00\x00\x00",
|
||||
])
|
||||
self.assertEqual(results, {b"F1526-07-1": {b"10", b"40", b"71", b"80"}})
|
||||
assert results == {b"F1526-07-1": {b"10", b"40", b"71", b"80"}}
|
||||
|
||||
results = get_platform_codes([
|
||||
b"\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00",
|
||||
b"\x028646F4104100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00",
|
||||
])
|
||||
self.assertEqual(results, {b"8646F-41-04": {b"100"}})
|
||||
assert results == {b"8646F-41-04": {b"100"}}
|
||||
|
||||
# Short version has no part number
|
||||
results = get_platform_codes([
|
||||
b"\x0235870000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
b"\x0235883000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
])
|
||||
self.assertEqual(results, {b"58-70": {b"000"}, b"58-83": {b"000"}})
|
||||
assert results == {b"58-70": {b"000"}, b"58-83": {b"000"}}
|
||||
|
||||
results = get_platform_codes([
|
||||
b"F152607110\x00\x00\x00\x00\x00\x00",
|
||||
@@ -146,7 +143,7 @@ class TestToyotaFingerprint(unittest.TestCase):
|
||||
b"\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00",
|
||||
b"\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
])
|
||||
self.assertEqual(results, {b"F1526-07-1": {b"10", b"40"}, b"8646F-41-04": {b"100"}, b"58-79": {b"000"}})
|
||||
assert results == {b"F1526-07-1": {b"10", b"40"}, b"8646F-41-04": {b"100"}, b"58-79": {b"000"}}
|
||||
|
||||
def test_fuzzy_excluded_platforms(self):
|
||||
# Asserts a list of platforms that will not fuzzy fingerprint with platform codes due to them being shared.
|
||||
@@ -162,13 +159,9 @@ class TestToyotaFingerprint(unittest.TestCase):
|
||||
CP = car.CarParams.new_message(carFw=car_fw)
|
||||
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS)
|
||||
if len(matches) == 1:
|
||||
self.assertEqual(list(matches)[0], platform)
|
||||
assert list(matches)[0] == platform
|
||||
else:
|
||||
# If a platform has multiple matches, add it and its matches
|
||||
platforms_with_shared_codes |= {str(platform), *matches}
|
||||
|
||||
self.assertEqual(platforms_with_shared_codes, FUZZY_EXCLUDED_PLATFORMS, (len(platforms_with_shared_codes), len(FW_VERSIONS)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert platforms_with_shared_codes == FUZZY_EXCLUDED_PLATFORMS, (len(platforms_with_shared_codes), len(FW_VERSIONS))
|
||||
|
||||
@@ -112,7 +112,7 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, self.ext_bus, CS.gra_stock_values,
|
||||
cancel=CC.cruiseControl.cancel, resume=CC.cruiseControl.resume))
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steer = self.apply_steer_last / self.CCP.STEER_MAX
|
||||
new_actuators.steerOutputCan = self.apply_steer_last
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ class CarState(CarStateBase):
|
||||
# DISABLED means the EPS hasn't been configured to support Lane Assist
|
||||
self.eps_init_complete = self.eps_init_complete or (hca_status in ("DISABLED", "READY", "ACTIVE") or self.frame > 600)
|
||||
perm_fault = hca_status == "DISABLED" or (self.eps_init_complete and hca_status in ("INITIALIZING", "FAULT"))
|
||||
temp_fault = hca_status == "REJECTED" or not self.eps_init_complete
|
||||
temp_fault = hca_status in ("REJECTED", "PREEMPTED") or not self.eps_init_complete
|
||||
return temp_fault, perm_fault
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -228,6 +228,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x870DD300046F \xf1\x891601',
|
||||
b'\xf1\x870GC300012A \xf1\x891401',
|
||||
b'\xf1\x870GC300012A \xf1\x891403',
|
||||
b'\xf1\x870GC300012A \xf1\x891422',
|
||||
b'\xf1\x870GC300012M \xf1\x892301',
|
||||
b'\xf1\x870GC300014B \xf1\x892401',
|
||||
b'\xf1\x870GC300014B \xf1\x892403',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car.volkswagen.values import CAR, FW_QUERY_CONFIG, WMI
|
||||
@@ -14,35 +13,35 @@ CHASSIS_CODE_PATTERN = re.compile('[A-Z0-9]{2}')
|
||||
SPARE_PART_FW_PATTERN = re.compile(b'\xf1\x87(?P<gateway>[0-9][0-9A-Z]{2})(?P<unknown>[0-9][0-9A-Z][0-9])(?P<unknown2>[0-9A-Z]{2}[0-9])([A-Z0-9]| )')
|
||||
|
||||
|
||||
class TestVolkswagenPlatformConfigs(unittest.TestCase):
|
||||
def test_spare_part_fw_pattern(self):
|
||||
class TestVolkswagenPlatformConfigs:
|
||||
def test_spare_part_fw_pattern(self, subtests):
|
||||
# Relied on for determining if a FW is likely VW
|
||||
for platform, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(platform=platform):
|
||||
with subtests.test(platform=platform.value):
|
||||
for fws in ecus.values():
|
||||
for fw in fws:
|
||||
self.assertNotEqual(SPARE_PART_FW_PATTERN.match(fw), None, f"Bad FW: {fw}")
|
||||
assert SPARE_PART_FW_PATTERN.match(fw) is not None, f"Bad FW: {fw}"
|
||||
|
||||
def test_chassis_codes(self):
|
||||
def test_chassis_codes(self, subtests):
|
||||
for platform in CAR:
|
||||
with self.subTest(platform=platform):
|
||||
self.assertTrue(len(platform.config.wmis) > 0, "WMIs not set")
|
||||
self.assertTrue(len(platform.config.chassis_codes) > 0, "Chassis codes not set")
|
||||
self.assertTrue(all(CHASSIS_CODE_PATTERN.match(cc) for cc in
|
||||
platform.config.chassis_codes), "Bad chassis codes")
|
||||
with subtests.test(platform=platform.value):
|
||||
assert len(platform.config.wmis) > 0, "WMIs not set"
|
||||
assert len(platform.config.chassis_codes) > 0, "Chassis codes not set"
|
||||
assert all(CHASSIS_CODE_PATTERN.match(cc) for cc in \
|
||||
platform.config.chassis_codes), "Bad chassis codes"
|
||||
|
||||
# No two platforms should share chassis codes
|
||||
for comp in CAR:
|
||||
if platform == comp:
|
||||
continue
|
||||
self.assertEqual(set(), platform.config.chassis_codes & comp.config.chassis_codes,
|
||||
f"Shared chassis codes: {comp}")
|
||||
assert set() == platform.config.chassis_codes & comp.config.chassis_codes, \
|
||||
f"Shared chassis codes: {comp}"
|
||||
|
||||
def test_custom_fuzzy_fingerprinting(self):
|
||||
def test_custom_fuzzy_fingerprinting(self, subtests):
|
||||
all_radar_fw = list({fw for ecus in FW_VERSIONS.values() for fw in ecus[Ecu.fwdRadar, 0x757, None]})
|
||||
|
||||
for platform in CAR:
|
||||
with self.subTest(platform=platform):
|
||||
with subtests.test(platform=platform.name):
|
||||
for wmi in WMI:
|
||||
for chassis_code in platform.config.chassis_codes | {"00"}:
|
||||
vin = ["0"] * 17
|
||||
@@ -59,8 +58,4 @@ class TestVolkswagenPlatformConfigs(unittest.TestCase):
|
||||
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fws, vin, FW_VERSIONS)
|
||||
|
||||
expected_matches = {platform} if should_match else set()
|
||||
self.assertEqual(expected_matches, matches, "Bad match")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert expected_matches == matches, "Bad match"
|
||||
|
||||
@@ -214,10 +214,11 @@ class CAR(Platforms):
|
||||
VWCarDocs("Volkswagen Arteon 2018-23", video_link="https://youtu.be/FAomFKPFlDA"),
|
||||
VWCarDocs("Volkswagen Arteon R 2020-23", video_link="https://youtu.be/FAomFKPFlDA"),
|
||||
VWCarDocs("Volkswagen Arteon eHybrid 2020-23", video_link="https://youtu.be/FAomFKPFlDA"),
|
||||
VWCarDocs("Volkswagen Arteon Shooting Brake 2020-23", video_link="https://youtu.be/FAomFKPFlDA"),
|
||||
VWCarDocs("Volkswagen CC 2018-22", video_link="https://youtu.be/FAomFKPFlDA"),
|
||||
],
|
||||
VolkswagenCarSpecs(mass=1733, wheelbase=2.84),
|
||||
chassis_codes={"AN"},
|
||||
chassis_codes={"AN", "3H"},
|
||||
wmis={WMI.VOLKSWAGEN_EUROPE_CAR},
|
||||
)
|
||||
VOLKSWAGEN_ATLAS_MK1 = VolkswagenMQBPlatformConfig(
|
||||
|
||||
@@ -18,8 +18,7 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
|
||||
from openpilot.selfdrive.car.car_helpers import get_startup_event
|
||||
from openpilot.selfdrive.car.card import CarD
|
||||
from openpilot.selfdrive.car.car_helpers import get_car_interface, get_startup_event
|
||||
from openpilot.selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, clip_curvature
|
||||
from openpilot.selfdrive.controls.lib.events import Events, ET
|
||||
@@ -61,16 +60,19 @@ ENABLED_STATES = (State.preEnabled, *ACTIVE_STATES)
|
||||
|
||||
class Controls:
|
||||
def __init__(self, CI=None):
|
||||
self.card = CarD(CI)
|
||||
|
||||
self.params = Params()
|
||||
|
||||
with car.CarParams.from_bytes(self.params.get("CarParams", block=True)) as msg:
|
||||
# TODO: this shouldn't need to be a builder
|
||||
self.CP = msg.as_builder()
|
||||
|
||||
self.CI = self.card.CI
|
||||
if CI is None:
|
||||
cloudlog.info("controlsd is waiting for CarParams")
|
||||
with car.CarParams.from_bytes(self.params.get("CarParams", block=True)) as msg:
|
||||
# TODO: this shouldn't need to be a builder
|
||||
self.CP = msg.as_builder()
|
||||
cloudlog.info("controlsd got CarParams")
|
||||
|
||||
# Uses car interface helper functions, altering state won't be considered by card for actuation
|
||||
self.CI = get_car_interface(self.CP)
|
||||
else:
|
||||
self.CI, self.CP = CI, CI.CP
|
||||
|
||||
# Ensure the current branch is cached, otherwise the first iteration of controlsd lags
|
||||
self.branch = get_short_branch()
|
||||
@@ -83,9 +85,15 @@ class Controls:
|
||||
|
||||
self.log_sock = messaging.sub_sock('androidLog')
|
||||
|
||||
# TODO: de-couple controlsd with card/conflate on carState without introducing controls mismatches
|
||||
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
|
||||
|
||||
ignore = self.sensor_packets + ['testJoystick']
|
||||
if SIMULATION:
|
||||
ignore += ['driverCameraState', 'managerState']
|
||||
if REPLAY:
|
||||
# no vipc in replay will make them ignored anyways
|
||||
ignore += ['roadCameraState', 'wideRoadCameraState']
|
||||
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
|
||||
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'liveLocationKalman',
|
||||
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
|
||||
@@ -96,7 +104,6 @@ class Controls:
|
||||
self.joystick_mode = self.params.get_bool("JoystickDebugMode")
|
||||
|
||||
# read params
|
||||
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
|
||||
self.is_metric = self.params.get_bool("IsMetric")
|
||||
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
|
||||
|
||||
@@ -142,7 +149,6 @@ class Controls:
|
||||
self.logged_comm_issue = None
|
||||
self.not_running_prev = None
|
||||
self.steer_limited = False
|
||||
self.last_actuators = car.CarControl.Actuators.new_message()
|
||||
self.desired_curvature = 0.0
|
||||
self.experimental_mode = False
|
||||
self.personality = self.read_personality_param()
|
||||
@@ -166,12 +172,12 @@ class Controls:
|
||||
elif self.CP.passive:
|
||||
self.events.add(EventName.dashcamMode, static=True)
|
||||
|
||||
# controlsd is driven by can recv, expected at 100Hz
|
||||
# controlsd is driven by carState, expected at 100Hz
|
||||
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
||||
|
||||
def set_initial_state(self):
|
||||
if REPLAY:
|
||||
controls_state = Params().get("ReplayControlsState")
|
||||
controls_state = self.params.get("ReplayControlsState")
|
||||
if controls_state is not None:
|
||||
with log.ControlsState.from_bytes(controls_state) as controls_state:
|
||||
self.v_cruise_helper.v_cruise_kph = controls_state.vCruise
|
||||
@@ -208,18 +214,6 @@ class Controls:
|
||||
if not self.CP.pcmCruise and not self.v_cruise_helper.v_cruise_initialized and resume_pressed:
|
||||
self.events.add(EventName.resumeBlocked)
|
||||
|
||||
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
|
||||
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
|
||||
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
|
||||
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
|
||||
self.events.add(EventName.pedalPressed)
|
||||
|
||||
if CS.brakePressed and CS.standstill:
|
||||
self.events.add(EventName.preEnableStandstill)
|
||||
|
||||
if CS.gasPressed:
|
||||
self.events.add(EventName.gasPressedOverride)
|
||||
|
||||
if not self.CP.notCar:
|
||||
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
|
||||
self.events.add_from_msg(self.sm['longitudinalPlanSP'].events)
|
||||
@@ -318,7 +312,7 @@ class Controls:
|
||||
self.events.add(EventName.cameraFrameRate)
|
||||
if not REPLAY and self.rk.lagging:
|
||||
self.events.add(EventName.controlsdLagging)
|
||||
if len(self.sm['radarState'].radarErrors) or (not self.rk.lagging and not self.sm.all_checks(['radarState'])):
|
||||
if len(self.sm['radarState'].radarErrors) or ((not self.rk.lagging or REPLAY) and not self.sm.all_checks(['radarState'])):
|
||||
self.events.add(EventName.radarFault)
|
||||
if not self.sm.valid['pandaStates']:
|
||||
self.events.add(EventName.usbError)
|
||||
@@ -330,7 +324,7 @@ class Controls:
|
||||
# generic catch-all. ideally, a more specific event should be added above instead
|
||||
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
|
||||
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
|
||||
if (not self.sm.all_checks() or self.card.can_rcv_timeout) and no_system_errors:
|
||||
if (not self.sm.all_checks() or CS.canRcvTimeout) and no_system_errors:
|
||||
if not self.sm.all_alive():
|
||||
self.events.add(EventName.commIssue)
|
||||
elif not self.sm.all_freq_ok():
|
||||
@@ -342,7 +336,7 @@ class Controls:
|
||||
'invalid': [s for s, valid in self.sm.valid.items() if not valid],
|
||||
'not_alive': [s for s, alive in self.sm.alive.items() if not alive],
|
||||
'not_freq_ok': [s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok],
|
||||
'can_rcv_timeout': self.card.can_rcv_timeout,
|
||||
'can_rcv_timeout': CS.canRcvTimeout,
|
||||
}
|
||||
if logs != self.logged_comm_issue:
|
||||
cloudlog.event("commIssue", error=True, **logs)
|
||||
@@ -402,9 +396,10 @@ class Controls:
|
||||
self.events.add(EventName.modeldLagging)
|
||||
|
||||
def data_sample(self):
|
||||
"""Receive data from sockets and update carState"""
|
||||
"""Receive data from sockets"""
|
||||
|
||||
CS = self.card.state_update()
|
||||
car_state = messaging.recv_one(self.car_state_sock)
|
||||
CS = car_state.carState if car_state else self.CS_prev
|
||||
|
||||
self.sm.update(0)
|
||||
|
||||
@@ -418,12 +413,8 @@ class Controls:
|
||||
if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams:
|
||||
self.sm.ignore_alive.append('wideRoadCameraState')
|
||||
|
||||
if not self.CP.passive:
|
||||
self.card.initialize()
|
||||
|
||||
self.initialized = True
|
||||
self.set_initial_state()
|
||||
self.params.put_bool_nonblocking("ControlsReady", True)
|
||||
|
||||
cloudlog.event(
|
||||
"controlsd.initialized",
|
||||
@@ -627,7 +618,7 @@ class Controls:
|
||||
undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2
|
||||
turning = abs(lac_log.desiredLateralAccel) > 1.0
|
||||
good_speed = CS.vEgo > 5
|
||||
max_torque = abs(self.last_actuators.steer) > 0.99
|
||||
max_torque = abs(self.sm['carOutput'].actuatorsOutput.steer) > 0.99
|
||||
if undershooting and turning and good_speed and max_torque:
|
||||
lac_log.active and self.events.add(EventName.steerSaturated)
|
||||
elif lac_log.saturated:
|
||||
@@ -668,8 +659,6 @@ class Controls:
|
||||
def publish_logs(self, CS, start_time, CC, lac_log):
|
||||
"""Send actuators and hud commands to the car, send controlsstate and MPC logging"""
|
||||
|
||||
CO = self.sm['carOutput']
|
||||
|
||||
# Orientation and angle rates can be useful for carcontroller
|
||||
# Only calibrated (car) frame is relevant for the carcontroller
|
||||
orientation_value = list(self.sm['liveLocationKalman'].calibratedOrientationNED.value)
|
||||
@@ -733,8 +722,7 @@ class Controls:
|
||||
hudControl.visualAlert = current_alert.visual_alert
|
||||
|
||||
if not self.CP.passive and self.initialized:
|
||||
self.card.controls_update(CC)
|
||||
self.last_actuators = CO.actuatorsOutput
|
||||
CO = self.sm['carOutput']
|
||||
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
|
||||
self.steer_limited = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
|
||||
STEER_ANGLE_SATURATION_THRESHOLD
|
||||
@@ -781,7 +769,6 @@ class Controls:
|
||||
controlsState.cumLagMs = -self.rk.remaining * 1000.
|
||||
controlsState.startMonoTime = int(start_time * 1e9)
|
||||
controlsState.forceDecel = bool(force_decel)
|
||||
controlsState.canErrorCounter = self.card.can_rcv_cum_timeout_counter
|
||||
controlsState.experimentalMode = self.experimental_mode
|
||||
controlsState.personality = self.personality
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import bisect
|
||||
import math
|
||||
import os
|
||||
from enum import IntEnum
|
||||
@@ -61,8 +62,8 @@ class Events:
|
||||
|
||||
def add(self, event_name: int, static: bool=False) -> None:
|
||||
if static:
|
||||
self.static_events.append(event_name)
|
||||
self.events.append(event_name)
|
||||
bisect.insort(self.static_events, event_name)
|
||||
bisect.insort(self.events, event_name)
|
||||
|
||||
def clear(self) -> None:
|
||||
self.events_prev = {k: (v + 1 if k in self.events else 0) for k, v in self.events_prev.items()}
|
||||
@@ -92,7 +93,7 @@ class Events:
|
||||
|
||||
def add_from_msg(self, events):
|
||||
for e in events:
|
||||
self.events.append(e.name.raw)
|
||||
bisect.insort(self.events, e.name.raw)
|
||||
|
||||
def to_msg(self):
|
||||
ret = []
|
||||
@@ -340,6 +341,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
# ********** events with no alerts **********
|
||||
|
||||
EventName.stockFcw: {},
|
||||
EventName.actuatorsApiUnavailable: {},
|
||||
|
||||
# ********** events only containing alerts displayed in all states **********
|
||||
|
||||
@@ -516,7 +518,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
"Press Resume to Exit Standstill",
|
||||
"",
|
||||
AlertStatus.userPrompt, AlertSize.small,
|
||||
Priority.MID, VisualAlert.none, AudibleAlert.none, .2),
|
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
|
||||
},
|
||||
|
||||
EventName.belowSteerSpeed: {
|
||||
|
||||
@@ -128,7 +128,7 @@ def gen_lat_ocp():
|
||||
return ocp
|
||||
|
||||
|
||||
class LateralMpc():
|
||||
class LateralMpc:
|
||||
def __init__(self, x0=None):
|
||||
if x0 is None:
|
||||
x0 = np.zeros(X_DIM)
|
||||
|
||||
@@ -4,6 +4,7 @@ import time
|
||||
import numpy as np
|
||||
from cereal import log
|
||||
from openpilot.common.numpy_fast import clip
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
# WARNING: imports outside of constants will not trigger a rebuild
|
||||
from openpilot.selfdrive.modeld.constants import index_function
|
||||
@@ -220,8 +221,9 @@ def gen_long_ocp():
|
||||
|
||||
|
||||
class LongitudinalMpc:
|
||||
def __init__(self, mode='acc'):
|
||||
def __init__(self, mode='acc', dt=DT_MDL):
|
||||
self.mode = mode
|
||||
self.dt = dt
|
||||
self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
|
||||
self.reset()
|
||||
self.source = SOURCES[2]
|
||||
@@ -440,7 +442,7 @@ class LongitudinalMpc:
|
||||
self.a_solution = self.x_sol[:,2]
|
||||
self.j_solution = self.u_sol[:,0]
|
||||
|
||||
self.prev_a = np.interp(T_IDXS + 0.05, T_IDXS, self.a_solution)
|
||||
self.prev_a = np.interp(T_IDXS + self.dt, T_IDXS, self.a_solution)
|
||||
|
||||
t = time.monotonic()
|
||||
if self.solution_status != 0:
|
||||
|
||||
@@ -48,7 +48,7 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
|
||||
class LongitudinalPlanner:
|
||||
def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL):
|
||||
self.CP = CP
|
||||
self.mpc = LongitudinalMpc()
|
||||
self.mpc = LongitudinalMpc(dt=dt)
|
||||
self.fcw = False
|
||||
self.dt = dt
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from numbers import Number
|
||||
from openpilot.common.numpy_fast import clip, interp
|
||||
|
||||
|
||||
class PIDController():
|
||||
class PIDController:
|
||||
def __init__(self, k_p, k_i, k_f=0., k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
|
||||
self._k_p = k_p
|
||||
self._k_i = k_i
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import unittest
|
||||
|
||||
from openpilot.selfdrive.controls.lib.events import Alert, EVENTS
|
||||
from openpilot.selfdrive.controls.lib.alertmanager import AlertManager
|
||||
|
||||
|
||||
class TestAlertManager(unittest.TestCase):
|
||||
class TestAlertManager:
|
||||
|
||||
def test_duration(self):
|
||||
"""
|
||||
@@ -38,8 +37,4 @@ class TestAlertManager(unittest.TestCase):
|
||||
|
||||
shown = current_alert is not None
|
||||
should_show = frame <= show_duration
|
||||
self.assertEqual(shown, should_show, msg=f"{frame=} {add_duration=} {duration=}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert shown == should_show, f"{frame=} {add_duration=} {duration=}"
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from parameterized import parameterized
|
||||
|
||||
from cereal import car, log
|
||||
@@ -15,7 +13,7 @@ from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel
|
||||
from openpilot.common.mock.generators import generate_liveLocationKalman
|
||||
|
||||
|
||||
class TestLatControl(unittest.TestCase):
|
||||
class TestLatControl:
|
||||
|
||||
@parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque), (NISSAN.NISSAN_LEAF, LatControlAngle)])
|
||||
def test_saturation(self, car_name, controller):
|
||||
@@ -36,8 +34,4 @@ class TestLatControl(unittest.TestCase):
|
||||
for _ in range(1000):
|
||||
_, _, lac_log = controller.update(True, CS, VM, params, False, 1, llk)
|
||||
|
||||
self.assertTrue(lac_log.saturated)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert lac_log.saturated
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import math
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
from control import StateSpace
|
||||
@@ -10,8 +10,8 @@ from openpilot.selfdrive.car.honda.values import CAR
|
||||
from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices
|
||||
|
||||
|
||||
class TestVehicleModel(unittest.TestCase):
|
||||
def setUp(self):
|
||||
class TestVehicleModel:
|
||||
def setup_method(self):
|
||||
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
|
||||
self.VM = VehicleModel(CP)
|
||||
|
||||
@@ -23,7 +23,7 @@ class TestVehicleModel(unittest.TestCase):
|
||||
yr = self.VM.yaw_rate(sa, u, roll)
|
||||
new_sa = self.VM.get_steer_from_yaw_rate(yr, u, roll)
|
||||
|
||||
self.assertAlmostEqual(sa, new_sa)
|
||||
assert sa == pytest.approx(new_sa)
|
||||
|
||||
def test_dyn_ss_sol_against_yaw_rate(self):
|
||||
"""Verify that the yaw_rate helper function matches the results
|
||||
@@ -38,7 +38,7 @@ class TestVehicleModel(unittest.TestCase):
|
||||
|
||||
# Compute yaw rate using direct computations
|
||||
yr2 = self.VM.yaw_rate(sa, u, roll)
|
||||
self.assertAlmostEqual(float(yr1[0]), yr2)
|
||||
assert float(yr1[0]) == pytest.approx(yr2)
|
||||
|
||||
def test_syn_ss_sol_simulate(self):
|
||||
"""Verifies that dyn_ss_sol matches a simulation"""
|
||||
@@ -63,8 +63,3 @@ class TestVehicleModel(unittest.TestCase):
|
||||
x2 = dyn_ss_sol(sa, u, roll, self.VM)
|
||||
|
||||
np.testing.assert_almost_equal(x1, x2, decimal=3)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import importlib
|
||||
import math
|
||||
from collections import deque
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import capnp
|
||||
from cereal import messaging, log, car
|
||||
@@ -208,7 +208,7 @@ class RadarD:
|
||||
|
||||
self.ready = False
|
||||
|
||||
def update(self, sm: messaging.SubMaster, rr: Optional[car.RadarData]):
|
||||
def update(self, sm: messaging.SubMaster, rr):
|
||||
self.ready = sm.seen['modelV2']
|
||||
self.current_time = 1e-9*max(sm.logMonoTime.values())
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
import random
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
@@ -25,10 +24,10 @@ for event_types in EVENTS.values():
|
||||
ALERTS.append(alert)
|
||||
|
||||
|
||||
class TestAlerts(unittest.TestCase):
|
||||
class TestAlerts:
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
def setup_class(cls):
|
||||
with open(OFFROAD_ALERTS_PATH) as f:
|
||||
cls.offroad_alerts = json.loads(f.read())
|
||||
|
||||
@@ -45,7 +44,7 @@ class TestAlerts(unittest.TestCase):
|
||||
for name, e in events.items():
|
||||
if not name.endswith("DEPRECATED"):
|
||||
fail_msg = "%s @%d not in EVENTS" % (name, e)
|
||||
self.assertTrue(e in EVENTS.keys(), msg=fail_msg)
|
||||
assert e in EVENTS.keys(), fail_msg
|
||||
|
||||
# ensure alert text doesn't exceed allowed width
|
||||
def test_alert_text_length(self):
|
||||
@@ -80,7 +79,7 @@ class TestAlerts(unittest.TestCase):
|
||||
left, _, right, _ = draw.textbbox((0, 0), txt, font)
|
||||
width = right - left
|
||||
msg = f"type: {alert.alert_type} msg: {txt}"
|
||||
self.assertLessEqual(width, max_text_width, msg=msg)
|
||||
assert width <= max_text_width, msg
|
||||
|
||||
def test_alert_sanity_check(self):
|
||||
for event_types in EVENTS.values():
|
||||
@@ -90,21 +89,21 @@ class TestAlerts(unittest.TestCase):
|
||||
continue
|
||||
|
||||
if a.alert_size == AlertSize.none:
|
||||
self.assertEqual(len(a.alert_text_1), 0)
|
||||
self.assertEqual(len(a.alert_text_2), 0)
|
||||
assert len(a.alert_text_1) == 0
|
||||
assert len(a.alert_text_2) == 0
|
||||
elif a.alert_size == AlertSize.small:
|
||||
self.assertGreater(len(a.alert_text_1), 0)
|
||||
self.assertEqual(len(a.alert_text_2), 0)
|
||||
assert len(a.alert_text_1) > 0
|
||||
assert len(a.alert_text_2) == 0
|
||||
elif a.alert_size == AlertSize.mid:
|
||||
self.assertGreater(len(a.alert_text_1), 0)
|
||||
self.assertGreater(len(a.alert_text_2), 0)
|
||||
assert len(a.alert_text_1) > 0
|
||||
assert len(a.alert_text_2) > 0
|
||||
else:
|
||||
self.assertGreater(len(a.alert_text_1), 0)
|
||||
assert len(a.alert_text_1) > 0
|
||||
|
||||
self.assertGreaterEqual(a.duration, 0.)
|
||||
assert a.duration >= 0.
|
||||
|
||||
if event_type not in (ET.WARNING, ET.PERMANENT, ET.PRE_ENABLE):
|
||||
self.assertEqual(a.creation_delay, 0.)
|
||||
assert a.creation_delay == 0.
|
||||
|
||||
def test_offroad_alerts(self):
|
||||
params = Params()
|
||||
@@ -113,11 +112,11 @@ class TestAlerts(unittest.TestCase):
|
||||
alert = copy.copy(self.offroad_alerts[a])
|
||||
set_offroad_alert(a, True)
|
||||
alert['extra'] = ''
|
||||
self.assertTrue(json.dumps(alert) == params.get(a, encoding='utf8'))
|
||||
assert json.dumps(alert) == params.get(a, encoding='utf8')
|
||||
|
||||
# then delete it
|
||||
set_offroad_alert(a, False)
|
||||
self.assertTrue(params.get(a) is None)
|
||||
assert params.get(a) is None
|
||||
|
||||
def test_offroad_alerts_extra_text(self):
|
||||
params = Params()
|
||||
@@ -128,8 +127,5 @@ class TestAlerts(unittest.TestCase):
|
||||
set_offroad_alert(a, True, extra_text="a"*i)
|
||||
|
||||
written_alert = json.loads(params.get(a, encoding='utf8'))
|
||||
self.assertTrue("a"*i == written_alert['extra'])
|
||||
self.assertTrue(alert["text"] == written_alert['text'])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert "a"*i == written_alert['extra']
|
||||
assert alert["text"] == written_alert['text']
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import itertools
|
||||
import numpy as np
|
||||
import unittest
|
||||
|
||||
from parameterized import parameterized_class
|
||||
from cereal import log
|
||||
@@ -36,19 +36,19 @@ def run_cruise_simulation(cruise, e2e, personality, t_end=20.):
|
||||
[True, False], # e2e
|
||||
log.LongitudinalPersonality.schema.enumerants, # personality
|
||||
[5,35])) # speed
|
||||
class TestCruiseSpeed(unittest.TestCase):
|
||||
class TestCruiseSpeed:
|
||||
def test_cruise_speed(self):
|
||||
print(f'Testing {self.speed} m/s')
|
||||
cruise_speed = float(self.speed)
|
||||
|
||||
simulation_steady_state = run_cruise_simulation(cruise_speed, self.e2e, self.personality)
|
||||
self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {self.speed} m/s')
|
||||
assert simulation_steady_state == pytest.approx(cruise_speed, abs=.01), f'Did not reach {self.speed} m/s'
|
||||
|
||||
|
||||
# TODO: test pcmCruise
|
||||
@parameterized_class(('pcm_cruise',), [(False,)])
|
||||
class TestVCruiseHelper(unittest.TestCase):
|
||||
def setUp(self):
|
||||
class TestVCruiseHelper:
|
||||
def setup_method(self):
|
||||
self.CP = car.CarParams(pcmCruise=self.pcm_cruise)
|
||||
self.v_cruise_helper = VCruiseHelper(self.CP)
|
||||
self.reset_cruise_speed_state()
|
||||
@@ -75,7 +75,7 @@ class TestVCruiseHelper(unittest.TestCase):
|
||||
CS.buttonEvents = [ButtonEvent(type=btn, pressed=pressed)]
|
||||
|
||||
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
|
||||
self.assertEqual(pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
|
||||
assert pressed == (self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
|
||||
|
||||
def test_rising_edge_enable(self):
|
||||
"""
|
||||
@@ -94,7 +94,7 @@ class TestVCruiseHelper(unittest.TestCase):
|
||||
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False)
|
||||
|
||||
# Expected diff on enabling. Speed should not change on falling edge of pressed
|
||||
self.assertEqual(not pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
|
||||
assert not pressed == self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last
|
||||
|
||||
def test_resume_in_standstill(self):
|
||||
"""
|
||||
@@ -111,7 +111,7 @@ class TestVCruiseHelper(unittest.TestCase):
|
||||
|
||||
# speed should only update if not at standstill and button falling edge
|
||||
should_equal = standstill or pressed
|
||||
self.assertEqual(should_equal, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
|
||||
assert should_equal == (self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
|
||||
|
||||
def test_set_gas_pressed(self):
|
||||
"""
|
||||
@@ -135,7 +135,7 @@ class TestVCruiseHelper(unittest.TestCase):
|
||||
# TODO: fix skipping first run due to enabled on rising edge exception
|
||||
if v_ego == 0.0:
|
||||
continue
|
||||
self.assertEqual(expected_v_cruise_kph, self.v_cruise_helper.v_cruise_kph)
|
||||
assert expected_v_cruise_kph == self.v_cruise_helper.v_cruise_kph
|
||||
|
||||
def test_initialize_v_cruise(self):
|
||||
"""
|
||||
@@ -145,12 +145,8 @@ class TestVCruiseHelper(unittest.TestCase):
|
||||
for experimental_mode in (True, False):
|
||||
for v_ego in np.linspace(0, 100, 101):
|
||||
self.reset_cruise_speed_state()
|
||||
self.assertFalse(self.v_cruise_helper.v_cruise_initialized)
|
||||
assert not self.v_cruise_helper.v_cruise_initialized
|
||||
|
||||
self.enable(float(v_ego), experimental_mode)
|
||||
self.assertTrue(V_CRUISE_INITIAL <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX)
|
||||
self.assertTrue(self.v_cruise_helper.v_cruise_initialized)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert V_CRUISE_INITIAL <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX
|
||||
assert self.v_cruise_helper.v_cruise_initialized
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
import pytest
|
||||
import itertools
|
||||
from parameterized import parameterized_class
|
||||
|
||||
@@ -32,14 +32,10 @@ def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False, personalit
|
||||
log.LongitudinalPersonality.standard,
|
||||
log.LongitudinalPersonality.aggressive],
|
||||
[0,10,35])) # speed
|
||||
class TestFollowingDistance(unittest.TestCase):
|
||||
class TestFollowingDistance:
|
||||
def test_following_distance(self):
|
||||
v_lead = float(self.speed)
|
||||
simulation_steady_state = run_following_distance_simulation(v_lead, e2e=self.e2e, personality=self.personality)
|
||||
correct_steady_state = desired_follow_distance(v_lead, v_lead, get_T_FOLLOW(self.personality))
|
||||
err_ratio = 0.2 if self.e2e else 0.1
|
||||
self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(err_ratio * correct_steady_state + .5))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert simulation_steady_state == pytest.approx(correct_steady_state, abs=err_ratio * correct_steady_state + .5)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user