Merge remote-tracking branch 'upstream/master' into lta

pull/30109/head
Shane Smiskol 2 years ago
commit 7972bdb859
  1. 3
      .devcontainer/.gitignore
  2. 11
      .devcontainer/Dockerfile
  3. 14
      .devcontainer/container_post_create.sh
  4. 7
      .devcontainer/container_post_start.sh
  5. 34
      .devcontainer/devcontainer.json
  6. 27
      .devcontainer/host_setup.sh
  7. 61
      .github/labeler.yaml
  8. 4
      .github/workflows/badges.yaml
  9. 27
      .github/workflows/compile-openpilot/action.yaml
  10. 18
      .github/workflows/labeler.yaml
  11. 18
      .github/workflows/prebuilt.yaml
  12. 16
      .github/workflows/repo-maintenance.yaml
  13. 163
      .github/workflows/selfdrive_tests.yaml
  14. 54
      .github/workflows/setup-with-retry/action.yaml
  15. 61
      .github/workflows/setup/action.yaml
  16. 69
      .github/workflows/tools_tests.yaml
  17. 3
      .gitignore
  18. 23
      .pre-commit-config.yaml
  19. 1
      Dockerfile.openpilot
  20. 22
      Dockerfile.openpilot_base
  21. 279
      Jenkinsfile
  22. 16
      README.md
  23. 9
      RELEASES.md
  24. 88
      SConstruct
  25. 2
      body
  26. 2
      cereal
  27. 34
      common/SConscript
  28. 4
      common/api/__init__.py
  29. 2
      common/basedir.py
  30. 24
      common/clock.pyx
  31. 4
      common/clutil.cc
  32. 1
      common/clutil.h
  33. 8
      common/gpio.cc
  34. 4
      common/gpio.h
  35. 19
      common/i2c.cc
  36. 2
      common/i2c.h
  37. 4
      common/kalman/SConscript
  38. 13
      common/kalman/simple_kalman.py
  39. 8
      common/kalman/tests/test_simple_kalman.py
  40. 2
      common/logging_extra.py
  41. 3
      common/params.cc
  42. 6
      common/params.h
  43. 3
      common/params.py
  44. 2
      common/params_pyx.pyx
  45. 30
      common/prefix.py
  46. 40
      common/ratekeeper.cc
  47. 23
      common/ratekeeper.h
  48. 17
      common/realtime.py
  49. 2
      common/spinner.py
  50. 12
      common/swaglog.cc
  51. 2
      common/swaglog.h
  52. 3
      common/tests/.gitignore
  53. 4
      common/tests/test_file_helpers.py
  54. 2
      common/tests/test_numpy_fast.py
  55. 19
      common/tests/test_params.py
  56. 23
      common/tests/test_ratekeeper.cc
  57. 2
      common/tests/test_runner.cc
  58. 9
      common/tests/test_swaglog.cc
  59. 8
      common/tests/test_util.cc
  60. 2
      common/text_window.py
  61. 5
      common/transformations/SConscript
  62. 2
      common/transformations/camera.py
  63. 6
      common/transformations/coordinates.cc
  64. 2
      common/transformations/coordinates.hpp
  65. 7
      common/transformations/coordinates.py
  66. 71
      common/transformations/model.py
  67. 4
      common/transformations/orientation.cc
  68. 2
      common/transformations/orientation.hpp
  69. 3
      common/transformations/orientation.py
  70. 2
      common/transformations/tests/test_coordinates.py
  71. 2
      common/transformations/tests/test_orientation.py
  72. 30
      common/transformations/transformations.pyx
  73. 9
      common/util.cc
  74. 21
      common/util.h
  75. 2
      common/watchdog.cc
  76. 4
      common/window.py
  77. 10
      conftest.py
  78. 31
      docs/CARS.md
  79. 5
      docs/conf.py
  80. 11
      docs/docker/Dockerfile
  81. 2
      laika_repo
  82. 16
      mypy.ini
  83. 2
      opendbc
  84. 2
      panda
  85. 2186
      poetry.lock
  86. 79
      pyproject.toml
  87. 2
      rednose_repo
  88. 39
      release/files_common
  89. 2
      release/files_pc
  90. 2
      scripts/count_cars.py
  91. 2
      scripts/disable-powersave.py
  92. 6
      scripts/dump_pll.c
  93. 4
      scripts/pyqt_demo.py
  94. 10
      scripts/waste.py
  95. 1
      selfdrive/assets/.gitignore
  96. 2
      selfdrive/assets/assets.qrc
  97. 0
      selfdrive/assets/navigation/direction_notification_right.png
  98. 0
      selfdrive/assets/navigation/direction_notification_sharp_right.png
  99. 65
      selfdrive/athena/athenad.py
  100. 8
      selfdrive/athena/manage_athenad.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,3 @@
.Xauthority
.env
.host/

@ -0,0 +1,11 @@
FROM ghcr.io/commaai/openpilot-base:latest
# remove gitconfig if exists, since its gonna be replaced by host one
RUN rm -f /root/.gitconfig
RUN apt update && apt install -y vim net-tools usbutils htop ripgrep tmux
RUN pip install ipython jupyter jupyterlab
RUN cd $HOME && \
curl -O https://raw.githubusercontent.com/commaai/agnos-builder/master/userspace/home/.tmux.conf && \
curl -O https://github.com/commaai/agnos-builder/blob/master/userspace/home/.vimrc

@ -0,0 +1,14 @@
#!/usr/bin/env bash
source .devcontainer/.host/.env
# override display flag for mac
if [[ $HOST_OS == darwin ]]; then
echo "Setting up DISPLAY override for macOS..."
cat <<EOF >> /root/.bashrc
if [ -n "\$DISPLAY" ]; then
DISPLAY_NUM=\$(echo "\$DISPLAY" | awk -F: '{print \$NF}')
export DISPLAY=host.docker.internal:\$DISPLAY_NUM
fi
EOF
fi

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# setup safe directories for submodules
SUBMODULE_DIRS=$(git config --file .gitmodules --get-regexp path | awk '{ print $2 }')
for DIR in $SUBMODULE_DIRS; do
git config --global --add safe.directory "$PWD/$DIR"
done

@ -0,0 +1,34 @@
{
"name": "openpilot devcontainer",
"build": {
"dockerfile": "Dockerfile"
},
"postCreateCommand": ".devcontainer/container_post_create.sh",
"postStartCommand": ".devcontainer/container_post_start.sh",
"initializeCommand": ".devcontainer/host_setup.sh",
"privileged": true,
"containerEnv": {
"DISPLAY": "${localEnv:DISPLAY}",
"PYTHONPATH": "${containerWorkspaceFolder}",
"force_color_prompt": "1"
},
"runArgs": [
"--volume=/tmp/.X11-unix:/tmp/.X11-unix",
"--volume=${localWorkspaceFolder}/.devcontainer/.host/.Xauthority:/root/.Xauthority",
"--volume=${localEnv:HOME}/.comma:/root/.comma",
"--volume=/tmp/comma_download_cache:/tmp/comma_download_cache",
"--volume=/tmp/devcontainer_scons_cache:/tmp/scons_cache",
"--shm-size=1G"
],
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-vscode.cpptools",
"ms-toolsai.jupyter",
"guyskk.language-cython",
"lharri73.dbc"
]
}
}
}

@ -0,0 +1,27 @@
#!/usr/bin/env bash
# pull base image
docker pull ghcr.io/commaai/openpilot-base:latest
# setup .host dir
mkdir -p .devcontainer/.host
# setup links to Xauthority
XAUTHORITY_LINK=".devcontainer/.host/.Xauthority"
rm -f $XAUTHORITY_LINK
if [[ -z $XAUTHORITY ]]; then
echo "XAUTHORITY not set. Fallback to ~/.Xauthority ..."
if ! [[ -f $HOME/.Xauthority ]]; then
echo "~/.XAuthority file does not exist. GUI tools may not work properly."
touch $XAUTHORITY_LINK # dummy file to satisfy container volume mount
else
ln -sf $HOME/.Xauthority $XAUTHORITY_LINK
fi
else
ln -sf $XAUTHORITY $XAUTHORITY_LINK
fi
# setup host env file
HOST_INFO_FILE=".devcontainer/.host/.env"
SYSTEM=$(uname -s | tr '[:upper:]' '[:lower:]')
echo "HOST_OS=\"$SYSTEM\"" > $HOST_INFO_FILE

@ -0,0 +1,61 @@
CI / testing:
- all:
- changed-files: ['.github/**']
- all:
- changed-files: ['**/test_*']
car:
- all:
- changed-files: ['selfdrive/car/**']
body:
- all:
- changed-files: ['selfdrive/car/body/*']
chrysler:
- all:
- changed-files: ['selfdrive/car/chrysler/*']
ford:
- all:
- changed-files: ['selfdrive/car/ford/*']
gm:
- all:
- changed-files: ['selfdrive/car/gm/*']
honda:
- all:
- changed-files: ['selfdrive/car/honda/*']
hyundai:
- all:
- changed-files: ['selfdrive/car/hyundai/*']
mazda:
- all:
- changed-files: ['selfdrive/car/mazda/*']
nissan:
- all:
- changed-files: ['selfdrive/car/nissan/*']
subaru:
- all:
- changed-files: ['selfdrive/car/subaru/*']
tesla:
- all:
- changed-files: ['selfdrive/car/tesla/*']
toyota:
- all:
- changed-files: ['selfdrive/car/toyota/*']
volkswagen:
- all:
- changed-files: ['selfdrive/car/volkswagen/*']
simulation:
- all:
- changed-files: ['tools/sim/**']
ui:
- all:
- changed-files: ['selfdrive/ui/**']
tools:
- all:
- changed-files: ['tools/**']
multilanguage:
- all:
- changed-files: ['selfdrive/ui/translations/**']

@ -7,7 +7,7 @@ on:
env: env:
BASE_IMAGE: openpilot-base BASE_IMAGE: openpilot-base
DOCKER_REGISTRY: ghcr.io/commaai DOCKER_REGISTRY: ghcr.io/commaai
RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v ~/scons_cache:/tmp/scons_cache -v ~/comma_download_cache:/tmp/comma_download_cache -v ~/openpilot_cache:/tmp/openpilot_cache $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/sh -c RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/sh -c
jobs: jobs:
badges: badges:
@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
- name: Push badges - name: Push badges
run: | run: |
${{ env.RUN }} "scons -j$(nproc) && python selfdrive/ui/translations/create_badges.py" ${{ env.RUN }} "scons -j$(nproc) && python selfdrive/ui/translations/create_badges.py"

@ -0,0 +1,27 @@
name: 'compile openpilot'
inputs:
cache_key_prefix:
description: 'Prefix for caching key'
required: false
default: 'scons'
runs:
using: "composite"
steps:
- shell: bash
name: Build openpilot with all flags
run: |
${{ env.RUN }} "scons -j$(nproc)"
${{ env.RUN }} "release/check-dirty.sh"
- shell: bash
name: Cleanup scons cache and rebuild
run: |
${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \
scons -j$(nproc) --cache-populate"
- name: Save scons cache
uses: actions/cache/save@v3
if: github.ref == 'refs/heads/master'
with:
path: .ci_cache/scons_cache
key: ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}

@ -0,0 +1,18 @@
name: "Pull Request Labeler"
on:
pull_request_target:
jobs:
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: false
- uses: actions/labeler@v5.0.0-alpha.1
with:
dot: true
configuration-path: .github/labeler.yaml

@ -5,12 +5,8 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
BASE_IMAGE: openpilot-base
DOCKER_REGISTRY: ghcr.io/commaai
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: | BUILD: selfdrive/test/docker_build.sh prebuilt
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
jobs: jobs:
build_prebuilt: build_prebuilt:
@ -18,7 +14,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
if: github.repository == 'commaai/openpilot' if: github.repository == 'commaai/openpilot'
env: env:
IMAGE_NAME: openpilot-prebuilt PUSH_IMAGE: true
steps: steps:
- name: Wait for green check mark - name: Wait for green check mark
if: ${{ github.event_name != 'workflow_dispatch' }} if: ${{ github.event_name != 'workflow_dispatch' }}
@ -31,13 +27,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- name: Build Docker image - name: Build and Push docker image
run: |
eval "$BUILD"
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f Dockerfile.openpilot .
- name: Push to container registry
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest eval "$BUILD"
docker tag $DOCKER_REGISTRY/$IMAGE_NAME:latest $DOCKER_REGISTRY/$IMAGE_NAME:$GITHUB_SHA
docker push $DOCKER_REGISTRY/$IMAGE_NAME:$GITHUB_SHA

@ -1,4 +1,4 @@
name: repo name: repo maintenance
on: on:
schedule: schedule:
@ -6,13 +6,17 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
pre-commit-autoupdate: updates:
name: pre-commit autoupdate name: updates
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
container: container:
image: ghcr.io/commaai/openpilot-base:latest image: ghcr.io/commaai/openpilot-base:latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: poetry lock
run: |
pip install poetry
poetry lock
- name: pre-commit autoupdate - name: pre-commit autoupdate
run: | run: |
git config --global --add safe.directory '*' git config --global --add safe.directory '*'
@ -21,8 +25,8 @@ jobs:
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5
with: with:
token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} token: ${{ secrets.ACTIONS_CREATE_PR_PAT }}
commit-message: Update pre-commit hook versions commit-message: Update Python packages and pre-commit hooks
title: 'pre-commit: autoupdate hooks' title: 'Update Python packages and pre-commit hooks'
branch: pre-commit-updates branch: auto-package-updates
base: master base: master
delete-branch: true delete-branch: true

