mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-07-09 20:42:12 +08:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10124e8e4e | |||
| 1039cac2fd | |||
| 1700f6cdb0 | |||
| 747db17aa1 | |||
| 9be2f5628d | |||
| 60d6e7efce | |||
| ba11b97c59 | |||
| aaf35b391e | |||
| a3c9d72a1e | |||
| 6fa78b1db4 | |||
| 8f0639ca6c | |||
| 6a0bc76e63 | |||
| 23e27bea92 | |||
| 3be1fde36b | |||
| 473349e7b7 | |||
| 71ffd05027 | |||
| ab8547d04b | |||
| 74eacd2d44 | |||
| 6a059c6877 | |||
| eb9b9a13ff | |||
| 1d4da71784 | |||
| 094b856509 | |||
| 3da0dfd47f | |||
| 6edaf619bf | |||
| 82510dc48c | |||
| 5c6750b2ad | |||
| a13e5816bb | |||
| e1b2b10638 | |||
| 58fb1b44b6 | |||
| d78e66deef | |||
| 4b1920ecce | |||
| dc45bda8b8 | |||
| b824810be2 | |||
| 08962ad619 | |||
| 652a24f13e | |||
| ca92ffcb57 | |||
| 80ebb43365 | |||
| 23f417f450 | |||
| e63ad5a011 | |||
| b8a82755e6 | |||
| 70e7747f74 | |||
| de1b084902 | |||
| 097c976a49 | |||
| 5efd1d63bb | |||
| fa3ce0e2a3 | |||
| 835cb71e3b | |||
| 4603fd0f03 | |||
| 7f2660d0ad | |||
| aa0f588248 | |||
| e3e65de225 | |||
| 37b461a591 | |||
| 2f09489746 | |||
| a9c1e7e277 | |||
| ca0020e613 | |||
| 5326b18130 | |||
| c99299af1f | |||
| 9422a3cf47 | |||
| 872abdda17 | |||
| 1c2e9bd5b1 | |||
| 70cf693d15 | |||
| 06dc945b09 | |||
| 8fc21127bd | |||
| eecffb9c8a | |||
| 71eb28e1d8 | |||
| e605398f55 | |||
| 1a988c716d | |||
| 0403a48e88 | |||
| d9fb57e5f8 | |||
| b38798c1a5 | |||
| 555a753f4c | |||
| 8485cd2013 | |||
| 0fa1ff07f5 | |||
| d2cbdeb683 | |||
| 7c633afe7e | |||
| e5a34e9023 | |||
| 81708364b8 | |||
| e0e940dd55 | |||
| c0b92dee04 | |||
| 249e7c2fa2 | |||
| fae91beaae | |||
| 376cbbdf83 | |||
| b497bc607f | |||
| 90a1ff46b4 | |||
| c05191949a | |||
| 9d5ea83f5a | |||
| 6a74dc253b | |||
| 434a89d83e | |||
| 20e6dc4246 | |||
| 96b5b7caeb | |||
| 85cdce477e | |||
| cc06b0271c | |||
| e67833ae7d | |||
| 42e3061748 | |||
| 738484e628 | |||
| 519ab8ec3e | |||
| 00bc34b125 | |||
| ff87abb45b | |||
| 000bb1b5b3 | |||
| 7c95b43e1a | |||
| 9570a9240f | |||
| 676702ae3c | |||
| c27b112bbc | |||
| 0a1a878897 | |||
| 3a593f85dc | |||
| 6db0d94b83 | |||
| 66b900aea6 | |||
| 9c57f6bad0 | |||
| 1b64a7debd | |||
| bd19439a4c | |||
| 18a237c0c0 | |||
| dd5ff7e1d3 | |||
| 515c00c379 | |||
| 7dc4073c9b | |||
| a9c775bffe | |||
| c06d8db015 | |||
| 8bb6c8fc17 | |||
| d58be609ac | |||
| 434fab00f3 | |||
| 8e62914e69 |
@@ -0,0 +1,3 @@
|
||||
.Xauthority
|
||||
.env
|
||||
.host/
|
||||
@@ -0,0 +1,18 @@
|
||||
FROM ghcr.io/commaai/openpilot-base:latest
|
||||
|
||||
RUN apt update && apt install -y vim net-tools usbutils htop ripgrep tmux wget mesa-utils xvfb libxtst6 libxv1 libglu1-mesa gdb bash-completion
|
||||
RUN python3 -m ensurepip --upgrade
|
||||
RUN pip3 install ipython jupyter jupyterlab
|
||||
|
||||
RUN cd /tmp && \
|
||||
ARCH=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) && \
|
||||
curl -L -o virtualgl.deb "https://github.com/VirtualGL/virtualgl/releases/download/3.1.1/virtualgl_3.1.1_$ARCH.deb" && \
|
||||
dpkg -i virtualgl.deb
|
||||
|
||||
RUN usermod -aG video batman
|
||||
|
||||
USER batman
|
||||
|
||||
RUN cd $HOME && \
|
||||
curl -O https://raw.githubusercontent.com/commaai/agnos-builder/master/userspace/home/.tmux.conf && \
|
||||
curl -O https://raw.githubusercontent.com/commaai/agnos-builder/master/userspace/home/.vimrc
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
TARGET_USER=batman
|
||||
source .devcontainer/.host/.env
|
||||
|
||||
# override display flag for mac hosts
|
||||
if [[ $HOST_OS == darwin ]]; then
|
||||
echo "Setting up DISPLAY override for macOS..."
|
||||
cat <<EOF >> /home/$TARGET_USER/.bashrc
|
||||
source .devcontainer/.host/.env
|
||||
if [ -n "\$HOST_DISPLAY" ]; then
|
||||
DISPLAY_NUM=\$(echo "\$HOST_DISPLAY" | awk -F: '{print \$NF}')
|
||||
export DISPLAY=host.docker.internal:\$DISPLAY_NUM
|
||||
fi
|
||||
EOF
|
||||
fi
|
||||
|
||||
# setup virtualgl for mac hosts
|
||||
if [[ $HOST_OS == darwin ]]; then
|
||||
echo "Setting up virtualgl for macOS..."
|
||||
cat <<EOF >> /home/$TARGET_USER/.bashrc
|
||||
if [ -n "\$HOST_DISPLAY" ]; then
|
||||
export VGL_PORT=10000
|
||||
export VGL_CLIENT=host.docker.internal
|
||||
export VGL_COMPRESS=rgb
|
||||
export VGL_DISPLAY=:99
|
||||
export VGL_FPS=60
|
||||
# prevent vglrun from running exec
|
||||
alias exec=:; source vglrun :; unalias exec
|
||||
fi
|
||||
EOF
|
||||
fi
|
||||
|
||||
# These lines are temporary, to remain backwards compatible with old devcontainers
|
||||
# that were running as root and therefore had their caches written as root
|
||||
sudo chown -R $TARGET_USER: /tmp/scons_cache
|
||||
sudo chown -R $TARGET_USER: /tmp/comma_download_cache
|
||||
sudo chown -R $TARGET_USER: /home/batman/.comma
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source .devcontainer/.host/.env
|
||||
|
||||
# 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
|
||||
|
||||
# virtual display for virtualgl
|
||||
if [[ "$HOST_OS" == "darwin" ]] && [[ -n "$HOST_DISPLAY" ]]; then
|
||||
echo "Starting virtual display at :99 ..."
|
||||
tmux new-session -d -s fakedisplay Xvfb :99 -screen 0 1920x1080x24
|
||||
fi
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "openpilot devcontainer",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"postCreateCommand": ".devcontainer/container_post_create.sh",
|
||||
"postStartCommand": ".devcontainer/container_post_start.sh",
|
||||
"initializeCommand": [".devcontainer/host_setup"],
|
||||
"privileged": true,
|
||||
"containerEnv": {
|
||||
"DISPLAY": "${localEnv:DISPLAY}",
|
||||
"PYTHONPATH": "${containerWorkspaceFolder}",
|
||||
"TERM": "xterm-256color",
|
||||
"force_color_prompt": "1"
|
||||
},
|
||||
"runArgs": [
|
||||
"--volume=/dev:/dev",
|
||||
"--volume=/tmp/.X11-unix:/tmp/.X11-unix",
|
||||
"--volume=${localWorkspaceFolder}/.devcontainer/.host/.Xauthority:/home/batman/.Xauthority",
|
||||
"--volume=${localEnv:HOME}/.comma:/home/batman/.comma",
|
||||
"--volume=${localEnv:HOME}/.azure:/home/batman/.azure",
|
||||
"--volume=/tmp/comma_download_cache:/tmp/comma_download_cache",
|
||||
"--shm-size=1G",
|
||||
"--add-host=host.docker.internal:host-gateway", // required to use host.docker.internal on linux
|
||||
"--publish=0.0.0.0:8070-8079:8070-8079" // body ZMQ services
|
||||
],
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/common-utils:2": {
|
||||
"installZsh": false,
|
||||
"installOhMyZsh": false,
|
||||
"upgradePackages": false,
|
||||
"username": "batman"
|
||||
},
|
||||
"ghcr.io/devcontainers-contrib/features/gh-cli:1": {},
|
||||
"ghcr.io/devcontainers/features/azure-cli:1": {}
|
||||
},
|
||||
"containerUser": "batman",
|
||||
"remoteUser": "batman",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-vscode.cpptools",
|
||||
"ms-toolsai.jupyter",
|
||||
"guyskk.language-cython",
|
||||
"lharri73.dbc"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mounts": [
|
||||
"type=volume,source=scons_cache,target=/tmp/scons_cache"
|
||||
]
|
||||
}
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# pull base image
|
||||
if [[ -z $USE_LOCAL_IMAGE ]]; then
|
||||
echo "Updating openpilot_base image if needed..."
|
||||
docker pull ghcr.io/commaai/openpilot-base:latest
|
||||
fi
|
||||
|
||||
# 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
|
||||
echo "HOST_DISPLAY=\"$DISPLAY\"" >> $HOST_INFO_FILE
|
||||
|
||||
# run virtualgl if macos
|
||||
if [[ $SYSTEM == "darwin" ]]; then
|
||||
echo
|
||||
if [[ -f /opt/VirtualGL/bin/vglclient ]]; then
|
||||
echo "Starting VirtualGL client at port 10000..."
|
||||
VGL_LOG_FILE=".devcontainer/.host/.vgl/vglclient.log"
|
||||
mkdir -p "$(dirname $VGL_LOG_FILE)"
|
||||
/opt/VirtualGL/bin/vglclient -l "$VGL_LOG_FILE" -display "$DISPLAY" -port 10000 -detach
|
||||
else
|
||||
echo "VirtualGL not found. GUI tools may not work properly. Some GUI tools require OpenGL to work properly. To use them with XQuartz on mac, VirtualGL needs to be installed. To install it run:"
|
||||
echo
|
||||
echo " brew install --cask virtualgl"
|
||||
echo
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,10 @@
|
||||
:: pull base image
|
||||
IF NOT DEFINED USE_LOCAL_IMAGE ^
|
||||
echo "Updating openpilot_base image if needed..." && ^
|
||||
docker pull ghcr.io/commaai/openpilot-base:latest
|
||||
|
||||
:: setup .host dir
|
||||
mkdir .devcontainer\.host
|
||||
|
||||
:: setup host env file
|
||||
echo "" > .devcontainer\.host\.env
|
||||
@@ -8,7 +8,7 @@ assignees: ''
|
||||
|
||||
**Checklist**
|
||||
|
||||
- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs
|
||||
- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `selfdrive/opcar/docs.py` to generate new docs
|
||||
- [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/selfdrive/car/tests/routes.py)
|
||||
- [ ] route with openpilot:
|
||||
- [ ] route with stock system:
|
||||
|
||||
@@ -44,7 +44,7 @@ Explain how you tested this bug fix.
|
||||
|
||||
**Checklist**
|
||||
|
||||
- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs
|
||||
- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `selfdrive/opcar/docs.py` to generate new docs
|
||||
- [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/selfdrive/car/tests/routes.py)
|
||||
- [ ] route with openpilot:
|
||||
- [ ] route with stock system:
|
||||
|
||||
@@ -14,25 +14,17 @@ inputs:
|
||||
description: 'whether to save the cache'
|
||||
default: 'false'
|
||||
required: false
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: 'cache hit occurred'
|
||||
value: ${{ (contains(runner.name, 'nsc') && steps.ns-cache.outputs.cache-hit) ||
|
||||
(!contains(runner.name, 'nsc') && inputs.save != 'false' && steps.gha-cache.outputs.cache-hit) ||
|
||||
(!contains(runner.name, 'nsc') && inputs.save == 'false' && steps.gha-cache-ro.outputs.cache-hit) }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: setup namespace cache
|
||||
id: ns-cache
|
||||
if: ${{ contains(runner.name, 'nsc') }}
|
||||
uses: namespacelabs/nscloud-cache-action@v1
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
|
||||
- name: setup github cache
|
||||
id: gha-cache
|
||||
if: ${{ !contains(runner.name, 'nsc') && inputs.save != 'false' }}
|
||||
uses: 'actions/cache@v4'
|
||||
with:
|
||||
@@ -41,7 +33,6 @@ runs:
|
||||
restore-keys: ${{ inputs.restore-keys }}
|
||||
|
||||
- name: setup github cache
|
||||
id: gha-cache-ro
|
||||
if: ${{ !contains(runner.name, 'nsc') && inputs.save == 'false' }}
|
||||
uses: 'actions/cache/restore@v4'
|
||||
with:
|
||||
|
||||
@@ -15,3 +15,7 @@ jobs:
|
||||
uses: commaai/openpilot/.github/workflows/selfdrive_tests.yaml@master
|
||||
with:
|
||||
run_number: ${{ inputs.run_number }}
|
||||
tools_tests:
|
||||
uses: commaai/openpilot/.github/workflows/tools_tests.yaml@master
|
||||
with:
|
||||
run_number: ${{ inputs.run_number }}
|
||||
|
||||
@@ -19,9 +19,8 @@ jobs:
|
||||
docs:
|
||||
name: build docs
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
steps:
|
||||
- uses: commaai/timeout@v1
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
git add .
|
||||
- name: update car docs
|
||||
run: |
|
||||
scons -j$(nproc) --minimal opendbc_repo
|
||||
scons -j$(nproc) --minimal opendbc
|
||||
PYTHONPATH=. python selfdrive/car/docs.py
|
||||
git add docs/CARS.md
|
||||
- name: Create Pull Request
|
||||
|
||||
@@ -32,9 +32,9 @@ env:
|
||||
jobs:
|
||||
build_release:
|
||||
name: build release
|
||||
runs-on:
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-latest' }}
|
||||
env:
|
||||
STRIPPED_DIR: /tmp/releasepilot
|
||||
steps:
|
||||
@@ -75,9 +75,15 @@ jobs:
|
||||
${{ env.RUN }} "scripts/lint/lint.sh --skip check_added_large_files"
|
||||
|
||||
build:
|
||||
runs-on:
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ${{ fromJson(
|
||||
((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && '["x86_64", "aarch64"]' || '["x86_64"]' ) }}
|
||||
runs-on: ${{ ((matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8') ||
|
||||
((matrix.arch == 'x86_64') && ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16') ||
|
||||
'ubuntu-latest'}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -86,14 +92,17 @@ jobs:
|
||||
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
|
||||
run: |
|
||||
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
|
||||
echo "TARGET_ARCHITECTURE=${{ matrix.arch }}" >> "$GITHUB_ENV"
|
||||
$DOCKER_LOGIN
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
with:
|
||||
docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }}
|
||||
- uses: ./.github/workflows/compile-openpilot
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache
|
||||
|
||||
build_mac:
|
||||
name: build macOS
|
||||
runs-on: ${{ github.repository == 'commaai/openpilot' && 'namespace-profile-macos-8x14' || 'macos-latest' }}
|
||||
runs-on: namespace-profile-macos-8x14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -116,11 +125,28 @@ jobs:
|
||||
- name: Building openpilot
|
||||
run: . .venv/bin/activate && scons -j$(nproc)
|
||||
|
||||
docker_push_multiarch:
|
||||
name: docker push multiarch tag
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: false
|
||||
- name: Setup docker
|
||||
run: |
|
||||
$DOCKER_LOGIN
|
||||
- name: Merge x64 and arm64 tags
|
||||
run: |
|
||||
export PUSH_IMAGE=true
|
||||
scripts/retry.sh selfdrive/test/docker_tag_multiarch.sh base x86_64 aarch64
|
||||
|
||||
static_analysis:
|
||||
name: static analysis
|
||||
runs-on:
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
env:
|
||||
PYTHONWARNINGS: default
|
||||
steps:
|
||||
@@ -134,16 +160,24 @@ jobs:
|
||||
|
||||
unit_tests:
|
||||
name: unit tests
|
||||
runs-on:
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
with:
|
||||
docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }}
|
||||
- name: Build openpilot
|
||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Setup cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
path: .ci_cache/comma_download_cache
|
||||
key: unit_tests_${{ hashFiles('.github/workflows/selfdrive_tests.yaml') }}
|
||||
- name: Run unit tests
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 1 || 20 }}
|
||||
run: |
|
||||
@@ -161,25 +195,27 @@ jobs:
|
||||
|
||||
process_replay:
|
||||
name: process replay
|
||||
runs-on:
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
with:
|
||||
docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }}
|
||||
- name: Cache test routes
|
||||
id: dependency-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .ci_cache/comma_download_cache
|
||||
key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_processes.py') }}
|
||||
key: proc-replay-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_regen.py') }}
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run replay
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.dependency-cache.outputs.cache-hit == 'true') && 1 || 20 }}
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 1 || 20 }}
|
||||
run: |
|
||||
${{ env.RUN }} "coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
|
||||
chmod -R 777 /tmp/comma_download_cache && \
|
||||
@@ -214,9 +250,9 @@ jobs:
|
||||
|
||||
test_cars:
|
||||
name: cars
|
||||
runs-on:
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-latest' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -227,17 +263,17 @@ jobs:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Cache test routes
|
||||
id: routes-cache
|
||||
uses: actions/cache@v4
|
||||
id: dependency-cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
path: .ci_cache/comma_download_cache
|
||||
key: car_models-${{ hashFiles('selfdrive/car/tests/test_models.py', 'selfdrive/car/tests/routes.py') }}-${{ matrix.job }}
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Test car models
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.routes-cache.outputs.cache-hit == 'true') && 1 || 20 }}
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 1 || 20 }}
|
||||
run: |
|
||||
${{ env.RUN }} "MAX_EXAMPLES=1 $PYTEST selfdrive/car/tests/test_models.py && \
|
||||
${{ env.RUN }} "FILEREADER_CACHE=1 MAX_EXAMPLES=1 $PYTEST selfdrive/car/tests/test_models.py && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
env:
|
||||
NUM_JOBS: 4
|
||||
@@ -306,9 +342,9 @@ jobs:
|
||||
|
||||
simulator_driving:
|
||||
name: simulator driving
|
||||
runs-on:
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -327,9 +363,9 @@ jobs:
|
||||
create_ui_report:
|
||||
# This job name needs to be the same as UI_JOB_NAME in ui_preview.yaml
|
||||
name: Create UI Report
|
||||
runs-on:
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
- ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-experiments:docker.builds.local-cache=separate' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
@@ -17,6 +17,7 @@ runs:
|
||||
uses: ./.github/workflows/setup
|
||||
continue-on-error: true
|
||||
with:
|
||||
docker_hub_pat: ${{ inputs.docker_hub_pat }}
|
||||
is_retried: true
|
||||
- if: steps.setup1.outcome == 'failure'
|
||||
shell: bash
|
||||
@@ -26,6 +27,7 @@ runs:
|
||||
uses: ./.github/workflows/setup
|
||||
continue-on-error: true
|
||||
with:
|
||||
docker_hub_pat: ${{ inputs.docker_hub_pat }}
|
||||
is_retried: true
|
||||
- if: steps.setup2.outcome == 'failure'
|
||||
shell: bash
|
||||
@@ -34,4 +36,5 @@ runs:
|
||||
if: steps.setup2.outcome == 'failure'
|
||||
uses: ./.github/workflows/setup
|
||||
with:
|
||||
docker_hub_pat: ${{ inputs.docker_hub_pat }}
|
||||
is_retried: true
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
name: 'openpilot env setup'
|
||||
|
||||
inputs:
|
||||
docker_hub_pat:
|
||||
description: 'Auth token for Docker Hub, required for BuildJet jobs'
|
||||
required: true
|
||||
default: ''
|
||||
is_retried:
|
||||
description: 'A mock param that asserts that we use the setup-with-retry instead of this action directly'
|
||||
required: false
|
||||
@@ -20,9 +24,11 @@ runs:
|
||||
name: No retries!
|
||||
run: |
|
||||
if [ "${{ github.run_attempt }}" -gt 1 ]; then
|
||||
echo -e "\033[0;31m##################################################"
|
||||
echo -e "\033[0;31m Retries not allowed! Fix the flaky test! "
|
||||
echo -e "\033[0;31m##################################################\033[0m"
|
||||
echo -e "\033[31m"
|
||||
echo "##################################################"
|
||||
echo " Retries not allowed! Fix the flaky test! "
|
||||
echo "##################################################"
|
||||
echo -e "\033[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -30,6 +36,19 @@ runs:
|
||||
- shell: bash
|
||||
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
|
||||
- id: date
|
||||
shell: bash
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
name: tools
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_number:
|
||||
default: '1'
|
||||
required: true
|
||||
type: string
|
||||
concurrency:
|
||||
group: tools-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
BASE_IMAGE: openpilot-base
|
||||
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
BUILD: selfdrive/test/docker_build.sh base
|
||||
|
||||
RUN: docker run --shm-size 2G -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/bash -c
|
||||
|
||||
|
||||
jobs:
|
||||
devcontainer:
|
||||
name: devcontainer
|
||||
runs-on: ubuntu-latest
|
||||
if: false # we can re-enable once this is faster
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Use local image for testing devcontainer with latest base image
|
||||
run: |
|
||||
echo "USE_LOCAL_IMAGE=true" >> "$GITHUB_ENV"
|
||||
- name: Setup Dev Container CLI
|
||||
run: npm install -g @devcontainers/cli
|
||||
- name: Build dev container image
|
||||
run: ./scripts/retry.sh devcontainer build --workspace-folder .
|
||||
- name: Run dev container
|
||||
run: |
|
||||
mkdir -p /tmp/devcontainer_scons_cache/
|
||||
cp -r $GITHUB_WORKSPACE/.ci_cache/scons_cache/. /tmp/devcontainer_scons_cache/
|
||||
devcontainer up --workspace-folder .
|
||||
- name: Test environment
|
||||
run: |
|
||||
devcontainer exec --workspace-folder . scons -j$(nproc) cereal/ common/
|
||||
devcontainer exec --workspace-folder . pip3 install pip-install-test
|
||||
devcontainer exec --workspace-folder . touch /home/batman/.comma/auth.json
|
||||
devcontainer exec --workspace-folder . sudo touch /root/test.txt
|
||||
+6
-6
@@ -1,18 +1,18 @@
|
||||
[submodule "panda"]
|
||||
path = panda
|
||||
url = ../../commaai/panda.git
|
||||
url = https://github.com/sunnyhaibin/panda.git
|
||||
[submodule "opendbc"]
|
||||
path = opendbc_repo
|
||||
url = ../../commaai/opendbc.git
|
||||
url = https://github.com/sunnypilot/opendbc.git
|
||||
[submodule "msgq"]
|
||||
path = msgq_repo
|
||||
url = ../../commaai/msgq.git
|
||||
url = https://github.com/sunnypilot/msgq.git
|
||||
[submodule "rednose_repo"]
|
||||
path = rednose_repo
|
||||
url = ../../commaai/rednose.git
|
||||
url = https://github.com/commaai/rednose.git
|
||||
[submodule "teleoprtc_repo"]
|
||||
path = teleoprtc_repo
|
||||
url = ../../commaai/teleoprtc
|
||||
url = https://github.com/commaai/teleoprtc
|
||||
[submodule "tinygrad"]
|
||||
path = tinygrad_repo
|
||||
url = https://github.com/commaai/tinygrad.git
|
||||
url = https://github.com/tinygrad/tinygrad.git
|
||||
@@ -68,14 +68,17 @@ RUN usermod -aG sudo $USER
|
||||
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||
USER $USER
|
||||
|
||||
COPY --chown=$USER pyproject.toml uv.lock /home/$USER
|
||||
COPY --chown=$USER tools/install_python_dependencies.sh /home/$USER/tools/
|
||||
COPY --chown=$USER pyproject.toml uv.lock /tmp/
|
||||
COPY --chown=$USER tools/install_python_dependencies.sh /tmp/tools/
|
||||
|
||||
ENV VIRTUAL_ENV=/home/$USER/.venv
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
RUN cd /home/$USER && \
|
||||
RUN cd /tmp && \
|
||||
tools/install_python_dependencies.sh && \
|
||||
rm -rf tools/ pyproject.toml uv.lock .cache
|
||||
mkdir -p $VIRTUAL_ENV && \
|
||||
cp -r /tmp/.venv/* $VIRTUAL_ENV && \
|
||||
rm -rf /tmp/* && \
|
||||
rm -rf /home/$USER/.cache
|
||||
|
||||
USER root
|
||||
RUN sudo git config --global --add safe.directory /tmp/openpilot
|
||||
|
||||
Vendored
+4
-13
@@ -79,10 +79,6 @@ def deviceStage(String stageName, String deviceType, List extra_env, def steps)
|
||||
return
|
||||
}
|
||||
|
||||
if (isReplay()) {
|
||||
error("REPLAYING TESTS IS NOT ALLOWED. FIX THEM INSTEAD.")
|
||||
}
|
||||
|
||||
def extra = extra_env.collect { "export ${it}" }.join('\n');
|
||||
def branch = env.BRANCH_NAME ?: 'master';
|
||||
def gitDiff = sh returnStdout: true, script: 'curl -s -H "Authorization: Bearer ${GITHUB_COMMENTS_TOKEN}" https://api.github.com/repos/commaai/openpilot/compare/master...${GIT_BRANCH} | jq .files[].filename || echo "/"', label: 'Getting changes'
|
||||
@@ -127,11 +123,6 @@ def hasPathChanged(String gitDiff, List<String> paths) {
|
||||
return false
|
||||
}
|
||||
|
||||
def isReplay() {
|
||||
def replayClass = "org.jenkinsci.plugins.workflow.cps.replay.ReplayCause"
|
||||
return currentBuild.rawBuild.getCauses().any{ cause -> cause.toString().contains(replayClass) }
|
||||
}
|
||||
|
||||
def setupCredentials() {
|
||||
withCredentials([
|
||||
string(credentialsId: 'azure_token', variable: 'AZURE_TOKEN'),
|
||||
@@ -216,8 +207,8 @@ node {
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
||||
step("test pigeond", "pytest system/ubloxd/tests/test_pigeond.py", [diffPaths: ["system/ubloxd/"]]),
|
||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"),
|
||||
step("test pigeond", "pytest system/ubloxd/tests/test_pigeond.py"),
|
||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
||||
])
|
||||
},
|
||||
@@ -252,7 +243,7 @@ node {
|
||||
'replay': {
|
||||
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/"]]),
|
||||
])
|
||||
},
|
||||
'tizi': {
|
||||
@@ -262,7 +253,7 @@ node {
|
||||
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||
step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]),
|
||||
step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py"),
|
||||
])
|
||||
},
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Custom MIT License
|
||||
|
||||
Copyright (c) 2024, Haibin Wen, SUNNYPILOT LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to view and modify the Software, subject to the following conditions:
|
||||
|
||||
1. **Permission Required**: Permission Required for Commercial, For-Profit, or Closed Source Use: Use of the Software, in whole or in part, for any commercial purposes, for-profit projects, or in closed source projects requires explicit written permission from the original author(s).
|
||||
|
||||
2. **Redistribution**: Any redistribution of the Software, modified or unmodified, must retain this license notice and the following acknowledgment:
|
||||
"This software is licensed under a custom license requiring permission for use."
|
||||
|
||||
3. **Visibility**: Any project that uses the Software must visibly mention the following acknowledgment:
|
||||
"This project uses software from Haibin Wen and SUNNYPILOT LLC and is licensed under a custom license requiring permission for use."
|
||||
|
||||
4. **No Warranty**: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Contact sunnypilot Support <support@sunnypilot.ai> for permission requests.
|
||||
|
||||
---
|
||||
|
||||
Haibin Wen, SUNNYPILOT LLC
|
||||
+1
-2
@@ -349,7 +349,7 @@ Export('common', 'gpucommon')
|
||||
env_swaglog = env.Clone()
|
||||
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
|
||||
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
|
||||
SConscript(['opendbc_repo/SConscript'], exports={'env': env_swaglog})
|
||||
SConscript(['opendbc/can/SConscript'], exports={'env': env_swaglog})
|
||||
|
||||
SConscript(['cereal/SConscript'])
|
||||
|
||||
@@ -366,7 +366,6 @@ SConscript(['rednose/SConscript'])
|
||||
|
||||
# Build system services
|
||||
SConscript([
|
||||
'system/ui/SConscript',
|
||||
'system/proclogd/SConscript',
|
||||
'system/ubloxd/SConscript',
|
||||
'system/loggerd/SConscript',
|
||||
|
||||
+17
-1
@@ -8,7 +8,23 @@ $Cxx.namespace("cereal");
|
||||
# cereal, so use these if you want custom events in your fork.
|
||||
|
||||
# you can rename the struct, but don't change the identifier
|
||||
struct CustomReserved0 @0x81c2f05a394cf4af {
|
||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||
mads @0 :ModularAssistiveDrivingSystem;
|
||||
|
||||
struct ModularAssistiveDrivingSystem {
|
||||
state @0 :ModularAssistiveDrivingSystemState;
|
||||
enabled @1 :Bool;
|
||||
active @2 :Bool;
|
||||
available @3 :Bool;
|
||||
|
||||
enum ModularAssistiveDrivingSystemState {
|
||||
disabled @0;
|
||||
paused @1;
|
||||
enabled @2;
|
||||
softDisabling @3;
|
||||
overriding @4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomReserved1 @0xaedffd8f31e7b55d {
|
||||
|
||||
+13
-5
@@ -126,6 +126,17 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
||||
personalityChanged @91;
|
||||
aeb @92;
|
||||
|
||||
# sunnypilot
|
||||
lkasEnable @93;
|
||||
lkasDisable @94;
|
||||
manualSteeringRequired @95;
|
||||
manualLongitudinalRequired @96;
|
||||
silentPedalPressed @97;
|
||||
silentLkasEnable @98;
|
||||
silentBrakeHold @99;
|
||||
silentWrongGear @100;
|
||||
silentReverseGear @101;
|
||||
|
||||
soundsUnavailableDEPRECATED @47;
|
||||
}
|
||||
}
|
||||
@@ -486,9 +497,6 @@ struct DeviceState @0xa4d8b5af2aa492eb {
|
||||
nvmeTempC @35 :List(Float32);
|
||||
modemTempC @36 :List(Float32);
|
||||
pmicTempC @39 :List(Float32);
|
||||
intakeTempC @46 :Float32;
|
||||
exhaustTempC @47 :Float32;
|
||||
caseTempC @48 :Float32;
|
||||
maxTempC @44 :Float32; # max of other temps, used to control fan
|
||||
thermalZones @38 :List(ThermalZone);
|
||||
thermalStatus @14 :ThermalStatus;
|
||||
@@ -589,6 +597,7 @@ struct PandaState @0xa7649e2575e4591e {
|
||||
|
||||
# safety stuff
|
||||
controlsAllowed @3 :Bool;
|
||||
controlsAllowedLat @5 :Bool;
|
||||
safetyRxInvalid @19 :UInt32;
|
||||
safetyTxBlocked @24 :UInt32;
|
||||
safetyModel @14 :Car.CarParams.SafetyModel;
|
||||
@@ -696,7 +705,6 @@ struct PandaState @0xa7649e2575e4591e {
|
||||
}
|
||||
|
||||
gasInterceptorDetectedDEPRECATED @4 :Bool;
|
||||
startedSignalDetectedDEPRECATED @5 :Bool;
|
||||
hasGpsDEPRECATED @6 :Bool;
|
||||
gmlanSendErrsDEPRECATED @9 :UInt32;
|
||||
fanSpeedRpmDEPRECATED @11 :UInt16;
|
||||
@@ -2547,7 +2555,7 @@ struct Event {
|
||||
customReservedRawData2 @126 :Data;
|
||||
|
||||
# *********** Custom: reserved for forks ***********
|
||||
customReserved0 @107 :Custom.CustomReserved0;
|
||||
selfdriveStateSP @107 :Custom.SelfdriveStateSP;
|
||||
customReserved1 @108 :Custom.CustomReserved1;
|
||||
customReserved2 @109 :Custom.CustomReserved2;
|
||||
customReserved3 @110 :Custom.CustomReserved3;
|
||||
|
||||
@@ -17,5 +17,5 @@ class TestServices:
|
||||
|
||||
def test_generated_header(self):
|
||||
with tempfile.NamedTemporaryFile(suffix=".h") as f:
|
||||
ret = os.system(f"python3 {services.__file__} > {f.name} && clang++ {f.name} -std=c++11")
|
||||
ret = os.system(f"python3 {services.__file__} > {f.name} && clang++ {f.name}")
|
||||
assert ret == 0, "generated services header is not valid C"
|
||||
|
||||
@@ -73,6 +73,9 @@ _services: dict[str, tuple] = {
|
||||
"userFlag": (True, 0., 1),
|
||||
"microphone": (True, 10., 10),
|
||||
|
||||
# sunnypilot
|
||||
"selfdriveStateSP": (True, 100., 10),
|
||||
|
||||
# debug
|
||||
"uiDebug": (True, 0., 1),
|
||||
"testJoystick": (True, 0.),
|
||||
|
||||
@@ -112,6 +112,7 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"DisablePowerDown", PERSISTENT},
|
||||
{"DisableUpdates", PERSISTENT},
|
||||
{"DisengageOnAccelerator", PERSISTENT},
|
||||
{"DmModelInitialized", CLEAR_ON_ONROAD_TRANSITION},
|
||||
{"DongleId", PERSISTENT},
|
||||
{"DoReboot", CLEAR_ON_MANAGER_START},
|
||||
{"DoShutdown", CLEAR_ON_MANAGER_START},
|
||||
@@ -200,6 +201,12 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"UpdaterTargetBranch", CLEAR_ON_MANAGER_START},
|
||||
{"UpdaterLastFetchTime", PERSISTENT},
|
||||
{"Version", PERSISTENT},
|
||||
|
||||
// sunnypilot params
|
||||
{"Mads", PERSISTENT},
|
||||
{"MadsCruiseMain", PERSISTENT},
|
||||
{"MadsDisengageLateralOnBrake", PERSISTENT},
|
||||
{"MadsUnifiedEngagementMode", PERSISTENT},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ public:
|
||||
if (prefix.empty()) {
|
||||
prefix = util::random_string(15);
|
||||
}
|
||||
msgq_path = Path::shm_path() + "/" + prefix;
|
||||
msgq_path = "/dev/shm/" + prefix;
|
||||
bool ret = util::create_directories(msgq_path, 0777);
|
||||
assert(ret);
|
||||
setenv("OPENPILOT_PREFIX", prefix.c_str(), 1);
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||
class OpenpilotPrefix:
|
||||
def __init__(self, prefix: str = None, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
|
||||
self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15])
|
||||
self.msgq_path = os.path.join(Paths.shm_path(), self.prefix)
|
||||
self.msgq_path = os.path.join('/dev/shm', self.prefix)
|
||||
self.clean_dirs_on_exit = clean_dirs_on_exit
|
||||
self.shared_download_cache = shared_download_cache
|
||||
|
||||
|
||||
+2
-6
@@ -48,13 +48,13 @@ class Ratekeeper:
|
||||
def __init__(self, rate: float, print_delay_threshold: float | None = 0.0) -> None:
|
||||
"""Rate in Hz for ratekeeping. print_delay_threshold must be nonnegative."""
|
||||
self._interval = 1. / rate
|
||||
self._next_frame_time = time.monotonic() + self._interval
|
||||
self._print_delay_threshold = print_delay_threshold
|
||||
self._frame = 0
|
||||
self._remaining = 0.0
|
||||
self._process_name = getproctitle()
|
||||
self._dts = deque([self._interval], maxlen=100)
|
||||
self._last_monitor_time = -1.
|
||||
self._next_frame_time = -1.
|
||||
self._last_monitor_time = time.monotonic()
|
||||
|
||||
@property
|
||||
def frame(self) -> int:
|
||||
@@ -79,10 +79,6 @@ class Ratekeeper:
|
||||
|
||||
# Monitors the cumulative lag, but does not enforce a rate
|
||||
def monitor_time(self) -> bool:
|
||||
if self._last_monitor_time < 0:
|
||||
self._next_frame_time = time.monotonic() + self._interval
|
||||
self._last_monitor_time = time.monotonic()
|
||||
|
||||
prev = self._last_monitor_time
|
||||
self._last_monitor_time = time.monotonic()
|
||||
self._dts.append(self._last_monitor_time - prev)
|
||||
|
||||
+1
-2
@@ -2,9 +2,8 @@
|
||||
|
||||
#include "common/watchdog.h"
|
||||
#include "common/util.h"
|
||||
#include "system/hardware/hw.h"
|
||||
|
||||
const std::string watchdog_fn_prefix = Path::shm_path() + "/wd_"; // + <pid>
|
||||
const std::string watchdog_fn_prefix = "/dev/shm/wd_"; // + <pid>
|
||||
|
||||
bool watchdog_kick(uint64_t ts) {
|
||||
static std::string fn = watchdog_fn_prefix + std::to_string(getpid());
|
||||
|
||||
+2
-2
@@ -167,7 +167,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Lexus|ES 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES 2017-18">Buy Here</a></sub></details>||
|
||||
|Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES 2019-24">Buy Here</a></sub></details>||
|
||||
|Lexus|ES Hybrid 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES Hybrid 2017-18">Buy Here</a></sub></details>||
|
||||
|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES Hybrid 2019-25">Buy Here</a></sub></details>|<a href="https://youtu.be/BZ29osRVJeg?t=12" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Lexus|ES Hybrid 2019-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES Hybrid 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/BZ29osRVJeg?t=12" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=GS F 2016">Buy Here</a></sub></details>||
|
||||
|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=IS 2017-19">Buy Here</a></sub></details>||
|
||||
|Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=IS 2022-23">Buy Here</a></sub></details>||
|
||||
@@ -188,7 +188,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lincoln&model=Aviator Plug-in Hybrid 2020-24">Buy Here</a></sub></details>||
|
||||
|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=MAN&model=eTGE 2020-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=MAN&model=TGE 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Mazda&model=CX-5 2022-25">Buy Here</a></sub></details>||
|
||||
|Mazda|CX-5 2022-24|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Mazda&model=CX-5 2022-24">Buy Here</a></sub></details>||
|
||||
|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Mazda&model=CX-9 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/dA3duO4a0O4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Nissan&model=Altima 2019-20">Buy Here</a></sub></details>||
|
||||
|Nissan|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Nissan&model=Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
||||
export VECLIB_MAXIMUM_THREADS=1
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="11.3"
|
||||
export AGNOS_VERSION="11.2"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
+1
-1
Submodule msgq_repo updated: 434ed2312c...3e17f865bb
+1
-1
Submodule opendbc_repo updated: cc30feb6fb...0a55b1a7aa
+1
-1
Submodule panda updated: c7cc2deaf0...1ae97c112e
+1
-4
@@ -31,9 +31,6 @@ dependencies = [
|
||||
# body / webrtcd
|
||||
"aiohttp",
|
||||
"aiortc",
|
||||
# aiortc does not put an upper bound on pyopenssl and is now incompatible
|
||||
# with the latest release
|
||||
"pyopenssl < 24.3.0",
|
||||
"pyaudio",
|
||||
|
||||
# panda
|
||||
@@ -101,6 +98,7 @@ dev = [
|
||||
"azure-identity",
|
||||
"azure-storage-blob",
|
||||
"dictdiffer",
|
||||
"flaky",
|
||||
"lru-dict",
|
||||
"matplotlib",
|
||||
"parameterized >=0.8, <0.9",
|
||||
@@ -241,7 +239,6 @@ exclude = [
|
||||
"cereal",
|
||||
"panda",
|
||||
"opendbc",
|
||||
"opendbc_repo",
|
||||
"rednose_repo",
|
||||
"tinygrad_repo",
|
||||
"teleoprtc",
|
||||
|
||||
@@ -32,6 +32,7 @@ blacklist = [
|
||||
|
||||
".git/",
|
||||
".github/",
|
||||
".devcontainer/",
|
||||
"Darwin/",
|
||||
".vscode",
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ for f in sorted(pyf):
|
||||
lns = len(src.split("\n"))
|
||||
tree = ast.parse(src)
|
||||
Analyzer().visit(tree)
|
||||
print(f"{lns:5d} {f} {xbit}")
|
||||
print("%5d %s %s" % (lns, f, xbit))
|
||||
if 'test' in f:
|
||||
testlns += lns
|
||||
elif f.startswith(('tools/', 'scripts/', 'selfdrive/debug')):
|
||||
@@ -47,8 +47,8 @@ for f in sorted(pyf):
|
||||
else:
|
||||
tlns += lns
|
||||
|
||||
print(f"{tlns} lines of openpilot python")
|
||||
print(f"{carlns} lines of car ports")
|
||||
print(f"{scriptlns} lines of tools/scripts/debug")
|
||||
print(f"{testlns} lines of tests")
|
||||
print("%d lines of openpilot python" % tlns)
|
||||
print("%d lines of car ports" % carlns)
|
||||
print("%d lines of tools/scripts/debug" % scriptlns)
|
||||
print("%d lines of tests" % testlns)
|
||||
#print(sorted(list(imps)))
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
FAIL=0
|
||||
|
||||
if grep -n '#include "third_party/raylib/include/raylib\.h"' $@ | grep -v '^system/ui/raylib/raylib\.h'; then
|
||||
echo -e "Bad raylib include found! Use '#include \"system/ui/raylib/raylib.h\"' instead\n"
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
exit $FAIL
|
||||
@@ -53,7 +53,6 @@ function run_tests() {
|
||||
run "check_shebang_scripts_are_executable" python3 -m pre_commit_hooks.check_shebang_scripts_are_executable $ALL_FILES
|
||||
run "check_shebang_format" $DIR/check_shebang_format.sh $ALL_FILES
|
||||
run "check_nomerge_comments" $DIR/check_nomerge_comments.sh $ALL_FILES
|
||||
run "check_raylib_includes" $DIR/check_raylib_includes.sh $ALL_FILES
|
||||
|
||||
if [[ -z "$FAST" ]]; then
|
||||
run "mypy" mypy $PYTHON_FILES
|
||||
|
||||
+2
-2
@@ -16,9 +16,9 @@ def waste(core):
|
||||
j = 0
|
||||
while 1:
|
||||
if (i % 100) == 0:
|
||||
setproctitle(f"{core:3d}: {i:8d}")
|
||||
setproctitle("%3d: %8d" % (core, i))
|
||||
lt = time.monotonic()
|
||||
print(f"{core:3d}: {i:8d} {lt-st:f} {j:.2f}")
|
||||
print("%3d: %8d %f %.2f" % (core, i, lt-st, j))
|
||||
st = lt
|
||||
i += 1
|
||||
j = np.sum(np.matmul(m1, m2))
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f7565541b4e6213221174839b9b2b67397ced0b9807ea56413989fd37325b3b6
|
||||
size 4908
|
||||
@@ -148,7 +148,8 @@ class CarSpecificEvents:
|
||||
# To avoid re-engaging when openpilot cancels, check user engagement intention via buttons
|
||||
# Main button also can trigger an engagement on these cars
|
||||
self.cruise_buttons.append(any(ev.type in HYUNDAI_ENABLE_BUTTONS for ev in CS.buttonEvents))
|
||||
events = self.create_common_events(CS, CS_prev, pcm_enable=self.CP.pcmCruise, allow_enable=any(self.cruise_buttons))
|
||||
events = self.create_common_events(CS, CS_prev, pcm_enable=self.CP.pcmCruise, allow_enable=any(self.cruise_buttons),
|
||||
allow_cancel=any(self.CP.pcmCruise and b.type == ButtonType.cancel and b.pressed for b in CS.buttonEvents) or not self.CP.pcmCruise)
|
||||
|
||||
# low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s)
|
||||
if CS.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.:
|
||||
@@ -164,7 +165,8 @@ class CarSpecificEvents:
|
||||
return events
|
||||
|
||||
def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears=None, pcm_enable=True,
|
||||
allow_enable=True, enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise)):
|
||||
allow_enable=True, enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise),
|
||||
allow_cancel=True):
|
||||
events = Events()
|
||||
|
||||
if CS.doorOpen:
|
||||
@@ -215,7 +217,7 @@ class CarSpecificEvents:
|
||||
if not self.CP.pcmCruise and (b.type in enable_buttons and not b.pressed):
|
||||
events.add(EventName.buttonEnable)
|
||||
# Disable on rising and falling edge of cancel for both stock and OP long
|
||||
if b.type == ButtonType.cancel:
|
||||
if b.type == ButtonType.cancel and allow_cancel:
|
||||
events.add(EventName.buttonCancel)
|
||||
|
||||
# Handle permanent and temporary steering faults
|
||||
|
||||
@@ -22,6 +22,8 @@ from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
|
||||
from openpilot.selfdrive.car.cruise import VCruiseHelper
|
||||
from openpilot.selfdrive.car.car_specific import MockCarState
|
||||
|
||||
from opendbc.sunnypilot.mads.mads import MadsParams
|
||||
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
|
||||
EventName = log.OnroadEvent.EventName
|
||||
@@ -113,6 +115,11 @@ class Car:
|
||||
if not disengage_on_accelerator:
|
||||
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
|
||||
|
||||
# mads
|
||||
data_services = list(self.sm.data.keys()) + ['selfdriveStateSP']
|
||||
self.sm = messaging.SubMaster(data_services, poll='selfdriveStateSP')
|
||||
MadsParams().set_alternative_experience(self.CP, self.params)
|
||||
|
||||
openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle")
|
||||
|
||||
controller_available = self.CI.CC is not None and openpilot_enabled_toggle and not self.CP.dashcamOnly
|
||||
|
||||
@@ -17,7 +17,7 @@ class TestCarDocs:
|
||||
with open(CARS_MD_OUT) as f:
|
||||
current_cars_md = f.read()
|
||||
|
||||
assert generated_cars_md == current_cars_md, "Run selfdrive/car/docs.py to update the compatibility documentation"
|
||||
assert generated_cars_md == current_cars_md, "Run selfdrive/opcar/docs.py to update the compatibility documentation"
|
||||
|
||||
def test_docs_diff(self):
|
||||
dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump")
|
||||
|
||||
@@ -394,7 +394,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
for msg in filter(lambda m: m.src in range(64), can.can):
|
||||
to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
|
||||
ret = self.safety.safety_rx_hook(to_send)
|
||||
self.assertEqual(1, ret, f"safety rx failed ({ret=}): {(msg.address, msg.src % 4)}")
|
||||
self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}")
|
||||
|
||||
# Skip first frame so CS_prev is properly initialized
|
||||
if idx == 0:
|
||||
|
||||
@@ -56,6 +56,9 @@ class Controls:
|
||||
elif self.CP.lateralTuning.which() == 'torque':
|
||||
self.LaC = LatControlTorque(self.CP, self.CI)
|
||||
|
||||
data_services = list(self.sm.data.keys()) + ['selfdriveStateSP']
|
||||
self.sm = messaging.SubMaster(data_services, poll='selfdriveState')
|
||||
|
||||
def update(self):
|
||||
self.sm.update(15)
|
||||
if self.sm.updated["liveCalibration"]:
|
||||
@@ -88,7 +91,12 @@ class Controls:
|
||||
|
||||
# Check which actuators can be enabled
|
||||
standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill
|
||||
CC.latActive = self.sm['selfdriveState'].active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and not standstill
|
||||
|
||||
ss_sp = self.sm['selfdriveStateSP']
|
||||
CC.madsActive = ss_sp.mads.available and ss_sp.mads.active
|
||||
|
||||
_lat_active = ss_sp.mads.enabled if ss_sp.mads.available else self.sm['selfdriveState'].active
|
||||
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and not standstill
|
||||
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and self.CP.openpilotLongitudinalControl
|
||||
|
||||
actuators = CC.actuators
|
||||
|
||||
@@ -28,7 +28,7 @@ def can_printer(bus, max_msg, addr, ascii_decode):
|
||||
x = binascii.hexlify(msgs[_addr][-1]).decode('ascii')
|
||||
freq = len(msgs[_addr]) / (time.monotonic() - start)
|
||||
if max_msg is None or _addr < max_msg:
|
||||
dd += f"{_addr:04X}({_addr:4d})({len(msgs[_addr]):6d})({freq:3}dHz) {x.ljust(20)} {a}\n"
|
||||
dd += "%04X(%4d)(%6d)(%3dHz) %s %s\n" % (_addr, _addr, len(msgs[_addr]), freq, x.ljust(20), a)
|
||||
print(dd)
|
||||
lp = time.monotonic()
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ if __name__ == '__main__':
|
||||
start_t = time.process_time_ns()
|
||||
for msg in msgs:
|
||||
can_list = can_capnp_to_list([msg])
|
||||
for cp in tm.CI.can_parsers.values():
|
||||
for cp in tm.CI.can_parsers:
|
||||
if cp is not None:
|
||||
cp.update_strings(can_list)
|
||||
ets.append((time.process_time_ns() - start_t) * 1e-6)
|
||||
|
||||
@@ -8,7 +8,6 @@ from typing import cast
|
||||
|
||||
from cereal.services import SERVICE_LIST
|
||||
from openpilot.tools.lib.logreader import LogReader, ReadMode
|
||||
from openpilot.selfdrive.test.process_replay.migration import migrate_all
|
||||
|
||||
if __name__ == "__main__":
|
||||
cnt_events: Counter = Counter()
|
||||
@@ -21,7 +20,7 @@ if __name__ == "__main__":
|
||||
start_time = math.inf
|
||||
end_time = -math.inf
|
||||
ignition_off = None
|
||||
for msg in migrate_all(LogReader(sys.argv[1], ReadMode.QLOG)):
|
||||
for msg in LogReader(sys.argv[1], ReadMode.QLOG):
|
||||
t = (msg.logMonoTime - start_time) / 1e9
|
||||
end_time = max(end_time, msg.logMonoTime)
|
||||
start_time = min(start_time, msg.logMonoTime)
|
||||
|
||||
@@ -22,7 +22,7 @@ def get_fingerprint(lr):
|
||||
msgs[c.address] = len(c.dat)
|
||||
|
||||
# show CAN fingerprint
|
||||
fingerprint = ', '.join(f"{v[0]}: {v[1]}" for v in sorted(msgs.items()))
|
||||
fingerprint = ', '.join("%d: %d" % v for v in sorted(msgs.items()))
|
||||
print(f"\nfound {len(msgs)} messages. CAN fingerprint:\n")
|
||||
print(fingerprint)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ while True:
|
||||
if c.src % 0x80 == 0 and c.address < 0x800 and c.address not in (0x7df, 0x7e0, 0x7e8):
|
||||
msgs[c.address] = len(c.dat)
|
||||
|
||||
fingerprint = ', '.join(f"{v[0]}: {v[1]}" for v in sorted(msgs.items()))
|
||||
fingerprint = ', '.join("%d: %d" % v for v in sorted(msgs.items()))
|
||||
|
||||
print(f"number of messages {len(msgs)}:")
|
||||
print(f"fingerprint {fingerprint}")
|
||||
|
||||
@@ -8,7 +8,6 @@ from enum import Enum
|
||||
from collections import defaultdict
|
||||
|
||||
from cereal import log, messaging
|
||||
from cereal.services import SERVICE_LIST
|
||||
from openpilot.common.transformations.orientation import rot_from_euler
|
||||
from openpilot.common.realtime import config_realtime_process
|
||||
from openpilot.common.params import Params
|
||||
@@ -24,10 +23,8 @@ MIN_STD_SANITY_CHECK = 1e-5 # m or rad
|
||||
MAX_FILTER_REWIND_TIME = 0.8 # s
|
||||
MAX_SENSOR_TIME_DIFF = 0.1 # s
|
||||
YAWRATE_CROSS_ERR_CHECK_FACTOR = 30
|
||||
INPUT_INVALID_THRESHOLD = 0.5 # 0 bad inputs ignored
|
||||
TIMING_INVALID_THRESHOLD = 2.5 # 2 bad timings ignored
|
||||
INPUT_INVALID_DECAY = 0.9993 # ~10 secs to resume after exceeding allowed bad inputs by one (at 100hz)
|
||||
TIMING_INVALID_DECAY = 0.9990 # ~2 secs to resume after exceeding allowed bad timings by one (at 100hz)
|
||||
INPUT_INVALID_THRESHOLD = 0.5
|
||||
INPUT_INVALID_DECAY = 0.9993 # ~10 secs to resume after a bad input
|
||||
POSENET_STD_INITIAL_VALUE = 10.0
|
||||
POSENET_STD_HIST_HALF = 20
|
||||
|
||||
@@ -268,13 +265,10 @@ def main():
|
||||
estimator = LocationEstimator(DEBUG)
|
||||
|
||||
filter_initialized = False
|
||||
critcal_services = ["accelerometer", "gyroscope", "cameraOdometry"]
|
||||
observation_timing_invalid = defaultdict(int)
|
||||
critcal_services = ["accelerometer", "gyroscope", "liveCalibration", "cameraOdometry"]
|
||||
observation_timing_invalid = False
|
||||
observation_input_invalid = defaultdict(int)
|
||||
|
||||
input_invalid_decay = {s: INPUT_INVALID_DECAY ** (100. / SERVICE_LIST[s].frequency) for s in critcal_services}
|
||||
timing_invalid_decay = {s: TIMING_INVALID_DECAY ** (100. / SERVICE_LIST[s].frequency) for s in critcal_services}
|
||||
|
||||
initial_pose = params.get("LocationFilterInitialState")
|
||||
if initial_pose is not None:
|
||||
initial_pose = json.loads(initial_pose)
|
||||
@@ -288,6 +282,8 @@ def main():
|
||||
acc_msgs, gyro_msgs = (messaging.drain_sock(sock) for sock in sensor_sockets)
|
||||
|
||||
if filter_initialized:
|
||||
observation_timing_invalid = False
|
||||
|
||||
msgs = []
|
||||
for msg in acc_msgs + gyro_msgs:
|
||||
t, valid, which, data = msg.logMonoTime, msg.valid, msg.which(), getattr(msg, msg.which())
|
||||
@@ -302,23 +298,18 @@ def main():
|
||||
if valid:
|
||||
t = log_mono_time * 1e-9
|
||||
res = estimator.handle_log(t, which, msg)
|
||||
if which not in critcal_services:
|
||||
continue
|
||||
|
||||
if res == HandleLogResult.TIMING_INVALID:
|
||||
observation_timing_invalid[which] += 1
|
||||
observation_timing_invalid = True
|
||||
elif res == HandleLogResult.INPUT_INVALID:
|
||||
observation_input_invalid[which] += 1
|
||||
else:
|
||||
observation_input_invalid[which] *= input_invalid_decay[which]
|
||||
observation_timing_invalid[which] *= timing_invalid_decay[which]
|
||||
observation_input_invalid[which] *= INPUT_INVALID_DECAY
|
||||
else:
|
||||
filter_initialized = sm.all_checks() and sensor_all_checks(acc_msgs, gyro_msgs, sensor_valid, sensor_recv_time, sensor_alive, SIMULATION)
|
||||
|
||||
if sm.updated["cameraOdometry"]:
|
||||
critical_service_inputs_valid = all(observation_input_invalid[s] < INPUT_INVALID_THRESHOLD for s in critcal_services)
|
||||
critical_service_timing_valid = all(observation_timing_invalid[s] < TIMING_INVALID_THRESHOLD for s in critcal_services)
|
||||
inputs_valid = sm.all_valid() and critical_service_inputs_valid and critical_service_timing_valid
|
||||
inputs_valid = sm.all_valid() and critical_service_inputs_valid and not observation_timing_invalid
|
||||
sensors_valid = sensor_all_checks(acc_msgs, gyro_msgs, sensor_valid, sensor_recv_time, sensor_alive, SIMULATION)
|
||||
|
||||
msg = estimator.get_msg(sensors_valid, inputs_valid, filter_initialized)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import pytest
|
||||
import numpy as np
|
||||
from collections import defaultdict
|
||||
from enum import Enum
|
||||
@@ -16,7 +17,6 @@ SELECT_COMPARE_FIELDS = {
|
||||
'sensors_flag': ['sensorsOK'],
|
||||
}
|
||||
JUNK_IDX = 100
|
||||
CONSISTENT_SPIKES_COUNT = 10
|
||||
|
||||
|
||||
class Scenario(Enum):
|
||||
@@ -25,8 +25,6 @@ class Scenario(Enum):
|
||||
GYRO_SPIKE_MIDWAY = 'gyro_spike_midway'
|
||||
ACCEL_OFF = 'accel_off'
|
||||
ACCEL_SPIKE_MIDWAY = 'accel_spike_midway'
|
||||
SENSOR_TIMING_SPIKE_MIDWAY = 'timing_spikes'
|
||||
SENSOR_TIMING_CONSISTENT_SPIKES = 'timing_consistent_spikes'
|
||||
|
||||
|
||||
def get_select_fields_data(logs):
|
||||
@@ -45,17 +43,6 @@ def get_select_fields_data(logs):
|
||||
return data
|
||||
|
||||
|
||||
def modify_logs_midway(logs, which, count, fn):
|
||||
non_which = [x for x in logs if x.which() != which]
|
||||
which = [x for x in logs if x.which() == which]
|
||||
temps = which[len(which) // 2:len(which) // 2 + count]
|
||||
for i, temp in enumerate(temps):
|
||||
temp = temp.as_builder()
|
||||
fn(temp)
|
||||
which[len(which) // 2 + i] = temp.as_reader()
|
||||
return sorted(non_which + which, key=lambda x: x.logMonoTime)
|
||||
|
||||
|
||||
def run_scenarios(scenario, logs):
|
||||
if scenario == Scenario.BASE:
|
||||
pass
|
||||
@@ -64,28 +51,30 @@ def run_scenarios(scenario, logs):
|
||||
logs = sorted([x for x in logs if x.which() != 'gyroscope'], key=lambda x: x.logMonoTime)
|
||||
|
||||
elif scenario == Scenario.GYRO_SPIKE_MIDWAY:
|
||||
def gyro_spike(msg):
|
||||
msg.gyroscope.gyroUncalibrated.v[0] += 3.0
|
||||
logs = modify_logs_midway(logs, 'gyroscope', 1, gyro_spike)
|
||||
non_gyro = [x for x in logs if x.which() not in 'gyroscope']
|
||||
gyro = [x for x in logs if x.which() in 'gyroscope']
|
||||
temp = gyro[len(gyro) // 2].as_builder()
|
||||
temp.gyroscope.gyroUncalibrated.v[0] += 3.0
|
||||
gyro[len(gyro) // 2] = temp.as_reader()
|
||||
logs = sorted(non_gyro + gyro, key=lambda x: x.logMonoTime)
|
||||
|
||||
elif scenario == Scenario.ACCEL_OFF:
|
||||
logs = sorted([x for x in logs if x.which() != 'accelerometer'], key=lambda x: x.logMonoTime)
|
||||
|
||||
elif scenario == Scenario.ACCEL_SPIKE_MIDWAY:
|
||||
def acc_spike(msg):
|
||||
msg.accelerometer.acceleration.v[0] += 10.0
|
||||
logs = modify_logs_midway(logs, 'accelerometer', 1, acc_spike)
|
||||
|
||||
elif scenario == Scenario.SENSOR_TIMING_SPIKE_MIDWAY or scenario == Scenario.SENSOR_TIMING_CONSISTENT_SPIKES:
|
||||
def timing_spike(msg):
|
||||
msg.accelerometer.timestamp -= int(0.150 * 1e9)
|
||||
count = 1 if scenario == Scenario.SENSOR_TIMING_SPIKE_MIDWAY else CONSISTENT_SPIKES_COUNT
|
||||
logs = modify_logs_midway(logs, 'accelerometer', count, timing_spike)
|
||||
non_accel = [x for x in logs if x.which() not in 'accelerometer']
|
||||
accel = [x for x in logs if x.which() in 'accelerometer']
|
||||
temp = accel[len(accel) // 2].as_builder()
|
||||
temp.accelerometer.acceleration.v[0] += 10.0
|
||||
accel[len(accel) // 2] = temp.as_reader()
|
||||
logs = sorted(non_accel + accel, key=lambda x: x.logMonoTime)
|
||||
|
||||
replayed_logs = replay_process_with_name(name='locationd', lr=logs)
|
||||
return get_select_fields_data(logs), get_select_fields_data(replayed_logs)
|
||||
|
||||
|
||||
@pytest.mark.xdist_group("test_locationd_scenarios")
|
||||
@pytest.mark.shared_download_cache
|
||||
class TestLocationdScenarios:
|
||||
"""
|
||||
Test locationd with different scenarios. In all these scenarios, we expect the following:
|
||||
@@ -133,7 +122,7 @@ class TestLocationdScenarios:
|
||||
assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.35))
|
||||
assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.55))
|
||||
assert np.diff(replayed_data['inputs_flag'])[499] == -1.0
|
||||
assert np.diff(replayed_data['inputs_flag'])[704] == 1.0
|
||||
assert np.diff(replayed_data['inputs_flag'])[696] == 1.0
|
||||
|
||||
def test_accel_off(self):
|
||||
"""
|
||||
@@ -157,21 +146,3 @@ class TestLocationdScenarios:
|
||||
orig_data, replayed_data = run_scenarios(Scenario.ACCEL_SPIKE_MIDWAY, self.logs)
|
||||
assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.35))
|
||||
assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.55))
|
||||
|
||||
def test_single_timing_spike(self):
|
||||
"""
|
||||
Test: timing of 150ms off for the single accelerometer message in the middle of the segment
|
||||
Expected Result: the message is ignored, and inputsOK is False for that time
|
||||
"""
|
||||
orig_data, replayed_data = run_scenarios(Scenario.SENSOR_TIMING_SPIKE_MIDWAY, self.logs)
|
||||
assert np.all(replayed_data['inputs_flag'] == orig_data['inputs_flag'])
|
||||
assert np.all(replayed_data['sensors_flag'] == orig_data['sensors_flag'])
|
||||
|
||||
def test_consistent_timing_spikes(self):
|
||||
"""
|
||||
Test: consistent timing spikes for N accelerometer messages in the middle of the segment
|
||||
Expected Result: inputsOK becomes False after N of bad measurements
|
||||
"""
|
||||
orig_data, replayed_data = run_scenarios(Scenario.SENSOR_TIMING_CONSISTENT_SPIKES, self.logs)
|
||||
assert np.diff(replayed_data['inputs_flag'])[500] == -1.0
|
||||
assert np.diff(replayed_data['inputs_flag'])[787] == 1.0
|
||||
|
||||
@@ -12,6 +12,7 @@ from cereal import messaging
|
||||
from cereal.messaging import PubMaster, SubMaster
|
||||
from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import set_realtime_priority
|
||||
from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime
|
||||
from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext
|
||||
@@ -125,6 +126,7 @@ def main():
|
||||
cl_context = CLContext()
|
||||
model = ModelState(cl_context)
|
||||
cloudlog.warning("models loaded, dmonitoringmodeld starting")
|
||||
Params().put_bool("DmModelInitialized", True)
|
||||
|
||||
cloudlog.warning("connecting to driver stream")
|
||||
vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9dc64f5d1e7d6b67f1d4659a3483f03b4324b4c7b969a5ba90c4e37e62bf6fce
|
||||
size 50320584
|
||||
oid sha256:c829d824ebc73d15da82516592c07d9784369ccbf710698e919e06a702e70924
|
||||
size 50320138
|
||||
|
||||
@@ -145,10 +145,6 @@ void Panda::set_can_speed_kbps(uint16_t bus, uint16_t speed) {
|
||||
handle->control_write(0xde, bus, (speed * 10));
|
||||
}
|
||||
|
||||
void Panda::set_can_fd_auto(uint16_t bus, bool enabled) {
|
||||
handle->control_write(0xe8, bus, enabled);
|
||||
}
|
||||
|
||||
void Panda::set_data_speed_kbps(uint16_t bus, uint16_t speed) {
|
||||
handle->control_write(0xf9, bus, (speed * 10));
|
||||
}
|
||||
|
||||
@@ -77,7 +77,6 @@ public:
|
||||
void enable_deepsleep();
|
||||
void send_heartbeat(bool engaged);
|
||||
void set_can_speed_kbps(uint16_t bus, uint16_t speed);
|
||||
void set_can_fd_auto(uint16_t bus, bool enabled);
|
||||
void set_data_speed_kbps(uint16_t bus, uint16_t speed);
|
||||
void set_canfd_non_iso(uint16_t bus, bool non_iso);
|
||||
void can_send(const capnp::List<cereal::CanData>::Reader &can_data_list);
|
||||
|
||||
@@ -67,10 +67,6 @@ Panda *connect(std::string serial="", uint32_t index=0) {
|
||||
}
|
||||
//panda->enable_deepsleep();
|
||||
|
||||
for (int i = 0; i < PANDA_BUS_CNT; i++) {
|
||||
panda->set_can_fd_auto(i, true);
|
||||
}
|
||||
|
||||
if (!panda->up_to_date() && !getenv("BOARDD_SKIP_FW_CHECK")) {
|
||||
throw std::runtime_error("Panda firmware out of date. Run pandad.py to update.");
|
||||
}
|
||||
@@ -144,6 +140,7 @@ void fill_panda_state(cereal::PandaState::Builder &ps, cereal::PandaState::Panda
|
||||
ps.setIgnitionLine(health.ignition_line_pkt);
|
||||
ps.setIgnitionCan(health.ignition_can_pkt);
|
||||
ps.setControlsAllowed(health.controls_allowed_pkt);
|
||||
ps.setControlsAllowedLat(health.controls_allowed_lat_pkt);
|
||||
ps.setTxBufferOverflow(health.tx_buffer_overflow_pkt);
|
||||
ps.setRxBufferOverflow(health.rx_buffer_overflow_pkt);
|
||||
ps.setPandaType(hw_type);
|
||||
|
||||
@@ -239,7 +239,7 @@ int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint1
|
||||
// due to full TX buffers
|
||||
nack_count += 1;
|
||||
if (nack_count > 3) {
|
||||
SPILOG(LOGD, "NACK sleep %d", nack_count);
|
||||
SPILOG(LOGE, "NACK sleep %d", nack_count);
|
||||
usleep(std::clamp(nack_count*10, 200, 2000));
|
||||
}
|
||||
}
|
||||
@@ -418,7 +418,7 @@ fail:
|
||||
}
|
||||
}
|
||||
|
||||
if (ret >= 0) ret = -1;
|
||||
if (ret > 0) ret = -1;
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,6 @@ import cereal.messaging as messaging
|
||||
from cereal import log
|
||||
from openpilot.common.gpio import gpio_set, gpio_init
|
||||
from panda import Panda, PandaDFU, PandaProtocolMismatch
|
||||
from openpilot.common.retry import retry
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.hardware.tici.pins import GPIO
|
||||
@@ -52,7 +51,6 @@ class TestPandad:
|
||||
assert not Panda.wait_for_dfu(None, 3)
|
||||
assert not Panda.wait_for_panda(None, 3)
|
||||
|
||||
@retry(attempts=3)
|
||||
def _flash_bootstub_and_test(self, fn, expect_mismatch=False):
|
||||
self._go_to_dfu()
|
||||
pd = PandaDFU(None)
|
||||
|
||||
@@ -14,7 +14,7 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.timeout import Timeout
|
||||
from openpilot.selfdrive.pandad import can_list_to_can_capnp
|
||||
from openpilot.system.hardware import TICI
|
||||
from openpilot.selfdrive.test.helpers import with_processes
|
||||
from openpilot.selfdrive.test.helpers import phone_only, with_processes
|
||||
|
||||
|
||||
@retry(attempts=3)
|
||||
@@ -72,6 +72,7 @@ class TestBoarddLoopback:
|
||||
os.environ['STARTED'] = '1'
|
||||
os.environ['BOARDD_LOOPBACK'] = '1'
|
||||
|
||||
@phone_only
|
||||
@with_processes(['pandad'])
|
||||
def test_loopback(self):
|
||||
num_pandas = 2 if TICI and "SINGLE_PANDA" not in os.environ else 1
|
||||
|
||||
@@ -7,7 +7,7 @@ import random
|
||||
import cereal.messaging as messaging
|
||||
from cereal.services import SERVICE_LIST
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.selfdrive.test.helpers import with_processes
|
||||
from openpilot.selfdrive.test.helpers import phone_only, with_processes
|
||||
from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, send_random_can_messages
|
||||
|
||||
JUNGLE_SPAM = "JUNGLE_SPAM" in os.environ
|
||||
@@ -23,6 +23,7 @@ class TestBoarddSpi:
|
||||
if not JUNGLE_SPAM:
|
||||
os.environ['BOARDD_LOOPBACK'] = '1'
|
||||
|
||||
@phone_only
|
||||
@with_processes(['pandad'])
|
||||
def test_spi_corruption(self, subtests):
|
||||
setup_pandad(1)
|
||||
|
||||
@@ -105,6 +105,17 @@ class Events:
|
||||
ret.append(event)
|
||||
return ret
|
||||
|
||||
def has(self, event_name: int) -> bool:
|
||||
return event_name in self.events
|
||||
|
||||
def remove(self, event_name: int, static: bool = False) -> None:
|
||||
if static and event_name in self.static_events:
|
||||
self.static_events.remove(event_name)
|
||||
|
||||
if event_name in self.events:
|
||||
self.event_counters[event_name] = self.event_counters[event_name] + 1
|
||||
self.events.remove(event_name)
|
||||
|
||||
|
||||
class Alert:
|
||||
def __init__(self,
|
||||
@@ -951,6 +962,85 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
ET.WARNING: personality_changed_alert,
|
||||
},
|
||||
|
||||
# sunnypilot
|
||||
EventName.lkasEnable: {
|
||||
ET.ENABLE: EngagementAlert(AudibleAlert.engage),
|
||||
},
|
||||
|
||||
EventName.lkasDisable: {
|
||||
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
||||
},
|
||||
|
||||
EventName.manualSteeringRequired: {
|
||||
ET.USER_DISABLE: Alert(
|
||||
"Automatic Lane Centering is OFF",
|
||||
"Manual Steering Required",
|
||||
AlertStatus.normal, AlertSize.mid,
|
||||
Priority.LOW, VisualAlert.none, AudibleAlert.disengage, 1.),
|
||||
},
|
||||
|
||||
EventName.manualLongitudinalRequired: {
|
||||
ET.WARNING: Alert(
|
||||
"Smart/Adaptive Cruise Control is OFF",
|
||||
"Manual Gas/Brakes Required",
|
||||
AlertStatus.normal, AlertSize.mid,
|
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 1.),
|
||||
},
|
||||
|
||||
EventName.silentPedalPressed: {
|
||||
ET.USER_DISABLE: Alert(
|
||||
"",
|
||||
"",
|
||||
AlertStatus.normal, AlertSize.none,
|
||||
Priority.MID, VisualAlert.none, AudibleAlert.none, .2),
|
||||
ET.NO_ENTRY: NoEntryAlert("Pedal Pressed During Attempt",
|
||||
visual_alert=VisualAlert.brakePressed),
|
||||
},
|
||||
|
||||
EventName.silentLkasEnable: {
|
||||
ET.ENABLE: Alert(
|
||||
"",
|
||||
"",
|
||||
AlertStatus.normal, AlertSize.none,
|
||||
Priority.MID, VisualAlert.none, AudibleAlert.none, .2, 0.),
|
||||
},
|
||||
|
||||
EventName.silentBrakeHold: {
|
||||
ET.USER_DISABLE: Alert(
|
||||
"",
|
||||
"",
|
||||
AlertStatus.normal, AlertSize.none,
|
||||
Priority.MID, VisualAlert.none, AudibleAlert.none, .2, 0.),
|
||||
ET.NO_ENTRY: NoEntryAlert("Brake Hold Active"),
|
||||
},
|
||||
|
||||
EventName.silentWrongGear: {
|
||||
ET.USER_DISABLE: Alert(
|
||||
"Gear not D",
|
||||
"openpilot Unavailable",
|
||||
AlertStatus.normal, AlertSize.mid,
|
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 3.),
|
||||
ET.NO_ENTRY: Alert(
|
||||
"Gear not D",
|
||||
"openpilot Unavailable",
|
||||
AlertStatus.normal, AlertSize.mid,
|
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 3.),
|
||||
},
|
||||
|
||||
EventName.silentReverseGear: {
|
||||
ET.PERMANENT: Alert(
|
||||
"Reverse\nGear",
|
||||
"",
|
||||
AlertStatus.normal, AlertSize.full,
|
||||
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2, creation_delay=0.5),
|
||||
ET.USER_DISABLE: Alert(
|
||||
"Reverse\nGear",
|
||||
"",
|
||||
AlertStatus.normal, AlertSize.full,
|
||||
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2, creation_delay=0.5),
|
||||
ET.NO_ENTRY: NoEntryAlert("Reverse Gear"),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ from openpilot.selfdrive.controls.lib.latcontrol import MIN_LATERAL_CONTROL_SPEE
|
||||
|
||||
from openpilot.system.version import get_build_metadata
|
||||
|
||||
from opendbc.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
|
||||
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
SIMULATION = "SIMULATION" in os.environ
|
||||
TESTING_CLOSET = "TESTING_CLOSET" in os.environ
|
||||
@@ -131,6 +133,10 @@ class SelfdriveD:
|
||||
elif self.CP.passive:
|
||||
self.events.add(EventName.dashcamMode, static=True)
|
||||
|
||||
self.mads = ModularAssistiveDrivingSystem(self)
|
||||
sock_services = list(self.pm.sock.keys()) + ['selfdriveStateSP']
|
||||
self.pm = messaging.PubMaster(sock_services)
|
||||
|
||||
def update_events(self, CS):
|
||||
"""Compute onroadEvents from carState"""
|
||||
|
||||
@@ -451,11 +457,24 @@ class SelfdriveD:
|
||||
self.pm.send('onroadEvents', ce_send)
|
||||
self.events_prev = self.events.names.copy()
|
||||
|
||||
# selfdriveStateSP
|
||||
ss_sp_msg = messaging.new_message('selfdriveStateSP')
|
||||
ss_sp_msg.valid = True
|
||||
ss_sp = ss_sp_msg.selfdriveStateSP
|
||||
mads = ss_sp.mads
|
||||
mads.state = self.mads.state_machine.state
|
||||
mads.enabled = self.mads.enabled
|
||||
mads.active = self.mads.active
|
||||
mads.available = self.mads.enabled_toggle
|
||||
|
||||
self.pm.send('selfdriveStateSP', ss_sp_msg)
|
||||
|
||||
def step(self):
|
||||
CS = self.data_sample()
|
||||
self.update_events(CS)
|
||||
if not self.CP.passive and self.initialized:
|
||||
self.enabled, self.active = self.state_machine.update(self.events)
|
||||
self.mads.update(CS)
|
||||
self.update_alerts(CS)
|
||||
|
||||
self.publish_selfdriveState(CS)
|
||||
|
||||
@@ -42,7 +42,7 @@ class TestAlerts:
|
||||
|
||||
for name, e in events.items():
|
||||
if not name.endswith("DEPRECATED"):
|
||||
fail_msg = f"{name} @{e} not in EVENTS"
|
||||
fail_msg = "%s @%d not in EVENTS" % (name, e)
|
||||
assert e in EVENTS.keys(), fail_msg
|
||||
|
||||
# ensure alert text doesn't exceed allowed width
|
||||
|
||||
@@ -10,6 +10,7 @@ from functools import wraps
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
from openpilot.system.hardware import PC
|
||||
from openpilot.system.version import training_version, terms_version
|
||||
|
||||
|
||||
@@ -28,6 +29,14 @@ def set_params_enabled():
|
||||
msg.liveCalibration.rpyCalib = [0.0, 0.0, 0.0]
|
||||
params.put("CalibrationParams", msg.to_bytes())
|
||||
|
||||
def phone_only(f):
|
||||
@wraps(f)
|
||||
def wrap(self, *args, **kwargs):
|
||||
if PC:
|
||||
pytest.skip("This test is not meant to run on PC")
|
||||
return f(self, *args, **kwargs)
|
||||
return wrap
|
||||
|
||||
def release_only(f):
|
||||
@wraps(f)
|
||||
def wrap(self, *args, **kwargs):
|
||||
|
||||
@@ -7,20 +7,19 @@ import tempfile
|
||||
from itertools import zip_longest
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
from openpilot.common.git import get_commit
|
||||
from openpilot.system.hardware import PC
|
||||
from openpilot.tools.lib.openpilotci import get_url
|
||||
from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff
|
||||
from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process
|
||||
from openpilot.tools.lib.framereader import FrameReader, NumpyFrameReader
|
||||
from openpilot.tools.lib.framereader import FrameReader
|
||||
from openpilot.tools.lib.logreader import LogReader, save_log
|
||||
from openpilot.tools.lib.github_utils import GithubUtils
|
||||
|
||||
TEST_ROUTE = "2f4452b03ccb98f0|2022-12-03--13-45-30"
|
||||
SEGMENT = 6
|
||||
MAX_FRAMES = 100 if PC else 400
|
||||
MAX_FRAMES = 100 if PC else 600
|
||||
|
||||
NO_MODEL = "NO_MODEL" in os.environ
|
||||
SEND_EXTRA_INPUTS = bool(int(os.getenv("SEND_EXTRA_INPUTS", "0")))
|
||||
@@ -32,14 +31,14 @@ GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN)
|
||||
|
||||
|
||||
def get_log_fn(test_route, ref="master"):
|
||||
return f"{test_route}_model_tici_{ref}.zst"
|
||||
return f"{test_route}_model_tici_{ref}.bz2"
|
||||
|
||||
def plot(proposed, master, title, tmp):
|
||||
proposed = list(proposed)
|
||||
master = list(master)
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(master, label='MASTER')
|
||||
ax.plot(proposed, label='PROPOSED')
|
||||
ax.plot(master, label='MASTER')
|
||||
plt.legend(loc='best')
|
||||
plt.title(title)
|
||||
plt.savefig(f'{tmp}/{title}.png')
|
||||
@@ -152,44 +151,21 @@ def model_replay(lr, frs):
|
||||
dmonitoringmodeld = get_process_config("dmonitoringmodeld")
|
||||
|
||||
modeld_msgs = replay_process(modeld, modeld_logs, frs)
|
||||
if isinstance(frs['roadCameraState'], NumpyFrameReader):
|
||||
del frs['roadCameraState'].frames
|
||||
del frs['wideRoadCameraState'].frames
|
||||
dmonitoringmodeld_msgs = replay_process(dmonitoringmodeld, dmodeld_logs, frs)
|
||||
return modeld_msgs + dmonitoringmodeld_msgs
|
||||
|
||||
|
||||
def get_frames():
|
||||
regen_cache = "--regen-cache" in sys.argv
|
||||
cache = "--cache" in sys.argv or not PC or regen_cache
|
||||
videos = ('fcamera.hevc', 'dcamera.hevc', 'ecamera.hevc')
|
||||
cams = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState')
|
||||
|
||||
if cache:
|
||||
frames_cache = '/tmp/model_replay_cache' if PC else '/data/model_replay_cache'
|
||||
os.makedirs(frames_cache, exist_ok=True)
|
||||
|
||||
cache_size = 200
|
||||
for v in videos:
|
||||
if not all(os.path.isfile(f'{frames_cache}/{TEST_ROUTE}_{v}_{i}.npy') for i in range(MAX_FRAMES//cache_size)) or regen_cache:
|
||||
f = FrameReader(get_url(TEST_ROUTE, SEGMENT, v)).get(0, MAX_FRAMES + 1, pix_fmt="nv12")
|
||||
print(f'Caching {v}...')
|
||||
for i in range(MAX_FRAMES//cache_size):
|
||||
np.save(f'{frames_cache}/{TEST_ROUTE}_{v}_{i}', f[(i * cache_size) + 1:((i + 1) * cache_size) + 1])
|
||||
del f
|
||||
|
||||
return {c : NumpyFrameReader(f"{frames_cache}/{TEST_ROUTE}_{v}", 1928, 1208, cache_size) for c,v in zip(cams, videos, strict=True)}
|
||||
else:
|
||||
return {c : FrameReader(get_url(TEST_ROUTE, SEGMENT, v), readahead=True) for c,v in zip(cams, videos, strict=True)}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
update = "--update" in sys.argv or (os.getenv("GIT_BRANCH", "") == 'master')
|
||||
replay_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# load logs
|
||||
lr = list(LogReader(get_url(TEST_ROUTE, SEGMENT, "rlog.zst")))
|
||||
frs = get_frames()
|
||||
lr = list(LogReader(get_url(TEST_ROUTE, SEGMENT, "rlog.bz2")))
|
||||
frs = {
|
||||
'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "fcamera.hevc"), readahead=True),
|
||||
'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "dcamera.hevc"), readahead=True),
|
||||
'wideRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "ecamera.hevc"), readahead=True)
|
||||
}
|
||||
|
||||
log_msgs = []
|
||||
# run replays
|
||||
|
||||
@@ -5,13 +5,13 @@ import copy
|
||||
import json
|
||||
import heapq
|
||||
import signal
|
||||
import platform
|
||||
from collections import Counter, OrderedDict
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
from collections.abc import Callable, Iterable
|
||||
from tqdm import tqdm
|
||||
import capnp
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from cereal import car
|
||||
@@ -780,7 +780,8 @@ def generate_params_config(lr=None, CP=None, fingerprint=None, custom_params=Non
|
||||
|
||||
def generate_environ_config(CP=None, fingerprint=None, log_dir=None) -> dict[str, Any]:
|
||||
environ_dict = {}
|
||||
environ_dict["PARAMS_ROOT"] = f"{Paths.shm_path()}/params"
|
||||
if platform.system() != "Darwin":
|
||||
environ_dict["PARAMS_ROOT"] = "/dev/shm/params"
|
||||
if log_dir is not None:
|
||||
environ_dict["LOG_ROOT"] = log_dir
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
255ceb08c75bc85379da5ec247e612be3716fb43
|
||||
2fc2e865ab77fd8145feab86d454f2111c5d9871
|
||||
|
||||
@@ -72,7 +72,7 @@ PROCS = {
|
||||
|
||||
PROCS.update({
|
||||
"tici": {
|
||||
"./pandad": 5.0,
|
||||
"./pandad": 4.0,
|
||||
"./ubloxd": 1.0,
|
||||
"system.ubloxd.pigeond": 6.0,
|
||||
},
|
||||
@@ -138,7 +138,6 @@ class TestOnroad:
|
||||
proc = None
|
||||
try:
|
||||
manager_path = os.path.join(BASEDIR, "system/manager/manager.py")
|
||||
cls.manager_st = time.monotonic()
|
||||
proc = subprocess.Popen(["python", manager_path])
|
||||
|
||||
sm = messaging.SubMaster(['carState'])
|
||||
@@ -203,10 +202,6 @@ class TestOnroad:
|
||||
with subtests.test(service=s):
|
||||
assert len(msgs) >= math.floor(SERVICE_LIST[s].frequency*int(TEST_DURATION*0.8))
|
||||
|
||||
def test_manager_starting_time(self):
|
||||
st = self.msgs['managerState'][0].logMonoTime / 1e9
|
||||
assert (st - self.manager_st) < 10, f"manager.py took {st - self.manager_st}s to publish the first 'managerState' msg"
|
||||
|
||||
def test_cloudlog_size(self):
|
||||
msgs = self.msgs['logMessage']
|
||||
|
||||
@@ -300,18 +295,13 @@ class TestOnroad:
|
||||
assert cpu_ok
|
||||
|
||||
def test_memory_usage(self):
|
||||
print("\n------------------------------------------------")
|
||||
print("--------------- Memory Usage -------------------")
|
||||
print("------------------------------------------------")
|
||||
offset = int(SERVICE_LIST['deviceState'].frequency * LOG_OFFSET)
|
||||
mems = [m.deviceState.memoryUsagePercent for m in self.msgs['deviceState'][offset:]]
|
||||
print("Memory usage: ", mems)
|
||||
|
||||
# check for big leaks. note that memory usage is
|
||||
# expected to go up while the MSGQ buffers fill up
|
||||
assert np.average(mems) <= 65, "Average memory usage above 65%"
|
||||
assert np.max(np.diff(mems)) <= 4, "Max memory increase too high"
|
||||
assert np.average(np.diff(mems)) <= 1, "Average memory increase too high"
|
||||
assert max(mems) - min(mems) <= 3.0
|
||||
|
||||
def test_gpu_usage(self):
|
||||
assert self.gpu_procs == {"weston", "ui", "camerad", "selfdrive.modeld.modeld", "selfdrive.modeld.dmonitoringmodeld"}
|
||||
|
||||
@@ -110,3 +110,5 @@ if GetOption('extras') and arch != "Darwin":
|
||||
# build watch3
|
||||
if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'):
|
||||
qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'msgq', 'visionipc'])
|
||||
|
||||
SConscript(['raylib/SConscript'])
|
||||
|
||||
@@ -124,7 +124,6 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
|
||||
// left: PrimeAdWidget
|
||||
QStackedWidget *left_widget = new QStackedWidget(this);
|
||||
QVBoxLayout *left_prime_layout = new QVBoxLayout();
|
||||
left_prime_layout->setContentsMargins(0, 0, 0, 0);
|
||||
QWidget *prime_user = new PrimeUserWidget();
|
||||
prime_user->setStyleSheet(R"(
|
||||
border-radius: 10px;
|
||||
|
||||
@@ -49,6 +49,20 @@ void DeveloperPanel::updateToggles(bool _offroad) {
|
||||
}
|
||||
|
||||
offroad = _offroad;
|
||||
|
||||
// FIXME-SP: Move to sunnypilot panels before merging
|
||||
auto madsToggle = new ParamControl("Mads", tr("Modular Assistive Driving System (MADS)"), "", "");
|
||||
addItem(madsToggle);
|
||||
|
||||
// TODO-SP: Rename toggle
|
||||
auto madsCruiseMainToggle = new ParamControl("MadsCruiseMain", tr("MADS: Cruise Main"), "", "");
|
||||
addItem(madsCruiseMainToggle);
|
||||
|
||||
auto madsDisengageLateralOnBrakeToggle = new ParamControl("MadsDisengageLateralOnBrake", tr("MADS: Disengage Lateral on Brake"), "", "");
|
||||
addItem(madsDisengageLateralOnBrakeToggle);
|
||||
|
||||
auto madsUnifiedEngagementModeToggle = new ParamControl("MadsUnifiedEngagementMode", tr("MADS: Unified Engagement Mode"), "", "");
|
||||
addItem(madsUnifiedEngagementModeToggle);
|
||||
}
|
||||
|
||||
void DeveloperPanel::showEvent(QShowEvent *event) {
|
||||
|
||||
@@ -408,7 +408,7 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) {
|
||||
std::stringstream buffer;
|
||||
buffer << std::ifstream("/sys/class/hwmon/hwmon1/in1_input").rdbuf();
|
||||
float voltage = (float)std::atoi(buffer.str().c_str()) / 1000.;
|
||||
if (voltage < 7) {
|
||||
if (voltage > 0 && voltage < 7) {
|
||||
addWidget(low_voltage());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
_spinner
|
||||
@@ -0,0 +1,17 @@
|
||||
Import('env', 'arch', 'common')
|
||||
|
||||
raylib_env = env.Clone()
|
||||
raylib_util_lib = env.Library("raylib_util_lib", ['util.cc'], LIBS='raylib')
|
||||
linked_libs = ['raylib', raylib_util_lib, common]
|
||||
raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/']
|
||||
|
||||
mac_frameworks = []
|
||||
if arch == "Darwin":
|
||||
mac_frameworks += ['OpenCL', 'CoreVideo', 'Cocoa', 'GLUT', 'CoreFoundation', 'OpenGL', 'IOKit']
|
||||
elif arch == 'larch64':
|
||||
linked_libs += []
|
||||
else:
|
||||
linked_libs += ['OpenCL', 'dl', 'pthread']
|
||||
|
||||
if arch != 'aarch64':
|
||||
raylib_env.Program("_spinner", ["spinner.cc"], LIBS=linked_libs, FRAMEWORKS=mac_frameworks)
|
||||
@@ -2,7 +2,8 @@
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
#include "system/ui/raylib/util.h"
|
||||
#include "selfdrive/ui/raylib/util.h"
|
||||
#include "third_party/raylib/include/raylib.h"
|
||||
|
||||
constexpr int kProgressBarWidth = 1000;
|
||||
constexpr int kProgressBarHeight = 20;
|
||||
@@ -18,15 +19,15 @@ int main(int argc, char *argv[]) {
|
||||
std::cin.sync_with_stdio(false);
|
||||
std::cin.tie(nullptr);
|
||||
|
||||
Texture2D commaTexture = LoadTextureResized("../../selfdrive/assets/img_spinner_comma.png", kTextureSize);
|
||||
Texture2D spinnerTexture = LoadTextureResized("../../selfdrive/assets/img_spinner_track.png", kTextureSize);
|
||||
Texture2D commaTexture = LoadTextureResized("../../assets/img_spinner_comma.png", kTextureSize);
|
||||
Texture2D spinnerTexture = LoadTextureResized("../../assets/img_spinner_track.png", kTextureSize);
|
||||
|
||||
float rotation = 0.0f;
|
||||
std::string userInput;
|
||||
|
||||
while (!WindowShouldClose()) {
|
||||
BeginDrawing();
|
||||
ClearBackground(RAYLIB_BLACK);
|
||||
ClearBackground(BLACK);
|
||||
|
||||
rotation = fmod(rotation + kRotationRate, 360.0f);
|
||||
Vector2 center = {GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f};
|
||||
@@ -36,8 +37,8 @@ int main(int argc, char *argv[]) {
|
||||
// Draw rotating spinner and static comma logo
|
||||
DrawTexturePro(spinnerTexture, {0, 0, (float)kTextureSize, (float)kTextureSize},
|
||||
{center.x, center.y, (float)kTextureSize, (float)kTextureSize},
|
||||
spinnerOrigin, rotation, RAYLIB_WHITE);
|
||||
DrawTextureV(commaTexture, commaPosition, RAYLIB_WHITE);
|
||||
spinnerOrigin, rotation, WHITE);
|
||||
DrawTextureV(commaTexture, commaPosition, WHITE);
|
||||
|
||||
// Check for user input
|
||||
if (std::cin.rdbuf()->in_avail() > 0) {
|
||||
@@ -49,14 +50,14 @@ int main(int argc, char *argv[]) {
|
||||
float yPos = GetScreenHeight() - kMargin - kProgressBarHeight;
|
||||
if (std::all_of(userInput.begin(), userInput.end(), ::isdigit)) {
|
||||
Rectangle bar = {center.x - kProgressBarWidth / 2.0f, yPos, kProgressBarWidth, kProgressBarHeight};
|
||||
DrawRectangleRounded(bar, 0.5f, 10, RAYLIB_GRAY);
|
||||
DrawRectangleRounded(bar, 0.5f, 10, GRAY);
|
||||
|
||||
int progress = std::clamp(std::stoi(userInput), 0, 100);
|
||||
bar.width *= progress / 100.0f;
|
||||
DrawRectangleRounded(bar, 0.5f, 10, RAYLIB_RAYWHITE);
|
||||
DrawRectangleRounded(bar, 0.5f, 10, RAYWHITE);
|
||||
} else {
|
||||
Vector2 textSize = MeasureTextEx(getFont(), userInput.c_str(), kFontSize, 1.0);
|
||||
DrawTextEx(getFont(), userInput.c_str(), {center.x - textSize.x / 2, yPos}, kFontSize, 1.0, RAYLIB_WHITE);
|
||||
DrawTextEx(getFont(), userInput.c_str(), {center.x - textSize.x / 2, yPos}, kFontSize, 1.0, WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "system/ui/raylib/util.h"
|
||||
#include "selfdrive/ui/raylib/util.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
@@ -23,6 +23,7 @@ struct FontManager {
|
||||
FontManager() {
|
||||
for (int i = 0; i < fonts.size(); ++i) {
|
||||
fonts[i] = LoadFontEx(FONT_FILE_PATHS[i], 120, nullptr, 250);
|
||||
SetTextureFilter(fonts[i].texture, TEXTURE_FILTER_TRILINEAR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +43,7 @@ Texture2D LoadTextureResized(const char *fileName, int size) {
|
||||
Image img = LoadImage(fileName);
|
||||
ImageResize(&img, size, size);
|
||||
Texture2D texture = LoadTextureFromImage(img);
|
||||
SetTextureFilter(texture, TEXTURE_FILTER_TRILINEAR);
|
||||
return texture;
|
||||
}
|
||||
|
||||
@@ -49,6 +51,6 @@ void initApp(const char *title, int fps) {
|
||||
Hardware::set_display_power(true);
|
||||
Hardware::set_brightness(65);
|
||||
// SetTraceLogLevel(LOG_NONE);
|
||||
InitWindow(2160, 1080, title);
|
||||
InitWindow(0, 0, title);
|
||||
SetTargetFPS(fps);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "system/ui/raylib/raylib.h"
|
||||
#include "third_party/raylib/include/raylib.h"
|
||||
|
||||
enum class FontWeight {
|
||||
Normal,
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation>وضع المناورة الطولية</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation>Modo de maniobra longitudinal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation>롱컨 기동 모드</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation>Modo Longitudinal Maneuver</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
<source>Longitudinal Maneuver Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Cruise Main</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Disengage Lateral on Brake</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modular Assistive Driving System (MADS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MADS: Unified Engagement Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
|
||||
+38
-49
@@ -54,7 +54,6 @@ RETRY_DELAY = 10 # seconds
|
||||
MAX_RETRY_COUNT = 30 # Try for at most 5 minutes if upload fails immediately
|
||||
MAX_AGE = 31 * 24 * 3600 # seconds
|
||||
WS_FRAME_SIZE = 4096
|
||||
DEVICE_STATE_UPDATE_INTERVAL = 1.0 # in seconds
|
||||
|
||||
NetworkType = log.DeviceState.NetworkType
|
||||
|
||||
@@ -100,9 +99,9 @@ send_queue: Queue[str] = queue.Queue()
|
||||
upload_queue: Queue[UploadItem] = queue.Queue()
|
||||
low_priority_send_queue: Queue[str] = queue.Queue()
|
||||
log_recv_queue: Queue[str] = queue.Queue()
|
||||
cancelled_uploads: set[str] = set()
|
||||
|
||||
cur_upload_items: dict[int, UploadItem | None] = {}
|
||||
cur_upload_items_lock = threading.Lock()
|
||||
|
||||
|
||||
def strip_zst_extension(fn: str) -> str:
|
||||
@@ -130,9 +129,8 @@ class UploadQueueCache:
|
||||
@staticmethod
|
||||
def cache(upload_queue: Queue[UploadItem]) -> None:
|
||||
try:
|
||||
with upload_queue.mutex:
|
||||
items = [asdict(item) for item in upload_queue.queue]
|
||||
|
||||
queue: list[UploadItem | None] = list(upload_queue.queue)
|
||||
items = [asdict(i) for i in queue if i is not None and (i.id not in cancelled_uploads)]
|
||||
Params().put("AthenadUploadQueue", json.dumps(items))
|
||||
except Exception:
|
||||
cloudlog.exception("athena.UploadQueueCache.cache.exception")
|
||||
@@ -199,13 +197,11 @@ def retry_upload(tid: int, end_event: threading.Event, increase_count: bool = Tr
|
||||
progress=0,
|
||||
current=False
|
||||
)
|
||||
|
||||
with cur_upload_items_lock:
|
||||
upload_queue.put_nowait(item)
|
||||
cur_upload_items[tid] = None
|
||||
|
||||
upload_queue.put_nowait(item)
|
||||
UploadQueueCache.cache(upload_queue)
|
||||
|
||||
cur_upload_items[tid] = None
|
||||
|
||||
for _ in range(RETRY_DELAY):
|
||||
time.sleep(1)
|
||||
if end_event.is_set():
|
||||
@@ -215,17 +211,15 @@ def retry_upload(tid: int, end_event: threading.Event, increase_count: bool = Tr
|
||||
def cb(sm, item, tid, end_event: threading.Event, sz: int, cur: int) -> None:
|
||||
# Abort transfer if connection changed to metered after starting upload
|
||||
# or if athenad is shutting down to re-connect the websocket
|
||||
if not item.allow_cellular:
|
||||
if (time.monotonic() - sm.recv_time['deviceState']) > DEVICE_STATE_UPDATE_INTERVAL:
|
||||
sm.update(0)
|
||||
if sm['deviceState'].networkMetered:
|
||||
raise AbortTransferException
|
||||
sm.update(0)
|
||||
metered = sm['deviceState'].networkMetered
|
||||
if metered and (not item.allow_cellular):
|
||||
raise AbortTransferException
|
||||
|
||||
if end_event.is_set():
|
||||
raise AbortTransferException
|
||||
|
||||
with cur_upload_items_lock:
|
||||
cur_upload_items[tid] = replace(item, progress=cur / sz if sz else 1)
|
||||
cur_upload_items[tid] = replace(item, progress=cur / sz if sz else 1)
|
||||
|
||||
|
||||
def upload_handler(end_event: threading.Event) -> None:
|
||||
@@ -233,10 +227,14 @@ def upload_handler(end_event: threading.Event) -> None:
|
||||
tid = threading.get_ident()
|
||||
|
||||
while not end_event.is_set():
|
||||
cur_upload_items[tid] = None
|
||||
|
||||
try:
|
||||
with cur_upload_items_lock:
|
||||
cur_upload_items[tid] = None
|
||||
cur_upload_items[tid] = item = replace(upload_queue.get(timeout=1), current=True)
|
||||
cur_upload_items[tid] = item = replace(upload_queue.get(timeout=1), current=True)
|
||||
|
||||
if item.id in cancelled_uploads:
|
||||
cancelled_uploads.remove(item.id)
|
||||
continue
|
||||
|
||||
# Remove item if too old
|
||||
age = datetime.now() - datetime.fromtimestamp(item.created_at / 1000)
|
||||
@@ -260,13 +258,13 @@ def upload_handler(end_event: threading.Event) -> None:
|
||||
sz = -1
|
||||
|
||||
cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered, retry_count=item.retry_count)
|
||||
response = _do_upload(item, partial(cb, sm, item, tid, end_event))
|
||||
|
||||
with _do_upload(item, partial(cb, sm, item, tid, end_event)) as response:
|
||||
if response.status_code not in (200, 201, 401, 403, 412):
|
||||
cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
retry_upload(tid, end_event)
|
||||
else:
|
||||
cloudlog.event("athena.upload_handler.success", fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
if response.status_code not in (200, 201, 401, 403, 412):
|
||||
cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
retry_upload(tid, end_event)
|
||||
else:
|
||||
cloudlog.event("athena.upload_handler.success", fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
|
||||
UploadQueueCache.cache(upload_queue)
|
||||
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError, requests.exceptions.SSLError):
|
||||
@@ -311,16 +309,13 @@ def getMessage(service: str, timeout: int = 1000) -> dict:
|
||||
raise Exception("invalid service")
|
||||
|
||||
socket = messaging.sub_sock(service, timeout=timeout)
|
||||
try:
|
||||
ret = messaging.recv_one(socket)
|
||||
ret = messaging.recv_one(socket)
|
||||
|
||||
if ret is None:
|
||||
raise TimeoutError
|
||||
if ret is None:
|
||||
raise TimeoutError
|
||||
|
||||
# this is because capnp._DynamicStructReader doesn't have typing information
|
||||
return cast(dict, ret.to_dict())
|
||||
finally:
|
||||
del socket
|
||||
# this is because capnp._DynamicStructReader doesn't have typing information
|
||||
return cast(dict, ret.to_dict())
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
@@ -415,10 +410,8 @@ def uploadFilesToUrls(files_data: list[UploadFileDict]) -> UploadFilesToUrlRespo
|
||||
|
||||
@dispatcher.add_method
|
||||
def listUploadQueue() -> list[UploadItemDict]:
|
||||
with cur_upload_items_lock, upload_queue.mutex:
|
||||
items = list(upload_queue.queue) + [item for item in cur_upload_items.values() if item is not None]
|
||||
|
||||
return [asdict(item) for item in items]
|
||||
items = list(upload_queue.queue) + list(cur_upload_items.values())
|
||||
return [asdict(i) for i in items if (i is not None) and (i.id not in cancelled_uploads)]
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
@@ -426,14 +419,13 @@ def cancelUpload(upload_id: str | list[str]) -> dict[str, int | str]:
|
||||
if not isinstance(upload_id, list):
|
||||
upload_id = [upload_id]
|
||||
|
||||
with upload_queue.mutex:
|
||||
remaining_items = [item for item in upload_queue.queue if item.id not in upload_id]
|
||||
if len(remaining_items) == len(upload_queue.queue):
|
||||
return {"success": 0, "error": "not found"}
|
||||
uploading_ids = {item.id for item in list(upload_queue.queue)}
|
||||
cancelled_ids = uploading_ids.intersection(upload_id)
|
||||
if len(cancelled_ids) == 0:
|
||||
return {"success": 0, "error": "not found"}
|
||||
|
||||
upload_queue.queue.clear()
|
||||
upload_queue.queue.extend(remaining_items)
|
||||
return {"success": 1}
|
||||
cancelled_uploads.update(cancelled_ids)
|
||||
return {"success": 1}
|
||||
|
||||
@dispatcher.add_method
|
||||
def setRouteViewed(route: str) -> dict[str, int | str]:
|
||||
@@ -634,9 +626,8 @@ def log_handler(end_event: threading.Event) -> None:
|
||||
|
||||
def stat_handler(end_event: threading.Event) -> None:
|
||||
STATS_DIR = Paths.stats_root()
|
||||
last_scan = 0.0
|
||||
|
||||
while not end_event.is_set():
|
||||
last_scan = 0.
|
||||
curr_scan = time.monotonic()
|
||||
try:
|
||||
if curr_scan - last_scan > 10:
|
||||
@@ -810,8 +801,6 @@ def main(exit_event: threading.Event = None):
|
||||
cur_upload_items.clear()
|
||||
|
||||
handle_long_poll(ws, exit_event)
|
||||
|
||||
ws.close()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
break
|
||||
except (ConnectionError, TimeoutError, WebSocketException):
|
||||
|
||||
@@ -78,6 +78,7 @@ class TestAthenadMethods:
|
||||
|
||||
athenad.upload_queue = queue.Queue()
|
||||
athenad.cur_upload_items.clear()
|
||||
athenad.cancelled_uploads.clear()
|
||||
|
||||
for i in os.listdir(Paths.log_root()):
|
||||
p = os.path.join(Paths.log_root(), i)
|
||||
@@ -239,7 +240,7 @@ class TestAthenadMethods:
|
||||
@with_upload_handler
|
||||
def test_upload_handler_retry(self, mocker, host, status, retry):
|
||||
mock_put = mocker.patch('requests.put')
|
||||
mock_put.return_value.__enter__.return_value.status_code = status
|
||||
mock_put.return_value.status_code = status
|
||||
fn = self._create_file('qlog.zst')
|
||||
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.zst", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
|
||||
|
||||
@@ -281,10 +282,13 @@ class TestAthenadMethods:
|
||||
athenad.upload_queue.put_nowait(item)
|
||||
dispatcher["cancelUpload"](item.id)
|
||||
|
||||
assert item.id in athenad.cancelled_uploads
|
||||
|
||||
self._wait_for_upload()
|
||||
time.sleep(0.1)
|
||||
|
||||
assert athenad.upload_queue.qsize() == 0
|
||||
assert len(athenad.cancelled_uploads) == 0
|
||||
|
||||
@with_upload_handler
|
||||
def test_cancel_expiry(self):
|
||||
@@ -327,7 +331,7 @@ class TestAthenadMethods:
|
||||
assert items[0] == asdict(item)
|
||||
assert not items[0]['current']
|
||||
|
||||
dispatcher["cancelUpload"](item.id)
|
||||
athenad.cancelled_uploads.add(item.id)
|
||||
items = dispatcher["listUploadQueue"]()
|
||||
assert len(items) == 0
|
||||
|
||||
@@ -339,7 +343,7 @@ class TestAthenadMethods:
|
||||
athenad.upload_queue.put_nowait(item2)
|
||||
|
||||
# Ensure canceled items are not persisted
|
||||
dispatcher["cancelUpload"](item2.id)
|
||||
athenad.cancelled_uploads.add(item2.id)
|
||||
|
||||
# serialize item
|
||||
athenad.UploadQueueCache.cache(athenad.upload_queue)
|
||||
|
||||
@@ -65,19 +65,16 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *
|
||||
const SensorInfo *sensor = cam->sensor.get();
|
||||
|
||||
is_raw = cam->is_raw;
|
||||
camera_bufs_raw = std::make_unique<VisionBuf[]>(frame_buf_count);
|
||||
frame_metadata = std::make_unique<FrameMetadata[]>(frame_buf_count);
|
||||
|
||||
// RAW frames from ISP
|
||||
if (is_raw) {
|
||||
camera_bufs_raw = std::make_unique<VisionBuf[]>(frame_buf_count);
|
||||
|
||||
const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride;
|
||||
for (int i = 0; i < frame_buf_count; i++) {
|
||||
camera_bufs_raw[i].allocate(raw_frame_size);
|
||||
camera_bufs_raw[i].init_cl(device_id, context);
|
||||
}
|
||||
LOGD("allocated %d CL buffers", frame_buf_count);
|
||||
// RAW + final frames from ISP
|
||||
const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride;
|
||||
for (int i = 0; i < frame_buf_count; i++) {
|
||||
camera_bufs_raw[i].allocate(raw_frame_size);
|
||||
camera_bufs_raw[i].init_cl(device_id, context);
|
||||
}
|
||||
LOGD("allocated %d CL buffers", frame_buf_count);
|
||||
|
||||
out_img_width = sensor->frame_width;
|
||||
out_img_height = sensor->hdr_offset > 0 ? (sensor->frame_height - sensor->hdr_offset) / 2 : sensor->frame_height;
|
||||
@@ -86,8 +83,8 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *
|
||||
// TODO: VENUS_BUFFER_SIZE should give the size, but it's too small. dependent on encoder settings?
|
||||
size_t nv12_size = (out_img_width <= 1344 ? 2900 : 2346)*cam->stride;
|
||||
|
||||
vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, nv12_size, cam->stride, cam->uv_offset);
|
||||
LOGD("created %d YUV vipc buffers with size %dx%d", VIPC_BUFFER_COUNT, cam->stride, cam->y_height);
|
||||
vipc_server->create_buffers_with_sizes(stream_type, YUV_BUFFER_COUNT, out_img_width, out_img_height, nv12_size, cam->stride, cam->uv_offset);
|
||||
LOGD("created %d YUV vipc buffers with size %dx%d", YUV_BUFFER_COUNT, cam->stride, cam->y_height);
|
||||
|
||||
imgproc = new ImgProc(device_id, context, this, sensor, cam->cc.camera_num, cam->stride, cam->uv_offset);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "common/util.h"
|
||||
|
||||
|
||||
const int VIPC_BUFFER_COUNT = 18;
|
||||
const int YUV_BUFFER_COUNT = 20;
|
||||
|
||||
typedef struct FrameMetadata {
|
||||
uint32_t frame_id;
|
||||
|
||||
@@ -69,10 +69,10 @@ public:
|
||||
};
|
||||
|
||||
void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) {
|
||||
camera.camera_open(v, device_id, ctx);
|
||||
|
||||
if (!camera.enabled) return;
|
||||
|
||||
camera.camera_open(v, device_id, ctx);
|
||||
|
||||
fl_pix = camera.cc.focal_len / camera.sensor->pixel_size_mm;
|
||||
set_exposure_rect();
|
||||
|
||||
@@ -140,14 +140,14 @@ void CameraState::set_camera_exposure(float grey_frac) {
|
||||
// Therefore we use the target EV from 3 frames ago, the grey fraction that was just measured was the result of that control action.
|
||||
// TODO: Lower latency to 2 frames, by using the histogram outputted by the sensor we can do AE before the debayering is complete
|
||||
|
||||
const float cur_ev_ = cur_ev[camera.buf.cur_frame_data.frame_id % 3];
|
||||
const auto &sensor = camera.sensor;
|
||||
const float cur_ev_ = cur_ev[camera.buf.cur_frame_data.frame_id % 3] * sensor->ev_scale;
|
||||
|
||||
// Scale target grey between 0.1 and 0.4 depending on lighting conditions
|
||||
float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + sensor->target_grey_factor*cur_ev_) / log2(6000.0), 0.1, 0.4);
|
||||
float target_grey = (1.0 - k_grey) * target_grey_fraction + k_grey * new_target_grey;
|
||||
|
||||
float desired_ev = std::clamp(cur_ev_ / sensor->ev_scale * target_grey / grey_frac, sensor->min_ev, sensor->max_ev);
|
||||
float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, sensor->min_ev, sensor->max_ev);
|
||||
float k = (1.0 - k_ev) / 3.0;
|
||||
desired_ev = (k * cur_ev[0]) + (k * cur_ev[1]) + (k * cur_ev[2]) + (k_ev * desired_ev);
|
||||
|
||||
|
||||
@@ -242,9 +242,6 @@ SpectraCamera::SpectraCamera(SpectraMaster *master, const CameraConfig &config,
|
||||
cc(config),
|
||||
is_raw(raw) {
|
||||
mm.init(m->video0_fd);
|
||||
|
||||
ife_buf_depth = is_raw ? 4 : VIPC_BUFFER_COUNT;
|
||||
assert(ife_buf_depth < MAX_IFE_BUFS);
|
||||
}
|
||||
|
||||
SpectraCamera::~SpectraCamera() {
|
||||
@@ -264,12 +261,12 @@ int SpectraCamera::clear_req_queue() {
|
||||
}
|
||||
|
||||
void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) {
|
||||
if (!enabled) return;
|
||||
|
||||
if (!openSensor()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enabled) return;
|
||||
|
||||
// size is driven by all the HW that handles frames,
|
||||
// the video encoder has certain alignment requirements in this case
|
||||
stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, sensor->frame_width);
|
||||
@@ -291,15 +288,14 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c
|
||||
linkDevices();
|
||||
|
||||
LOGD("camera init %d", cc.camera_num);
|
||||
buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type);
|
||||
buf.init(device_id, ctx, this, v, FRAME_BUF_COUNT, cc.stream_type);
|
||||
camera_map_bufs();
|
||||
enqueue_req_multi(1, ife_buf_depth, 0);
|
||||
}
|
||||
|
||||
void SpectraCamera::enqueue_req_multi(uint64_t start, int n, bool dp) {
|
||||
for (uint64_t i = start; i < start + n; ++i) {
|
||||
request_ids[(i - 1) % ife_buf_depth] = i;
|
||||
enqueue_buffer((i - 1) % ife_buf_depth, dp);
|
||||
request_ids[(i - 1) % FRAME_BUF_COUNT] = i;
|
||||
enqueue_buffer((i - 1) % FRAME_BUF_COUNT, dp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,7 +670,7 @@ void SpectraCamera::enqueue_buffer(int i, bool dp) {
|
||||
int ret;
|
||||
uint64_t request_id = request_ids[i];
|
||||
|
||||
if (sync_objs[i]) {
|
||||
if (buf_handle_raw[i] && sync_objs[i]) {
|
||||
// wait
|
||||
struct cam_sync_wait sync_wait = {0};
|
||||
sync_wait.sync_obj = sync_objs[i];
|
||||
@@ -737,31 +733,31 @@ void SpectraCamera::enqueue_buffer(int i, bool dp) {
|
||||
|
||||
void SpectraCamera::camera_map_bufs() {
|
||||
int ret;
|
||||
for (int i = 0; i < ife_buf_depth; i++) {
|
||||
for (int i = 0; i < FRAME_BUF_COUNT; i++) {
|
||||
// configure ISP to put the image in place
|
||||
struct cam_mem_mgr_map_cmd mem_mgr_map_cmd = {0};
|
||||
mem_mgr_map_cmd.flags = CAM_MEM_FLAG_HW_READ_WRITE;
|
||||
mem_mgr_map_cmd.mmu_hdls[0] = m->device_iommu;
|
||||
mem_mgr_map_cmd.num_hdl = 1;
|
||||
//mem_mgr_map_cmd.mmu_hdls[1] = m->icp_device_iommu;
|
||||
//mem_mgr_map_cmd.num_hdl = 2;
|
||||
mem_mgr_map_cmd.num_hdl = 1;
|
||||
mem_mgr_map_cmd.flags = CAM_MEM_FLAG_HW_READ_WRITE;
|
||||
|
||||
if (is_raw) {
|
||||
// RAW bayer images
|
||||
mem_mgr_map_cmd.fd = buf.camera_bufs_raw[i].fd;
|
||||
ret = do_cam_control(m->video0_fd, CAM_REQ_MGR_MAP_BUF, &mem_mgr_map_cmd, sizeof(mem_mgr_map_cmd));
|
||||
assert(ret == 0);
|
||||
LOGD("map buf req: (fd: %d) 0x%x %d", buf.camera_bufs_raw[i].fd, mem_mgr_map_cmd.out.buf_handle, ret);
|
||||
buf_handle_raw[i] = mem_mgr_map_cmd.out.buf_handle;
|
||||
} else {
|
||||
// final processed images
|
||||
VisionBuf *vb = buf.vipc_server->get_buffer(buf.stream_type, i);
|
||||
mem_mgr_map_cmd.fd = vb->fd;
|
||||
ret = do_cam_control(m->video0_fd, CAM_REQ_MGR_MAP_BUF, &mem_mgr_map_cmd, sizeof(mem_mgr_map_cmd));
|
||||
LOGD("map buf req: (fd: %d) 0x%x %d", vb->fd, mem_mgr_map_cmd.out.buf_handle, ret);
|
||||
buf_handle_yuv[i] = mem_mgr_map_cmd.out.buf_handle;
|
||||
}
|
||||
// RAW bayer images
|
||||
mem_mgr_map_cmd.fd = buf.camera_bufs_raw[i].fd;
|
||||
ret = do_cam_control(m->video0_fd, CAM_REQ_MGR_MAP_BUF, &mem_mgr_map_cmd, sizeof(mem_mgr_map_cmd));
|
||||
assert(ret == 0);
|
||||
LOGD("map buf req: (fd: %d) 0x%x %d", buf.camera_bufs_raw[i].fd, mem_mgr_map_cmd.out.buf_handle, ret);
|
||||
buf_handle_raw[i] = mem_mgr_map_cmd.out.buf_handle;
|
||||
|
||||
// TODO: this needs to match camera bufs length
|
||||
// final processed images
|
||||
VisionBuf *vb = buf.vipc_server->get_buffer(buf.stream_type, i);
|
||||
mem_mgr_map_cmd.fd = vb->fd;
|
||||
ret = do_cam_control(m->video0_fd, CAM_REQ_MGR_MAP_BUF, &mem_mgr_map_cmd, sizeof(mem_mgr_map_cmd));
|
||||
LOGD("map buf req: (fd: %d) 0x%x %d", vb->fd, mem_mgr_map_cmd.out.buf_handle, ret);
|
||||
buf_handle_yuv[i] = mem_mgr_map_cmd.out.buf_handle;
|
||||
}
|
||||
enqueue_req_multi(1, FRAME_BUF_COUNT, 0);
|
||||
}
|
||||
|
||||
bool SpectraCamera::openSensor() {
|
||||
@@ -874,7 +870,7 @@ void SpectraCamera::configISP() {
|
||||
// allocate IFE memory, then configure it
|
||||
ife_cmd.init(m, 67984, 0x20,
|
||||
CAM_MEM_FLAG_HW_READ_WRITE | CAM_MEM_FLAG_KMD_ACCESS | CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE,
|
||||
m->device_iommu, m->cdm_iommu, ife_buf_depth);
|
||||
m->device_iommu, m->cdm_iommu, FRAME_BUF_COUNT);
|
||||
if (!is_raw) {
|
||||
ife_gamma_lut.init(m, 64*sizeof(uint32_t), 0x20,
|
||||
CAM_MEM_FLAG_HW_READ_WRITE | CAM_MEM_FLAG_KMD_ACCESS | CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE,
|
||||
@@ -929,7 +925,7 @@ void SpectraCamera::configICP() {
|
||||
|
||||
// BPS CMD buffer
|
||||
unsigned char striping_out[] = "\x00";
|
||||
bps_cmd.init(m, ife_buf_depth*ALIGNED_SIZE(464, 0x20), 0x20,
|
||||
bps_cmd.init(m, FRAME_BUF_COUNT*ALIGNED_SIZE(464, 0x20), 0x20,
|
||||
CAM_MEM_FLAG_HW_READ_WRITE | CAM_MEM_FLAG_KMD_ACCESS | CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE | CAM_MEM_FLAG_HW_SHARED_ACCESS,
|
||||
m->icp_device_iommu);
|
||||
|
||||
@@ -1050,8 +1046,9 @@ void SpectraCamera::camera_close() {
|
||||
ret = device_control(csiphy_fd, CAM_RELEASE_DEV, session_handle, csiphy_dev_handle);
|
||||
LOGD("release csiphy: %d", ret);
|
||||
|
||||
for (int i = 0; i < ife_buf_depth; i++) {
|
||||
release(m->video0_fd, is_raw ? buf_handle_raw[i] : buf_handle_yuv[i]);
|
||||
for (int i = 0; i < FRAME_BUF_COUNT; i++) {
|
||||
release(m->video0_fd, buf_handle_yuv[i]);
|
||||
release(m->video0_fd, buf_handle_raw[i]);
|
||||
}
|
||||
LOGD("released buffers");
|
||||
}
|
||||
@@ -1074,13 +1071,13 @@ void SpectraCamera::handle_camera_event(const cam_req_mgr_message *event_data) {
|
||||
|
||||
if (real_id != 0) { // next ready
|
||||
if (real_id == 1) {idx_offset = main_id;}
|
||||
int buf_idx = (real_id - 1) % ife_buf_depth;
|
||||
int buf_idx = (real_id - 1) % FRAME_BUF_COUNT;
|
||||
|
||||
// check for skipped frames
|
||||
if (main_id > frame_id_last + 1 && !skipped) {
|
||||
LOGE("camera %d realign", cc.camera_num);
|
||||
clear_req_queue();
|
||||
enqueue_req_multi(real_id + 1, ife_buf_depth - 1, 0);
|
||||
enqueue_req_multi(real_id + 1, FRAME_BUF_COUNT - 1, 0);
|
||||
skipped = true;
|
||||
} else if (main_id == frame_id_last + 1) {
|
||||
skipped = false;
|
||||
@@ -1089,7 +1086,7 @@ void SpectraCamera::handle_camera_event(const cam_req_mgr_message *event_data) {
|
||||
// check for dropped requests
|
||||
if (real_id > request_id_last + 1) {
|
||||
LOGE("camera %d dropped requests %ld %ld", cc.camera_num, real_id, request_id_last);
|
||||
enqueue_req_multi(request_id_last + 1 + ife_buf_depth, real_id - (request_id_last + 1), 0);
|
||||
enqueue_req_multi(request_id_last + 1 + FRAME_BUF_COUNT, real_id - (request_id_last + 1), 0);
|
||||
}
|
||||
|
||||
// metas
|
||||
@@ -1102,12 +1099,12 @@ void SpectraCamera::handle_camera_event(const cam_req_mgr_message *event_data) {
|
||||
meta_data.timestamp_sof = timestamp; // this is timestamped in the kernel's SOF IRQ callback
|
||||
|
||||
// dispatch
|
||||
enqueue_req_multi(real_id + ife_buf_depth, 1, 1);
|
||||
enqueue_req_multi(real_id + FRAME_BUF_COUNT, 1, 1);
|
||||
} else { // not ready
|
||||
if (main_id > frame_id_last + 10) {
|
||||
LOGE("camera %d reset after half second of no response", cc.camera_num);
|
||||
clear_req_queue();
|
||||
enqueue_req_multi(request_id_last + 1, ife_buf_depth, 0);
|
||||
enqueue_req_multi(request_id_last + 1, FRAME_BUF_COUNT, 0);
|
||||
frame_id_last = main_id;
|
||||
skipped = true;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "system/camerad/cameras/camera_common.h"
|
||||
#include "system/camerad/sensors/sensor.h"
|
||||
|
||||
#define MAX_IFE_BUFS 20
|
||||
#define FRAME_BUF_COUNT 4
|
||||
|
||||
const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py
|
||||
|
||||
@@ -116,7 +116,6 @@ public:
|
||||
|
||||
// *** state ***
|
||||
|
||||
int ife_buf_depth = -1;
|
||||
bool open = false;
|
||||
bool enabled = true;
|
||||
CameraConfig cc;
|
||||
@@ -152,11 +151,11 @@ public:
|
||||
SpectraBuf bps_iq;
|
||||
SpectraBuf bps_striping;
|
||||
|
||||
int buf_handle_yuv[MAX_IFE_BUFS] = {};
|
||||
int buf_handle_raw[MAX_IFE_BUFS] = {};
|
||||
int sync_objs[MAX_IFE_BUFS] = {};
|
||||
int sync_objs_bps_out[MAX_IFE_BUFS] = {};
|
||||
uint64_t request_ids[MAX_IFE_BUFS] = {};
|
||||
int buf_handle_yuv[FRAME_BUF_COUNT] = {};
|
||||
int buf_handle_raw[FRAME_BUF_COUNT] = {};
|
||||
int sync_objs[FRAME_BUF_COUNT] = {};
|
||||
int sync_objs_bps_out[FRAME_BUF_COUNT] = {};
|
||||
uint64_t request_ids[FRAME_BUF_COUNT] = {};
|
||||
uint64_t request_id_last = 0;
|
||||
uint64_t frame_id_last = 0;
|
||||
uint64_t idx_offset = 0;
|
||||
|
||||
@@ -43,28 +43,27 @@ OS04C10::OS04C10() {
|
||||
frame_data_type = 0x2c;
|
||||
mclk_frequency = 24000000; // Hz
|
||||
|
||||
ev_scale = 150.0;
|
||||
dc_gain_factor = 1;
|
||||
dc_gain_min_weight = 1; // always on is fine
|
||||
dc_gain_max_weight = 1;
|
||||
dc_gain_on_grey = 0.9;
|
||||
dc_gain_off_grey = 1.0;
|
||||
exposure_time_min = 2;
|
||||
exposure_time_max = 1684;
|
||||
exposure_time_max = 2400;
|
||||
analog_gain_min_idx = 0x0;
|
||||
analog_gain_rec_idx = 0x0; // 1x
|
||||
analog_gain_max_idx = 0x28;
|
||||
analog_gain_max_idx = 0x36;
|
||||
analog_gain_cost_delta = -1;
|
||||
analog_gain_cost_low = 0.4;
|
||||
analog_gain_cost_high = 6.4;
|
||||
for (int i = 0; i <= analog_gain_max_idx; i++) {
|
||||
sensor_analog_gains[i] = sensor_analog_gains_OS04C10[i];
|
||||
}
|
||||
min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx];
|
||||
min_ev = (exposure_time_min) * sensor_analog_gains[analog_gain_min_idx];
|
||||
max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx];
|
||||
target_grey_factor = 0.01;
|
||||
|
||||
black_level = 48;
|
||||
black_level = 64;
|
||||
color_correct_matrix = {
|
||||
0x000000c2, 0x00000fe0, 0x00000fde,
|
||||
0x00000fa7, 0x000000d9, 0x00001000,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user