@ -12,23 +12,21 @@ concurrency:
env: env:
PYTHONWARNINGS: error PYTHONWARNINGS: error
BASE_IMAGE: openpilot-base BASE_IMAGE: openpilot-base
CL_BASE_IMAGE: openpilot-base-cl CL_BASE_IMAGE: openpilot-base-cl
DOCKER_REGISTRY: ghcr.io/commaai
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }} AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: | BUILD: selfdrive/test/docker_build.sh base
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c
RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v ~/scons_cache:/tmp/scons_cache -v ~/comma_download_cache:/tmp/comma_download_cache -v ~/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c BUILD_CL: selfdrive/test/docker_build.sh cl
BUILD_CL: | RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c
DOCKER_BUILDKIT=1 docker build --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl .
RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v ~/scons_cache:/tmp/scons_cache -v ~/comma_download_cache:/tmp/comma_download_cache -v ~/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c
UNIT_TEST: coverage run --append -m unittest discover UNIT_TEST: coverage run --append -m unittest discover
PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5
jobs: jobs:
build_release: build_release:
@ -43,7 +41,7 @@ jobs:
- name: Build devel - name: Build devel
timeout-minutes: 1 timeout-minutes: 1
run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
- name: Check submodules - name: Check submodules
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
timeout-minutes: 1 timeout-minutes: 1
@ -64,34 +62,28 @@ jobs:
run: | run: |
cd $GITHUB_WORKSPACE cd $GITHUB_WORKSPACE
cp .pre-commit-config.yaml $STRIPPED_DIR cp .pre-commit-config.yaml $STRIPPED_DIR
cp mypy.ini $STRIPPED_DIR
cp pyproject.toml $STRIPPED_DIR cp pyproject.toml $STRIPPED_DIR
cp poetry.lock $STRIPPED_DIR cp poetry.lock $STRIPPED_DIR
cd $STRIPPED_DIR cd $STRIPPED_DIR
${{ env.RUN }} "unset PYTHONWARNINGS && pre-commit run --all" ${{ env.RUN }} "unset PYTHONWARNINGS && pre-commit run --all"
build_all: build:
name: build all strategy:
runs-on: ubuntu-20.04 matrix:
arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }}
runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
- name: Build openpilot with all flags with:
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 12 || 30) }} # allow more time when we missed the scons cache docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }}
run: ${{ env.RUN }} "scons -j$(nproc) --extras && release/check-dirty.sh" cache_key_prefix: scons_${{ matrix.arch }}
- name: Cleanup scons cache and rebuild - uses: ./.github/workflows/compile-openpilot
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 2 || 30) }} # allow more time when we missed the scons cache timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache
run: |
${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \
scons -j$(nproc) --cache-populate"
- name: Save scons cache
uses: actions/cache/save@v3
if: github.ref == 'refs/heads/master'
with: with:
path: ~/scons_cache cache_key_prefix: scons_${{ matrix.arch }}
key: scons-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
build_mac: build_mac:
name: build macos name: build macos
@ -118,6 +110,8 @@ jobs:
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: | path: |
.env
.venv
~/github_brew_cache_entries.txt ~/github_brew_cache_entries.txt
~/.pyenv ~/.pyenv
~/Library/Caches/pypoetry ~/Library/Caches/pypoetry
@ -143,24 +137,23 @@ jobs:
fi fi
- name: Install dependencies - name: Install dependencies
if: steps.dependency-cache.outputs.cache-hit != 'true' if: steps.dependency-cache.outputs.cache-hit != 'true'
run: ./tools/mac_setup.sh run: SKIP_PROMPT=1 ./tools/mac_setup.sh
env: env:
# package install has DeprecationWarnings # package install has DeprecationWarnings
PYTHONWARNINGS: default PYTHONWARNINGS: default
- name: Build openpilot - name: Build openpilot
run: | run: |
source tools/openpilot_env.sh eval "$(pyenv init --path)"
poetry run scons -j$(nproc) poetry run scons -j$(nproc)
- name: Run tests - name: Run tests
run: | run: |
source tools/openpilot_env.sh eval "$(pyenv init --path)"
export PYTHONPATH=$PWD
poetry run tools/plotjuggler/test_plotjuggler.py poetry run tools/plotjuggler/test_plotjuggler.py
- name: Pre Cache - Cleanup scons cache - name: Pre Cache - Cleanup scons cache
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
run: | run: |
source tools/openpilot_env.sh
rm -rf /tmp/scons_cache/* rm -rf /tmp/scons_cache/*
eval "$(pyenv init --path)"
poetry run scons -j$(nproc) --cache-populate poetry run scons -j$(nproc) --cache-populate
- name: Save scons cache - name: Save scons cache
id: scons-save-cache id: scons-save-cache
@ -186,28 +179,46 @@ jobs:
docker_push: docker_push:
name: docker push name: docker push
runs-on: ubuntu-20.04 strategy:
matrix:
arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }}
runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }}
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- name: Build Docker image - name: Setup to push to repo
run: eval "$BUILD"
- name: Push to container registry
run: | run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
echo "TARGET_ARCHITECTURE=${{ matrix.arch }}" >> "$GITHUB_ENV"
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest - uses: ./.github/workflows/setup-with-retry
docker tag $DOCKER_REGISTRY/$BASE_IMAGE:latest $DOCKER_REGISTRY/$BASE_IMAGE:$GITHUB_SHA with:
docker push $DOCKER_REGISTRY/$BASE_IMAGE:$GITHUB_SHA git-lfs: false
- name: Build CL Docker image docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }}
run: eval "$BUILD_CL" - name: Build and push CL Docker image
- name: Push to container registry if: matrix.arch == 'x86_64'
run: |
unset TARGET_ARCHITECTURE
eval "$BUILD_CL"
docker_push_multiarch:
name: docker push multiarch tag
runs-on: ubuntu-20.04
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
needs: [docker_push]
steps:
- uses: actions/checkout@v3
with:
submodules: false
- name: Setup docker
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest - name: Merge x64 and arm64 tags
docker tag $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest $DOCKER_REGISTRY/$CL_BASE_IMAGE:$GITHUB_SHA run: |
docker push $DOCKER_REGISTRY/$CL_BASE_IMAGE:$GITHUB_SHA export PUSH_IMAGE=true
selfdrive/test/docker_tag_multiarch.sh base x86_64 aarch64
static_analysis: static_analysis:
name: static analysis name: static analysis
@ -229,7 +240,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
- name: Build openpilot - name: Build openpilot
run: ${{ env.RUN }} "scons -j$(nproc)" run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Run valgrind - name: Run valgrind
@ -247,68 +258,47 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
- name: Build openpilot - name: Build openpilot
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache 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)" run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Run unit tests - name: Run unit tests
timeout-minutes: 40 timeout-minutes: 15
run: | run: |
${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \ ${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \
$UNIT_TEST common && \ $PYTEST -n auto --dist=loadscope --timeout 30 -o cpp_files=test_* && \
$UNIT_TEST opendbc/can && \
$UNIT_TEST selfdrive/boardd && \
$UNIT_TEST selfdrive/controls && \
$UNIT_TEST selfdrive/monitoring && \
$UNIT_TEST system/loggerd && \
$UNIT_TEST selfdrive/car && \
$UNIT_TEST selfdrive/locationd && \
$UNIT_TEST selfdrive/test/longitudinal_maneuvers && \
$UNIT_TEST system/tests && \
$UNIT_TEST system/ubloxd && \
selfdrive/locationd/test/_test_locationd_lib.py && \
./system/ubloxd/tests/test_glonass_runner && \
$UNIT_TEST selfdrive/athena && \
$UNIT_TEST selfdrive/thermald && \
$UNIT_TEST system/hardware/tici && \
$UNIT_TEST tools/lib/tests && \
./selfdrive/ui/tests/create_test_translations.sh && \ ./selfdrive/ui/tests/create_test_translations.sh && \
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
./selfdrive/ui/tests/test_translations.py && \ ./selfdrive/ui/tests/test_translations.py && \
./common/tests/test_util && \
./common/tests/test_swaglog && \
./selfdrive/boardd/tests/test_boardd_usbprotocol && \
./system/loggerd/tests/test_logger &&\
./system/proclogd/tests/test_proclog && \
./tools/replay/tests/test_replay && \
./tools/cabana/tests/test_cabana && \
./system/camerad/test/ae_gray_test && \ ./system/camerad/test/ae_gray_test && \
./selfdrive/test/process_replay/test_fuzzy.py && \ ./selfdrive/test/process_replay/test_fuzzy.py"
coverage xml"
- name: "Upload coverage to Codecov" - name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
process_replay: process_replay:
name: process replay name: process replay
runs-on: ubuntu-20.04 runs-on: ${{ ((github.event.pull_request.head.repo.full_name == 'commaai/openpilot') || (github.repository == 'commaai/openpilot')) && 'buildjet-8vcpu-ubuntu-2004' || 'ubuntu-20.04' }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
with:
docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }}
- name: Cache test routes - name: Cache test routes
id: dependency-cache id: dependency-cache
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: ~/comma_download_cache 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') }}
- name: Build openpilot - name: Build openpilot
run: | run: |
${{ env.RUN }} "scons -j$(nproc)" ${{ env.RUN }} "scons -j$(nproc)"
- name: Run replay - name: Run replay
timeout-minutes: 20 timeout-minutes: 30
run: | run: |
${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
chmod -R 777 /tmp/comma_download_cache && \
coverage xml" coverage xml"
- name: Print diff - name: Print diff
id: print-diff id: print-diff
@ -323,7 +313,7 @@ jobs:
- name: Upload reference logs - name: Upload reference logs
if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }}
run: | run: |
${{ env.RUN }} "CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" ${{ env.RUN }} "unset PYTHONWARNINGS && CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
- name: "Upload coverage to Codecov" - name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
@ -334,7 +324,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
- name: Build base Docker image - name: Build base Docker image
run: eval "$BUILD" run: eval "$BUILD"
- name: Build Docker image - name: Build Docker image
@ -345,7 +335,7 @@ jobs:
${{ env.RUN }} "scons -j$(nproc)" ${{ env.RUN }} "scons -j$(nproc)"
# PYTHONWARNINGS triggers a SyntaxError in onnxruntime # PYTHONWARNINGS triggers a SyntaxError in onnxruntime
- name: Run model replay with ONNX - name: Run model replay with ONNX
timeout-minutes: 2 timeout-minutes: 3
run: | run: |
${{ env.RUN_CL }} "unset PYTHONWARNINGS && \ ${{ env.RUN_CL }} "unset PYTHONWARNINGS && \
ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \
@ -370,20 +360,19 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
- name: Cache test routes - name: Cache test routes
id: dependency-cache id: dependency-cache
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: ~/comma_download_cache path: .ci_cache/comma_download_cache
key: car_models-${{ hashFiles('selfdrive/car/tests/test_models.py', 'selfdrive/car/tests/routes.py') }}-${{ matrix.job }} key: car_models-${{ hashFiles('selfdrive/car/tests/test_models.py', 'selfdrive/car/tests/routes.py') }}-${{ matrix.job }}
- name: Build openpilot - name: Build openpilot
run: ${{ env.RUN }} "scons -j$(nproc)" run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Test car models - name: Test car models
timeout-minutes: 25 timeout-minutes: 25
run: | run: |
${{ env.RUN }} "coverage run -m pytest selfdrive/car/tests/test_models.py && \ ${{ env.RUN }} "$PYTEST -n auto --dist=loadscope selfdrive/car/tests/test_models.py && \
coverage xml && \
chmod -R 777 /tmp/comma_download_cache" chmod -R 777 /tmp/comma_download_cache"
env: env:
NUM_JOBS: 5 NUM_JOBS: 5
@ -400,7 +389,7 @@ jobs:
with: with:
submodules: true submodules: true
ref: ${{ github.event.pull_request.base.ref }} ref: ${{ github.event.pull_request.base.ref }}
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
- name: Get base car info - name: Get base car info
run: | run: |
${{ env.RUN }} "scons -j$(nproc) && python selfdrive/debug/dump_car_info.py --path /tmp/openpilot_cache/base_car_info" ${{ env.RUN }} "scons -j$(nproc) && python selfdrive/debug/dump_car_info.py --path /tmp/openpilot_cache/base_car_info"
@ -408,9 +397,11 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
path: current
- name: Save car docs diff - name: Save car docs diff
id: save_diff id: save_diff
run: | run: |
cd current
${{ env.RUN }} "scons -j$(nproc)" ${{ env.RUN }} "scons -j$(nproc)"
output=$(${{ env.RUN }} "python selfdrive/debug/print_docs_diff.py --path /tmp/openpilot_cache/base_car_info") output=$(${{ env.RUN }} "python selfdrive/debug/print_docs_diff.py --path /tmp/openpilot_cache/base_car_info")
output="${output//$'\n'/'%0A'}" output="${output//$'\n'/'%0A'}"

@ -0,0 +1,54 @@
name: 'openpilot env setup, with retry on failure'
inputs:
docker_hub_pat:
description: 'Auth token for Docker Hub, required for BuildJet jobs'
required: false
default: ''
git_lfs:
description: 'Whether or not to pull the git lfs'
required: false
default: 'true'
cache_key_prefix:
description: 'Prefix for caching key'
required: false
default: 'scons_x86_64'
sleep_time:
description: 'Time to sleep between retries'
required: false
default: 30
runs:
using: "composite"
steps:
- id: setup1
uses: ./.github/workflows/setup
continue-on-error: true
with:
docker_hub_pat: ${{ inputs.docker_hub_pat }}
git_lfs: ${{ inputs.git_lfs }}
cache_key_prefix: ${{ inputs.cache_key_prefix }}
is_retried: true
- if: steps.setup1.outcome == 'failure'
shell: bash
run: sleep ${{ inputs.sleep_time }}
- id: setup2
if: steps.setup1.outcome == 'failure'
uses: ./.github/workflows/setup
continue-on-error: true
with:
docker_hub_pat: ${{ inputs.docker_hub_pat }}
git_lfs: ${{ inputs.git_lfs }}
cache_key_prefix: ${{ inputs.cache_key_prefix }}
is_retried: true
- if: steps.setup2.outcome == 'failure'
shell: bash
run: sleep ${{ inputs.sleep_time }}
- id: setup3
if: steps.setup2.outcome == 'failure'
uses: ./.github/workflows/setup
with:
docker_hub_pat: ${{ inputs.docker_hub_pat }}
git_lfs: ${{ inputs.git_lfs }}
cache_key_prefix: ${{ inputs.cache_key_prefix }}
is_retried: true

@ -1,19 +1,51 @@
name: 'openpilot env setup' name: 'openpilot env setup'
inputs: inputs:
docker_hub_pat:
description: 'Auth token for Docker Hub, required for BuildJet jobs'
required: true
default: ''
git_lfs: git_lfs:
description: 'Whether or not to pull the git lfs' description: 'Whether or not to pull the git lfs'
required: false required: true
default: 'true' default: 'true'
cache_key_prefix:
description: 'Prefix for caching key'
required: true
default: 'scons_x86_64'
is_retried:
description: 'A mock param that asserts that we use the setup-with-retry instead of this action directly'
required: false
default: 'false'
runs: runs:
using: "composite" using: "composite"
steps: steps:
# assert that this action is retried using the setup-with-retry
- shell: bash
if: ${{ inputs.is_retried == 'false' }}
run: |
echo "You should not run this action directly. Use setup-with-retry instead"
exit 1
# do this after checkout to ensure our custom LFS config is used to pull from GitLab # do this after checkout to ensure our custom LFS config is used to pull from GitLab
- shell: bash - shell: bash
if: ${{ inputs.git_lfs == 'true' }} if: ${{ inputs.git_lfs == 'true' }}
run: git lfs pull run: git lfs pull
# on BuildJet runners, must be logged into DockerHub to avoid rate limiting
# https://buildjet.com/for-github-actions/docs/guides/docker
- shell: bash
if: ${{ contains(runner.name, 'buildjet') && inputs.docker_hub_pat == '' }}
run: |
echo "Need to set the Docker Hub PAT secret as an input to this action"
exit 1
- name: Login to Docker Hub
if: contains(runner.name, 'buildjet')
shell: bash
run: |
docker login -u adeebshihadeh -p ${{ inputs.docker_hub_pat }}
# build cache # build cache
- id: date - id: date
shell: bash shell: bash
@ -23,12 +55,29 @@ runs:
- id: restore-scons-cache - id: restore-scons-cache
uses: actions/cache/restore@v3 uses: actions/cache/restore@v3
with: with:
path: ~/scons_cache path: .ci_cache/scons_cache
key: scons-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} key: ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
restore-keys: | restore-keys: |
scons-${{ env.CACHE_COMMIT_DATE }}- ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-
scons- ${{ inputs.cache_key_prefix }}-
# if we didn't get a cache hit, make the directory manually so it doesn't fail on future steps
- id: scons-cache-setup
shell: bash
if: steps.restore-scons-cache.outputs.cache-hit != 'true'
run: mkdir -p $GITHUB_WORKSPACE/.ci_cache/scons_cache
# as suggested here: https://github.com/moby/moby/issues/32816#issuecomment-910030001
- id: normalize-file-permissions
shell: bash
name: Normalize file permissions to ensure a consistent docker build cache
run: |
find . -type f -executable -not -perm 755 -exec chmod 755 {} \;
find . -type f -not -executable -not -perm 644 -exec chmod 644 {} \;
- id: setup-buildx-action
if: contains(runner.name, 'buildjet')
name: Set up Docker Buildx on buildjet to ensure a consistent cache
uses: docker/setup-buildx-action@v2
with:
driver: docker-container
# build our docker image # build our docker image
- shell: bash - shell: bash
run: eval ${{ env.BUILD }} run: eval ${{ env.BUILD }}

@ -13,17 +13,15 @@ concurrency:
env: env:
BASE_IMAGE: openpilot-base BASE_IMAGE: openpilot-base
CL_BASE_IMAGE: openpilot-base-cl CL_BASE_IMAGE: openpilot-base-cl
DOCKER_REGISTRY: ghcr.io/commaai
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: | BUILD: selfdrive/test/docker_build.sh base
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v ~/scons_cache:/tmp/scons_cache -v ~/comma_download_cache:/tmp/comma_download_cache -v ~/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c
BUILD_CL: | BUILD_CL: selfdrive/test/docker_build.sh cl
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl .
RUN_CL: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v ~/scons_cache:/tmp/scons_cache -v ~/comma_download_cache:/tmp/comma_download_cache -v ~/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c RUN_CL: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c
jobs: jobs:
@ -35,57 +33,68 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- name: Build Docker image - uses: ./.github/workflows/setup-with-retry
run: eval "$BUILD" - name: Build openpilot
- name: Unit test timeout-minutes: 5
run: ${{ env.RUN }} "scons -j$(nproc) cereal/ common/ --minimal"
- name: Test PlotJuggler
timeout-minutes: 2 timeout-minutes: 2
run: | run: |
${{ env.RUN }} "scons -j$(nproc) --directory=/tmp/openpilot/cereal && \ ${{ env.RUN }} "pytest tools/plotjuggler/"
apt-get update && \
apt-get install -y libdw-dev libqt5svg5-dev libqt5x11extras5-dev && \
cd /tmp/openpilot/tools/plotjuggler && \
./test_plotjuggler.py"
simulator: simulator:
name: simulator name: simulator
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
env:
IMAGE_NAME: openpilot-sim
if: github.repository == 'commaai/openpilot' if: github.repository == 'commaai/openpilot'
timeout-minutes: 45 timeout-minutes: 45
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
- name: Build base cl image - name: Build base cl image
run: eval "$BUILD_CL" run: eval "$BUILD_CL"
- name: Build simulator image - name: Setup to push to repo
run: DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f tools/sim/Dockerfile.sim .
- name: Push to container registry
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
run: | run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest - name: Build and push sim image
run: |
selfdrive/test/docker_build.sh sim
docs: docs:
name: build docs name: build docs
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 45 timeout-minutes: 45
env:
BUILD: |
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/openpilot-docs-base:latest -t $DOCKER_REGISTRY/openpilot-docs-base:latest -f docs/docker/Dockerfile --target openpilot-docs-base .
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/openpilot-docs-base:latest --cache-from $DOCKER_REGISTRY/openpilot-docs:latest -t $DOCKER_REGISTRY/openpilot-docs:latest -f docs/docker/Dockerfile .
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup-with-retry
with: with:
git_lfs: false git_lfs: false
- name: Push docker container - name: Setup to push to repo
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
run: | run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/openpilot-docs-base:latest - name: Build and push docs image
docker push $DOCKER_REGISTRY/openpilot-docs:latest run: |
selfdrive/test/docker_build.sh docs
devcontainer:
name: devcontainer
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Setup Dev Container CLI
run: npm install -g @devcontainers/cli
- name: Build dev container image
run: devcontainer build --workspace-folder .
- name: Run dev container
run: devcontainer up --workspace-folder .
- name: Test environment
run: devcontainer exec --workspace-folder . scons --dry-run

3
.gitignore vendored

@ -1,12 +1,12 @@
venv/ venv/
.venv/ .venv/
.ci_cache
.env .env
.clang-format .clang-format
.DS_Store .DS_Store
.tags .tags
.ipynb_checkpoints .ipynb_checkpoints
.idea .idea
.moc_files
.overlay_init .overlay_init
.overlay_consistent .overlay_consistent
.sconsign.dblite .sconsign.dblite
@ -48,6 +48,7 @@ selfdrive/mapd/default_speeds_by_region.json
system/proclogd/proclogd system/proclogd/proclogd
selfdrive/ui/_ui selfdrive/ui/_ui
selfdrive/ui/translations/alerts_generated.h selfdrive/ui/translations/alerts_generated.h
selfdrive/ui/translations/tmp
selfdrive/test/longitudinal_maneuvers/out selfdrive/test/longitudinal_maneuvers/out
selfdrive/car/tests/cars_dump selfdrive/car/tests/cars_dump
system/camerad/camerad system/camerad/camerad

@ -1,3 +1,4 @@
exclude: '^(tinygrad_repo)'
repos: repos:
- repo: meta - repo: meta
hooks: hooks:
@ -14,6 +15,8 @@ repos:
- id: check-yaml - id: check-yaml
- id: check-merge-conflict - id: check-merge-conflict
- id: check-symlinks - id: check-symlinks
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: check-added-large-files - id: check-added-large-files
args: ['--maxkb=100'] args: ['--maxkb=100']
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
@ -35,7 +38,7 @@ repos:
args: ['--explicit-package-bases'] args: ['--explicit-package-bases']
exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)'
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.284 rev: v0.0.288
hooks: hooks:
- id: ruff - id: ruff
exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)'
@ -53,6 +56,18 @@ repos:
- --quiet - --quiet
- --force - --force
- -j8 - -j8
- repo: https://github.com/cpplint/cpplint
rev: 1.6.1
hooks:
- id: cpplint
exclude: '^(third_party/)|(cereal/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(generated/)'
args:
- --quiet
- --counting=total
- --linelength=240
# https://google.github.io/styleguide/cppguide.html
# relevant rules are whitelisted, see all options with: cpplint --filter=
- --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces
- repo: local - repo: local
hooks: hooks:
- id: test_translations - id: test_translations
@ -61,10 +76,14 @@ repos:
language: script language: script
pass_filenames: false pass_filenames: false
- repo: https://github.com/python-poetry/poetry - repo: https://github.com/python-poetry/poetry
rev: '1.5.0' rev: '1.6.0'
hooks: hooks:
- id: poetry-check - id: poetry-check
- id: poetry-lock - id: poetry-lock
name: validate poetry lock name: validate poetry lock
args: args:
- --check - --check
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.26.3
hooks:
- id: check-github-workflows

@ -10,6 +10,7 @@ WORKDIR ${OPENPILOT_PATH}
COPY SConstruct ${OPENPILOT_PATH} COPY SConstruct ${OPENPILOT_PATH}
COPY ./openpilot ${OPENPILOT_PATH}/openpilot
COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./third_party ${OPENPILOT_PATH}/third_party
COPY ./site_scons ${OPENPILOT_PATH}/site_scons COPY ./site_scons ${OPENPILOT_PATH}/site_scons
COPY ./laika ${OPENPILOT_PATH}/laika COPY ./laika ${OPENPILOT_PATH}/laika

@ -12,22 +12,28 @@ ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8 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
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
ENV POETRY_VIRTUALENVS_CREATE=false ENV POETRY_VIRTUALENVS_CREATE=false
ENV PYENV_VERSION=3.11.4 ENV PYENV_VERSION=3.11.4
ENV PYENV_ROOT="/root/.pyenv" ENV PYENV_ROOT="/root/.pyenv"
ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH" ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"
COPY pyproject.toml poetry.lock .python-version /tmp/ COPY pyproject.toml poetry.lock .python-version /tmp/
COPY tools/ubuntu_setup.sh tools/install_python_dependencies.sh /tmp/tools/ COPY tools/install_python_dependencies.sh /tmp/tools/
RUN cd /tmp && \ RUN cd /tmp && \
tools/ubuntu_setup.sh && \ tools/install_python_dependencies.sh && \
rm -rf /var/lib/apt/lists/* && \
rm -rf /tmp/* && \ rm -rf /tmp/* && \
rm -rf /root/.cache && \ rm -rf /root/.cache && \
pip uninstall -y poetry && \ pip uninstall -y poetry
# remove unused architectures from gcc for panda
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
RUN sudo git config --global --add safe.directory /tmp/openpilot RUN sudo git config --global --add safe.directory /tmp/openpilot

279
Jenkinsfile vendored

@ -1,4 +1,4 @@
def phone(String ip, String step_label, String cmd) { def device(String ip, String step_label, String cmd) {
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) { withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
def ssh_cmd = """ def ssh_cmd = """
ssh -tt -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END' ssh -tt -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END'
@ -51,225 +51,186 @@ END"""
} }
} }
def phone_steps(String device_type, steps) { def deviceStage(String stageName, String deviceType, List env, def steps) {
lock(resource: "", label: device_type, inversePrecedence: true, variable: 'device_ip', quantity: 1) { stage(stageName) {
timeout(time: 20, unit: 'MINUTES') { if (currentBuild.result != null) {
phone(device_ip, "git checkout", readFile("selfdrive/test/setup_device_ci.sh"),) return
steps.each { item ->
phone(device_ip, item[0], item[1])
}
} }
}
}
pipeline { def extra = env.collect { "export ${it}" }.join('\n');
agent none
environment {
CI = "1"
PYTHONWARNINGS = "error"
TEST_DIR = "/data/openpilot"
SOURCE_DIR = "/data/openpilot_source/"
AZURE_TOKEN = credentials('azure_token')
MAPBOX_TOKEN = credentials('mapbox_token')
}
options {
timeout(time: 3, unit: 'HOURS')
disableConcurrentBuilds(abortPrevious: env.BRANCH_NAME != 'master')
}
stages { docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') {
stage('build release3-staging') { lock(resource: "", label: deviceType, inversePrecedence: true, variable: 'device_ip', quantity: 1) {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } timeout(time: 20, unit: 'MINUTES') {
when { device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
branch 'devel-staging' steps.each { item ->
} device(device_ip, item[0], item[1])
steps {
phone_steps("tici-needs-can", [
["build release3-staging & dashcam3-staging", "RELEASE_BRANCH=release3-staging DASHCAM_BRANCH=dashcam3-staging $SOURCE_DIR/release/build_release.sh"],
])
} }
} }
stage('build nightly') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
when {
branch 'master-ci'
} }
steps {
phone_steps("tici-needs-can", [
["build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"],
])
} }
} }
}
stage('openpilot tests') { def pcStage(String stageName, Closure body) {
when { node {
not { stage(stageName) {
anyOf { if (currentBuild.result != null) {
branch 'master-ci'; branch 'devel'; branch 'devel-staging'; return
branch 'release3'; branch 'release3-staging'; branch 'dashcam3'; branch 'dashcam3-staging';
branch 'testing-closet*'; branch 'hotfix-*'
}
}
} }
parallel { checkout scm
/* def dockerArgs = '--user=root -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/scons_cache:/tmp/scons_cache';
stage('simulator') { docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .").inside(dockerArgs) {
agent { timeout(time: 20, unit: 'MINUTES') {
dockerfile { try {
filename 'Dockerfile.sim_nvidia'
dir 'tools/sim'
args '--user=root'
}
}
steps {
sh "git config --global --add safe.directory '*'" sh "git config --global --add safe.directory '*'"
sh "git submodule update --init --recursive" sh "git submodule update --init --recursive"
sh "git lfs pull" sh "git lfs pull"
lock(resource: "", label: "simulator", inversePrecedence: true, quantity: 1) { body()
sh "${WORKSPACE}/tools/sim/build_container.sh" } finally {
sh "DETACH=1 ${WORKSPACE}/tools/sim/start_carla.sh" sh "rm -rf ${env.WORKSPACE}/* || true"
sh "${WORKSPACE}/tools/sim/start_openpilot_docker.sh"
}
}
post {
always {
sh "docker kill carla_sim || true"
sh "rm -rf ${WORKSPACE}/* || true"
sh "rm -rf .* || true" sh "rm -rf .* || true"
} }
} }
} }
*/
stage('PC tests') {
agent {
dockerfile {
filename 'Dockerfile.openpilot_base'
args '--user=root -v /tmp/comma_download_cache:/tmp/comma_download_cache'
} }
} }
steps { }
sh "git config --global --add safe.directory '*'"
sh "git submodule update --init --depth=1 --recursive"
sh "git lfs pull"
// tests that our build system's dependencies are configured properly, needs a machine with lots of cores
sh "scons --clean && scons --no-cache --random -j42"
sh "INTERNAL_SEG_CNT=500 INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt FILEREADER_CACHE=1 \
pytest -n42 --dist=loadscope selfdrive/car/tests/test_models.py"
}
post { def setupCredentials() {
always { withCredentials([
sh "rm -rf ${WORKSPACE}/* || true" string(credentialsId: 'azure_token', variable: 'AZURE_TOKEN'),
sh "rm -rf .* || true" string(credentialsId: 'mapbox_token', variable: 'MAPBOX_TOKEN')
} ]) {
} env.AZURE_TOKEN = "${AZURE_TOKEN}"
env.MAPBOX_TOKEN = "${MAPBOX_TOKEN}"
} }
}
stage('tizi-tests') { node {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } env.CI = "1"
steps { env.PYTHONWARNINGS = "error"
phone_steps("tizi", [ env.TEST_DIR = "/data/openpilot"
["build openpilot", "cd selfdrive/manager && ./build.py"], env.SOURCE_DIR = "/data/openpilot_source/"
["test boardd loopback", "SINGLE_PANDA=1 pytest selfdrive/boardd/tests/test_boardd_loopback.py"], setupCredentials()
["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"],
["test sensord", "cd system/sensord/tests && pytest test_sensord.py"], env.GIT_BRANCH = checkout(scm).GIT_BRANCH
["test camerad", "pytest system/camerad/test/test_camerad.py"], env.GIT_COMMIT = checkout(scm).GIT_COMMIT
["test exposure", "pytest system/camerad/test/test_exposure.py"],
["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"], def excludeBranches = ['master-ci', 'devel', 'devel-staging', 'release3', 'release3-staging',
["test hw", "pytest system/hardware/tici/tests/test_hardware.py"], 'dashcam3', 'dashcam3-staging', 'testing-closet*', 'hotfix-*']
["test rawgpsd", "pytest system/sensord/rawgps/test_rawgps.py"], def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
if (env.BRANCH_NAME != 'master') {
properties([
disableConcurrentBuilds(abortPrevious: true)
]) ])
} }
try {
if (env.BRANCH_NAME == 'devel-staging') {
deviceStage("build release3-staging", "tici-needs-can", [], [
["build release3-staging & dashcam3-staging", "RELEASE_BRANCH=release3-staging DASHCAM_BRANCH=dashcam3-staging $SOURCE_DIR/release/build_release.sh"],
])
} }
stage('build') { if (env.BRANCH_NAME == 'master-ci') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } deviceStage("build nightly", "tici-needs-can", [], [
environment { ["build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"],
R3_PUSH = "${env.BRANCH_NAME == 'master' ? '1' : ' '}" ])
} }
steps {
phone_steps("tici-needs-can", [ if (!env.BRANCH_NAME.matches(excludeRegex)) {
parallel (
// tici tests
'onroad tests': {
deviceStage("onroad", "tici-needs-can", ["SKIP_COPY=1"], [
["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR ./build_devel.sh"], ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR ./build_devel.sh"],
["build openpilot", "cd selfdrive/manager && ./build.py"], ["build openpilot", "cd selfdrive/manager && ./build.py"],
["check dirty", "release/check-dirty.sh"], ["check dirty", "release/check-dirty.sh"],
["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"], ["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"],
["time to onroad", "cd selfdrive/test/ && pytest test_time_to_onroad.py"], ["time to onroad", "cd selfdrive/test/ && pytest test_time_to_onroad.py"],
]) ])
} },
} 'HW + Unit Tests': {
deviceStage("tici", "tici-common", ["UNSAFE=1"], [
stage('loopback-tests') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici-loopback", [
["build openpilot", "cd selfdrive/manager && ./build.py"],
["test boardd loopback", "pytest selfdrive/boardd/tests/test_boardd_loopback.py"],
])
}
}
stage('HW + Unit Tests') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici-common", [
["build", "cd selfdrive/manager && ./build.py"], ["build", "cd selfdrive/manager && ./build.py"],
["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"],
["test power draw", "pytest system/hardware/tici/tests/test_power_draw.py"], ["test power draw", "./system/hardware/tici/tests/test_power_draw.py"],
["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"], ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"],
["test pigeond", "pytest system/sensord/tests/test_pigeond.py"], ["test pigeond", "pytest system/sensord/tests/test_pigeond.py"],
["test manager", "pytest selfdrive/manager/test/test_manager.py"], ["test manager", "pytest selfdrive/manager/test/test_manager.py"],
["test nav", "pytest selfdrive/navd/tests/"], ["test nav", "pytest selfdrive/navd/tests/"],
]) ])
} },
} 'loopback': {
deviceStage("tici", "tici-loopback", ["UNSAFE=1"], [
stage('camerad') { ["build openpilot", "cd selfdrive/manager && ./build.py"],
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } ["test boardd loopback", "pytest selfdrive/boardd/tests/test_boardd_loopback.py"],
steps { ])
phone_steps("tici-ar0231", [ },
'camerad': {
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
["build", "cd selfdrive/manager && ./build.py"], ["build", "cd selfdrive/manager && ./build.py"],
["test camerad", "pytest system/camerad/test/test_camerad.py"], ["test camerad", "pytest system/camerad/test/test_camerad.py"],
["test exposure", "pytest system/camerad/test/test_exposure.py"], ["test exposure", "pytest system/camerad/test/test_exposure.py"],
]) ])
phone_steps("tici-ox03c10", [ deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
["build", "cd selfdrive/manager && ./build.py"], ["build", "cd selfdrive/manager && ./build.py"],
["test camerad", "pytest system/camerad/test/test_camerad.py"], ["test camerad", "pytest system/camerad/test/test_camerad.py"],
["test exposure", "pytest system/camerad/test/test_exposure.py"], ["test exposure", "pytest system/camerad/test/test_exposure.py"],
]) ])
} },
} 'sensord': {
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
stage('sensord') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici-lsmc", [
["build", "cd selfdrive/manager && ./build.py"], ["build", "cd selfdrive/manager && ./build.py"],
["test sensord", "cd system/sensord/tests && pytest test_sensord.py"], ["test sensord", "cd system/sensord/tests && pytest test_sensord.py"],
]) ])
phone_steps("tici-bmx-lsm", [ deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
["build", "cd selfdrive/manager && ./build.py"], ["build", "cd selfdrive/manager && ./build.py"],
["test sensord", "cd system/sensord/tests && pytest test_sensord.py"], ["test sensord", "cd system/sensord/tests && pytest test_sensord.py"],
]) ])
} },
} 'replay': {
deviceStage("tici", "tici-replay", ["UNSAFE=1"], [
stage('replay') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici-replay", [
["build", "cd selfdrive/manager && ./build.py"], ["build", "cd selfdrive/manager && ./build.py"],
["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"],
]) ])
},
'tizi': {
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
["build openpilot", "cd selfdrive/manager && ./build.py"],
["test boardd loopback", "SINGLE_PANDA=1 pytest selfdrive/boardd/tests/test_boardd_loopback.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"],
["test rawgpsd", "pytest system/sensord/rawgps/test_rawgps.py"],
])
},
// *** PC tests ***
'PC tests': {
pcStage("PC tests") {
// tests that our build system's dependencies are configured properly,
// needs a machine with lots of cores
sh label: "test multi-threaded build", script: "scons --no-cache --random -j42"
}
},
'car tests': {
pcStage("car tests") {
sh "scons -j30"
sh label: "test_models.py", script: "INTERNAL_SEG_CNT=250 INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt FILEREADER_CACHE=1 \
pytest -n42 --dist=loadscope selfdrive/car/tests/test_models.py"
sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest -n42 selfdrive/car/tests/test_car_interfaces.py"
} }
} },
)
} }
} } catch (Exception e) {
currentBuild.result = 'FAILED'
throw e
} }
} }

@ -39,13 +39,17 @@ Running on a dedicated device in a car
------ ------
To use openpilot in a car, you need four things To use openpilot in a car, you need four things
* A supported device to run this software: a [comma 3X](https://comma.ai/shop/comma-3x) or comma three. 1. **Supported Device:** A comma 3/3X. You can purchase these devices from (https://comma.ai/shop/comma-3x)
* This software. The setup procedure of the comma 3/3X allows the user to enter a URL for custom software.
The URL, openpilot.comma.ai will install the release version of openpilot. To install openpilot master, you can use installer.comma.ai/commaai/master, and replacing commaai with another GitHub username can install a fork.
* One of [the 250+ supported cars](docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run openpilot.
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car.
We have detailed instructions for [how to mount the device in a car](https://comma.ai/setup). 2. **Software:** The setup procedure for the comma 3/3X allows users to enter a URL for custom software.
To install the release version of openpilot, use the URL `openpilot.comma.ai`.
To install openpilot master (for more advanced users), use the URL `installer.comma.ai/commaai/master`. You can replace "commaai" with another GitHub username to install a fork.
3. **Supported Car:** Ensure that you have one of [the 250+ supported cars](docs/CARS.md). openpilot supports a wide range of car makes including Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and many more.
If your car is not officially listed as supported but has adaptive cruise control and lane-keeping assist, it's likely capable of running openpilot.
4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3/3X to your car.
We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup).
Running on PC Running on PC
------ ------

@ -1,5 +1,12 @@
Version 0.9.5 (202X-XX-XX) Version 0.9.5 (2023-09-27)
======================== ========================
* New driving model
* Improved navigate on openpilot performance using navigation instructions as an additional model input
* Hyundai Azera 2022 support thanks to sunnyhaibin!
* Hyundai Ioniq 6 2023 support thanks to sunnyhaibin, alamo3, and sshane!
* Hyundai Kona Electric 2023 (Korean version) support thanks to sunnyhaibin and haram-KONA!
* Kia K8 Hybrid (with HDA II) 2023 support thanks to sunnyhaibin!
* Kia Sorento Hybrid 2023 support thanks to sunnyhaibin!
* Lexus IS 2023 support thanks to L3R5! * Lexus IS 2023 support thanks to L3R5!
Version 0.9.4 (2023-07-27) Version 0.9.4 (2023-07-27)

@ -1,5 +1,4 @@
import os import os
import shutil
import subprocess import subprocess
import sys import sys
import sysconfig import sysconfig
@ -15,10 +14,6 @@ AGNOS = TICI
Decider('MD5-timestamp') Decider('MD5-timestamp')
AddOption('--extras',
action='store_true',
help='build misc extras, like setup and installer files')
AddOption('--kaitai', AddOption('--kaitai',
action='store_true', action='store_true',
help='Regenerate kaitai struct parsers') help='Regenerate kaitai struct parsers')
@ -49,21 +44,16 @@ AddOption('--external-sconscript',
dest='external_sconscript', dest='external_sconscript',
help='add an external SConscript to the build') help='add an external SConscript to the build')
AddOption('--no-thneed',
action='store_true',
dest='no_thneed',
help='avoid using thneed')
AddOption('--pc-thneed', AddOption('--pc-thneed',
action='store_true', action='store_true',
dest='pc_thneed', dest='pc_thneed',
help='use thneed on pc') help='use thneed on pc')
AddOption('--no-test', AddOption('--minimal',
action='store_false', action='store_false',
dest='test', dest='extras',
default=os.path.islink(Dir('#laika/').abspath), default=os.path.islink(Dir('#laika/').abspath),
help='skip building test files') help='the minimum build to run openpilot. no tests, tools, etc.')
## Architecture name breakdown (arch) ## Architecture name breakdown (arch)
## - larch64: linux tici aarch64 ## - larch64: linux tici aarch64
@ -254,18 +244,6 @@ def progress_function(node):
if os.environ.get('SCONS_PROGRESS'): if os.environ.get('SCONS_PROGRESS'):
Progress(progress_function, interval=node_interval) Progress(progress_function, interval=node_interval)
SHARED = False
# TODO: this can probably be removed
def abspath(x):
if arch == 'aarch64':
pth = os.path.join("/data/pythonpath", x[0].path)
env.Depends(pth, x)
return File(pth)
else:
# rpath works elsewhere
return x[0].path.rsplit("/", 1)[1][:-3]
# Cython build environment # Cython build environment
py_include = sysconfig.get_paths()['include'] py_include = sysconfig.get_paths()['include']
envCython = env.Clone() envCython = env.Clone()
@ -336,18 +314,6 @@ qt_env['CXXFLAGS'] += qt_flags
qt_env['LIBPATH'] += ['#selfdrive/ui'] qt_env['LIBPATH'] += ['#selfdrive/ui']
qt_env['LIBS'] = qt_libs qt_env['LIBS'] = qt_libs
# Have to respect cache-readonly
if GetOption('cache_readonly'):
local_moc_files_dir = Dir("#.moc_files").abspath
cache_moc_files_dir = cache_dir + "/moc_files"
if os.path.exists(local_moc_files_dir):
shutil.rmtree(local_moc_files_dir)
if os.path.exists(cache_moc_files_dir):
shutil.copytree(cache_moc_files_dir, local_moc_files_dir)
qt_env['QT3_MOCHPREFIX'] = local_moc_files_dir + "/moc_"
else:
qt_env['QT3_MOCHPREFIX'] = cache_dir + '/moc_files/moc_'
if GetOption("clazy"): if GetOption("clazy"):
checks = [ checks = [
"level0", "level0",
@ -359,33 +325,35 @@ if GetOption("clazy"):
qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0] qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0]
qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks) qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks)
Export('env', 'qt_env', 'arch', 'real_arch', 'SHARED') Export('env', 'qt_env', 'arch', 'real_arch')
# Build common module
SConscript(['common/SConscript']) SConscript(['common/SConscript'])
Import('_common', '_gpucommon') Import('_common', '_gpucommon')
if SHARED: common = [_common, 'json11']
common, gpucommon = abspath(common), abspath(gpucommon) gpucommon = [_gpucommon]
else:
common = [_common, 'json11']
gpucommon = [_gpucommon]
Export('common', 'gpucommon') Export('common', 'gpucommon')
# cereal and messaging are shared with the system # Build cereal and messaging
SConscript(['cereal/SConscript']) SConscript(['cereal/SConscript'])
if SHARED:
cereal = abspath([File('cereal/libcereal_shared.so')])
messaging = abspath([File('cereal/libmessaging_shared.so')])
else:
cereal = [File('#cereal/libcereal.a')]
messaging = [File('#cereal/libmessaging.a')]
visionipc = [File('#cereal/libvisionipc.a')]
Export('cereal', 'messaging', 'visionipc') cereal = [File('#cereal/libcereal.a')]
messaging = [File('#cereal/libmessaging.a')]
visionipc = [File('#cereal/libvisionipc.a')]
messaging_python = [File('#cereal/messaging/messaging_pyx.so')]
# Build rednose library and ekf models Export('cereal', 'messaging', 'messaging_python', 'visionipc')
# Build other submodules
SConscript([
'body/board/SConscript',
'opendbc/can/SConscript',
'panda/SConscript',
])
# Build rednose library and ekf models
rednose_deps = [ rednose_deps = [
"#selfdrive/locationd/models/constants.py", "#selfdrive/locationd/models/constants.py",
"#selfdrive/locationd/models/gnss_helpers.py", "#selfdrive/locationd/models/gnss_helpers.py",
@ -427,20 +395,8 @@ if arch != "Darwin":
]) ])
# Build openpilot # Build openpilot
# build submodules
SConscript([
'body/board/SConscript',
'cereal/SConscript',
'opendbc/can/SConscript',
'panda/SConscript',
])
SConscript(['third_party/SConscript']) SConscript(['third_party/SConscript'])
SConscript(['common/kalman/SConscript'])
SConscript(['common/transformations/SConscript'])
SConscript(['selfdrive/boardd/SConscript']) SConscript(['selfdrive/boardd/SConscript'])
SConscript(['selfdrive/controls/lib/lateral_mpc_lib/SConscript']) SConscript(['selfdrive/controls/lib/lateral_mpc_lib/SConscript'])
SConscript(['selfdrive/controls/lib/longitudinal_mpc_lib/SConscript']) SConscript(['selfdrive/controls/lib/longitudinal_mpc_lib/SConscript'])
@ -449,7 +405,7 @@ SConscript(['selfdrive/navd/SConscript'])
SConscript(['selfdrive/modeld/SConscript']) SConscript(['selfdrive/modeld/SConscript'])
SConscript(['selfdrive/ui/SConscript']) SConscript(['selfdrive/ui/SConscript'])
if (arch in ['x86_64', 'aarch64', 'Darwin'] and Dir('#tools/cabana/').exists()) or GetOption('extras'): if arch in ['x86_64', 'aarch64', 'Darwin'] and Dir('#tools/cabana/').exists() and GetOption('extras'):
SConscript(['tools/replay/SConscript']) SConscript(['tools/replay/SConscript'])
SConscript(['tools/cabana/SConscript']) SConscript(['tools/cabana/SConscript'])

@ -1 +1 @@
Subproject commit 396fa7b923099640d6843f338950ed41300793ec Subproject commit 6ff44357a3e416d29044b1d085a3e9223db9691a

@ -1 +1 @@
Subproject commit 1ee48e0110a46fbdd9db50ed89a38bb5a748cfcb Subproject commit 4b334f6f10877e4a666b23983de2d27934ebf3b1

@ -1,9 +1,4 @@
Import('env', 'envCython', 'arch', 'SHARED') Import('env', 'envCython', 'arch')
if SHARED:
fxn = env.SharedLibrary
else:
fxn = env.Library
common_libs = [ common_libs = [
'params.cc', 'params.cc',
@ -12,24 +7,35 @@ common_libs = [
'util.cc', 'util.cc',
'i2c.cc', 'i2c.cc',
'watchdog.cc', 'watchdog.cc',
'ratekeeper.cc'
] ]
if arch != "Darwin": if arch != "Darwin":
common_libs.append('gpio.cc') common_libs.append('gpio.cc')
_common = fxn('common', common_libs, LIBS="json11") _common = env.Library('common', common_libs, LIBS="json11")
files = [ files = [
'clutil.cc', 'clutil.cc',
] ]
_gpucommon = fxn('gpucommon', files) _gpucommon = env.Library('gpucommon', files)
Export('_common', '_gpucommon') Export('_common', '_gpucommon')
if GetOption('test'): if GetOption('extras'):
env.Program('tests/test_util', ['tests/test_util.cc'], LIBS=[_common]) env.Program('tests/test_common',
env.Program('tests/test_swaglog', ['tests/test_swaglog.cc'], LIBS=[_common, 'json11', 'zmq', 'pthread']) ['tests/test_runner.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc', 'tests/test_ratekeeper.cc'],
LIBS=[_common, 'json11', 'zmq', 'pthread'])
# Cython bindings
params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11'])
SConscript([
'kalman/SConscript',
'transformations/SConscript'
])
Import('simple_kalman_python', 'transformations_python')
common_python = [params_python, simple_kalman_python, transformations_python]
# Cython Export('common_python')
envCython.Program('clock.so', 'clock.pyx')
envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11'])

@ -2,8 +2,8 @@ import jwt
import os import os
import requests import requests
from datetime import datetime, timedelta from datetime import datetime, timedelta
from common.basedir import PERSIST from openpilot.common.basedir import PERSIST
from system.version import get_version from openpilot.system.version import get_version
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')

@ -1,7 +1,7 @@
import os import os
from pathlib import Path from pathlib import Path
from system.hardware import PC from openpilot.system.hardware import PC
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../"))

@ -1,24 +0,0 @@
# distutils: language = c++
# cython: language_level = 3
from posix.time cimport clock_gettime, timespec, CLOCK_MONOTONIC_RAW, clockid_t
IF UNAME_SYSNAME == "Darwin":
# Darwin doesn't have a CLOCK_BOOTTIME
CLOCK_BOOTTIME = CLOCK_MONOTONIC_RAW
ELSE:
from posix.time cimport CLOCK_BOOTTIME
cdef double readclock(clockid_t clock_id):
cdef timespec ts
cdef double current
clock_gettime(clock_id, &ts)
current = ts.tv_sec + (ts.tv_nsec / 1000000000.)
return current
def monotonic_time():
return readclock(CLOCK_MONOTONIC_RAW)
def sec_since_boot():
return readclock(CLOCK_BOOTTIME)

@ -75,6 +75,10 @@ cl_device_id cl_get_device_id(cl_device_type device_type) {
return nullptr; return nullptr;
} }
cl_context cl_create_context(cl_device_id device_id) {
return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err));
}
cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) {
return cl_program_from_source(ctx, device_id, util::read_file(path), args); return cl_program_from_source(ctx, device_id, util::read_file(path), args);
} }

@ -22,6 +22,7 @@
}) })
cl_device_id cl_get_device_id(cl_device_type device_type); cl_device_id cl_get_device_id(cl_device_type device_type);
cl_context cl_create_context(cl_device_id device_id);
cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr);
cl_program cl_program_from_binary(cl_context ctx, cl_device_id device_id, const uint8_t* binary, size_t length, const char* args = nullptr); cl_program cl_program_from_binary(cl_context ctx, cl_device_id device_id, const uint8_t* binary, size_t length, const char* args = nullptr);
cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args);

@ -1,5 +1,7 @@
#include "common/gpio.h" #include "common/gpio.h"
#include <string>
#ifdef __APPLE__ #ifdef __APPLE__
int gpio_init(int pin_nr, bool output) { int gpio_init(int pin_nr, bool output) {
return 0; return 0;
@ -29,7 +31,7 @@ int gpio_init(int pin_nr, bool output) {
char pin_dir_path[50]; char pin_dir_path[50];
int pin_dir_path_len = snprintf(pin_dir_path, sizeof(pin_dir_path), int pin_dir_path_len = snprintf(pin_dir_path, sizeof(pin_dir_path),
"/sys/class/gpio/gpio%d/direction", pin_nr); "/sys/class/gpio/gpio%d/direction", pin_nr);
if(pin_dir_path_len <= 0) { if (pin_dir_path_len <= 0) {
return -1; return -1;
} }
const char *value = output ? "out" : "in"; const char *value = output ? "out" : "in";
@ -40,7 +42,7 @@ int gpio_set(int pin_nr, bool high) {
char pin_val_path[50]; char pin_val_path[50];
int pin_val_path_len = snprintf(pin_val_path, sizeof(pin_val_path), int pin_val_path_len = snprintf(pin_val_path, sizeof(pin_val_path),
"/sys/class/gpio/gpio%d/value", pin_nr); "/sys/class/gpio/gpio%d/value", pin_nr);
if(pin_val_path_len <= 0) { if (pin_val_path_len <= 0) {
return -1; return -1;
} }
return util::write_file(pin_val_path, (void*)(high ? "1" : "0"), 1); return util::write_file(pin_val_path, (void*)(high ? "1" : "0"), 1);
@ -68,7 +70,7 @@ int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int p
rq.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES; rq.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES;
strncpy(rq.consumer_label, consumer_label, std::size(rq.consumer_label) - 1); strncpy(rq.consumer_label, consumer_label, std::size(rq.consumer_label) - 1);
int ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &rq); int ret = util::safe_ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &rq);
if (ret == -1) { if (ret == -1) {
LOGE("Unable to get line event from ioctl : %s", strerror(errno)); LOGE("Unable to get line event from ioctl : %s", strerror(errno));
close(fd); close(fd);

@ -5,7 +5,7 @@
#define GPIO_HUB_RST_N 30 #define GPIO_HUB_RST_N 30
#define GPIO_UBLOX_RST_N 32 #define GPIO_UBLOX_RST_N 32
#define GPIO_UBLOX_SAFEBOOT_N 33 #define GPIO_UBLOX_SAFEBOOT_N 33
#define GPIO_UBLOX_PWR_EN 34 #define GPIO_GNSS_PWR_EN 34 /* SCHEMATIC LABEL: GPIO_UBLOX_PWR_EN */
#define GPIO_STM_RST_N 124 #define GPIO_STM_RST_N 124
#define GPIO_STM_BOOT0 134 #define GPIO_STM_BOOT0 134
#define GPIO_BMX_ACCEL_INT 21 #define GPIO_BMX_ACCEL_INT 21
@ -17,7 +17,7 @@
#define GPIO_HUB_RST_N 0 #define GPIO_HUB_RST_N 0
#define GPIO_UBLOX_RST_N 0 #define GPIO_UBLOX_RST_N 0
#define GPIO_UBLOX_SAFEBOOT_N 0 #define GPIO_UBLOX_SAFEBOOT_N 0
#define GPIO_UBLOX_PWR_EN 0 #define GPIO_GNSS_PWR_EN 0 /* SCHEMATIC LABEL: GPIO_UBLOX_PWR_EN */
#define GPIO_STM_RST_N 0 #define GPIO_STM_RST_N 0
#define GPIO_STM_BOOT0 0 #define GPIO_STM_BOOT0 0
#define GPIO_BMX_ACCEL_INT 0 #define GPIO_BMX_ACCEL_INT 0

@ -8,7 +8,6 @@
#include <cstdio> #include <cstdio>
#include <stdexcept> #include <stdexcept>
#include "common/util.h"
#include "common/swaglog.h" #include "common/swaglog.h"
#include "common/util.h" #include "common/util.h"
@ -26,36 +25,42 @@ I2CBus::I2CBus(uint8_t bus_id) {
snprintf(bus_name, 20, "/dev/i2c-%d", bus_id); snprintf(bus_name, 20, "/dev/i2c-%d", bus_id);
i2c_fd = HANDLE_EINTR(open(bus_name, O_RDWR)); i2c_fd = HANDLE_EINTR(open(bus_name, O_RDWR));
if(i2c_fd < 0) { if (i2c_fd < 0) {
throw std::runtime_error("Failed to open I2C bus"); throw std::runtime_error("Failed to open I2C bus");
} }
} }
I2CBus::~I2CBus() { I2CBus::~I2CBus() {
if(i2c_fd >= 0) { close(i2c_fd); } if (i2c_fd >= 0) {
close(i2c_fd);
}
} }
int I2CBus::read_register(uint8_t device_address, uint register_address, uint8_t *buffer, uint8_t len) { int I2CBus::read_register(uint8_t device_address, uint register_address, uint8_t *buffer, uint8_t len) {
std::lock_guard lk(m);
int ret = 0; int ret = 0;
ret = HANDLE_EINTR(ioctl(i2c_fd, I2C_SLAVE, device_address)); ret = HANDLE_EINTR(ioctl(i2c_fd, I2C_SLAVE, device_address));
if(ret < 0) { goto fail; } if (ret < 0) { goto fail; }
ret = i2c_smbus_read_i2c_block_data(i2c_fd, register_address, len, buffer); ret = i2c_smbus_read_i2c_block_data(i2c_fd, register_address, len, buffer);
if((ret < 0) || (ret != len)) { goto fail; } if ((ret < 0) || (ret != len)) { goto fail; }
fail: fail:
return ret; return ret;
} }
int I2CBus::set_register(uint8_t device_address, uint register_address, uint8_t data) { int I2CBus::set_register(uint8_t device_address, uint register_address, uint8_t data) {
std::lock_guard lk(m);
int ret = 0; int ret = 0;
ret = HANDLE_EINTR(ioctl(i2c_fd, I2C_SLAVE, device_address)); ret = HANDLE_EINTR(ioctl(i2c_fd, I2C_SLAVE, device_address));
if(ret < 0) { goto fail; } if (ret < 0) { goto fail; }
ret = i2c_smbus_write_byte_data(i2c_fd, register_address, data); ret = i2c_smbus_write_byte_data(i2c_fd, register_address, data);
if(ret < 0) { goto fail; } if (ret < 0) { goto fail; }
fail: fail:
return ret; return ret;

@ -1,12 +1,14 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <mutex>
#include <sys/types.h> #include <sys/types.h>
class I2CBus { class I2CBus {
private: private:
int i2c_fd; int i2c_fd;
std::mutex m;
public: public:
I2CBus(uint8_t bus_id); I2CBus(uint8_t bus_id);

@ -1,3 +1,5 @@
Import('envCython') Import('envCython')
envCython.Program('simple_kalman_impl.so', 'simple_kalman_impl.pyx') simple_kalman_python = envCython.Program('simple_kalman_impl.so', 'simple_kalman_impl.pyx')
Export('simple_kalman_python')

@ -1,3 +1,12 @@
# pylint: skip-file from openpilot.common.kalman.simple_kalman_impl import KF1D as KF1D
from common.kalman.simple_kalman_impl import KF1D as KF1D
assert KF1D assert KF1D
import numpy as np
def get_kalman_gain(dt, A, C, Q, R, iterations=100):
P = np.zeros_like(Q)
for _ in range(iterations):
P = A.dot(P).dot(A.T) + dt * Q
S = C.dot(P).dot(C.T) + R
K = P.dot(C.T).dot(np.linalg.inv(S))
P = (np.eye(len(P)) - K.dot(C)).dot(P)
return K

@ -3,8 +3,8 @@ import random
import timeit import timeit
import numpy as np import numpy as np
from common.kalman.simple_kalman import KF1D from openpilot.common.kalman.simple_kalman import KF1D
from common.kalman.simple_kalman_old import KF1D as KF1D_old from openpilot.common.kalman.simple_kalman_old import KF1D as KF1D_old
class TestSimpleKalman(unittest.TestCase): class TestSimpleKalman(unittest.TestCase):
@ -54,8 +54,8 @@ class TestSimpleKalman(unittest.TestCase):
setup = """ setup = """
import numpy as np import numpy as np
from common.kalman.simple_kalman import KF1D from openpilot.common.kalman.simple_kalman import KF1D
from common.kalman.simple_kalman_old import KF1D as KF1D_old from openpilot.common.kalman.simple_kalman_old import KF1D as KF1D_old
dt = 0.01 dt = 0.01
x0_0 = 0.0 x0_0 = 0.0

@ -197,7 +197,7 @@ class SwagLogger(logging.Logger):
filename = os.path.normcase(co.co_filename) filename = os.path.normcase(co.co_filename)
# TODO: is this pylint exception correct? # TODO: is this pylint exception correct?
if filename == _srcfile: # pylint: disable=comparison-with-callable if filename == _srcfile:
f = f.f_back f = f.f_back
continue continue
sinfo = None sinfo = None

@ -64,7 +64,7 @@ bool create_params_path(const std::string &param_path, const std::string &key_pa
std::string ensure_params_path(const std::string &prefix, const std::string &path = {}) { std::string ensure_params_path(const std::string &prefix, const std::string &path = {}) {
std::string params_path = path.empty() ? Path::params() : path; std::string params_path = path.empty() ? Path::params() : path;
if (!create_params_path(params_path, params_path + prefix)) { if (!create_params_path(params_path, params_path + prefix)) {
throw std::runtime_error(util::string_format("Failed to ensure params path, errno=%d", errno)); throw std::runtime_error(util::string_format("Failed to ensure params path, errno=%d, path=%s", errno, params_path.c_str()));
} }
return params_path; return params_path;
} }
@ -158,6 +158,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"LongitudinalPersonality", PERSISTENT}, {"LongitudinalPersonality", PERSISTENT},
{"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"NavDestinationWaypoints", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"NavDestinationWaypoints", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"NavPastDestinations", PERSISTENT},
{"NavSettingLeftSide", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT},
{"NavSettingTime24h", PERSISTENT}, {"NavSettingTime24h", PERSISTENT},
{"NavdRender", PERSISTENT}, {"NavdRender", PERSISTENT},

@ -15,7 +15,11 @@ enum ParamKeyType {
class Params { class Params {
public: public:
Params(const std::string &path = {}); explicit Params(const std::string &path = {});
// Not copyable.
Params(const Params&) = delete;
Params& operator=(const Params&) = delete;
std::vector<std::string> allKeys() const; std::vector<std::string> allKeys() const;
bool checkKey(const std::string &key); bool checkKey(const std::string &key);
ParamKeyType getKeyType(const std::string &key); ParamKeyType getKeyType(const std::string &key);

@ -1,4 +1,5 @@
from common.params_pyx import Params, ParamKeyType, UnknownKeyName, put_nonblocking, put_bool_nonblocking # pylint: disable=no-name-in-module, import-error from openpilot.common.params_pyx import Params, ParamKeyType, UnknownKeyName, put_nonblocking, \
put_bool_nonblocking
assert Params assert Params
assert ParamKeyType assert ParamKeyType
assert UnknownKeyName assert UnknownKeyName

@ -14,7 +14,7 @@ cdef extern from "common/params.h":
ALL ALL
cdef cppclass c_Params "Params": cdef cppclass c_Params "Params":
c_Params(string) nogil c_Params(string) nogil except +
string get(string, bool) nogil string get(string, bool) nogil
bool getBool(string, bool) nogil bool getBool(string, bool) nogil
int remove(string) nogil int remove(string) nogil

@ -2,13 +2,14 @@ import os
import shutil import shutil
import uuid import uuid
from typing import List, Optional from typing import Optional
from common.params import Params from openpilot.common.params import Params
from openpilot.system.hardware.hw import Paths
class OpenpilotPrefix(object): class OpenpilotPrefix:
def __init__(self, prefix: Optional[str] = None, clean_dirs_on_exit: bool = True): def __init__(self, prefix: Optional[str] = None, clean_dirs_on_exit: bool = True):
self.prefix = prefix if prefix else str(uuid.uuid4()) self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15])
self.msgq_path = os.path.join('/dev/shm', self.prefix) self.msgq_path = os.path.join('/dev/shm', self.prefix)
self.clean_dirs_on_exit = clean_dirs_on_exit self.clean_dirs_on_exit = clean_dirs_on_exit
@ -18,13 +19,17 @@ class OpenpilotPrefix(object):
os.mkdir(self.msgq_path) os.mkdir(self.msgq_path)
except FileExistsError: except FileExistsError:
pass pass
os.makedirs(Paths.log_root(), exist_ok=True)
return self return self
def __exit__(self, exc_type, exc_obj, exc_tb): def __exit__(self, exc_type, exc_obj, exc_tb):
if self.clean_dirs_on_exit: if self.clean_dirs_on_exit:
self.clean_dirs() self.clean_dirs()
try:
del os.environ['OPENPILOT_PREFIX'] del os.environ['OPENPILOT_PREFIX']
except KeyError:
pass
return False return False
def clean_dirs(self): def clean_dirs(self):
@ -33,17 +38,6 @@ class OpenpilotPrefix(object):
shutil.rmtree(os.path.realpath(symlink_path), ignore_errors=True) shutil.rmtree(os.path.realpath(symlink_path), ignore_errors=True)
os.remove(symlink_path) os.remove(symlink_path)
shutil.rmtree(self.msgq_path, ignore_errors=True) shutil.rmtree(self.msgq_path, ignore_errors=True)
shutil.rmtree(Paths.log_root(), ignore_errors=True)
shutil.rmtree(Paths.download_cache_root(), ignore_errors=True)
class DummySocket: shutil.rmtree(Paths.comma_home(), ignore_errors=True)
def __init__(self):
self.data: List[bytes] = []
def receive(self, non_blocking: bool = False) -> Optional[bytes]:
if non_blocking:
return None
return self.data.pop()
def send(self, data: bytes):
self.data.append(data)

@ -0,0 +1,40 @@
#include "common/ratekeeper.h"
#include <algorithm>
#include "common/swaglog.h"
#include "common/timing.h"
#include "common/util.h"
RateKeeper::RateKeeper(const std::string &name, float rate, float print_delay_threshold)
: name(name),
print_delay_threshold(std::max(0.f, print_delay_threshold)) {
interval = 1 / rate;
last_monitor_time = seconds_since_boot();
next_frame_time = last_monitor_time + interval;
}
bool RateKeeper::keepTime() {
bool lagged = monitorTime();
if (remaining_ > 0) {
util::sleep_for(remaining_ * 1000);
}
return lagged;
}
bool RateKeeper::monitorTime() {
++frame_;
last_monitor_time = seconds_since_boot();
remaining_ = next_frame_time - last_monitor_time;
bool lagged = remaining_ < 0;
if (lagged) {
if (print_delay_threshold > 0 && remaining_ < -print_delay_threshold) {
LOGW("%s lagging by %.2f ms", name.c_str(), -remaining_ * 1000);
}
next_frame_time = last_monitor_time + interval;
} else {
next_frame_time += interval;
}
return lagged;
}

@ -0,0 +1,23 @@
#pragma once
#include <cstdint>
#include <string>
class RateKeeper {
public:
RateKeeper(const std::string &name, float rate, float print_delay_threshold = 0);
~RateKeeper() {}
bool keepTime();
bool monitorTime();
inline double frame() const { return frame_; }
inline double remaining() const { return remaining_; }
private:
double interval;
double next_frame_time;
double last_monitor_time;
double remaining_ = 0;
float print_delay_threshold = 0;
uint64_t frame_ = 0;
std::string name;
};

@ -5,10 +5,9 @@ import time
from collections import deque from collections import deque
from typing import Optional, List, Union from typing import Optional, List, Union
from setproctitle import getproctitle # pylint: disable=no-name-in-module from setproctitle import getproctitle
from common.clock import sec_since_boot # pylint: disable=no-name-in-module, import-error from openpilot.system.hardware import PC
from system.hardware import PC
# time step for each process # time step for each process
@ -31,12 +30,12 @@ class Priority:
def set_realtime_priority(level: int) -> None: def set_realtime_priority(level: int) -> None:
if not PC: if not PC:
os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level)) # pylint: disable=no-member os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level))
def set_core_affinity(cores: List[int]) -> None: def set_core_affinity(cores: List[int]) -> None:
if not PC: if not PC:
os.sched_setaffinity(0, cores) # pylint: disable=no-member os.sched_setaffinity(0, cores)
def config_realtime_process(cores: Union[int, List[int]], priority: int) -> None: def config_realtime_process(cores: Union[int, List[int]], priority: int) -> None:
@ -50,13 +49,13 @@ class Ratekeeper:
def __init__(self, rate: float, print_delay_threshold: Optional[float] = 0.0) -> None: def __init__(self, rate: float, print_delay_threshold: Optional[float] = 0.0) -> None:
"""Rate in Hz for ratekeeping. print_delay_threshold must be nonnegative.""" """Rate in Hz for ratekeeping. print_delay_threshold must be nonnegative."""
self._interval = 1. / rate self._interval = 1. / rate
self._next_frame_time = sec_since_boot() + self._interval self._next_frame_time = time.monotonic() + self._interval
self._print_delay_threshold = print_delay_threshold self._print_delay_threshold = print_delay_threshold
self._frame = 0 self._frame = 0
self._remaining = 0.0 self._remaining = 0.0
self._process_name = getproctitle() self._process_name = getproctitle()
self._dts = deque([self._interval], maxlen=100) self._dts = deque([self._interval], maxlen=100)
self._last_monitor_time = sec_since_boot() self._last_monitor_time = time.monotonic()
@property @property
def frame(self) -> int: def frame(self) -> int:
@ -82,11 +81,11 @@ class Ratekeeper:
# this only monitor the cumulative lag, but does not enforce a rate # this only monitor the cumulative lag, but does not enforce a rate
def monitor_time(self) -> bool: def monitor_time(self) -> bool:
prev = self._last_monitor_time prev = self._last_monitor_time
self._last_monitor_time = sec_since_boot() self._last_monitor_time = time.monotonic()
self._dts.append(self._last_monitor_time - prev) self._dts.append(self._last_monitor_time - prev)
lagged = False lagged = False
remaining = self._next_frame_time - sec_since_boot() remaining = self._next_frame_time - time.monotonic()
self._next_frame_time += self._interval self._next_frame_time += self._interval
if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold: if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold:
print(f"{self._process_name} lagging by {-remaining * 1000:.2f} ms") print(f"{self._process_name} lagging by {-remaining * 1000:.2f} ms")

@ -1,6 +1,6 @@
import os import os
import subprocess import subprocess
from common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
class Spinner(): class Spinner():

@ -12,7 +12,7 @@
#include <string> #include <string>
#include <zmq.h> #include <zmq.h>
#include "json11.hpp" #include "third_party/json11/json11.hpp"
#include "common/util.h" #include "common/util.h"
#include "common/version.h" #include "common/version.h"
@ -20,7 +20,7 @@
class SwaglogState : public LogState { class SwaglogState : public LogState {
public: public:
SwaglogState() : LogState("ipc:///tmp/logmessage") {} SwaglogState() : LogState(Path::swaglog_ipc().c_str()) {}
json11::Json::object ctx_j; json11::Json::object ctx_j;
@ -64,8 +64,7 @@ static void log(int levelnum, const char* filename, int lineno, const char* func
if (levelnum >= s.print_level) { if (levelnum >= s.print_level) {
printf("%s: %s\n", filename, msg); printf("%s: %s\n", filename, msg);
} }
char levelnum_c = levelnum; zmq_send(s.sock, log_s.data(), log_s.length(), ZMQ_NOBLOCK);
zmq_send(s.sock, (levelnum_c + log_s).c_str(), log_s.length() + 1, ZMQ_NOBLOCK);
} }
static void cloudlog_common(int levelnum, const char* filename, int lineno, const char* func, static void cloudlog_common(int levelnum, const char* filename, int lineno, const char* func,
@ -87,8 +86,11 @@ static void cloudlog_common(int levelnum, const char* filename, int lineno, cons
log_j["msg"] = msg_j; log_j["msg"] = msg_j;
} }
std::string log_s = ((json11::Json)log_j).dump(); std::string log_s;
log_s += (char)levelnum;
((json11::Json)log_j).dump(log_s);
log(levelnum, filename, lineno, func, msg_buf, log_s); log(levelnum, filename, lineno, func, msg_buf, log_s);
free(msg_buf); free(msg_buf);
} }

@ -44,7 +44,7 @@ void cloudlog_te(int levelnum, const char* filename, int lineno, const char* fun
int __millis = (millis); \ int __millis = (millis); \
uint64_t __ts = nanos_since_boot(); \ uint64_t __ts = nanos_since_boot(); \
\ \
if (!__begin) __begin = __ts; \ if (!__begin) { __begin = __ts; } \
\ \
if (__begin + __millis*1000000ULL < __ts) { \ if (__begin + __millis*1000000ULL < __ts) { \
if (__missed) { \ if (__missed) { \

@ -1,2 +1 @@
test_util test_common
test_swaglog

@ -2,8 +2,8 @@ import os
import unittest import unittest
from uuid import uuid4 from uuid import uuid4
from common.file_helpers import atomic_write_on_fs_tmp from openpilot.common.file_helpers import atomic_write_on_fs_tmp
from common.file_helpers import atomic_write_in_dir from openpilot.common.file_helpers import atomic_write_in_dir
class TestFileHelpers(unittest.TestCase): class TestFileHelpers(unittest.TestCase):

@ -1,7 +1,7 @@
import numpy as np import numpy as np
import unittest import unittest
from common.numpy_fast import interp from openpilot.common.numpy_fast import interp
class InterpTest(unittest.TestCase): class InterpTest(unittest.TestCase):

@ -1,21 +1,14 @@
import os import os
import threading import threading
import time import time
import tempfile
import shutil
import uuid import uuid
import unittest import unittest
from common.params import Params, ParamKeyType, UnknownKeyName, put_nonblocking, put_bool_nonblocking from openpilot.common.params import Params, ParamKeyType, UnknownKeyName, put_nonblocking, put_bool_nonblocking
class TestParams(unittest.TestCase): class TestParams(unittest.TestCase):
def setUp(self): def setUp(self):
self.tmpdir = tempfile.mkdtemp() self.params = Params()
print("using", self.tmpdir)
self.params = Params(self.tmpdir)
def tearDown(self):
shutil.rmtree(self.tmpdir)
def test_params_put_and_get(self): def test_params_put_and_get(self):
self.params.put("DongleId", "cb38263377b873ee") self.params.put("DongleId", "cb38263377b873ee")
@ -90,19 +83,19 @@ class TestParams(unittest.TestCase):
self.assertFalse(self.params.get_bool("IsMetric")) self.assertFalse(self.params.get_bool("IsMetric"))
def test_put_non_blocking_with_get_block(self): def test_put_non_blocking_with_get_block(self):
q = Params(self.tmpdir) q = Params()
def _delayed_writer(): def _delayed_writer():
time.sleep(0.1) time.sleep(0.1)
put_nonblocking("CarParams", "test", self.tmpdir) put_nonblocking("CarParams", "test")
threading.Thread(target=_delayed_writer).start() threading.Thread(target=_delayed_writer).start()
assert q.get("CarParams") is None assert q.get("CarParams") is None
assert q.get("CarParams", True) == b"test" assert q.get("CarParams", True) == b"test"
def test_put_bool_non_blocking_with_get_block(self): def test_put_bool_non_blocking_with_get_block(self):
q = Params(self.tmpdir) q = Params()
def _delayed_writer(): def _delayed_writer():
time.sleep(0.1) time.sleep(0.1)
put_bool_nonblocking("CarParams", True, self.tmpdir) put_bool_nonblocking("CarParams", True)
threading.Thread(target=_delayed_writer).start() threading.Thread(target=_delayed_writer).start()
assert q.get("CarParams") is None assert q.get("CarParams") is None
assert q.get("CarParams", True) == b"1" assert q.get("CarParams", True) == b"1"

@ -0,0 +1,23 @@
#include "catch2/catch.hpp"
#include "common/ratekeeper.h"
#include "common/timing.h"
#include "common/util.h"
TEST_CASE("RateKeeper") {
float freq = GENERATE(10, 50, 100);
RateKeeper rk("Test RateKeeper", freq);
int lags = 0;
int bad_keep_times = 0;
for (int i = 0; i < freq; ++i) {
double begin = seconds_since_boot();
util::sleep_for(util::random_int(0, 1000.0 / freq - 1));
bool lagged = rk.keepTime();
lags += lagged;
bad_keep_times += (seconds_since_boot() - begin - (1 / freq)) > 1e-3;
}
// need a tolerance here due to scheduling
REQUIRE(lags < 5);
REQUIRE(bad_keep_times < 5);
}

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"

@ -1,15 +1,14 @@
#include <zmq.h> #include <zmq.h>
#include <iostream> #include <iostream>
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include "json11.hpp" #include "catch2/catch.hpp"
#include "common/swaglog.h" #include "common/swaglog.h"
#include "common/util.h" #include "common/util.h"
#include "common/version.h" #include "common/version.h"
#include "system/hardware/hw.h" #include "system/hardware/hw.h"
#include "third_party/json11/json11.hpp"
const char *SWAGLOG_ADDR = "ipc:///tmp/logmessage";
std::string daemon_name = "testy"; std::string daemon_name = "testy";
std::string dongle_id = "test_dongle_id"; std::string dongle_id = "test_dongle_id";
int LINE_NO = 0; int LINE_NO = 0;
@ -25,7 +24,7 @@ void log_thread(int thread_id, int msg_cnt) {
void recv_log(int thread_cnt, int thread_msg_cnt) { void recv_log(int thread_cnt, int thread_msg_cnt) {
void *zctx = zmq_ctx_new(); void *zctx = zmq_ctx_new();
void *sock = zmq_socket(zctx, ZMQ_PULL); void *sock = zmq_socket(zctx, ZMQ_PULL);
zmq_bind(sock, SWAGLOG_ADDR); zmq_bind(sock, Path::swaglog_ipc().c_str());
std::vector<int> thread_msgs(thread_cnt); std::vector<int> thread_msgs(thread_cnt);
int total_count = 0; int total_count = 0;

@ -5,10 +5,10 @@
#include <algorithm> #include <algorithm>
#include <climits> #include <climits>
#include <fstream>
#include <random> #include <random>
#include <string> #include <string>
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
#include "common/util.h" #include "common/util.h"
@ -38,7 +38,8 @@ TEST_CASE("util::read_file") {
std::string content = random_bytes(64 * 1024); std::string content = random_bytes(64 * 1024);
write(fd, content.c_str(), content.size()); write(fd, content.c_str(), content.size());
std::string ret = util::read_file(filename); std::string ret = util::read_file(filename);
REQUIRE(ret == content); bool equal = (ret == content);
REQUIRE(equal);
close(fd); close(fd);
} }
SECTION("read directory") { SECTION("read directory") {
@ -108,7 +109,8 @@ TEST_CASE("util::safe_fwrite") {
REQUIRE(ret == 0); REQUIRE(ret == 0);
ret = fclose(f); ret = fclose(f);
REQUIRE(ret == 0); REQUIRE(ret == 0);
REQUIRE(dat == util::read_file(filename)); bool equal = (dat == util::read_file(filename));
REQUIRE(equal);
} }
TEST_CASE("util::create_directories") { TEST_CASE("util::create_directories") {

@ -2,7 +2,7 @@
import os import os
import time import time
import subprocess import subprocess
from common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
class TextWindow: class TextWindow:

@ -1,6 +1,5 @@
Import('env', 'envCython') Import('env', 'envCython')
transformations = env.Library('transformations', ['orientation.cc', 'coordinates.cc']) transformations = env.Library('transformations', ['orientation.cc', 'coordinates.cc'])
Export('transformations') transformations_python = envCython.Program('transformations.so', 'transformations.pyx')
Export('transformations', 'transformations_python')
envCython.Program('transformations.so', 'transformations.pyx')

@ -1,6 +1,6 @@
import numpy as np import numpy as np
import common.transformations.orientation as orient import openpilot.common.transformations.orientation as orient
## -- hardcoded hardware params -- ## -- hardcoded hardware params --
eon_f_focal_length = 910.0 eon_f_focal_length = 910.0

@ -1,13 +1,11 @@
#define _USE_MATH_DEFINES #define _USE_MATH_DEFINES
#include "common/transformations/coordinates.hpp"
#include <iostream> #include <iostream>
#include <cmath> #include <cmath>
#include <eigen3/Eigen/Dense> #include <eigen3/Eigen/Dense>
#include "coordinates.hpp"
double a = 6378137; // lgtm [cpp/short-global-name] double a = 6378137; // lgtm [cpp/short-global-name]
double b = 6356752.3142; // lgtm [cpp/short-global-name] double b = 6356752.3142; // lgtm [cpp/short-global-name]
double esq = 6.69437999014 * 0.001; // lgtm [cpp/short-global-name] double esq = 6.69437999014 * 0.001; // lgtm [cpp/short-global-name]

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <eigen3/Eigen/Dense>
#define DEG2RAD(x) ((x) * M_PI / 180.0) #define DEG2RAD(x) ((x) * M_PI / 180.0)
#define RAD2DEG(x) ((x) * 180.0 / M_PI) #define RAD2DEG(x) ((x) * 180.0 / M_PI)

@ -1,8 +1,7 @@
# pylint: skip-file from openpilot.common.transformations.orientation import numpy_wrap
from common.transformations.orientation import numpy_wrap from openpilot.common.transformations.transformations import (ecef2geodetic_single,
from common.transformations.transformations import (ecef2geodetic_single,
geodetic2ecef_single) geodetic2ecef_single)
from common.transformations.transformations import LocalCoord as LocalCoord_single from openpilot.common.transformations.transformations import LocalCoord as LocalCoord_single
class LocalCoord(LocalCoord_single): class LocalCoord(LocalCoord_single):

@ -1,7 +1,9 @@
import numpy as np import numpy as np
from common.transformations.camera import (FULL_FRAME_SIZE, from openpilot.common.transformations.orientation import rot_from_euler
get_view_frame_from_calib_frame) from openpilot.common.transformations.camera import (
FULL_FRAME_SIZE, get_view_frame_from_calib_frame, view_frame_from_device_frame,
eon_fcam_intrinsics, tici_ecam_intrinsics, tici_fcam_intrinsics)
# segnet # segnet
SEGNET_SIZE = (512, 384) SEGNET_SIZE = (512, 384)
@ -57,61 +59,20 @@ medmodel_frame_from_calib_frame = np.dot(medmodel_intrinsics,
medmodel_frame_from_bigmodel_frame = np.dot(medmodel_intrinsics, np.linalg.inv(bigmodel_intrinsics)) medmodel_frame_from_bigmodel_frame = np.dot(medmodel_intrinsics, np.linalg.inv(bigmodel_intrinsics))
calib_from_medmodel = np.linalg.inv(medmodel_frame_from_calib_frame[:, :3])
calib_from_sbigmodel = np.linalg.inv(sbigmodel_frame_from_calib_frame[:, :3])
### This function mimics the update_calibration logic in modeld.cc # This function is verified to give similar results to xx.uncommon.utils.transform_img
### Manually verified to give similar results to xx.uncommon.utils.transform_img def get_warp_matrix(device_from_calib_euler: np.ndarray, wide_camera: bool = False, bigmodel_frame: bool = False, tici: bool = True) -> np.ndarray:
def get_warp_matrix(rpy_calib, wide_cam=False, big_model=False, tici=True): if tici and wide_camera:
from common.transformations.orientation import rot_from_euler cam_intrinsics = tici_ecam_intrinsics
from common.transformations.camera import view_frame_from_device_frame, eon_fcam_intrinsics, tici_ecam_intrinsics, tici_fcam_intrinsics
if tici and wide_cam:
intrinsics = tici_ecam_intrinsics
elif tici:
intrinsics = tici_fcam_intrinsics
else:
intrinsics = eon_fcam_intrinsics
if big_model:
sbigmodel_from_calib = sbigmodel_frame_from_calib_frame[:, (0,1,2)]
calib_from_model = np.linalg.inv(sbigmodel_from_calib)
else:
medmodel_from_calib = medmodel_frame_from_calib_frame[:, (0,1,2)]
calib_from_model = np.linalg.inv(medmodel_from_calib)
device_from_calib = rot_from_euler(rpy_calib)
camera_from_calib = intrinsics.dot(view_frame_from_device_frame.dot(device_from_calib))
warp_matrix = camera_from_calib.dot(calib_from_model)
return warp_matrix
### This is old, just for debugging
def get_warp_matrix_old(rpy_calib, wide_cam=False, big_model=False, tici=True):
from common.transformations.orientation import rot_from_euler
from common.transformations.camera import view_frame_from_device_frame, eon_fcam_intrinsics, tici_ecam_intrinsics, tici_fcam_intrinsics
def get_view_frame_from_road_frame(roll, pitch, yaw, height):
device_from_road = rot_from_euler([roll, pitch, yaw]).dot(np.diag([1, -1, -1]))
view_from_road = view_frame_from_device_frame.dot(device_from_road)
return np.hstack((view_from_road, [[0], [height], [0]]))
if tici and wide_cam:
intrinsics = tici_ecam_intrinsics
elif tici: elif tici:
intrinsics = tici_fcam_intrinsics cam_intrinsics = tici_fcam_intrinsics
else: else:
intrinsics = eon_fcam_intrinsics cam_intrinsics = eon_fcam_intrinsics
model_height = 1.22 calib_from_model = calib_from_sbigmodel if bigmodel_frame else calib_from_medmodel
if big_model: device_from_calib = rot_from_euler(device_from_calib_euler)
model_from_road = np.dot(sbigmodel_intrinsics, camera_from_calib = cam_intrinsics @ view_frame_from_device_frame @ device_from_calib
get_view_frame_from_road_frame(0, 0, 0, model_height)) warp_matrix: np.ndarray = camera_from_calib @ calib_from_model
else:
model_from_road = np.dot(medmodel_intrinsics,
get_view_frame_from_road_frame(0, 0, 0, model_height))
ground_from_model = np.linalg.inv(model_from_road[:, (0, 1, 3)])
E = get_view_frame_from_road_frame(*rpy_calib, 1.22)
camera_frame_from_road_frame = intrinsics.dot(E)
camera_frame_from_ground = camera_frame_from_road_frame[:,(0,1,3)]
warp_matrix = camera_frame_from_ground .dot(ground_from_model)
return warp_matrix return warp_matrix

@ -4,8 +4,8 @@
#include <cmath> #include <cmath>
#include <eigen3/Eigen/Dense> #include <eigen3/Eigen/Dense>
#include "orientation.hpp" #include "common/transformations/orientation.hpp"
#include "coordinates.hpp" #include "common/transformations/coordinates.hpp"
Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat){ Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat){
if (quat.w() > 0){ if (quat.w() > 0){

@ -1,6 +1,6 @@
#pragma once #pragma once
#include <eigen3/Eigen/Dense> #include <eigen3/Eigen/Dense>
#include "coordinates.hpp" #include "common/transformations/coordinates.hpp"
Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat); Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat);

@ -1,8 +1,7 @@
# pylint: skip-file
import numpy as np import numpy as np
from typing import Callable from typing import Callable
from common.transformations.transformations import (ecef_euler_from_ned_single, from openpilot.common.transformations.transformations import (ecef_euler_from_ned_single,
euler2quat_single, euler2quat_single,
euler2rot_single, euler2rot_single,
ned_euler_from_ecef_single, ned_euler_from_ecef_single,

@ -3,7 +3,7 @@
import numpy as np import numpy as np
import unittest import unittest
import common.transformations.coordinates as coord import openpilot.common.transformations.coordinates as coord
geodetic_positions = np.array([[37.7610403, -122.4778699, 115], geodetic_positions = np.array([[37.7610403, -122.4778699, 115],
[27.4840915, -68.5867592, 2380], [27.4840915, -68.5867592, 2380],

@ -3,7 +3,7 @@
import numpy as np import numpy as np
import unittest import unittest
from common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \ from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \
rot2quat, quat2rot, \ rot2quat, quat2rot, \
ned_euler_from_ecef ned_euler_from_ecef

@ -1,20 +1,20 @@
# distutils: language = c++ # distutils: language = c++
# cython: language_level = 3 # cython: language_level = 3
from common.transformations.transformations cimport Matrix3, Vector3, Quaternion from openpilot.common.transformations.transformations cimport Matrix3, Vector3, Quaternion
from common.transformations.transformations cimport ECEF, NED, Geodetic from openpilot.common.transformations.transformations cimport ECEF, NED, Geodetic
from common.transformations.transformations cimport euler2quat as euler2quat_c from openpilot.common.transformations.transformations cimport euler2quat as euler2quat_c
from common.transformations.transformations cimport quat2euler as quat2euler_c from openpilot.common.transformations.transformations cimport quat2euler as quat2euler_c
from common.transformations.transformations cimport quat2rot as quat2rot_c from openpilot.common.transformations.transformations cimport quat2rot as quat2rot_c
from common.transformations.transformations cimport rot2quat as rot2quat_c from openpilot.common.transformations.transformations cimport rot2quat as rot2quat_c
from common.transformations.transformations cimport euler2rot as euler2rot_c from openpilot.common.transformations.transformations cimport euler2rot as euler2rot_c
from common.transformations.transformations cimport rot2euler as rot2euler_c from openpilot.common.transformations.transformations cimport rot2euler as rot2euler_c
from common.transformations.transformations cimport rot_matrix as rot_matrix_c from openpilot.common.transformations.transformations cimport rot_matrix as rot_matrix_c
from common.transformations.transformations cimport ecef_euler_from_ned as ecef_euler_from_ned_c from openpilot.common.transformations.transformations cimport ecef_euler_from_ned as ecef_euler_from_ned_c
from common.transformations.transformations cimport ned_euler_from_ecef as ned_euler_from_ecef_c from openpilot.common.transformations.transformations cimport ned_euler_from_ecef as ned_euler_from_ecef_c
from common.transformations.transformations cimport geodetic2ecef as geodetic2ecef_c from openpilot.common.transformations.transformations cimport geodetic2ecef as geodetic2ecef_c
from common.transformations.transformations cimport ecef2geodetic as ecef2geodetic_c from openpilot.common.transformations.transformations cimport ecef2geodetic as ecef2geodetic_c
from common.transformations.transformations cimport LocalCoord_c from openpilot.common.transformations.transformations cimport LocalCoord_c
import cython import cython

@ -3,7 +3,6 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/resource.h> #include <sys/resource.h>
#include <dirent.h>
#include <cassert> #include <cassert>
#include <cerrno> #include <cerrno>
@ -253,6 +252,14 @@ std::string dir_name(std::string const &path) {
return path.substr(0, pos); return path.substr(0, pos);
} }
bool starts_with(const std::string &s1, const std::string &s2) {
return strncmp(s1.c_str(), s2.c_str(), s2.size()) == 0;
}
bool ends_with(const std::string &s1, const std::string &s2) {
return strcmp(s1.c_str() + (s1.size() - s2.size()), s2.c_str()) == 0;
}
std::string check_output(const std::string& command) { std::string check_output(const std::string& command) {
char buffer[128]; char buffer[128];
std::string result; std::string result;

@ -77,6 +77,8 @@ float getenv(const char* key, float default_val);
std::string hexdump(const uint8_t* in, const size_t size); std::string hexdump(const uint8_t* in, const size_t size);
std::string dir_name(std::string const& path); std::string dir_name(std::string const& path);
bool starts_with(const std::string &s1, const std::string &s2);
bool ends_with(const std::string &s1, const std::string &s2);
// ***** random helpers ***** // ***** random helpers *****
int random_int(int min, int max); int random_int(int min, int max);
@ -115,7 +117,7 @@ public:
#ifndef __APPLE__ #ifndef __APPLE__
std::signal(SIGPWR, (sighandler_t)set_do_exit); std::signal(SIGPWR, (sighandler_t)set_do_exit);
#endif #endif
}; }
inline static std::atomic<bool> power_failure = false; inline static std::atomic<bool> power_failure = false;
inline static std::atomic<int> signal = 0; inline static std::atomic<int> signal = 0;
inline operator bool() { return do_exit; } inline operator bool() { return do_exit; }
@ -151,12 +153,18 @@ struct unique_fd {
class FirstOrderFilter { class FirstOrderFilter {
public: public:
FirstOrderFilter(float x0, float ts, float dt) { FirstOrderFilter(float x0, float ts, float dt, bool initialized = true) {
k_ = (dt / ts) / (1.0 + dt / ts); k_ = (dt / ts) / (1.0 + dt / ts);
x_ = x0; x_ = x0;
initialized_ = initialized;
} }
inline float update(float x) { inline float update(float x) {
if (initialized_) {
x_ = (1. - k_) * x_ + k_ * x; x_ = (1. - k_) * x_ + k_ * x;
} else {
initialized_ = true;
x_ = x;
}
return x_; return x_;
} }
inline void reset(float x) { x_ = x; } inline void reset(float x) { x_ = x; }
@ -164,12 +172,13 @@ public:
private: private:
float x_, k_; float x_, k_;
bool initialized_;
}; };
template<typename T> template<typename T>
void update_max_atomic(std::atomic<T>& max, T const& value) { void update_max_atomic(std::atomic<T>& max, T const& value) {
T prev = max; T prev = max;
while(prev < value && !max.compare_exchange_weak(prev, value)) {} while (prev < value && !max.compare_exchange_weak(prev, value)) {}
} }
class LogState { class LogState {
@ -179,9 +188,9 @@ class LogState {
void *zctx = nullptr; void *zctx = nullptr;
void *sock = nullptr; void *sock = nullptr;
int print_level; int print_level;
const char* endpoint; std::string endpoint;
LogState(const char* _endpoint) { LogState(std::string _endpoint) {
endpoint = _endpoint; endpoint = _endpoint;
} }
@ -193,7 +202,7 @@ class LogState {
int timeout = 100; int timeout = 100;
zmq_setsockopt(sock, ZMQ_LINGER, &timeout, sizeof(timeout)); zmq_setsockopt(sock, ZMQ_LINGER, &timeout, sizeof(timeout));
zmq_connect(sock, endpoint); zmq_connect(sock, endpoint.c_str());
initialized = true; initialized = true;
} }

@ -1,3 +1,5 @@
#include <string>
#include "common/watchdog.h" #include "common/watchdog.h"
#include "common/util.h" #include "common/util.h"

@ -1,6 +1,6 @@
import sys import sys
import pygame # pylint: disable=import-error import pygame
import cv2 # pylint: disable=import-error import cv2
class Window: class Window:
def __init__(self, w, h, caption="window", double=False, halve=False): def __init__(self, w, h, caption="window", double=False, halve=False):

@ -0,0 +1,10 @@
import pytest
from openpilot.common.prefix import OpenpilotPrefix
@pytest.fixture(scope="function", autouse=True)
def global_setup_and_teardown():
# setup a clean environment for each test
with OpenpilotPrefix():
yield

@ -2,9 +2,9 @@
# Supported Cars # Supported Cars
A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. 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.
# 257 Supported Cars # 262 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>&nbsp;|Video| |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>&nbsp;|Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@ -71,6 +71,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Honda|Passport 2019-23|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Honda&model=Passport 2019-23">Buy Here</a></sub></details>|| |Honda|Passport 2019-23|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Honda&model=Passport 2019-23">Buy Here</a></sub></details>||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Honda&model=Pilot 2016-22">Buy Here</a></sub></details>|| |Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Honda&model=Pilot 2016-22">Buy Here</a></sub></details>||
|Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Honda&model=Ridgeline 2017-23">Buy Here</a></sub></details>|| |Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Honda&model=Ridgeline 2017-23">Buy Here</a></sub></details>||
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2022">Buy Here</a></sub></details>||
|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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-19">Buy Here</a></sub></details>|| |Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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-19">Buy Here</a></sub></details>||
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>||
@ -80,15 +81,17 @@ A supported vehicle is one that just works when you install a comma three. All s
|Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 (Southeast Asia only) 2022-23[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>6</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>|| |Hyundai|Ioniq 5 (with HDA II) 2022-23[<sup>6</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>||
|Hyundai|Ioniq 5 (without HDA II) 2022-23[<sup>6</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 (without HDA II) 2022-23">Buy Here</a></sub></details>|| |Hyundai|Ioniq 5 (without HDA II) 2022-23[<sup>6</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 (without HDA II) 2022-23">Buy Here</a></sub></details>||
|Hyundai|Ioniq 6 (with HDA II) 2023[<sup>6</sup>](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 6 (with HDA II) 2023">Buy Here</a></sub></details>||
|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 Electric 2019">Buy Here</a></sub></details>|| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 Electric 2019">Buy Here</a></sub></details>||
|Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 Electric 2020">Buy Here</a></sub></details>|| |Hyundai|Ioniq Electric 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 Electric 2020">Buy Here</a></sub></details>||
|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2017-19">Buy Here</a></sub></details>|| |Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2017-19">Buy Here</a></sub></details>||
|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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">Buy Here</a></sub></details>|| |Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>|
|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 Hybrid 2020">Buy Here</a></sub></details>|<a href="https://youtu.be/0dwpAHiZgFo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 Hybrid 2020">Buy Here</a></sub></details>|<a href="https://youtu.be/0dwpAHiZgFo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Palisade 2020-22">Buy Here</a></sub></details>|<a href="https://youtu.be/TAnDqjF4fDY?t=456" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Palisade 2020-22">Buy Here</a></sub></details>|<a href="https://youtu.be/TAnDqjF4fDY?t=456" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Hyundai|Santa Cruz 2022-23[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Santa Cruz 2022-23">Buy Here</a></sub></details>|| |Hyundai|Santa Cruz 2022-23[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Santa Cruz 2022-23">Buy Here</a></sub></details>||
@ -107,7 +110,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2023">Buy Here</a></sub></details>|| |Kia|Carnival 2023-24[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2023-24">Buy Here</a></sub></details>||
|Kia|Carnival (China only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|Carnival (China only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 (Southeast Asia only) 2022-23[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>||
@ -117,6 +120,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Kia|Forte 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|Forte 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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-22">Buy Here</a></sub></details>|| |Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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-22">Buy Here</a></sub></details>||
|Kia|K5 Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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">Buy Here</a></sub></details>|| |Kia|K5 Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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">Buy Here</a></sub></details>||
|Kia|K8 Hybrid (with HDA II) 2023[<sup>6</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>||
|Kia|Niro EV 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2019">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 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2019">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 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2020">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 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2020">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 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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>|
@ -124,15 +128,16 @@ A supported vehicle is one that just works when you install a comma three. All s
|Kia|Niro EV 2023[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 EV 2023[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2021-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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-22">Buy Here</a></sub></details>|| |Kia|Niro Hybrid 2021-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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-22">Buy Here</a></sub></details>||
|Kia|Niro Hybrid 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 Hybrid 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|openpilot available[<sup>1</sup>](#footnotes)|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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 2020|All|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Optima 2017">Buy Here</a></sub></details>|| |Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Optima 2017">Buy Here</a></sub></details>||
|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Optima 2019-20">Buy Here</a></sub></details>|| |Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Optima 2019-20">Buy Here</a></sub></details>||
|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Seltos 2021">Buy Here</a></sub></details>|| |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Seltos 2021">Buy Here</a></sub></details>||
|Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sorento 2018">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sorento 2018">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sorento 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sorento 2019">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Fkh3s6WHJz8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Kia|Sorento 2021-23[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sorento 2021-23">Buy Here</a></sub></details>|| |Kia|Sorento 2021-23[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sorento 2021-23">Buy Here</a></sub></details>||
|Kia|Sorento Plug-in Hybrid 2022-23[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sorento Plug-in Hybrid 2022-23">Buy Here</a></sub></details>|| |Kia|Sorento Hybrid 2023[<sup>6</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sorento Hybrid 2023">Buy Here</a></sub></details>||
|Kia|Sorento Plug-in Hybrid 2022-23[<sup>6</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sorento Plug-in Hybrid 2022-23">Buy Here</a></sub></details>||
|Kia|Sportage 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sportage 2023">Buy Here</a></sub></details>|| |Kia|Sportage 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sportage 2023">Buy Here</a></sub></details>||
|Kia|Sportage Hybrid 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sportage Hybrid 2023">Buy Here</a></sub></details>|| |Kia|Sportage Hybrid 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Sportage Hybrid 2023">Buy Here</a></sub></details>||
|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Stinger 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=MJ94qoofYw0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma power v2<br>- 1 comma three<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=Stinger 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=MJ94qoofYw0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
@ -183,7 +188,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Škoda|Kamiq 2021[<sup>9,11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Kamiq 2021">Buy Here</a></sub></details>[<sup>13</sup>](#footnotes)|| |Škoda|Kamiq 2021[<sup>9,11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Kamiq 2021">Buy Here</a></sub></details>[<sup>13</sup>](#footnotes)||
|Škoda|Karoq 2019-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Karoq 2019-23">Buy Here</a></sub></details>|| |Škoda|Karoq 2019-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Karoq 2019-23">Buy Here</a></sub></details>||
|Škoda|Kodiaq 2017-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Kodiaq 2017-23">Buy Here</a></sub></details>|| |Škoda|Kodiaq 2017-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Kodiaq 2017-23">Buy Here</a></sub></details>||
|Škoda|Octavia 2015, 2018-19[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Octavia 2015, 2018-19">Buy Here</a></sub></details>|| |Škoda|Octavia 2015-19[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Octavia 2015-19">Buy Here</a></sub></details>||
|Škoda|Octavia RS 2016[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Octavia RS 2016">Buy Here</a></sub></details>|| |Škoda|Octavia RS 2016[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Octavia RS 2016">Buy Here</a></sub></details>||
|Škoda|Scala 2020-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Scala 2020-23">Buy Here</a></sub></details>[<sup>13</sup>](#footnotes)|| |Škoda|Scala 2020-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Scala 2020-23">Buy Here</a></sub></details>[<sup>13</sup>](#footnotes)||
|Škoda|Superb 2015-22[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Superb 2015-22">Buy Here</a></sub></details>|| |Škoda|Superb 2015-22[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Škoda&model=Superb 2015-22">Buy Here</a></sub></details>||
@ -267,7 +272,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Touran 2016-23">Buy Here</a></sub></details>|| |Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<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=Touran 2016-23">Buy Here</a></sub></details>||
### Footnotes ### Footnotes
<sup>1</sup>Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`. <br /> <sup>1</sup>openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`. <br />
<sup>2</sup>By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. <b><i>NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).</i></b> <br /> <sup>2</sup>By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. <b><i>NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).</i></b> <br />
<sup>3</sup>Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia. <br /> <sup>3</sup>Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia. <br />
<sup>4</sup>Requires a <a href="https://github.com/commaai/openpilot/wiki/GM#hardware" target="_blank">community built ASCM harness</a>. <b><i>NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).</i></b> <br /> <sup>4</sup>Requires a <a href="https://github.com/commaai/openpilot/wiki/GM#hardware" target="_blank">community built ASCM harness</a>. <b><i>NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).</i></b> <br />

@ -1,5 +1,4 @@
# type: ignore # type: ignore
# pylint: skip-file
# Configuration file for the Sphinx documentation builder. # Configuration file for the Sphinx documentation builder.
# #
@ -17,8 +16,8 @@ import os
import sys import sys
from os.path import exists from os.path import exists
from common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from system.version import get_version from openpilot.system.version import get_version
sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))

@ -1,8 +1,8 @@
FROM ghcr.io/commaai/openpilot-base:latest as openpilot-docs-base FROM ghcr.io/commaai/openpilot-base:latest
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
ENV OPENPILOT_PATH /home/batman/openpilot/ ENV OPENPILOT_PATH /tmp/openpilot
ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH} ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH}
ENV POETRY_VIRUALENVS_CREATE false ENV POETRY_VIRUALENVS_CREATE false
@ -11,11 +11,12 @@ WORKDIR ${OPENPILOT_PATH}
COPY SConstruct ${OPENPILOT_PATH} COPY SConstruct ${OPENPILOT_PATH}
COPY ./openpilot ${OPENPILOT_PATH}/openpilot
COPY ./body ${OPENPILOT_PATH}/body COPY ./body ${OPENPILOT_PATH}/body
COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./third_party ${OPENPILOT_PATH}/third_party
COPY ./site_scons ${OPENPILOT_PATH}/site_scons COPY ./site_scons ${OPENPILOT_PATH}/site_scons
COPY ./laika ${OPENPILOT_PATH}/laika
COPY ./laika_repo ${OPENPILOT_PATH}/laika_repo COPY ./laika_repo ${OPENPILOT_PATH}/laika_repo
RUN ln -s ${OPENPILOT_PATH}/laika_repo/laika/ ${OPENPILOT_PATH}/laika
COPY ./rednose ${OPENPILOT_PATH}/rednose COPY ./rednose ${OPENPILOT_PATH}/rednose
COPY ./rednose_repo ${OPENPILOT_PATH}/rednose_repo COPY ./rednose_repo ${OPENPILOT_PATH}/rednose_repo
COPY ./tools ${OPENPILOT_PATH}/tools COPY ./tools ${OPENPILOT_PATH}/tools
@ -28,7 +29,7 @@ COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive
COPY ./system ${OPENPILOT_PATH}/system COPY ./system ${OPENPILOT_PATH}/system
COPY ./*.md ${OPENPILOT_PATH}/ COPY ./*.md ${OPENPILOT_PATH}/
RUN scons -j$(nproc) RUN --mount=type=bind,source=.ci_cache/scons_cache,target=/tmp/scons_cache,rw scons -j$(nproc) --cache-readonly
RUN apt update && apt install doxygen -y RUN apt update && apt install doxygen -y
COPY ./docs ${OPENPILOT_PATH}/docs COPY ./docs ${OPENPILOT_PATH}/docs
@ -37,5 +38,5 @@ WORKDIR ${OPENPILOT_PATH}/docs
RUN make html RUN make html
FROM nginx:1.21 FROM nginx:1.21
COPY --from=0 /home/batman/openpilot/build/docs/html /usr/share/nginx/html COPY --from=0 /tmp/openpilot/build/docs/html /usr/share/nginx/html
COPY ./docs/docker/nginx.conf /etc/nginx/conf.d/default.conf COPY ./docs/docker/nginx.conf /etc/nginx/conf.d/default.conf

@ -1 +1 @@
Subproject commit ef21c612f4b0d68c790b4b1a6d637cd5fce7732e Subproject commit 8861844c9b577ff7de7d03fab9f4d7f560415fc9

@ -1,16 +0,0 @@
[mypy]
python_version = 3.11
plugins = numpy.typing.mypy_plugin
files = body, common, docs, scripts, selfdrive, site_scons, system, tools
exclude = ^(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)
; third-party packages
ignore_missing_imports = True
; helpful warnings
warn_redundant_casts = True
warn_unreachable = True
warn_unused_ignores = True
; restrict dynamic typing
warn_return_any = True

@ -1 +1 @@
Subproject commit a1582f5e28fe0df23b6821c907188be477aac11c Subproject commit 4ab347baefb7473771ada0723c969c50d0c28d01

@ -1 +1 @@
Subproject commit 5681f84f4e79c45ac1387cedf70cf66be9729884 Subproject commit dfbee695affcc37378b57a2e9a96e157eb41b7ab

2186
poetry.lock generated

File diff suppressed because it is too large Load Diff

@ -1,8 +1,62 @@
[tool.pytest.ini_options] [tool.pytest.ini_options]
minversion = "6.0" minversion = "6.0"
addopts = "--ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/" addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/ -Werror --strict-config --strict-markers --durations=10"
#cpp_files = "test_*" # uncomment when agnos has pytest-cpp and remove from CI
python_files = "test_*.py" python_files = "test_*.py"
timeout = "30" # you get this long by default #timeout = "30" # you get this long by default
markers = [
"parallel: mark tests as parallelizable (tests with no global state, so can be run in parallel)"
]
testpaths = [
"common",
"selfdrive/athena",
"selfdrive/boardd",
"selfdrive/car",
"selfdrive/controls",
"selfdrive/locationd",
"selfdrive/monitoring",
"selfdrive/thermald",
"selfdrive/test/longitudinal_maneuvers",
"system/hardware/tici",
"system/loggerd",
"system/proclogd",
"system/tests",
"system/ubloxd",
"tools/lib/tests",
"tools/replay",
"tools/cabana"
]
[tool.mypy]
python_version = "3.11"
plugins = [
"numpy.typing.mypy_plugin",
]
exclude = [
"body/",
"cereal/",
"opendbc/",
"panda/",
"laika/",
"laika_repo/",
"rednose/",
"rednose_repo/",
"tinygrad/",
"tinygrad_repo/",
"third_party/",
]
# third-party packages
ignore_missing_imports=true
# helpful warnings
warn_redundant_casts=true
warn_unreachable=true
warn_unused_ignores=true
# restrict dynamic typing
warn_return_any=true
[tool.poetry] [tool.poetry]
name = "openpilot" name = "openpilot"
@ -22,6 +76,7 @@ aiohttp = "*"
aiortc = "*" aiortc = "*"
casadi = "==3.6.3" casadi = "==3.6.3"
cffi = "*" cffi = "*"
control = "*"
crcmod = "*" crcmod = "*"
cryptography = "*" cryptography = "*"
Cython = "*" Cython = "*"
@ -39,6 +94,7 @@ psutil = "*"
pyaudio = "*" pyaudio = "*"
pycapnp = "*" pycapnp = "*"
pycryptodome = "*" pycryptodome = "*"
pycurl = "*"
pydub = "*" pydub = "*"
PyJWT = "*" PyJWT = "*"
pyopencl = "*" pyopencl = "*"
@ -64,7 +120,8 @@ sconscontrib = {git = "https://github.com/SCons/scons-contrib.git"}
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
av = "*" av = "*"
azure-storage-blob = "~2.1" azure-identity = "*"
azure-storage-blob = "*"
breathe = "*" breathe = "*"
carla = { url = "https://github.com/commaai/carla/releases/download/3.11.4/carla-0.9.14-cp311-cp311-linux_x86_64.whl", platform = "linux", markers = "platform_machine == 'x86_64'" } carla = { url = "https://github.com/commaai/carla/releases/download/3.11.4/carla-0.9.14-cp311-cp311-linux_x86_64.whl", platform = "linux", markers = "platform_machine == 'x86_64'" }
coverage = "*" coverage = "*"
@ -75,6 +132,7 @@ inputs = "*"
lru-dict = "*" lru-dict = "*"
markdown-it-py = "*" markdown-it-py = "*"
matplotlib = "*" matplotlib = "*"
metadrive-simulator = { git = "https://github.com/metadriverse/metadrive.git", rev ="7d6f8ef707bfff67c6b88f3b4a98f8b1d58bf8e6", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies
mpld3 = "*" mpld3 = "*"
mypy = "*" mypy = "*"
myst-parser = "*" myst-parser = "*"
@ -84,11 +142,16 @@ pandas = "*"
parameterized = "^0.8" parameterized = "^0.8"
pprofile = "*" pprofile = "*"
pre-commit = "*" pre-commit = "*"
pycurl = "*"
pygame = "*" pygame = "*"
pyprof2calltree = "*" pyprof2calltree = "*"
pytest = "*" pytest = "*"
pytest-cov = "*"
pytest-cpp = "*"
pytest-subtests = "*"
pytest-xdist = "*" pytest-xdist = "*"
pytest-timeout = "*"
pytest-timeouts = "*"
ruff = "*"
scipy = "*" scipy = "*"
sphinx = "*" sphinx = "*"
sphinx-rtd-theme = "*" sphinx-rtd-theme = "*"
@ -110,7 +173,7 @@ build-backend = "poetry.core.masonry.api"
# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml
[tool.ruff] [tool.ruff]
select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF100", "A", "B"] select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF008", "RUF100", "A", "B", "TID251"]
ignore = ["W292", "E741", "E402", "C408", "ISC003", "B027", "B024"] ignore = ["W292", "E741", "E402", "C408", "ISC003", "B027", "B024"]
line-length = 160 line-length = 160
target-version="py311" target-version="py311"
@ -123,3 +186,9 @@ exclude = [
"third_party", "third_party",
] ]
flake8-implicit-str-concat.allow-multiline=false flake8-implicit-str-concat.allow-multiline=false
[tool.ruff.flake8-tidy-imports.banned-api]
"selfdrive".msg = "Use openpilot.selfdrive"
"common".msg = "Use openpilot.common"
"system".msg = "Use openpilot.system"
"third_party".msg = "Use openpilot.third_party"
"tools".msg = "Use openpilot.tools"

@ -1 +1 @@
Subproject commit 22f02dd650361e091f09f1ae94c5c96a9419c2d4 Subproject commit 8658bed29686b2ddae191fd18302986c85542431

@ -15,12 +15,14 @@ docs/INTEGRATION.md
docs/LIMITATIONS.md docs/LIMITATIONS.md
site_scons/site_tools/cython.py site_scons/site_tools/cython.py
openpilot/__init__.py
openpilot/**
common/.gitignore common/.gitignore
common/__init__.py common/__init__.py
common/conversions.py common/conversions.py
common/gpio.py common/gpio.py
common/realtime.py common/realtime.py
common/clock.pyx
common/timeout.py common/timeout.py
common/ffi_wrapper.py common/ffi_wrapper.py
common/file_helpers.py common/file_helpers.py
@ -160,6 +162,8 @@ common/clutil.cc
common/clutil.h common/clutil.h
common/params.h common/params.h
common/params.cc common/params.cc
common/ratekeeper.cc
common/ratekeeper.h
common/watchdog.cc common/watchdog.cc
common/watchdog.h common/watchdog.h
@ -205,6 +209,7 @@ system/hardware/__init__.py
system/hardware/base.h system/hardware/base.h
system/hardware/base.py system/hardware/base.py
system/hardware/hw.h system/hardware/hw.h
system/hardware/hw.py
system/hardware/tici/__init__.py system/hardware/tici/__init__.py
system/hardware/tici/hardware.h system/hardware/tici/hardware.h
system/hardware/tici/hardware.py system/hardware/tici/hardware.py
@ -314,6 +319,8 @@ selfdrive/ui/tests/test_translations.py
selfdrive/ui/qt/*.cc selfdrive/ui/qt/*.cc
selfdrive/ui/qt/*.h selfdrive/ui/qt/*.h
selfdrive/ui/qt/network/*.cc
selfdrive/ui/qt/network/*.h
selfdrive/ui/qt/offroad/*.cc selfdrive/ui/qt/offroad/*.cc
selfdrive/ui/qt/offroad/*.h selfdrive/ui/qt/offroad/*.h
selfdrive/ui/qt/offroad/*.qml selfdrive/ui/qt/offroad/*.qml
@ -350,15 +357,18 @@ selfdrive/manager/process.py
selfdrive/manager/test/__init__.py selfdrive/manager/test/__init__.py
selfdrive/manager/test/test_manager.py selfdrive/manager/test/test_manager.py
selfdrive/modeld/.gitignore
selfdrive/modeld/__init__.py selfdrive/modeld/__init__.py
selfdrive/modeld/SConscript selfdrive/modeld/SConscript
selfdrive/modeld/modeld.cc selfdrive/modeld/modeld.py
selfdrive/modeld/navmodeld.cc selfdrive/modeld/navmodeld.py
selfdrive/modeld/dmonitoringmodeld.cc selfdrive/modeld/dmonitoringmodeld.py
selfdrive/modeld/constants.py selfdrive/modeld/constants.py
selfdrive/modeld/modeld selfdrive/modeld/modeld
selfdrive/modeld/navmodeld
selfdrive/modeld/dmonitoringmodeld selfdrive/modeld/models/__init__.py
selfdrive/modeld/models/*.pxd
selfdrive/modeld/models/*.pyx
selfdrive/modeld/models/commonmodel.cc selfdrive/modeld/models/commonmodel.cc
selfdrive/modeld/models/commonmodel.h selfdrive/modeld/models/commonmodel.h
@ -367,12 +377,8 @@ selfdrive/modeld/models/driving.cc
selfdrive/modeld/models/driving.h selfdrive/modeld/models/driving.h
selfdrive/modeld/models/supercombo.onnx selfdrive/modeld/models/supercombo.onnx
selfdrive/modeld/models/dmonitoring.cc
selfdrive/modeld/models/dmonitoring.h
selfdrive/modeld/models/dmonitoring_model_q.dlc selfdrive/modeld/models/dmonitoring_model_q.dlc
selfdrive/modeld/models/nav.cc
selfdrive/modeld/models/nav.h
selfdrive/modeld/models/navmodel_q.dlc selfdrive/modeld/models/navmodel_q.dlc
selfdrive/modeld/transforms/loadyuv.cc selfdrive/modeld/transforms/loadyuv.cc
@ -388,12 +394,12 @@ selfdrive/modeld/thneed/thneed_common.cc
selfdrive/modeld/thneed/thneed_qcom2.cc selfdrive/modeld/thneed/thneed_qcom2.cc
selfdrive/modeld/thneed/serialize.cc selfdrive/modeld/thneed/serialize.cc
selfdrive/modeld/runners/snpemodel.cc selfdrive/modeld/runners/__init__.py
selfdrive/modeld/runners/snpemodel.h selfdrive/modeld/runners/*.pxd
selfdrive/modeld/runners/thneedmodel.cc selfdrive/modeld/runners/*.pyx
selfdrive/modeld/runners/thneedmodel.h selfdrive/modeld/runners/*.cc
selfdrive/modeld/runners/runmodel.h selfdrive/modeld/runners/*.h
selfdrive/modeld/runners/run.h selfdrive/modeld/runners/*.py
selfdrive/monitoring/dmonitoringd.py selfdrive/monitoring/dmonitoringd.py
selfdrive/monitoring/driver_monitor.py selfdrive/monitoring/driver_monitor.py
@ -565,6 +571,7 @@ opendbc/nissan_x_trail_2017_generated.dbc
opendbc/nissan_leaf_2018_generated.dbc opendbc/nissan_leaf_2018_generated.dbc
opendbc/subaru_global_2017_generated.dbc opendbc/subaru_global_2017_generated.dbc
opendbc/subaru_global_2020_hybrid_generated.dbc
opendbc/subaru_outback_2015_generated.dbc opendbc/subaru_outback_2015_generated.dbc
opendbc/subaru_outback_2019_generated.dbc opendbc/subaru_outback_2019_generated.dbc
opendbc/subaru_forester_2017_generated.dbc opendbc/subaru_forester_2017_generated.dbc

@ -1,5 +1,3 @@
selfdrive/modeld/runners/onnx*
third_party/mapbox-gl-native-qt/x86_64/*.so third_party/mapbox-gl-native-qt/x86_64/*.so
third_party/libyuv/x86_64/** third_party/libyuv/x86_64/**

@ -2,7 +2,7 @@
from collections import Counter from collections import Counter
from pprint import pprint from pprint import pprint
from selfdrive.car.docs import get_all_car_info from openpilot.selfdrive.car.docs import get_all_car_info
if __name__ == "__main__": if __name__ == "__main__":
cars = get_all_car_info() cars = get_all_car_info()

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from system.hardware import HARDWARE from openpilot.system.hardware import HARDWARE
if __name__ == "__main__": if __name__ == "__main__":
HARDWARE.set_power_save(False) HARDWARE.set_power_save(False)

@ -39,9 +39,9 @@ void hexdump(uint32_t *d, int l) {
int main() { int main() {
int fd = open("/dev/mem", O_RDWR); int fd = open("/dev/mem", O_RDWR);
volatile uint32_t *mb = (uint32_t*)mmap(0,0x1000,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0x06400000); volatile uint32_t *mb = (uint32_t *)mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x06400000);
volatile uint32_t *mc = (uint32_t*)mmap(0,0x1000,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0x06480000); volatile uint32_t *mc = (uint32_t *)mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x06480000);
volatile uint32_t *md = (uint32_t*)mmap(0,0x1000,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0x09A20000); volatile uint32_t *md = (uint32_t *)mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x09A20000);
while (1) { while (1) {
printf("PLL MODE:%x L_VAL:%x ALPHA:%x USER_CTL:%x CONFIG_CTL:%x CONFIG_CTL_HI:%x STATUS:%x TEST_CTL_LO:%x TEST_CTL_HI:%x\n", printf("PLL MODE:%x L_VAL:%x ALPHA:%x USER_CTL:%x CONFIG_CTL:%x CONFIG_CTL_HI:%x STATUS:%x TEST_CTL_LO:%x TEST_CTL_HI:%x\n",
mb[C0_PLL_MODE/4], mb[C0_PLL_L_VAL/4], mb[C0_PLL_ALPHA/4], mb[C0_PLL_MODE/4], mb[C0_PLL_L_VAL/4], mb[C0_PLL_ALPHA/4],

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from PyQt5.QtWidgets import QApplication, QLabel # pylint: disable=no-name-in-module, import-error from PyQt5.QtWidgets import QApplication, QLabel
from selfdrive.ui.qt.python_helpers import set_main_window from openpilot.selfdrive.ui.qt.python_helpers import set_main_window
if __name__ == "__main__": if __name__ == "__main__":

@ -1,23 +1,23 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import time
import numpy as np import numpy as np
from common.realtime import sec_since_boot
from multiprocessing import Process from multiprocessing import Process
from setproctitle import setproctitle # pylint: disable=no-name-in-module from setproctitle import setproctitle
def waste(core): def waste(core):
os.sched_setaffinity(0, [core,]) # pylint: disable=no-member os.sched_setaffinity(0, [core,])
m1 = np.zeros((200, 200)) + 0.8 m1 = np.zeros((200, 200)) + 0.8
m2 = np.zeros((200, 200)) + 1.2 m2 = np.zeros((200, 200)) + 1.2
i = 1 i = 1
st = sec_since_boot() st = time.monotonic()
j = 0 j = 0
while 1: while 1:
if (i % 100) == 0: if (i % 100) == 0:
setproctitle("%3d: %8d" % (core, i)) setproctitle("%3d: %8d" % (core, i))
lt = sec_since_boot() lt = time.monotonic()
print("%3d: %8d %f %.2f" % (core, i, lt-st, j)) print("%3d: %8d %f %.2f" % (core, i, lt-st, j))
st = lt st = lt
i += 1 i += 1

@ -1 +1,2 @@
*.cc *.cc
translations_assets.qrc

@ -14,5 +14,7 @@
<file>offroad/icon_wifi_strength_medium.svg</file> <file>offroad/icon_wifi_strength_medium.svg</file>
<file>offroad/icon_wifi_strength_high.svg</file> <file>offroad/icon_wifi_strength_high.svg</file>
<file>offroad/icon_wifi_strength_full.svg</file> <file>offroad/icon_wifi_strength_full.svg</file>
<file alias="languages.json">../ui/translations/languages.json</file>
</qresource> </qresource>
</RCC> </RCC>

@ -30,17 +30,17 @@ from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutExce
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal import log from cereal import log
from cereal.services import service_list from cereal.services import service_list
from common.api import Api from openpilot.common.api import Api
from common.basedir import PERSIST from openpilot.common.basedir import PERSIST
from common.file_helpers import CallbackReader from openpilot.common.file_helpers import CallbackReader
from common.params import Params from openpilot.common.params import Params
from common.realtime import sec_since_boot, set_core_affinity from openpilot.common.realtime import set_core_affinity
from system.hardware import HARDWARE, PC, AGNOS from openpilot.system.hardware import HARDWARE, PC, AGNOS
from system.loggerd.config import ROOT from openpilot.system.loggerd.xattr_cache import getxattr, setxattr
from system.loggerd.xattr_cache import getxattr, setxattr from openpilot.selfdrive.statsd import STATS_DIR
from selfdrive.statsd import STATS_DIR from openpilot.system.swaglog import cloudlog
from system.swaglog import SWAGLOG_DIR, cloudlog from openpilot.system.version import get_commit, get_origin, get_short_branch, get_version
from system.version import get_commit, get_origin, get_short_branch, get_version from openpilot.selfdrive.hardware.hw import Paths
# TODO: use socket constant when mypy recognizes this as a valid attribute # TODO: use socket constant when mypy recognizes this as a valid attribute
@ -75,7 +75,7 @@ class UploadFile:
allow_cellular: bool allow_cellular: bool
@classmethod @classmethod
def from_dict(cls, d: Dict) -> UploadFile: def from_dict(cls, d: dict) -> UploadFile:
return cls(d.get("fn", ""), d.get("url", ""), d.get("headers", {}), d.get("allow_cellular", False)) return cls(d.get("fn", ""), d.get("url", ""), d.get("headers", {}), d.get("allow_cellular", False))
@ -92,7 +92,7 @@ class UploadItem:
allow_cellular: bool = False allow_cellular: bool = False
@classmethod @classmethod
def from_dict(cls, d: Dict) -> UploadItem: def from_dict(cls, d: dict) -> UploadItem:
return cls(d["path"], d["url"], d["headers"], d["created_at"], d["id"], d["retry_count"], d["current"], return cls(d["path"], d["url"], d["headers"], d["created_at"], d["id"], d["retry_count"], d["current"],
d["progress"], d["allow_cellular"]) d["progress"], d["allow_cellular"])
@ -119,12 +119,11 @@ class AbortTransferException(Exception):
class UploadQueueCache: class UploadQueueCache:
params = Params()
@staticmethod @staticmethod
def initialize(upload_queue: Queue[UploadItem]) -> None: def initialize(upload_queue: Queue[UploadItem]) -> None:
try: try:
upload_queue_json = UploadQueueCache.params.get("AthenadUploadQueue") upload_queue_json = Params().get("AthenadUploadQueue")
if upload_queue_json is not None: if upload_queue_json is not None:
for item in json.loads(upload_queue_json): for item in json.loads(upload_queue_json):
upload_queue.put(UploadItem.from_dict(item)) upload_queue.put(UploadItem.from_dict(item))
@ -136,7 +135,7 @@ class UploadQueueCache:
try: try:
queue: List[Optional[UploadItem]] = list(upload_queue.queue) queue: List[Optional[UploadItem]] = list(upload_queue.queue)
items = [asdict(i) for i in queue if i is not None and (i.id not in cancelled_uploads)] items = [asdict(i) for i in queue if i is not None and (i.id not in cancelled_uploads)]
UploadQueueCache.params.put("AthenadUploadQueue", json.dumps(items)) Params().put("AthenadUploadQueue", json.dumps(items))
except Exception: except Exception:
cloudlog.exception("athena.UploadQueueCache.cache.exception") cloudlog.exception("athena.UploadQueueCache.cache.exception")
@ -309,7 +308,7 @@ def _do_upload(upload_item: UploadItem, callback: Optional[Callable] = None) ->
# security: user should be able to request any message from their car # security: user should be able to request any message from their car
@dispatcher.add_method @dispatcher.add_method
def getMessage(service: str, timeout: int = 1000) -> Dict: def getMessage(service: str, timeout: int = 1000) -> dict:
if service is None or service not in service_list: if service is None or service not in service_list:
raise Exception("invalid service") raise Exception("invalid service")
@ -320,7 +319,7 @@ def getMessage(service: str, timeout: int = 1000) -> Dict:
raise TimeoutError raise TimeoutError
# this is because capnp._DynamicStructReader doesn't have typing information # this is because capnp._DynamicStructReader doesn't have typing information
return cast(Dict, ret.to_dict()) return cast(dict, ret.to_dict())
@dispatcher.add_method @dispatcher.add_method
@ -352,7 +351,7 @@ def scan_dir(path: str, prefix: str) -> List[str]:
# (glob and friends traverse entire dir tree) # (glob and friends traverse entire dir tree)
with os.scandir(path) as i: with os.scandir(path) as i:
for e in i: for e in i:
rel_path = os.path.relpath(e.path, ROOT) rel_path = os.path.relpath(e.path, Paths.log_root())
if e.is_dir(follow_symlinks=False): if e.is_dir(follow_symlinks=False):
# add trailing slash # add trailing slash
rel_path = os.path.join(rel_path, '') rel_path = os.path.join(rel_path, '')
@ -367,7 +366,7 @@ def scan_dir(path: str, prefix: str) -> List[str]:
@dispatcher.add_method @dispatcher.add_method
def listDataDirectory(prefix='') -> List[str]: def listDataDirectory(prefix='') -> List[str]:
return scan_dir(ROOT, prefix) return scan_dir(Paths.log_root(), prefix)
@dispatcher.add_method @dispatcher.add_method
@ -408,7 +407,7 @@ def uploadFilesToUrls(files_data: List[UploadFileDict]) -> UploadFilesToUrlRespo
failed.append(file.fn) failed.append(file.fn)
continue continue
path = os.path.join(ROOT, file.fn) path = os.path.join(Paths.log_root(), file.fn)
if not os.path.exists(path) and not os.path.exists(strip_bz2_extension(path)): if not os.path.exists(path) and not os.path.exists(strip_bz2_extension(path)):
failed.append(file.fn) failed.append(file.fn)
continue continue
@ -552,7 +551,7 @@ def getNetworks():
@dispatcher.add_method @dispatcher.add_method
def takeSnapshot() -> Optional[Union[str, Dict[str, str]]]: def takeSnapshot() -> Optional[Union[str, Dict[str, str]]]:
from system.camerad.snapshot.snapshot import jpeg_write, snapshot from openpilot.system.camerad.snapshot.snapshot import jpeg_write, snapshot
ret = snapshot() ret = snapshot()
if ret is not None: if ret is not None:
def b64jpeg(x): def b64jpeg(x):
@ -572,8 +571,8 @@ def get_logs_to_send_sorted() -> List[str]:
# TODO: scan once then use inotify to detect file creation/deletion # TODO: scan once then use inotify to detect file creation/deletion
curr_time = int(time.time()) curr_time = int(time.time())
logs = [] logs = []
for log_entry in os.listdir(SWAGLOG_DIR): for log_entry in os.listdir(Paths.swaglog_root()):
log_path = os.path.join(SWAGLOG_DIR, log_entry) log_path = os.path.join(Paths.swaglog_root(), log_entry)
time_sent = 0 time_sent = 0
try: try:
value = getxattr(log_path, LOG_ATTR_NAME) value = getxattr(log_path, LOG_ATTR_NAME)
@ -593,10 +592,10 @@ def log_handler(end_event: threading.Event) -> None:
return return
log_files = [] log_files = []
last_scan = 0 last_scan = 0.
while not end_event.is_set(): while not end_event.is_set():
try: try:
curr_scan = sec_since_boot() curr_scan = time.monotonic()
if curr_scan - last_scan > 10: if curr_scan - last_scan > 10:
log_files = get_logs_to_send_sorted() log_files = get_logs_to_send_sorted()
last_scan = curr_scan last_scan = curr_scan
@ -608,7 +607,7 @@ def log_handler(end_event: threading.Event) -> None:
cloudlog.debug(f"athena.log_handler.forward_request {log_entry}") cloudlog.debug(f"athena.log_handler.forward_request {log_entry}")
try: try:
curr_time = int(time.time()) curr_time = int(time.time())
log_path = os.path.join(SWAGLOG_DIR, log_entry) log_path = os.path.join(Paths.swaglog_root(), log_entry)
setxattr(log_path, LOG_ATTR_NAME, int.to_bytes(curr_time, 4, sys.byteorder)) setxattr(log_path, LOG_ATTR_NAME, int.to_bytes(curr_time, 4, sys.byteorder))
with open(log_path) as f: with open(log_path) as f:
jsonrpc = { jsonrpc = {
@ -635,7 +634,7 @@ def log_handler(end_event: threading.Event) -> None:
log_success = "result" in log_resp and log_resp["result"].get("success") log_success = "result" in log_resp and log_resp["result"].get("success")
cloudlog.debug(f"athena.log_handler.forward_response {log_entry} {log_success}") cloudlog.debug(f"athena.log_handler.forward_response {log_entry} {log_success}")
if log_entry and log_success: if log_entry and log_success:
log_path = os.path.join(SWAGLOG_DIR, log_entry) log_path = os.path.join(Paths.swaglog_root(), log_entry)
try: try:
setxattr(log_path, LOG_ATTR_NAME, LOG_ATTR_VALUE_MAX_UNIX_TIME) setxattr(log_path, LOG_ATTR_NAME, LOG_ATTR_VALUE_MAX_UNIX_TIME)
except OSError: except OSError:
@ -652,8 +651,8 @@ def log_handler(end_event: threading.Event) -> None:
def stat_handler(end_event: threading.Event) -> None: def stat_handler(end_event: threading.Event) -> None:
while not end_event.is_set(): while not end_event.is_set():
last_scan = 0 last_scan = 0.
curr_scan = sec_since_boot() curr_scan = time.monotonic()
try: try:
if curr_scan - last_scan > 10: if curr_scan - last_scan > 10:
stat_filenames = list(filter(lambda name: not name.startswith(tempfile.gettempprefix()), os.listdir(STATS_DIR))) stat_filenames = list(filter(lambda name: not name.startswith(tempfile.gettempprefix()), os.listdir(STATS_DIR)))
@ -721,7 +720,7 @@ def ws_proxy_send(ws: WebSocket, local_sock: socket.socket, signal_sock: socket.
def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: def ws_recv(ws: WebSocket, end_event: threading.Event) -> None:
last_ping = int(sec_since_boot() * 1e9) last_ping = int(time.monotonic() * 1e9)
while not end_event.is_set(): while not end_event.is_set():
try: try:
opcode, data = ws.recv_data(control_frame=True) opcode, data = ws.recv_data(control_frame=True)
@ -730,10 +729,10 @@ def ws_recv(ws: WebSocket, end_event: threading.Event) -> None:
data = data.decode("utf-8") data = data.decode("utf-8")
recv_queue.put_nowait(data) recv_queue.put_nowait(data)
elif opcode == ABNF.OPCODE_PING: elif opcode == ABNF.OPCODE_PING:
last_ping = int(sec_since_boot() * 1e9) last_ping = int(time.monotonic() * 1e9)
Params().put("LastAthenaPingTime", str(last_ping)) Params().put("LastAthenaPingTime", str(last_ping))
except WebSocketTimeoutException: except WebSocketTimeoutException:
ns_since_last_ping = int(sec_since_boot() * 1e9) - last_ping ns_since_last_ping = int(time.monotonic() * 1e9) - last_ping
if ns_since_last_ping > RECONNECT_TIMEOUT_S * 1e9: if ns_since_last_ping > RECONNECT_TIMEOUT_S * 1e9:
cloudlog.exception("athenad.ws_recv.timeout") cloudlog.exception("athenad.ws_recv.timeout")
end_event.set() end_event.set()

@ -3,10 +3,10 @@
import time import time
from multiprocessing import Process from multiprocessing import Process
from common.params import Params from openpilot.common.params import Params
from selfdrive.manager.process import launcher from openpilot.selfdrive.manager.process import launcher
from system.swaglog import cloudlog from openpilot.system.swaglog import cloudlog
from system.version import get_version, is_dirty from openpilot.system.version import get_version, is_dirty
ATHENA_MGR_PID_PARAM = "AthenadPid" ATHENA_MGR_PID_PARAM = "AthenadPid"

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save