mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-10 11:23:42 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d68a6cd87d |
@@ -0,0 +1,11 @@
|
|||||||
|
* @sunnypilot/dev-internal
|
||||||
|
/.github/ @devtekve @sunnyhaibin
|
||||||
|
/release/ci/ @devtekve @sunnyhaibin
|
||||||
|
/tinygrad_repo @devtekve @Discountchubbs
|
||||||
|
/tinygrad/ @devtekve @Discountchubbs
|
||||||
|
/selfdrive/controls/lib/longitudinal_planner.py @devtekve @Discountchubbs
|
||||||
|
/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @devtekve @Discountchubbs
|
||||||
|
/selfdrive/modeld/ @devtekve @Discountchubbs
|
||||||
|
/sunnypilot/model* @devtekve @Discountchubbs
|
||||||
|
/sunnypilot/sunnylink/ @devtekve
|
||||||
|
/system/athena/ @devtekve
|
||||||
@@ -8,13 +8,13 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
target_hardware:
|
target_hardware:
|
||||||
description: 'Hardware target to compile for (qcom or chestnut)'
|
description: 'Hardware target to compile for (qcom or usbgpu)'
|
||||||
required: true
|
required: true
|
||||||
type: choice
|
type: choice
|
||||||
default: 'qcom'
|
default: 'qcom'
|
||||||
options:
|
options:
|
||||||
- qcom
|
- qcom
|
||||||
- chestnut
|
- usbgpu
|
||||||
hf_repo:
|
hf_repo:
|
||||||
description: 'Hugging Face dataset repository'
|
description: 'Hugging Face dataset repository'
|
||||||
required: false
|
required: false
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
id: get-json
|
id: get-json
|
||||||
run: |
|
run: |
|
||||||
cd docs/docs
|
cd docs/docs
|
||||||
PREFIX="driving_models_${{ inputs.target_hardware == 'chestnut' && 'chestnut_' || '' }}v"
|
PREFIX="driving_models_${{ inputs.target_hardware == 'usbgpu' && 'usbgpu_' || '' }}v"
|
||||||
latest=$(ls ${PREFIX}*.json | sed -E "s/${PREFIX}([0-9]+)\.json/\1/" | sort -n | tail -1)
|
latest=$(ls ${PREFIX}*.json | sed -E "s/${PREFIX}([0-9]+)\.json/\1/" | sort -n | tail -1)
|
||||||
next=$((latest+1))
|
next=$((latest+1))
|
||||||
json_file="${PREFIX}${next}.json"
|
json_file="${PREFIX}${next}.json"
|
||||||
@@ -78,7 +78,6 @@ jobs:
|
|||||||
- name: Get next recompiled dir number
|
- name: Get next recompiled dir number
|
||||||
id: create-recompiled-dir
|
id: create-recompiled-dir
|
||||||
env:
|
env:
|
||||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
|
||||||
HF_REPO: ${{ github.event.inputs.hf_repo }}
|
HF_REPO: ${{ github.event.inputs.hf_repo }}
|
||||||
run: |
|
run: |
|
||||||
pip install huggingface_hub
|
pip install huggingface_hub
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
outputs:
|
outputs:
|
||||||
model_name: ${{ steps.resolve.outputs.model_name }}
|
model_name: ${{ steps.resolve.outputs.model_name }}
|
||||||
safe_model_name: ${{ steps.resolve.outputs.safe_model_name }}
|
|
||||||
onnx_ref: ${{ steps.resolve.outputs.onnx_ref }}
|
onnx_ref: ${{ steps.resolve.outputs.onnx_ref }}
|
||||||
onnx_path: ${{ steps.resolve.outputs.onnx_path }}
|
onnx_path: ${{ steps.resolve.outputs.onnx_path }}
|
||||||
hf_defaults_path: ${{ steps.resolve.outputs.hf_defaults_path }}
|
hf_defaults_path: ${{ steps.resolve.outputs.hf_defaults_path }}
|
||||||
@@ -65,9 +64,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SAFE_NAME="${NAME// /-}"
|
|
||||||
echo "model_name=${NAME}" >> $GITHUB_OUTPUT
|
echo "model_name=${NAME}" >> $GITHUB_OUTPUT
|
||||||
echo "safe_model_name=${SAFE_NAME}" >> $GITHUB_OUTPUT
|
|
||||||
echo "onnx_ref=${ONNX_REF}" >> $GITHUB_OUTPUT
|
echo "onnx_ref=${ONNX_REF}" >> $GITHUB_OUTPUT
|
||||||
echo "onnx_path=${ONNX_PATH}" >> $GITHUB_OUTPUT
|
echo "onnx_path=${ONNX_PATH}" >> $GITHUB_OUTPUT
|
||||||
echo "hf_defaults_path=${HF_DEFAULTS_PATH}" >> $GITHUB_OUTPUT
|
echo "hf_defaults_path=${HF_DEFAULTS_PATH}" >> $GITHUB_OUTPUT
|
||||||
@@ -138,7 +135,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Prepare output
|
- name: Prepare output
|
||||||
env:
|
env:
|
||||||
MODEL_NAME: ${{ needs.resolve.outputs.safe_model_name }}
|
MODEL_NAME: ${{ needs.resolve.outputs.model_name }}
|
||||||
run: |
|
run: |
|
||||||
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
export PYTHONPATH=${{ github.workspace }}
|
export PYTHONPATH=${{ github.workspace }}
|
||||||
@@ -161,13 +158,13 @@ jobs:
|
|||||||
- name: Upload small model artifact
|
- name: Upload small model artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: model-${{ needs.resolve.outputs.safe_model_name }}-${{ github.run_number }}
|
name: model-${{ needs.resolve.outputs.model_name }}-${{ github.run_number }}
|
||||||
path: ${{ github.workspace }}/small_output/
|
path: ${{ github.workspace }}/small_output/
|
||||||
|
|
||||||
- name: Upload artifact name file
|
- name: Upload artifact name file
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifact-name-${{ needs.resolve.outputs.safe_model_name }}
|
name: artifact-name-${{ needs.resolve.outputs.model_name }}
|
||||||
path: ${{ github.workspace }}/small_output/artifact_name.txt
|
path: ${{ github.workspace }}/small_output/artifact_name.txt
|
||||||
|
|
||||||
- name: Re-enable powersave
|
- name: Re-enable powersave
|
||||||
@@ -179,7 +176,7 @@ jobs:
|
|||||||
build_big_model:
|
build_big_model:
|
||||||
needs: resolve
|
needs: resolve
|
||||||
if: ${{ inputs.target == 'big' }}
|
if: ${{ inputs.target == 'big' }}
|
||||||
runs-on: [self-hosted, chestnut]
|
runs-on: [self-hosted, usbgpu]
|
||||||
env:
|
env:
|
||||||
BIG_ONNX: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
|
BIG_ONNX: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx
|
||||||
BIG_PKL: openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl
|
BIG_PKL: openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl
|
||||||
@@ -257,7 +254,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Prepare output
|
- name: Prepare output
|
||||||
env:
|
env:
|
||||||
MODEL_NAME: ${{ needs.resolve.outputs.safe_model_name }}
|
MODEL_NAME: ${{ needs.resolve.outputs.model_name }}
|
||||||
run: |
|
run: |
|
||||||
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
export PYTHONPATH=${{ github.workspace }}
|
export PYTHONPATH=${{ github.workspace }}
|
||||||
@@ -280,13 +277,13 @@ jobs:
|
|||||||
- name: Upload big model artifact
|
- name: Upload big model artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: model-${{ needs.resolve.outputs.safe_model_name }}-${{ github.run_number }}
|
name: model-${{ needs.resolve.outputs.model_name }}-${{ github.run_number }}
|
||||||
path: ${{ github.workspace }}/big_output/
|
path: ${{ github.workspace }}/big_output/
|
||||||
|
|
||||||
- name: Upload artifact name file
|
- name: Upload artifact name file
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifact-name-${{ needs.resolve.outputs.safe_model_name }}
|
name: artifact-name-${{ needs.resolve.outputs.model_name }}
|
||||||
path: ${{ github.workspace }}/big_output/artifact_name.txt
|
path: ${{ github.workspace }}/big_output/artifact_name.txt
|
||||||
|
|
||||||
- name: Re-enable powersave
|
- name: Re-enable powersave
|
||||||
@@ -321,7 +318,7 @@ jobs:
|
|||||||
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
|
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifact-name-${{ needs.resolve.outputs.safe_model_name }}
|
name: artifact-name-${{ needs.resolve.outputs.model_name }}
|
||||||
path: artifact_name
|
path: artifact_name
|
||||||
|
|
||||||
- name: Read artifact name
|
- name: Read artifact name
|
||||||
@@ -341,7 +338,7 @@ jobs:
|
|||||||
- name: Upload model to HF
|
- name: Upload model to HF
|
||||||
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
|
if: ${{ inputs.target == 'small' || inputs.target == 'big' }}
|
||||||
env:
|
env:
|
||||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
HF_OIDC_RESOURCE: datasets/${{ env.HF_REPO }}
|
||||||
ARTIFACT_NAME: ${{ steps.artifact.outputs.artifact_name }}
|
ARTIFACT_NAME: ${{ steps.artifact.outputs.artifact_name }}
|
||||||
run: |
|
run: |
|
||||||
rm -f output/artifact_name.txt
|
rm -f output/artifact_name.txt
|
||||||
@@ -367,7 +364,7 @@ jobs:
|
|||||||
- name: Generate DM metadata and upload to HF
|
- name: Generate DM metadata and upload to HF
|
||||||
if: ${{ inputs.target == 'dm' }}
|
if: ${{ inputs.target == 'dm' }}
|
||||||
env:
|
env:
|
||||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
HF_OIDC_RESOURCE: datasets/${{ env.HF_REPO }}
|
||||||
run: |
|
run: |
|
||||||
export PYTHONPATH=$(pwd)
|
export PYTHONPATH=$(pwd)
|
||||||
python3 -c "
|
python3 -c "
|
||||||
@@ -484,29 +481,11 @@ jobs:
|
|||||||
print(f'Chunked {pkl} into {len(targets)} chunks')
|
print(f'Chunked {pkl} into {len(targets)} chunks')
|
||||||
"
|
"
|
||||||
|
|
||||||
- name: Compile DM warp
|
|
||||||
run: |
|
|
||||||
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
|
||||||
export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}"
|
|
||||||
|
|
||||||
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
|
||||||
MODEL_DIR="${{ github.workspace }}/openpilot/selfdrive/modeld"
|
|
||||||
DM_SIZE=$(python3 -c "from openpilot.common.transformations.model import DM_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
|
||||||
|
|
||||||
for res in $(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')"); do
|
|
||||||
WARP_PKL="${MODEL_DIR}/models/dm_warp_${res}_tinygrad.pkl"
|
|
||||||
taskset -c 7 env ${TG_FLAGS} python3 ${MODEL_DIR}/compile_dm_warp.py \
|
|
||||||
--camera-resolution ${res} \
|
|
||||||
--warp-to ${DM_SIZE} \
|
|
||||||
--output ${WARP_PKL}
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Prepare DM output
|
- name: Prepare DM output
|
||||||
run: |
|
run: |
|
||||||
mkdir -p dm_output
|
mkdir -p dm_output
|
||||||
cp ${{ github.workspace }}/${{ env.DM_PKL }}.chunk* dm_output/
|
cp ${{ github.workspace }}/${{ env.DM_PKL }}.chunk* dm_output/
|
||||||
cp ${{ github.workspace }}/${{ env.DM_PKL }}.chunkmanifest dm_output/
|
cp ${{ github.workspace }}/${{ env.DM_PKL }}.chunkmanifest dm_output/
|
||||||
cp ${{ github.workspace }}/openpilot/selfdrive/modeld/models/dm_warp_* dm_output/
|
|
||||||
|
|
||||||
- name: Upload DM artifact
|
- name: Upload DM artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
target_hardware:
|
target_hardware:
|
||||||
description: 'Hardware target to compile for (qcom or chestnut)'
|
description: 'Hardware target to compile for (qcom or usbgpu)'
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: 'qcom'
|
default: 'qcom'
|
||||||
@@ -101,7 +101,7 @@ on:
|
|||||||
default: 'qcom'
|
default: 'qcom'
|
||||||
options:
|
options:
|
||||||
- qcom
|
- qcom
|
||||||
- chestnut
|
- usbgpu
|
||||||
hf_repo:
|
hf_repo:
|
||||||
description: 'Hugging Face dataset repository'
|
description: 'Hugging Face dataset repository'
|
||||||
required: false
|
required: false
|
||||||
@@ -109,7 +109,7 @@ on:
|
|||||||
default: 'sunnypilot/sunnypilot_models_v1'
|
default: 'sunnypilot/sunnypilot_models_v1'
|
||||||
env:
|
env:
|
||||||
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
||||||
JSON_FILE: docs/docs/driving_models_${{ inputs.target_hardware == 'chestnut' && 'chestnut_v' || 'v' }}${{ inputs.json_version }}.json
|
JSON_FILE: docs/docs/driving_models_${{ inputs.target_hardware == 'usbgpu' && 'usbgpu_v' || 'v' }}${{ inputs.json_version }}.json
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_model:
|
build_model:
|
||||||
@@ -146,7 +146,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Validate hf_repo and JSON version
|
- name: Validate hf_repo and JSON version
|
||||||
env:
|
env:
|
||||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
HF_OIDC_RESOURCE: datasets/${{ inputs.hf_repo }}
|
||||||
run: |
|
run: |
|
||||||
if [ ! -f "$JSON_FILE" ]; then
|
if [ ! -f "$JSON_FILE" ]; then
|
||||||
echo "JSON file $JSON_FILE does not exist!"
|
echo "JSON file $JSON_FILE does not exist!"
|
||||||
@@ -155,8 +155,13 @@ jobs:
|
|||||||
python3 -c "
|
python3 -c "
|
||||||
import sys
|
import sys
|
||||||
from huggingface_hub import HfApi
|
from huggingface_hub import HfApi
|
||||||
HfApi().repo_info(repo_id=sys.argv[1], repo_type='dataset')
|
try:
|
||||||
print(f'Success: Repo {sys.argv[1]} exists.')
|
api = HfApi()
|
||||||
|
api.repo_info(repo_id=sys.argv[1], repo_type='dataset')
|
||||||
|
print(f'Success: Repo {sys.argv[1]} exists.')
|
||||||
|
except Exception as e:
|
||||||
|
print('HF validation failed:', e)
|
||||||
|
sys.exit(1)
|
||||||
" "${{ inputs.hf_repo }}"
|
" "${{ inputs.hf_repo }}"
|
||||||
|
|
||||||
- name: Download artifact name file
|
- name: Download artifact name file
|
||||||
@@ -187,7 +192,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload to Hugging Face
|
- name: Upload to Hugging Face
|
||||||
env:
|
env:
|
||||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
HF_OIDC_RESOURCE: datasets/${{ inputs.hf_repo }}
|
||||||
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
ARTIFACT_NAME: ${{ steps.read-artifact-name.outputs.artifact_name }}
|
||||||
run: |
|
run: |
|
||||||
hf upload ${{ inputs.hf_repo }} \
|
hf upload ${{ inputs.hf_repo }} \
|
||||||
|
|||||||
@@ -46,13 +46,6 @@ runs:
|
|||||||
printf '%s\t%s\n' "$ENCODED_URL" "${DEST_DIR}/${CANONICAL}.chunk${CHUNK_IDX}" >> "$DOWNLOAD_LIST"
|
printf '%s\t%s\n' "$ENCODED_URL" "${DEST_DIR}/${CANONICAL}.chunk${CHUNK_IDX}" >> "$DOWNLOAD_LIST"
|
||||||
done < <(echo "$ARTIFACT" | jq -r '.chunks[].file_name')
|
done < <(echo "$ARTIFACT" | jq -r '.chunks[].file_name')
|
||||||
echo "$NUM_CHUNKS" > "${DEST_DIR}/${CANONICAL}.chunkmanifest"
|
echo "$NUM_CHUNKS" > "${DEST_DIR}/${CANONICAL}.chunkmanifest"
|
||||||
|
|
||||||
if [ "$CANONICAL" = "dmonitoring_model_tinygrad.pkl" ]; then
|
|
||||||
for warp in dm_warp_1928x1208_tinygrad.pkl dm_warp_1344x760_tinygrad.pkl; do
|
|
||||||
ENCODED_URL=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${BASE_URL}/${warp}', safe=':/'))")
|
|
||||||
printf '%s\t%s\n' "$ENCODED_URL" "${DEST_DIR}/${warp}" >> "$DOWNLOAD_LIST"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "$MODELS_JSON" | jq -c '.[]' | while IFS= read -r model; do
|
echo "$MODELS_JSON" | jq -c '.[]' | while IFS= read -r model; do
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ on:
|
|||||||
type: string
|
type: string
|
||||||
default: ''
|
default: ''
|
||||||
target_hardware:
|
target_hardware:
|
||||||
description: 'Hardware target to compile for (qcom or chestnut)'
|
description: 'Hardware target to compile for (qcom or usbgpu)'
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: 'qcom'
|
default: 'qcom'
|
||||||
@@ -57,7 +57,7 @@ on:
|
|||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- qcom
|
- qcom
|
||||||
- chestnut
|
- usbgpu
|
||||||
default: 'qcom'
|
default: 'qcom'
|
||||||
|
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ jobs:
|
|||||||
cat $GITHUB_OUTPUT
|
cat $GITHUB_OUTPUT
|
||||||
- run: |
|
- run: |
|
||||||
cd ${{ github.workspace }}/openpilot/openpilot
|
cd ${{ github.workspace }}/openpilot/openpilot
|
||||||
if [ "${{ inputs.target_hardware }}" != "chestnut" ]; then
|
if [ "${{ inputs.target_hardware }}" != "usbgpu" ]; then
|
||||||
git lfs pull -X "**/selfdrive/modeld/models/big_*.onnx,**/selfdrive/modeld/models/dmonitoring_*.onnx"
|
git lfs pull -X "**/selfdrive/modeld/models/big_*.onnx,**/selfdrive/modeld/models/dmonitoring_*.onnx"
|
||||||
rm -f selfdrive/modeld/models/big_*.onnx selfdrive/modeld/models/dmonitoring_*.onnx
|
rm -f selfdrive/modeld/models/big_*.onnx selfdrive/modeld/models/dmonitoring_*.onnx
|
||||||
else
|
else
|
||||||
@@ -121,7 +121,7 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
build_model:
|
build_model:
|
||||||
runs-on: [self-hosted, "${{ inputs.target_hardware == 'chestnut' && 'chestnut' || 'tici' }}"]
|
runs-on: [self-hosted, usbgpu]
|
||||||
needs: get_model
|
needs: get_model
|
||||||
env:
|
env:
|
||||||
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
|
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
|
||||||
@@ -185,10 +185,10 @@ jobs:
|
|||||||
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
||||||
|
|
||||||
TG_FLAGS_QCOM="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
TG_FLAGS_QCOM="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
||||||
if [ "${{ inputs.target_hardware }}" == "chestnut" ]; then
|
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
||||||
echo "CHESTNUT build"
|
echo "USBGPU build"
|
||||||
export CHESTNUT=1
|
export USBGPU=1
|
||||||
TG_FLAGS="DEBUG=1 DEV=USB+AMD:LLVM FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0 TC_OPT=2 TC_OCCUPANCY_OPT=1"
|
TG_FLAGS="DEBUG=1 DEV=USB+AMD:LLVM WARP_DEV=QCOM FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0 TC_OPT=2"
|
||||||
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
||||||
else
|
else
|
||||||
echo "QCOM build"
|
echo "QCOM build"
|
||||||
|
|||||||
@@ -216,9 +216,6 @@ jobs:
|
|||||||
needs: [ prepare_strategy ]
|
needs: [ prepare_strategy ]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
if: ${{ needs.prepare_strategy.outputs.include_big_model == 'true' }}
|
if: ${{ needs.prepare_strategy.outputs.include_big_model == 'true' }}
|
||||||
concurrency:
|
|
||||||
group: prepare-chestnut
|
|
||||||
cancel-in-progress: false
|
|
||||||
outputs:
|
outputs:
|
||||||
onnx_sha256: ${{ steps.resolve.outputs.onnx_sha256 }}
|
onnx_sha256: ${{ steps.resolve.outputs.onnx_sha256 }}
|
||||||
env:
|
env:
|
||||||
@@ -231,10 +228,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
REF="${{ github.head_ref || github.ref_name }}"
|
REF="${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
BLOB_SHA=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx?ref=${REF}" --jq '.sha')
|
ONNX_HASH=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx?ref=${REF}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
|
||||||
ONNX_HASH=$(gh api "repos/${GH_REPO}/git/blobs/${BLOB_SHA}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
|
|
||||||
echo "ONNX hash: $ONNX_HASH"
|
echo "ONNX hash: $ONNX_HASH"
|
||||||
[ -n "$ONNX_HASH" ] || { echo "::error::Failed to extract ONNX hash"; exit 1; }
|
|
||||||
echo "onnx_sha256=$ONNX_HASH" >> $GITHUB_OUTPUT
|
echo "onnx_sha256=$ONNX_HASH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
|
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
|
||||||
@@ -243,7 +238,7 @@ jobs:
|
|||||||
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
|
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
|
||||||
|
|
||||||
check_defaults() {
|
check_defaults() {
|
||||||
DEFAULTS=$(curl -fsSL "${JSON_URL}?t=$(date +%s)" 2>/dev/null) || return 1
|
DEFAULTS=$(curl -fsSL "$JSON_URL" 2>/dev/null) || return 1
|
||||||
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
|
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
|
||||||
[ "$TINYGRAD_MATCH" = "true" ] || return 1
|
[ "$TINYGRAD_MATCH" = "true" ] || return 1
|
||||||
BUNDLE=$(echo "$DEFAULTS" | jq --arg hash "$ONNX_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
|
BUNDLE=$(echo "$DEFAULTS" | jq --arg hash "$ONNX_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
|
||||||
@@ -257,35 +252,18 @@ jobs:
|
|||||||
|
|
||||||
echo "No matching model on HF — dispatching build"
|
echo "No matching model on HF — dispatching build"
|
||||||
gh workflow run build-default-models.yaml --ref "$REF" -f target=big
|
gh workflow run build-default-models.yaml --ref "$REF" -f target=big
|
||||||
sleep 10
|
|
||||||
|
|
||||||
BUILD_RUN_ID=$(gh run list --workflow build-default-models.yaml --branch "$REF" --limit 1 --json databaseId --jq '.[0].databaseId')
|
echo "Polling HF for big model availability..."
|
||||||
echo "Dispatched build run: $BUILD_RUN_ID"
|
|
||||||
|
|
||||||
echo "Waiting for build run to complete..."
|
|
||||||
for i in $(seq 1 90); do
|
for i in $(seq 1 90); do
|
||||||
sleep 30
|
sleep 30
|
||||||
STATUS=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.status')
|
if check_defaults; then
|
||||||
CONCLUSION=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.conclusion')
|
echo "Big model available on HF after $((i * 30))s"
|
||||||
echo "Poll $i/90: status=$STATUS conclusion=$CONCLUSION"
|
exit 0
|
||||||
if [ "$STATUS" = "completed" ]; then
|
|
||||||
if [ "$CONCLUSION" = "success" ]; then
|
|
||||||
echo "Build run succeeded, verifying HF..."
|
|
||||||
sleep 10
|
|
||||||
if check_defaults; then
|
|
||||||
echo "Big model verified on HF"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "::error::Build succeeded but model not found on HF"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "::error::Build run failed with conclusion=$CONCLUSION"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
echo "Poll $i/90: not yet available"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "::error::Build run did not complete within 45 minutes"
|
echo "::error::Big model not available on HF after 45 minutes"
|
||||||
exit 1
|
exit 1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -299,9 +277,6 @@ jobs:
|
|||||||
prepare_small_model:
|
prepare_small_model:
|
||||||
needs: [ prepare_strategy ]
|
needs: [ prepare_strategy ]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
concurrency:
|
|
||||||
group: prepare-small-model
|
|
||||||
cancel-in-progress: false
|
|
||||||
outputs:
|
outputs:
|
||||||
driving_onnx_sha256: ${{ steps.resolve.outputs.driving_onnx_sha256 }}
|
driving_onnx_sha256: ${{ steps.resolve.outputs.driving_onnx_sha256 }}
|
||||||
env:
|
env:
|
||||||
@@ -314,10 +289,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
REF="${{ github.head_ref || github.ref_name }}"
|
REF="${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
BLOB_SHA=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/driving_supercombo.onnx?ref=${REF}" --jq '.sha')
|
DRIVING_HASH=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/driving_supercombo.onnx?ref=${REF}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
|
||||||
DRIVING_HASH=$(gh api "repos/${GH_REPO}/git/blobs/${BLOB_SHA}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
|
|
||||||
echo "Driving ONNX hash: $DRIVING_HASH"
|
echo "Driving ONNX hash: $DRIVING_HASH"
|
||||||
[ -n "$DRIVING_HASH" ] || { echo "::error::Failed to extract driving ONNX hash"; exit 1; }
|
|
||||||
echo "driving_onnx_sha256=$DRIVING_HASH" >> $GITHUB_OUTPUT
|
echo "driving_onnx_sha256=$DRIVING_HASH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
|
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
|
||||||
@@ -326,7 +299,7 @@ jobs:
|
|||||||
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
|
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
|
||||||
|
|
||||||
check_defaults() {
|
check_defaults() {
|
||||||
DEFAULTS=$(curl -fsSL "${JSON_URL}?t=$(date +%s)" 2>/dev/null) || return 1
|
DEFAULTS=$(curl -fsSL "$JSON_URL" 2>/dev/null) || return 1
|
||||||
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
|
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
|
||||||
[ "$TINYGRAD_MATCH" = "true" ] || return 1
|
[ "$TINYGRAD_MATCH" = "true" ] || return 1
|
||||||
DRIVING=$(echo "$DEFAULTS" | jq --arg hash "$DRIVING_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
|
DRIVING=$(echo "$DEFAULTS" | jq --arg hash "$DRIVING_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
|
||||||
@@ -340,35 +313,18 @@ jobs:
|
|||||||
|
|
||||||
echo "No matching model on HF — dispatching build"
|
echo "No matching model on HF — dispatching build"
|
||||||
gh workflow run build-default-models.yaml --ref "$REF" -f target=small
|
gh workflow run build-default-models.yaml --ref "$REF" -f target=small
|
||||||
sleep 10
|
|
||||||
|
|
||||||
BUILD_RUN_ID=$(gh run list --workflow build-default-models.yaml --branch "$REF" --limit 1 --json databaseId --jq '.[0].databaseId')
|
echo "Polling HF for model availability..."
|
||||||
echo "Dispatched build run: $BUILD_RUN_ID"
|
|
||||||
|
|
||||||
echo "Waiting for build run to complete..."
|
|
||||||
for i in $(seq 1 60); do
|
for i in $(seq 1 60); do
|
||||||
sleep 30
|
sleep 30
|
||||||
STATUS=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.status')
|
if check_defaults; then
|
||||||
CONCLUSION=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.conclusion')
|
echo "Model available on HF after $((i * 30))s"
|
||||||
echo "Poll $i/60: status=$STATUS conclusion=$CONCLUSION"
|
exit 0
|
||||||
if [ "$STATUS" = "completed" ]; then
|
|
||||||
if [ "$CONCLUSION" = "success" ]; then
|
|
||||||
echo "Build run succeeded, verifying HF..."
|
|
||||||
sleep 10
|
|
||||||
if check_defaults; then
|
|
||||||
echo "Small model verified on HF"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "::error::Build succeeded but model not found on HF"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "::error::Build run failed with conclusion=$CONCLUSION"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
echo "Poll $i/60: not yet available"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "::error::Small model build did not complete within 30 minutes"
|
echo "::error::Small driving model not available on HF after 30 minutes"
|
||||||
exit 1
|
exit 1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -382,9 +338,6 @@ jobs:
|
|||||||
prepare_dm_model:
|
prepare_dm_model:
|
||||||
needs: [ prepare_strategy ]
|
needs: [ prepare_strategy ]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
concurrency:
|
|
||||||
group: prepare-dm-model
|
|
||||||
cancel-in-progress: false
|
|
||||||
outputs:
|
outputs:
|
||||||
dm_onnx_sha256: ${{ steps.resolve.outputs.dm_onnx_sha256 }}
|
dm_onnx_sha256: ${{ steps.resolve.outputs.dm_onnx_sha256 }}
|
||||||
env:
|
env:
|
||||||
@@ -397,10 +350,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
REF="${{ github.head_ref || github.ref_name }}"
|
REF="${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
BLOB_SHA=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx?ref=${REF}" --jq '.sha')
|
DM_HASH=$(gh api "repos/${GH_REPO}/contents/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx?ref=${REF}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
|
||||||
DM_HASH=$(gh api "repos/${GH_REPO}/git/blobs/${BLOB_SHA}" --jq '.content' | base64 -d | grep '^oid sha256:' | cut -d: -f2)
|
|
||||||
echo "DM ONNX hash: $DM_HASH"
|
echo "DM ONNX hash: $DM_HASH"
|
||||||
[ -n "$DM_HASH" ] || { echo "::error::Failed to extract DM ONNX hash"; exit 1; }
|
|
||||||
echo "dm_onnx_sha256=$DM_HASH" >> $GITHUB_OUTPUT
|
echo "dm_onnx_sha256=$DM_HASH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
|
TINYGRAD_REF=$(gh api "repos/${GH_REPO}/contents/tinygrad_repo?ref=${REF}" --jq '.sha')
|
||||||
@@ -409,7 +360,7 @@ jobs:
|
|||||||
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
|
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
|
||||||
|
|
||||||
check_defaults() {
|
check_defaults() {
|
||||||
DEFAULTS=$(curl -fsSL "${JSON_URL}?t=$(date +%s)" 2>/dev/null) || return 1
|
DEFAULTS=$(curl -fsSL "$JSON_URL" 2>/dev/null) || return 1
|
||||||
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
|
TINYGRAD_MATCH=$(echo "$DEFAULTS" | jq -r --arg ref "$TINYGRAD_REF" '.tinygrad_ref == $ref' 2>/dev/null)
|
||||||
[ "$TINYGRAD_MATCH" = "true" ] || return 1
|
[ "$TINYGRAD_MATCH" = "true" ] || return 1
|
||||||
DM=$(echo "$DEFAULTS" | jq --arg hash "$DM_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
|
DM=$(echo "$DEFAULTS" | jq --arg hash "$DM_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
|
||||||
@@ -423,35 +374,18 @@ jobs:
|
|||||||
|
|
||||||
echo "No matching DM model on HF — dispatching build"
|
echo "No matching DM model on HF — dispatching build"
|
||||||
gh workflow run build-default-models.yaml --ref "$REF" -f target=dm
|
gh workflow run build-default-models.yaml --ref "$REF" -f target=dm
|
||||||
sleep 10
|
|
||||||
|
|
||||||
BUILD_RUN_ID=$(gh run list --workflow build-default-models.yaml --branch "$REF" --limit 1 --json databaseId --jq '.[0].databaseId')
|
echo "Polling HF for DM model availability..."
|
||||||
echo "Dispatched build run: $BUILD_RUN_ID"
|
|
||||||
|
|
||||||
echo "Waiting for build run to complete..."
|
|
||||||
for i in $(seq 1 60); do
|
for i in $(seq 1 60); do
|
||||||
sleep 30
|
sleep 30
|
||||||
STATUS=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.status')
|
if check_defaults; then
|
||||||
CONCLUSION=$(gh api "repos/${GH_REPO}/actions/runs/${BUILD_RUN_ID}" --jq '.conclusion')
|
echo "DM model available on HF after $((i * 30))s"
|
||||||
echo "Poll $i/60: status=$STATUS conclusion=$CONCLUSION"
|
exit 0
|
||||||
if [ "$STATUS" = "completed" ]; then
|
|
||||||
if [ "$CONCLUSION" = "success" ]; then
|
|
||||||
echo "Build run succeeded, verifying HF..."
|
|
||||||
sleep 10
|
|
||||||
if check_defaults; then
|
|
||||||
echo "DM model verified on HF"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "::error::Build succeeded but DM model not found on HF"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "::error::Build run failed with conclusion=$CONCLUSION"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
echo "Poll $i/60: not yet available"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "::error::DM model build did not complete within 30 minutes"
|
echo "::error::DM model not available on HF after 30 minutes"
|
||||||
exit 1
|
exit 1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
[submodule "opendbc"]
|
[submodule "opendbc"]
|
||||||
path = opendbc_repo
|
path = opendbc_repo
|
||||||
url = https://github.com/sunnypilot/opendbc.git
|
url = https://github.com/sunnypilot/opendbc.git
|
||||||
branch = tn
|
|
||||||
[submodule "msgq"]
|
[submodule "msgq"]
|
||||||
path = msgq_repo
|
path = msgq_repo
|
||||||
url = https://github.com/sunnypilot/msgq.git
|
url = https://github.com/sunnypilot/msgq.git
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
|
|||||||
export QCOM_PRIORITY=12
|
export QCOM_PRIORITY=12
|
||||||
|
|
||||||
if [ -z "$AGNOS_VERSION" ]; then
|
if [ -z "$AGNOS_VERSION" ]; then
|
||||||
export AGNOS_VERSION="19.7"
|
export AGNOS_VERSION="19.6"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export STAGING_ROOT="/data/safe_staging"
|
export STAGING_ROOT="/data/safe_staging"
|
||||||
|
|||||||
+1
-1
Submodule opendbc_repo updated: 28190c0386...06743dfb39
@@ -204,16 +204,11 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
|||||||
aTarget @5 :Float32;
|
aTarget @5 :Float32;
|
||||||
events @6 :List(OnroadEventSP.Event);
|
events @6 :List(OnroadEventSP.Event);
|
||||||
e2eAlerts @7 :E2eAlerts;
|
e2eAlerts @7 :E2eAlerts;
|
||||||
accelController @8 :AccelController;
|
|
||||||
|
|
||||||
struct DynamicExperimentalControl {
|
struct DynamicExperimentalControl {
|
||||||
state @0 :DynamicExperimentalControlState;
|
state @0 :DynamicExperimentalControlState;
|
||||||
enabled @1 :Bool;
|
enabled @1 :Bool;
|
||||||
active @2 :Bool;
|
active @2 :Bool;
|
||||||
decelIntent @3 :Float32;
|
|
||||||
curveDetected @4 :Bool;
|
|
||||||
wantBlended @5 :Bool;
|
|
||||||
leadVeto @6 :Bool;
|
|
||||||
|
|
||||||
enum DynamicExperimentalControlState {
|
enum DynamicExperimentalControlState {
|
||||||
acc @0;
|
acc @0;
|
||||||
@@ -311,17 +306,6 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
|||||||
greenLightAlert @0 :Bool;
|
greenLightAlert @0 :Bool;
|
||||||
leadDepartAlert @1 :Bool;
|
leadDepartAlert @1 :Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AccelController {
|
|
||||||
enabled @0 :Bool;
|
|
||||||
active @1 :Bool;
|
|
||||||
profile @2 :Profile;
|
|
||||||
enum Profile {
|
|
||||||
eco @0;
|
|
||||||
normal @1;
|
|
||||||
sport @2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OnroadEventSP @0xda96579883444c35 {
|
struct OnroadEventSP @0xda96579883444c35 {
|
||||||
@@ -369,7 +353,6 @@ struct OnroadEventSP @0xda96579883444c35 {
|
|||||||
speedLimitPending @22;
|
speedLimitPending @22;
|
||||||
e2eChime @23;
|
e2eChime @23;
|
||||||
laneChangeRoadEdge @24;
|
laneChangeRoadEdge @24;
|
||||||
bigModelReady @25;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -725,7 +725,6 @@ struct ChestnutState {
|
|||||||
pcieLtssm @7 :UInt8;
|
pcieLtssm @7 :UInt8;
|
||||||
supplyVoltage @8 :UInt16; # mV
|
supplyVoltage @8 :UInt16; # mV
|
||||||
supplyCurrent @9 :Int16; # mA
|
supplyCurrent @9 :Int16; # mA
|
||||||
supplyFault @10 :Bool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RadarState @0x9a185389d6fdd05f {
|
struct RadarState @0x9a185389d6fdd05f {
|
||||||
@@ -1005,7 +1004,6 @@ struct DrivingModelData {
|
|||||||
frameIdExtra @1 :UInt32;
|
frameIdExtra @1 :UInt32;
|
||||||
frameDropPerc @6 :Float32;
|
frameDropPerc @6 :Float32;
|
||||||
modelExecutionTime @7 :Float32;
|
modelExecutionTime @7 :Float32;
|
||||||
big @8 :Bool;
|
|
||||||
|
|
||||||
action @2 :ModelDataV2.Action;
|
action @2 :ModelDataV2.Action;
|
||||||
|
|
||||||
|
|||||||
@@ -56,29 +56,29 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "boot",
|
"name": "boot",
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-6ecf6f987cd11968104abcccabbe268485d329cdb73012dfd3c381a6b8deb27d.img.xz",
|
"url": "https://commadist.azureedge.net/agnosupdate/boot-b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd.img.xz",
|
||||||
"hash": "6ecf6f987cd11968104abcccabbe268485d329cdb73012dfd3c381a6b8deb27d",
|
"hash": "b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd",
|
||||||
"hash_raw": "6ecf6f987cd11968104abcccabbe268485d329cdb73012dfd3c381a6b8deb27d",
|
"hash_raw": "b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd",
|
||||||
"size": 46897152,
|
"size": 46897152,
|
||||||
"sparse": false,
|
"sparse": false,
|
||||||
"full_check": true,
|
"full_check": true,
|
||||||
"has_ab": true,
|
"has_ab": true,
|
||||||
"ondevice_hash": "d12e1e5b9455b62a1464558716493b33e470d7a7e88da1c4105a3b21d0961808"
|
"ondevice_hash": "6650e4c46df99ae6dfd6ee895a34b8a2a3cc490a8ce18e16cc3c451c3f822b6e"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "system",
|
"name": "system",
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/system-3c271e2b3d20d2f0a8bf6555a1319f3efb12845490967d6151195174a01e912f.img.xz",
|
"url": "https://commadist.azureedge.net/agnosupdate/system-5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3.img.xz",
|
||||||
"hash": "74ffc9c551e1f29cda897ace8a69080fe644f8039977c6885f2b48362e39b744",
|
"hash": "b134fd04e9da27fa1d359ea0f2742c216fa21a08b5c47e9be22ab3b0563d9b9b",
|
||||||
"hash_raw": "3c271e2b3d20d2f0a8bf6555a1319f3efb12845490967d6151195174a01e912f",
|
"hash_raw": "5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3",
|
||||||
"size": 4718592000,
|
"size": 4718592000,
|
||||||
"sparse": true,
|
"sparse": true,
|
||||||
"full_check": false,
|
"full_check": false,
|
||||||
"has_ab": true,
|
"has_ab": true,
|
||||||
"ondevice_hash": "6a992680183685eea9db99d915219a37935f45989330d9b619e880450257f448",
|
"ondevice_hash": "91242772af771ae96fe2eebc105f2b80a7e1dbaaf6003c2574b62d51b806f468",
|
||||||
"alt": {
|
"alt": {
|
||||||
"hash": "3c271e2b3d20d2f0a8bf6555a1319f3efb12845490967d6151195174a01e912f",
|
"hash": "5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3",
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/system-3c271e2b3d20d2f0a8bf6555a1319f3efb12845490967d6151195174a01e912f.img",
|
"url": "https://commadist.azureedge.net/agnosupdate/system-5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3.img",
|
||||||
"size": 4718592000
|
"size": 4718592000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import select
|
import select
|
||||||
import signal
|
import signal
|
||||||
import string
|
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -355,7 +354,7 @@ class Modem:
|
|||||||
imei = ""
|
imei = ""
|
||||||
|
|
||||||
iccid = (self._atv("AT+QCCID", "+QCCID:") or "").rstrip("F")
|
iccid = (self._atv("AT+QCCID", "+QCCID:") or "").rstrip("F")
|
||||||
if not all(c in string.hexdigits for c in iccid):
|
if not iccid.isdigit():
|
||||||
iccid = ""
|
iccid = ""
|
||||||
|
|
||||||
imsi = first_line("AT+CIMI")
|
imsi = first_line("AT+CIMI")
|
||||||
|
|||||||
@@ -4,17 +4,11 @@ from pathlib import Path
|
|||||||
CHESTNUT_FW_VERSION = "ed4e39b7"
|
CHESTNUT_FW_VERSION = "ed4e39b7"
|
||||||
CHESTNUT_USB_IDS = ((0xADD1, 0x0001), (0x3801, 0x0001))
|
CHESTNUT_USB_IDS = ((0xADD1, 0x0001), (0x3801, 0x0001))
|
||||||
CHESTNUT_ROM_USB_IDS = ((0x174C, 0x2464), (0x174C, 0x2463))
|
CHESTNUT_ROM_USB_IDS = ((0x174C, 0x2464), (0x174C, 0x2463))
|
||||||
CHESTNUT_USB_PRODUCT = f"custom {CHESTNUT_FW_VERSION}-CLEAN"
|
|
||||||
USB_DEVICES_PATH = Path("/sys/bus/usb/devices")
|
USB_DEVICES_PATH = Path("/sys/bus/usb/devices")
|
||||||
TYPEC_CC_ORIENTATION_PATH = Path("/sys/class/power_supply/usb/typec_cc_orientation")
|
TYPEC_CC_ORIENTATION_PATH = Path("/sys/class/power_supply/usb/typec_cc_orientation")
|
||||||
PRIMARY_USB_CONTROLLER = "a600000.ssusb"
|
PRIMARY_USB_CONTROLLER = "a600000.ssusb"
|
||||||
|
|
||||||
|
|
||||||
def is_chestnut_usb_id(vendor_id: int, product_id: int, include_bootloader: bool = False) -> bool:
|
|
||||||
ids = CHESTNUT_USB_IDS + CHESTNUT_ROM_USB_IDS if include_bootloader else CHESTNUT_USB_IDS
|
|
||||||
return (vendor_id, product_id) in ids
|
|
||||||
|
|
||||||
|
|
||||||
def get_usb_topology() -> set[str]:
|
def get_usb_topology() -> set[str]:
|
||||||
try:
|
try:
|
||||||
return set(os.listdir(USB_DEVICES_PATH))
|
return set(os.listdir(USB_DEVICES_PATH))
|
||||||
@@ -87,7 +81,7 @@ def set_usb_state(device_state, devices: list[dict]) -> None:
|
|||||||
entry.linkErrorCount = device["linkErrorCount"]
|
entry.linkErrorCount = device["linkErrorCount"]
|
||||||
entry.usb3Lane = device.get("usb3Lane", "unknown")
|
entry.usb3Lane = device.get("usb3Lane", "unknown")
|
||||||
|
|
||||||
if is_chestnut_usb_id(entry.vendorId, entry.productId):
|
if (entry.vendorId, entry.productId) in CHESTNUT_USB_IDS:
|
||||||
chestnut_present = True
|
chestnut_present = True
|
||||||
|
|
||||||
device_state.chestnutPresent = chestnut_present
|
device_state.chestnutPresent = chestnut_present
|
||||||
|
|||||||
@@ -92,12 +92,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"ObdMultiplexingEnabled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
{"ObdMultiplexingEnabled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||||
{"Offroad_CarUnrecognized", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
{"Offroad_CarUnrecognized", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||||
{"Offroad_ChestnutBranch", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_ChestnutBranch", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_ChestnutNotDetected", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
|
||||||
{"Offroad_ChestnutOverheated", {CLEAR_ON_MANAGER_START, JSON}},
|
|
||||||
{"Offroad_ChestnutPcieUnavailable", {CLEAR_ON_MANAGER_START, JSON}},
|
|
||||||
{"Offroad_ChestnutUncompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
|
||||||
{"Offroad_ChestnutUpdateFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
|
||||||
{"Offroad_ChestnutUsbSlow", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
|
||||||
{"Offroad_ConnectivityNeeded", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_ConnectivityNeeded", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_ExcessiveActuation", {PERSISTENT, JSON}},
|
{"Offroad_ExcessiveActuation", {PERSISTENT, JSON}},
|
||||||
@@ -136,9 +130,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"UpdaterLastFetchTime", {PERSISTENT, TIME}},
|
{"UpdaterLastFetchTime", {PERSISTENT, TIME}},
|
||||||
{"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}},
|
{"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}},
|
||||||
{"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
|
{"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
|
||||||
{"ChestnutActive", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
{"UsbGpuActive", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
||||||
{"ChestnutLoading", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
{"UsbGpuLoading", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
||||||
{"ChestnutModelError", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
|
||||||
{"Version", {PERSISTENT, STRING}},
|
{"Version", {PERSISTENT, STRING}},
|
||||||
|
|
||||||
// --- sunnypilot params --- //
|
// --- sunnypilot params --- //
|
||||||
@@ -194,12 +187,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|
||||||
// toyota specific params
|
|
||||||
{"ToyotaAutoHold", {PERSISTENT | BACKUP, BOOL, "0"}},
|
|
||||||
{"ToyotaEnhancedBsm", {PERSISTENT | BACKUP, BOOL, "0"}},
|
|
||||||
{"ToyotaTSS2Long", {PERSISTENT | BACKUP, BOOL, "0"}},
|
|
||||||
{"ToyotaDriveMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
|
||||||
|
|
||||||
// MADS params
|
// MADS params
|
||||||
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
{"MadsMainCruiseAllowed", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"MadsMainCruiseAllowed", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
@@ -208,16 +195,15 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
|
|
||||||
// Model Manager params
|
// Model Manager params
|
||||||
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
|
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
|
||||||
{"ModelManager_ActiveBundleUSBGPU", {PERSISTENT, JSON}}, //TODO-SP: kept for migration, remove on next sync?
|
{"ModelManager_ActiveBundleUSBGPU", {PERSISTENT, JSON}},
|
||||||
{"ModelManager_ActiveBundleChestnut", {PERSISTENT, JSON}},
|
|
||||||
{"ModelManager_ActiveJson", {CLEAR_ON_MANAGER_START, JSON}},
|
{"ModelManager_ActiveJson", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"ModelManager_DownloadRef", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
{"ModelManager_DownloadRef", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||||
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
||||||
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||||
{"ModelManager_LastSyncTime_Chestnut", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
{"ModelManager_LastSyncTime_USBGPU", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||||
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||||
{"ModelManager_ModelsCache_Chestnut", {PERSISTENT | BACKUP, JSON}},
|
{"ModelManager_ModelsCache_USBGPU", {PERSISTENT | BACKUP, JSON}},
|
||||||
|
|
||||||
// Neural Network Lateral Control
|
// Neural Network Lateral Control
|
||||||
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
@@ -249,10 +235,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|
||||||
// Accel Controller profiles (Eco / Normal / Sport)
|
|
||||||
{"AccelPersonalityEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
|
||||||
{"AccelPersonality", {PERSISTENT | BACKUP, INT, "1"}},
|
|
||||||
|
|
||||||
// sunnypilot model params
|
// sunnypilot model params
|
||||||
{"CameraOffset", {PERSISTENT | BACKUP, FLOAT, "0.0"}},
|
{"CameraOffset", {PERSISTENT | BACKUP, FLOAT, "0.0"}},
|
||||||
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ public:
|
|||||||
auto param_path = Params().getParamPath();
|
auto param_path = Params().getParamPath();
|
||||||
if (util::file_exists(param_path)) {
|
if (util::file_exists(param_path)) {
|
||||||
std::string real_path = util::readlink(param_path);
|
std::string real_path = util::readlink(param_path);
|
||||||
util::check_system(util::string_format("rm -rf %s", real_path.c_str()));
|
util::check_system(util::string_format("rm %s -rf", real_path.c_str()));
|
||||||
unlink(param_path.c_str());
|
unlink(param_path.c_str());
|
||||||
}
|
}
|
||||||
if (getenv("COMMA_CACHE") == nullptr) {
|
if (getenv("COMMA_CACHE") == nullptr) {
|
||||||
util::check_system(util::string_format("rm -rf %s", Path::download_cache_root().c_str()));
|
util::check_system(util::string_format("rm %s -rf", Path::download_cache_root().c_str()));
|
||||||
}
|
}
|
||||||
util::check_system(util::string_format("rm -rf %s", Path::comma_home().c_str()));
|
util::check_system(util::string_format("rm %s -rf", Path::comma_home().c_str()));
|
||||||
util::check_system(util::string_format("rm -rf %s", msgq_path.c_str()));
|
util::check_system(util::string_format("rm %s -rf", msgq_path.c_str()));
|
||||||
unsetenv("OPENPILOT_PREFIX");
|
unsetenv("OPENPILOT_PREFIX");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,16 +117,12 @@ class TestParams(OpenpilotTestCase):
|
|||||||
def test_params_default_value(self):
|
def test_params_default_value(self):
|
||||||
self.params.remove("LanguageSetting")
|
self.params.remove("LanguageSetting")
|
||||||
self.params.remove("LongitudinalPersonality")
|
self.params.remove("LongitudinalPersonality")
|
||||||
self.params.remove("AccelPersonalityEnabled")
|
|
||||||
self.params.remove("AccelPersonality")
|
|
||||||
self.params.remove("LiveParametersV2")
|
self.params.remove("LiveParametersV2")
|
||||||
|
|
||||||
assert self.params.get("LanguageSetting") is None
|
assert self.params.get("LanguageSetting") is None
|
||||||
assert self.params.get("LanguageSetting", return_default=False) is None
|
assert self.params.get("LanguageSetting", return_default=False) is None
|
||||||
assert isinstance(self.params.get("LanguageSetting", return_default=True), str)
|
assert isinstance(self.params.get("LanguageSetting", return_default=True), str)
|
||||||
assert isinstance(self.params.get("LongitudinalPersonality", return_default=True), int)
|
assert isinstance(self.params.get("LongitudinalPersonality", return_default=True), int)
|
||||||
assert self.params.get("AccelPersonalityEnabled", return_default=True) is False
|
|
||||||
assert self.params.get("AccelPersonality", return_default=True) == 1
|
|
||||||
assert self.params.get("LiveParametersV2") is None
|
assert self.params.get("LiveParametersV2") is None
|
||||||
assert self.params.get("LiveParametersV2", return_default=True) is None
|
assert self.params.get("LiveParametersV2", return_default=True) is None
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:845c40ff0d37612e8f2f482a36845744b5ae91ce2fcfc8117990d7d278b59820
|
|
||||||
size 13079
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8a8c5fece2a1c7587feb41cbe04c6aee08e768ecd9b5d00da6af9832a4ccc842
|
||||||
|
size 2034
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7409c53d7c72681c24982fd83b56ce70f80797c9c0f936d9296a5c18557ac472
|
||||||
|
size 7279
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:58bd6155433f623b1f75d134bd8ca4745d9aa71f6767eb807cdbcf7deb3089a1
|
||||||
|
size 10876
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:07bda2fe5d6be0b2854044053c384fe002e96406da119863a443b9344258b500
|
|
||||||
size 1544
|
|
||||||
@@ -11,13 +11,13 @@ from opendbc.car.structs import car
|
|||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
|
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
|
||||||
from openpilot.common.swaglog import cloudlog, ForwardingHandler
|
from openpilot.common.swaglog import cloudlog, ForwardingHandler
|
||||||
|
|
||||||
from opendbc.car import DT_CTRL, structs
|
from opendbc.car import DT_CTRL, structs
|
||||||
from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable
|
from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable
|
||||||
from opendbc.car.carlog import carlog
|
from opendbc.car.carlog import carlog
|
||||||
from opendbc.car.fw_versions import ObdCallback
|
from opendbc.car.fw_versions import ObdCallback
|
||||||
from opendbc.car.car_helpers import get_car, interfaces
|
from opendbc.car.car_helpers import get_car, interfaces
|
||||||
from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase
|
from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase
|
||||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
|
||||||
from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
|
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.cruise import VCruiseHelper
|
||||||
from openpilot.selfdrive.car.helpers import convert_carControlSP, convert_to_capnp
|
from openpilot.selfdrive.car.helpers import convert_carControlSP, convert_to_capnp
|
||||||
@@ -123,9 +123,6 @@ class Car:
|
|||||||
self.RI = RI
|
self.RI = RI
|
||||||
|
|
||||||
self.CP.alternativeExperience = 0
|
self.CP.alternativeExperience = 0
|
||||||
if self.params.get_bool("ToyotaAutoHold"):
|
|
||||||
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALLOW_AEB
|
|
||||||
|
|
||||||
# mads
|
# mads
|
||||||
set_alternative_experience(self.CP, self.CP_SP, self.params)
|
set_alternative_experience(self.CP, self.CP_SP, self.params)
|
||||||
set_car_specific_params(self.CP, self.CP_SP, self.params)
|
set_car_specific_params(self.CP, self.CP_SP, self.params)
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ IMPERIAL_INCREMENT = round(CV.MPH_TO_KPH, 1) # round here to avoid rounding err
|
|||||||
ButtonEvent = car.CarState.ButtonEvent
|
ButtonEvent = car.CarState.ButtonEvent
|
||||||
ButtonType = car.CarState.ButtonEvent.Type
|
ButtonType = car.CarState.ButtonEvent.Type
|
||||||
CRUISE_LONG_PRESS = 50
|
CRUISE_LONG_PRESS = 50
|
||||||
TOYOTA_VIRTUAL_CRUISE_LONG_PRESS = 65
|
|
||||||
CRUISE_NEAREST_FUNC = {
|
CRUISE_NEAREST_FUNC = {
|
||||||
ButtonType.accelCruise: math.ceil,
|
ButtonType.accelCruise: math.ceil,
|
||||||
ButtonType.decelCruise: math.floor,
|
ButtonType.decelCruise: math.floor,
|
||||||
@@ -44,30 +43,6 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
def v_cruise_initialized(self):
|
def v_cruise_initialized(self):
|
||||||
return self.v_cruise_kph != V_CRUISE_UNSET
|
return self.v_cruise_kph != V_CRUISE_UNSET
|
||||||
|
|
||||||
@property
|
|
||||||
def software_pcm_cruise_speed(self) -> bool:
|
|
||||||
return self.CP.brand == "toyota" and self.CP.pcmCruise and self.CP.openpilotLongitudinalControl and not self.CP_SP.pcmCruiseSpeed
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cruise_long_press_frames(self) -> int:
|
|
||||||
return TOYOTA_VIRTUAL_CRUISE_LONG_PRESS if self.software_pcm_cruise_speed else CRUISE_LONG_PRESS
|
|
||||||
|
|
||||||
@property
|
|
||||||
def software_pcm_cruise_initialized(self) -> bool:
|
|
||||||
return 0 < self.v_cruise_kph < V_CRUISE_UNSET and 0 < self.v_cruise_cluster_kph < V_CRUISE_UNSET
|
|
||||||
|
|
||||||
def _apply_software_pcm_cruise_delta(self, delta_kph: float, is_metric: bool) -> None:
|
|
||||||
"""Move Toyota's planner/display targets together while respecting both targets' bounds."""
|
|
||||||
cluster_min_kph = self.v_cruise_min if is_metric else self.v_cruise_min * CV.MPH_TO_KPH
|
|
||||||
min_delta = max(V_CRUISE_MIN - self.v_cruise_kph, cluster_min_kph - self.v_cruise_cluster_kph)
|
|
||||||
max_delta = min(V_CRUISE_MAX - self.v_cruise_kph, V_CRUISE_MAX - self.v_cruise_cluster_kph)
|
|
||||||
if delta_kph > 0:
|
|
||||||
applied_delta = min(delta_kph, max(0., max_delta))
|
|
||||||
else:
|
|
||||||
applied_delta = max(delta_kph, min(0., min_delta))
|
|
||||||
self.v_cruise_kph = round(self.v_cruise_kph + applied_delta, 1)
|
|
||||||
self.v_cruise_cluster_kph = round(self.v_cruise_cluster_kph + applied_delta, 1)
|
|
||||||
|
|
||||||
def update_v_cruise(self, CS, enabled, is_metric):
|
def update_v_cruise(self, CS, enabled, is_metric):
|
||||||
self.v_cruise_kph_last = self.v_cruise_kph
|
self.v_cruise_kph_last = self.v_cruise_kph
|
||||||
|
|
||||||
@@ -76,21 +51,11 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
_enabled = self.update_enabled_state(CS, enabled)
|
_enabled = self.update_enabled_state(CS, enabled)
|
||||||
|
|
||||||
if CS.cruiseState.available:
|
if CS.cruiseState.available:
|
||||||
software_pcm_enabled = not self.CP_SP.pcmCruiseSpeed and _enabled
|
if not self.CP.pcmCruise or (not self.CP_SP.pcmCruiseSpeed and _enabled):
|
||||||
if self.software_pcm_cruise_speed:
|
|
||||||
software_pcm_enabled = software_pcm_enabled and self.software_pcm_cruise_initialized
|
|
||||||
|
|
||||||
if not self.CP.pcmCruise or software_pcm_enabled:
|
|
||||||
# if stock cruise is completely disabled, then we can use our own set speed logic
|
# if stock cruise is completely disabled, then we can use our own set speed logic
|
||||||
self._update_v_cruise_non_pcm(CS, _enabled, is_metric)
|
self._update_v_cruise_non_pcm(CS, _enabled, is_metric)
|
||||||
v_cruise_kph_before_sla = self.v_cruise_kph
|
|
||||||
self.update_speed_limit_assist_v_cruise_non_pcm()
|
self.update_speed_limit_assist_v_cruise_non_pcm()
|
||||||
if self.software_pcm_cruise_speed:
|
self.v_cruise_cluster_kph = self.v_cruise_kph
|
||||||
sla_delta_kph = self.v_cruise_kph - v_cruise_kph_before_sla
|
|
||||||
self.v_cruise_kph = v_cruise_kph_before_sla
|
|
||||||
self._apply_software_pcm_cruise_delta(sla_delta_kph, is_metric)
|
|
||||||
else:
|
|
||||||
self.v_cruise_cluster_kph = self.v_cruise_kph
|
|
||||||
else:
|
else:
|
||||||
self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
|
self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
|
||||||
self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH
|
self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH
|
||||||
@@ -120,13 +85,13 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
|
|
||||||
for b in CS.buttonEvents:
|
for b in CS.buttonEvents:
|
||||||
if b.type.raw in self.button_timers and not b.pressed:
|
if b.type.raw in self.button_timers and not b.pressed:
|
||||||
if self.button_timers[b.type.raw] > self.cruise_long_press_frames:
|
if self.button_timers[b.type.raw] > CRUISE_LONG_PRESS:
|
||||||
return # end long press
|
return # end long press
|
||||||
button_type = b.type.raw
|
button_type = b.type.raw
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
for k, timer in self.button_timers.items():
|
for k, timer in self.button_timers.items():
|
||||||
if timer and timer % self.cruise_long_press_frames == 0:
|
if timer and timer % CRUISE_LONG_PRESS == 0:
|
||||||
button_type = k
|
button_type = k
|
||||||
long_press = True
|
long_press = True
|
||||||
break
|
break
|
||||||
@@ -150,26 +115,10 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
return
|
return
|
||||||
|
|
||||||
long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta)
|
long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta)
|
||||||
# Toyota's canonical PCM set speed and displayed cluster set speed can differ. In
|
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
|
||||||
# software-owned PCM mode, round the value the driver sees and apply the same delta
|
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
|
||||||
# to both targets so the planner/cluster calibration offset remains intact.
|
|
||||||
v_cruise_reference = self.v_cruise_cluster_kph if self.software_pcm_cruise_speed else self.v_cruise_kph
|
|
||||||
if long_press and v_cruise_reference % v_cruise_delta != 0: # partial interval
|
|
||||||
v_cruise_reference_new = CRUISE_NEAREST_FUNC[button_type](v_cruise_reference / v_cruise_delta) * v_cruise_delta
|
|
||||||
else:
|
else:
|
||||||
v_cruise_reference_new = v_cruise_reference + v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]
|
self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]
|
||||||
|
|
||||||
if self.software_pcm_cruise_speed:
|
|
||||||
delta_kph = v_cruise_reference_new - v_cruise_reference
|
|
||||||
|
|
||||||
# If SET is pressed while overriding, do not lower the target below the current speed.
|
|
||||||
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
|
|
||||||
delta_kph = max(delta_kph, CS.vEgo * CV.MS_TO_KPH - self.v_cruise_kph)
|
|
||||||
|
|
||||||
self._apply_software_pcm_cruise_delta(delta_kph, is_metric)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.v_cruise_kph += v_cruise_reference_new - v_cruise_reference
|
|
||||||
|
|
||||||
# If set is pressed while overriding, clip cruise speed to minimum of vEgo
|
# If set is pressed while overriding, clip cruise speed to minimum of vEgo
|
||||||
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
|
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
|
||||||
@@ -178,12 +127,6 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), self.v_cruise_min, V_CRUISE_MAX)
|
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), self.v_cruise_min, V_CRUISE_MAX)
|
||||||
|
|
||||||
def update_button_timers(self, CS, enabled):
|
def update_button_timers(self, CS, enabled):
|
||||||
if self.software_pcm_cruise_speed and (not enabled or not CS.cruiseState.available or not self.software_pcm_cruise_initialized):
|
|
||||||
for k in self.button_timers:
|
|
||||||
self.button_timers[k] = 0
|
|
||||||
self.button_change_states[k] = {"standstill": False, "enabled": False}
|
|
||||||
return
|
|
||||||
|
|
||||||
# increment timer for buttons still pressed
|
# increment timer for buttons still pressed
|
||||||
for k in self.button_timers:
|
for k in self.button_timers:
|
||||||
if self.button_timers[k] > 0:
|
if self.button_timers[k] > 0:
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ MAX_LATERAL_JERK = 5.0 # m/s^3
|
|||||||
MAX_LATERAL_ACCEL_NO_ROLL = 3.0 # m/s^2
|
MAX_LATERAL_ACCEL_NO_ROLL = 3.0 # m/s^2
|
||||||
|
|
||||||
|
|
||||||
STOPPING_SPEED = 0.25 # m/s, speed at which the car goes into the stopping state
|
|
||||||
|
|
||||||
|
|
||||||
def should_stop(v_ego: float, a_target: float) -> bool:
|
def should_stop(v_ego: float, a_target: float) -> bool:
|
||||||
return bool(v_ego < STOPPING_SPEED and a_target < 0.1)
|
return bool(v_ego < 0.3 and a_target < 0.1)
|
||||||
|
|
||||||
def clamp(val, min_val, max_val):
|
def clamp(val, min_val, max_val):
|
||||||
clamped_val = float(np.clip(val, min_val, max_val))
|
clamped_val = float(np.clip(val, min_val, max_val))
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ from openpilot.selfdrive.modeld.constants import ModelConstants
|
|||||||
|
|
||||||
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
|
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
|
||||||
|
|
||||||
STOPPING_DECEL_RATE = 0.3 # m/s^2/s while trying to stop
|
|
||||||
|
|
||||||
LongCtrlState = car.CarControl.Actuators.LongControlState
|
LongCtrlState = car.CarControl.Actuators.LongControlState
|
||||||
|
|
||||||
|
|
||||||
@@ -70,7 +68,7 @@ class LongControl:
|
|||||||
if output_accel > self.CP.stopAccel:
|
if output_accel > self.CP.stopAccel:
|
||||||
output_accel = min(output_accel, 0.0)
|
output_accel = min(output_accel, 0.0)
|
||||||
# TODO: can we just go straight to stopAccel?
|
# TODO: can we just go straight to stopAccel?
|
||||||
output_accel -= STOPPING_DECEL_RATE * DT_CTRL
|
output_accel -= 1.0 * DT_CTRL # m/s^2/s while trying to stop
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
else: # LongCtrlState.pid
|
else: # LongCtrlState.pid
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ def get_coast_accel(pitch):
|
|||||||
|
|
||||||
def get_cruise_accel(e2e, v_cruise, v_ego, a_cruise_prev, angle_steers, CP, dt, accel_coast, allow_throttle):
|
def get_cruise_accel(e2e, v_cruise, v_ego, a_cruise_prev, angle_steers, CP, dt, accel_coast, allow_throttle):
|
||||||
max_accel = ACCEL_MAX if e2e else get_max_accel(v_ego)
|
max_accel = ACCEL_MAX if e2e else get_max_accel(v_ego)
|
||||||
|
|
||||||
if not e2e:
|
if not e2e:
|
||||||
a_total_max = np.interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V)
|
a_total_max = np.interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V)
|
||||||
a_y = v_ego ** 2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase)
|
a_y = v_ego ** 2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase)
|
||||||
@@ -83,8 +84,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
v_ego = sm['carState'].vEgo
|
v_ego = sm['carState'].vEgo
|
||||||
v_cruise_kph = min(sm['carState'].vCruise, V_CRUISE_MAX)
|
v_cruise_kph = min(sm['carState'].vCruise, V_CRUISE_MAX)
|
||||||
v_cruise = v_cruise_kph * CV.KPH_TO_MS
|
v_cruise = v_cruise_kph * CV.KPH_TO_MS
|
||||||
force_decel = sm['controlsState'].forceDecel
|
if sm['controlsState'].forceDecel:
|
||||||
if force_decel:
|
|
||||||
v_cruise = 0.0
|
v_cruise = 0.0
|
||||||
|
|
||||||
long_control_off = sm['controlsState'].longControlState == LongCtrlState.off
|
long_control_off = sm['controlsState'].longControlState == LongCtrlState.off
|
||||||
@@ -118,7 +118,6 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality)
|
self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality)
|
||||||
self.mpc.set_cur_state(self.v_desired_filter.x, self.output_a_target)
|
self.mpc.set_cur_state(self.v_desired_filter.x, self.output_a_target)
|
||||||
self.mpc.update(sm['radarState'], personality=sm['selfdriveState'].personality)
|
self.mpc.update(sm['radarState'], personality=sm['selfdriveState'].personality)
|
||||||
self.update_dec(sm)
|
|
||||||
|
|
||||||
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
|
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
|
||||||
self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)
|
self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)
|
||||||
@@ -141,15 +140,9 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
|
|
||||||
is_e2e = self.is_e2e(sm)
|
is_e2e = self.is_e2e(sm)
|
||||||
|
|
||||||
a_cruise_prev = self.a_cruise
|
self.a_cruise = get_cruise_accel(is_e2e, v_cruise, v_ego,
|
||||||
gated_cruise = get_cruise_accel(is_e2e, v_cruise, v_ego, a_cruise_prev, steer_angle_without_offset,
|
self.a_cruise, steer_angle_without_offset, self.CP, self.dt,
|
||||||
self.CP, self.dt, accel_coast, self.allow_throttle)
|
accel_coast, self.allow_throttle)
|
||||||
ungated_cruise = get_cruise_accel(is_e2e, v_cruise, v_ego, a_cruise_prev, steer_angle_without_offset,
|
|
||||||
self.CP, self.dt, accel_coast, True)
|
|
||||||
self.a_cruise = self.arbitrate_cruise_candidate(
|
|
||||||
sm, gated_cruise, ungated_cruise, output_a_target_mpc, self.mpc.source,
|
|
||||||
allow_throttle=self.allow_throttle, e2e=is_e2e, force_decel=force_decel,
|
|
||||||
)
|
|
||||||
cruise_should_stop = should_stop(v_ego, self.a_cruise)
|
cruise_should_stop = should_stop(v_ego, self.a_cruise)
|
||||||
|
|
||||||
candidates = [(output_a_target_mpc, self.mpc.source, output_should_stop_mpc),
|
candidates = [(output_a_target_mpc, self.mpc.source, output_should_stop_mpc),
|
||||||
@@ -157,11 +150,9 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
if is_e2e:
|
if is_e2e:
|
||||||
candidates.append((output_a_target_e2e, LongitudinalPlanSource.e2e, output_should_stop_e2e))
|
candidates.append((output_a_target_e2e, LongitudinalPlanSource.e2e, output_should_stop_e2e))
|
||||||
|
|
||||||
output_a_target, self.mpc.source, self.output_should_stop = min(candidates, key=lambda candidate: candidate[0])
|
output_a_target, self.mpc.source, _ = min(candidates, key=lambda c: c[0])
|
||||||
output_a_target = self.accel_controller.limit_accel(output_a_target, v_ego)
|
self.output_should_stop = any(should_stop for _, _, should_stop in candidates)
|
||||||
|
|
||||||
self.output_a_target = np.clip(output_a_target, ACCEL_MIN, ACCEL_MAX)
|
self.output_a_target = np.clip(output_a_target, ACCEL_MIN, ACCEL_MAX)
|
||||||
self.accel_controller_active = self.is_accel_controller_active(force_decel, self.output_a_target)
|
|
||||||
|
|
||||||
self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.output_a_target + a_prev) / 2.0
|
self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.output_a_target + a_prev) / 2.0
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from openpilot.common.test import OpenpilotTestCase
|
from openpilot.common.test import OpenpilotTestCase
|
||||||
from openpilot.cereal import custom
|
from openpilot.cereal import custom
|
||||||
from openpilot.selfdrive.controls.lib.drive_helpers import STOPPING_SPEED, should_stop
|
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
|
||||||
from openpilot.selfdrive.controls.lib.longcontrol import STOPPING_DECEL_RATE, LongCtrlState, long_control_state_trans
|
|
||||||
|
|
||||||
|
|
||||||
class TestLongControlStateTransition(OpenpilotTestCase):
|
class TestLongControlStateTransition(OpenpilotTestCase):
|
||||||
@@ -43,13 +42,3 @@ class TestLongControlStateTransition(OpenpilotTestCase):
|
|||||||
next_state = long_control_state_trans(CP_SP, active, current_state,
|
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.pid
|
assert next_state == LongCtrlState.pid
|
||||||
|
|
||||||
class TestTerminalStop(OpenpilotTestCase):
|
|
||||||
def test_stopping_tune_is_gentler_than_upstream_default(self):
|
|
||||||
# Upstream #38394 hardcoded a 1.0 m/s^2/s ramp and a 0.3 m/s latch. comma's own one-stopping-tune uses
|
|
||||||
# 0.3 / 0.25, and every stop recorded on this car was driven with that pair. Both must stay on the less
|
|
||||||
# braking side, or a future edit re-deepens the terminal brake unnoticed - which already happened once.
|
|
||||||
assert 0.0 < STOPPING_DECEL_RATE <= 1.0
|
|
||||||
assert 0.0 < STOPPING_SPEED <= 0.3
|
|
||||||
assert should_stop(STOPPING_SPEED - 0.01, 0.0)
|
|
||||||
assert not should_stop(0.29, 0.0) # the band upstream would latch in and we do not
|
|
||||||
|
|||||||
@@ -7,9 +7,14 @@ from openpilot.common.file_chunker import chunk_file, get_chunk_targets, get_exi
|
|||||||
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
|
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
|
||||||
from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE
|
from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE
|
||||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||||
from openpilot.selfdrive.modeld.helpers import TG_INPUT_DEVICES_PATH, chestnut_present, modeld_pkl_path
|
from openpilot.selfdrive.modeld.helpers import TG_INPUT_DEVICES_PATH, usbgpu_present, modeld_pkl_path
|
||||||
|
|
||||||
|
|
||||||
|
CAMERA_CONFIGS = [
|
||||||
|
(_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208
|
||||||
|
(_os_fisheye.width, _os_fisheye.height), # mici: 1344x760
|
||||||
|
]
|
||||||
|
|
||||||
Import('env', 'arch')
|
Import('env', 'arch')
|
||||||
chunker_file = File("#openpilot/common/file_chunker.py")
|
chunker_file = File("#openpilot/common/file_chunker.py")
|
||||||
lenv = env.Clone()
|
lenv = env.Clone()
|
||||||
@@ -19,32 +24,30 @@ tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "
|
|||||||
if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))]
|
if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))]
|
||||||
|
|
||||||
def estimate_pickle_max_size(onnx_size):
|
def estimate_pickle_max_size(onnx_size):
|
||||||
# QCOM programs for models with spatial recurrent features can approach 2x
|
return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty
|
||||||
# the ONNX size. Overestimating only adds an empty trailing chunk.
|
|
||||||
return 2.0 * onnx_size + 10 * 1024 * 1024
|
|
||||||
|
|
||||||
if arch == 'comma_arm64':
|
if arch == 'comma_arm64':
|
||||||
from openpilot.common.hardware import HARDWARE
|
|
||||||
camera = _os_fisheye if HARDWARE.get_device_type() == "mici" else _ar_ox_fisheye
|
|
||||||
camera_configs = [(camera.width, camera.height)]
|
|
||||||
tg_backend = 'QCOM'
|
tg_backend = 'QCOM'
|
||||||
tg_flags = f'DEV={tg_backend} IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1'
|
tg_flags = f'DEV={tg_backend} IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1'
|
||||||
else:
|
else:
|
||||||
camera_configs = [(c.width, c.height) for c in (_ar_ox_fisheye, _os_fisheye)]
|
|
||||||
tg_backend = 'CPU'
|
tg_backend = 'CPU'
|
||||||
tg_flags = f'DEV=CPU' if arch == 'Darwin' else 'DEV=CPU:LLVM'
|
tg_flags = f'DEV=CPU' if arch == 'Darwin' else 'DEV=CPU:LLVM'
|
||||||
|
|
||||||
tg_devices = { # which device to put jit inputs to at runtime
|
tg_devices = { # which device to put jit inputs to at runtime
|
||||||
|
'openpilot.selfdrive.modeld.modeld': {
|
||||||
|
'default': {'WARP_DEV': tg_backend, 'QUEUE_DEV': tg_backend},
|
||||||
|
'usbgpu': {'WARP_DEV': tg_backend, 'QUEUE_DEV': 'AMD'}
|
||||||
|
},
|
||||||
'openpilot.selfdrive.modeld.dmonitoringmodeld': {
|
'openpilot.selfdrive.modeld.dmonitoringmodeld': {
|
||||||
'default': {'DEV': tg_backend}
|
'default': {'DEV': tg_backend}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
CHESTNUT = chestnut_present()
|
USBGPU = usbgpu_present()
|
||||||
if CHESTNUT:
|
if USBGPU:
|
||||||
chestnut_tg_flags = 'DEBUG=1 DEV=USB+AMD:LLVM FRAME_DEV=CPU FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0 TC_OPT=2 TC_OCCUPANCY_OPT=1'
|
usbgpu_tg_flags = f'DEBUG=2 DEV=USB+AMD:LLVM WARP_DEV={tg_backend} FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0 TC_OPT=2'
|
||||||
# the USB+AMD GPU takes an exclusive flock; serialize all targets that touch it
|
# the USB+AMD GPU takes an exclusive flock; serialize all targets that touch it
|
||||||
chestnut_lock = File("models/.chestnut.lock").abspath
|
usbgpu_lock = File("models/.usb_gpu.lock").abspath
|
||||||
|
|
||||||
def write_tg_devices(target, source, env):
|
def write_tg_devices(target, source, env):
|
||||||
with open(str(target[0]), "w") as f:
|
with open(str(target[0]), "w") as f:
|
||||||
@@ -71,11 +74,12 @@ model_w, model_h = MEDMODEL_INPUT_SIZE
|
|||||||
frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
||||||
|
|
||||||
if not os.getenv('SKIP_TINYGRAD_COMPILE'):
|
if not os.getenv('SKIP_TINYGRAD_COMPILE'):
|
||||||
for chestnut in [False, True] if CHESTNUT else [False]:
|
for usbgpu in [False, True] if USBGPU else [False]:
|
||||||
target_pkl_path = File(modeld_pkl_path(chestnut)).abspath
|
target_pkl_path = File(modeld_pkl_path(usbgpu)).abspath
|
||||||
file_prefix, cmd_flags = ('big_', chestnut_tg_flags) if chestnut else ('', tg_flags)
|
# BIG_INTO_SMALL=1 builds the default target from the big model, e.g. to test it without a USB GPU
|
||||||
|
file_prefix, cmd_flags = ('big_', usbgpu_tg_flags) if usbgpu else ('big_' if os.getenv('BIG_INTO_SMALL') else '', tg_flags)
|
||||||
driving_onnx_deps = get_existing_chunks(File(f"models/{file_prefix}driving_supercombo.onnx").abspath)
|
driving_onnx_deps = get_existing_chunks(File(f"models/{file_prefix}driving_supercombo.onnx").abspath)
|
||||||
camera_res_args = ' '.join(f'{cw}x{ch}' for cw, ch in camera_configs)
|
camera_res_args = ' '.join(f'{cw}x{ch}' for cw, ch in CAMERA_CONFIGS)
|
||||||
# CPU 7 is isolated with isolcpus on AGNOS, so explicitly pin the compiler to it.
|
# CPU 7 is isolated with isolcpus on AGNOS, so explicitly pin the compiler to it.
|
||||||
taskset = 'taskset -c 7 ' if arch == 'comma_arm64' else ''
|
taskset = 'taskset -c 7 ' if arch == 'comma_arm64' else ''
|
||||||
cmd = (f'{cmd_flags} {mac_brew_string} {taskset}python3 {modeld_dir}/compile_modeld.py '
|
cmd = (f'{cmd_flags} {mac_brew_string} {taskset}python3 {modeld_dir}/compile_modeld.py '
|
||||||
@@ -100,14 +104,14 @@ if not os.getenv('SKIP_TINYGRAD_COMPILE'):
|
|||||||
chunk_file(pkl, chunks)
|
chunk_file(pkl, chunks)
|
||||||
def do_chunk(target, source, env, pkl=target_pkl_path, chunks=chunk_targets):
|
def do_chunk(target, source, env, pkl=target_pkl_path, chunks=chunk_targets):
|
||||||
chunk_file(pkl, chunks)
|
chunk_file(pkl, chunks)
|
||||||
actions = Action(do_compile, " [CHESTNUT] $TARGET") if chestnut else [cmd, Action(do_chunk, " [CHUNK] $TARGET")]
|
actions = Action(do_compile, " [USBGPU] $TARGET") if usbgpu else [cmd, Action(do_chunk, " [CHUNK] $TARGET")]
|
||||||
node = lenv.Command(
|
node = lenv.Command(
|
||||||
chunk_targets,
|
chunk_targets,
|
||||||
tinygrad_files + compile_modeld_script + driving_onnx_deps + [Value(camera_res_args), Value(chunk_targets), chunker_file],
|
tinygrad_files + compile_modeld_script + driving_onnx_deps + [Value(chunk_targets), chunker_file],
|
||||||
actions,
|
actions,
|
||||||
)
|
)
|
||||||
if chestnut:
|
if usbgpu:
|
||||||
lenv.SideEffect(chestnut_lock, node)
|
lenv.SideEffect(usbgpu_lock, node)
|
||||||
|
|
||||||
# get model metadata
|
# get model metadata
|
||||||
fn = File(f"models/dmonitoring_model").abspath
|
fn = File(f"models/dmonitoring_model").abspath
|
||||||
@@ -117,7 +121,7 @@ lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_file
|
|||||||
|
|
||||||
dm_w, dm_h = DM_INPUT_SIZE
|
dm_w, dm_h = DM_INPUT_SIZE
|
||||||
compile_dm_warp_script = [File(f"{modeld_dir}/compile_dm_warp.py")]
|
compile_dm_warp_script = [File(f"{modeld_dir}/compile_dm_warp.py")]
|
||||||
for cam_w, cam_h in camera_configs:
|
for cam_w, cam_h in CAMERA_CONFIGS:
|
||||||
dm_pkl_path = File(f"models/dm_warp_{cam_w}x{cam_h}_tinygrad.pkl").abspath
|
dm_pkl_path = File(f"models/dm_warp_{cam_w}x{cam_h}_tinygrad.pkl").abspath
|
||||||
cmd = (f'{tg_flags} {mac_brew_string} python3 {modeld_dir}/compile_dm_warp.py '
|
cmd = (f'{tg_flags} {mac_brew_string} python3 {modeld_dir}/compile_dm_warp.py '
|
||||||
f'--camera-resolution {cam_w}x{cam_h} --warp-to {dm_w}x{dm_h} '
|
f'--camera-resolution {cam_w}x{cam_h} --warp-to {dm_w}x{dm_h} '
|
||||||
@@ -139,4 +143,5 @@ def tg_compile(flags, model_name):
|
|||||||
Action(do_chunk, " [CHUNK] $TARGET")],
|
Action(do_chunk, " [CHUNK] $TARGET")],
|
||||||
)
|
)
|
||||||
|
|
||||||
tg_compile(tg_flags, 'dmonitoring_model')
|
if not os.getenv('SKIP_TINYGRAD_COMPILE'):
|
||||||
|
tg_compile(tg_flags, 'dmonitoring_model')
|
||||||
|
|||||||
@@ -37,12 +37,17 @@ from tinygrad.engine.jit import TinyJit
|
|||||||
|
|
||||||
|
|
||||||
NV12Frame = namedtuple("NV12Frame", ['width', 'height', 'stride', 'y_height', 'uv_height', 'size'])
|
NV12Frame = namedtuple("NV12Frame", ['width', 'height', 'stride', 'y_height', 'uv_height', 'size'])
|
||||||
MODELD_INPUTS = ['img_q', 'big_img_q', 'feat_q', 'desire_q', 'packed_npy_inputs']
|
WARP_INPUTS = ['tfm', 'big_tfm']
|
||||||
|
POLICY_INPUTS = ['img_q', 'big_img_q', 'feat_q', 'desire_q', 'packed_npy_inputs']
|
||||||
|
|
||||||
|
UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32)
|
||||||
|
UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX)
|
||||||
|
|
||||||
|
WARP_DEV = os.getenv('WARP_DEV')
|
||||||
|
|
||||||
|
|
||||||
def nv12_copy_size(stride: int, y_height: int, uv_height: int) -> int:
|
def make_random_images(keys, shape, device=None):
|
||||||
# Retain the padded Y and UV plane storage, but skip the trailing kernel/guard allocation.
|
return {k: Tensor.randint(shape, low=0, high=256, dtype='uint8', device=device).realize() for k in keys}
|
||||||
return stride * (y_height + uv_height)
|
|
||||||
|
|
||||||
|
|
||||||
def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, border_fill_val=None):
|
def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, border_fill_val=None):
|
||||||
@@ -94,7 +99,7 @@ def make_frame_prepare(nv12: NV12Frame, model_w, model_h):
|
|||||||
|
|
||||||
def frame_prepare_tinygrad(input_frame, M_inv):
|
def frame_prepare_tinygrad(input_frame, M_inv):
|
||||||
# UV_SCALE @ M_inv @ UV_SCALE_INV simplifies to elementwise scaling
|
# UV_SCALE @ M_inv @ UV_SCALE_INV simplifies to elementwise scaling
|
||||||
M_inv_uv = M_inv * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]], device=Device.DEFAULT)
|
M_inv_uv = M_inv * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]], device=WARP_DEV)
|
||||||
# deinterleave NV12 UV plane (UVUV... -> separate U, V)
|
# deinterleave NV12 UV plane (UVUV... -> separate U, V)
|
||||||
uv = input_frame[uv_offset:uv_offset + uv_height * stride].reshape(uv_height, stride)
|
uv = input_frame[uv_offset:uv_offset + uv_height * stride].reshape(uv_height, stride)
|
||||||
with Context(SPLIT_REDUCEOP=0):
|
with Context(SPLIT_REDUCEOP=0):
|
||||||
@@ -113,43 +118,49 @@ def make_frame_prepare(nv12: NV12Frame, model_w, model_h):
|
|||||||
return frame_prepare_tinygrad
|
return frame_prepare_tinygrad
|
||||||
|
|
||||||
|
|
||||||
|
def make_warp_input_queues(vision_input_shapes, frame_skip, device):
|
||||||
|
img = vision_input_shapes['img'] # (1, 12, 128, 256)
|
||||||
|
n_frames = img[1] // 6
|
||||||
|
img_buf_shape = (frame_skip * (n_frames - 1) + 1, 6, img[2], img[3])
|
||||||
|
|
||||||
|
npy = {
|
||||||
|
'tfm': np.zeros((3, 3), dtype=np.float32),
|
||||||
|
'big_tfm': np.zeros((3, 3), dtype=np.float32),
|
||||||
|
}
|
||||||
|
input_queues = {
|
||||||
|
'img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
||||||
|
'big_img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
||||||
|
**{k: Tensor(v, device='NPY').realize() for k, v in npy.items()},
|
||||||
|
}
|
||||||
|
return input_queues, npy
|
||||||
|
|
||||||
|
|
||||||
def get_policy_npy_shapes(input_shapes):
|
def get_policy_npy_shapes(input_shapes):
|
||||||
dp = input_shapes['desire_pulse'] # (1, 25, 8)
|
dp = input_shapes['desire_pulse'] # (1, 25, 8)
|
||||||
tc = input_shapes['traffic_convention'] # (1, 2)
|
tc = input_shapes['traffic_convention'] # (1, 2)
|
||||||
at = input_shapes['action_t'] # (1, 2)
|
at = input_shapes['action_t'] # (1, 2)
|
||||||
fb = input_shapes['features_buffer'] # (1, T-1, ...) e.g. (1, 24, 32, 512) with spatial features
|
fb = input_shapes['features_buffer'] # (1, 24, 512)
|
||||||
feat_dim = math.prod(fb[2:])
|
|
||||||
# TODO prev_feat shouldn't exist and be handled inside the JIT, but corrupt on QCOM for now
|
# TODO prev_feat shouldn't exist and be handled inside the JIT, but corrupt on QCOM for now
|
||||||
shapes = {'desire': (dp[2],), 'traffic_convention': tuple(tc), 'action_t': tuple(at), 'prev_feat': (fb[0], feat_dim)}
|
shapes = {'desire': (dp[2],), 'traffic_convention': tuple(tc), 'action_t': tuple(at), 'prev_feat': (fb[0], fb[2])}
|
||||||
return shapes, [math.prod(s) for s in shapes.values()]
|
return shapes, [math.prod(s) for s in shapes.values()]
|
||||||
|
|
||||||
|
|
||||||
def make_input_queues(input_shapes, frame_skip, device, frame_copy_size):
|
def make_input_queues(input_shapes, frame_skip, device):
|
||||||
img = input_shapes['img'] # (1, 12, 128, 256)
|
input_queues, npy = make_warp_input_queues(input_shapes, frame_skip, device)
|
||||||
fb = input_shapes['features_buffer'] # (1, T-1, ...), past features only; the model appends the current frame's feature
|
|
||||||
feat_dim = math.prod(fb[2:])
|
|
||||||
dp = input_shapes['desire_pulse'] # (1, 25, 8)
|
|
||||||
n_frames = img[1] // 6
|
|
||||||
img_buf_shape = (frame_skip * (n_frames - 1) + 1, 6, img[2], img[3])
|
|
||||||
|
|
||||||
policy_shapes, _ = get_policy_npy_shapes(input_shapes)
|
fb = input_shapes['features_buffer'] # (1, 24, 512), past features only; the model appends the current frame's feature
|
||||||
shapes = {'tfm': (3, 3), 'big_tfm': (3, 3)} | policy_shapes
|
dp = input_shapes['desire_pulse'] # (1, 25, 8)
|
||||||
sizes = [math.prod(s) for s in shapes.values()]
|
|
||||||
packed_npy_size = sum(sizes) * np.dtype(np.float32).itemsize
|
shapes, sizes = get_policy_npy_shapes(input_shapes)
|
||||||
packed_input = np.zeros(packed_npy_size + 2 * frame_copy_size, dtype=np.uint8)
|
packed_npy_inputs = np.zeros(sum(sizes), dtype=np.float32)
|
||||||
packed_npy_inputs = packed_input[:packed_npy_size].view(np.float32)
|
|
||||||
frames = packed_input[packed_npy_size:]
|
|
||||||
frame_views = {'img': frames[:frame_copy_size], 'big_img': frames[frame_copy_size:]}
|
|
||||||
# views into the packed inputs, to be refilled at runtime
|
# views into the packed inputs, to be refilled at runtime
|
||||||
npy = {k: v.reshape(s) for (k, s), v in zip(shapes.items(), np.split(packed_npy_inputs, np.cumsum(sizes[:-1])), strict=True)}
|
npy.update({k: v.reshape(s) for (k, s), v in zip(shapes.items(), np.split(packed_npy_inputs, np.cumsum(sizes[:-1])), strict=True)})
|
||||||
input_queues = {
|
input_queues.update({
|
||||||
'img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
'feat_q': Tensor(np.zeros((frame_skip * fb[1], fb[0], fb[2]), dtype=np.float32), device=device).contiguous().realize(),
|
||||||
'big_img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
|
||||||
'feat_q': Tensor(np.zeros((frame_skip * fb[1], fb[0], feat_dim), dtype=np.float32), device=device).contiguous().realize(),
|
|
||||||
'desire_q': Tensor(np.zeros((frame_skip * dp[1], dp[0], dp[2]), dtype=np.float32), device=device).contiguous().realize(),
|
'desire_q': Tensor(np.zeros((frame_skip * dp[1], dp[0], dp[2]), dtype=np.float32), device=device).contiguous().realize(),
|
||||||
'packed_npy_inputs': Tensor(packed_input, device='NPY').realize(),
|
'packed_npy_inputs': Tensor(packed_npy_inputs, device='NPY').realize(),
|
||||||
}
|
})
|
||||||
return input_queues, npy, frame_views
|
return input_queues, npy
|
||||||
|
|
||||||
|
|
||||||
def shift_and_sample(buf, new_val, sample_fn):
|
def shift_and_sample(buf, new_val, sample_fn):
|
||||||
@@ -165,15 +176,13 @@ def sample_desire(buf, frame_skip):
|
|||||||
return buf.reshape(-1, frame_skip, *buf.shape[1:]).max(1).flatten(0, 1).unsqueeze(0)
|
return buf.reshape(-1, frame_skip, *buf.shape[1:]).max(1).flatten(0, 1).unsqueeze(0)
|
||||||
|
|
||||||
|
|
||||||
def make_warp(nv12, model_w, model_h):
|
def make_warp(nv12, model_w, model_h, frame_skip):
|
||||||
frame_prepare = make_frame_prepare(nv12, model_w, model_h)
|
frame_prepare = make_frame_prepare(nv12, model_w, model_h)
|
||||||
|
|
||||||
def warp(tfm, big_tfm, frame, big_frame):
|
def warp(tfm, big_tfm, frame, big_frame):
|
||||||
tfm = tfm.to(Device.DEFAULT)
|
tfm = tfm.to(WARP_DEV)
|
||||||
big_tfm = big_tfm.to(Device.DEFAULT)
|
big_tfm = big_tfm.to(WARP_DEV)
|
||||||
frame = frame.to(Device.DEFAULT)
|
Tensor.realize(tfm, big_tfm)
|
||||||
big_frame = big_frame.to(Device.DEFAULT)
|
|
||||||
Tensor.realize(tfm, big_tfm, frame, big_frame)
|
|
||||||
|
|
||||||
warped_frame = frame_prepare(frame, tfm).unsqueeze(0)
|
warped_frame = frame_prepare(frame, tfm).unsqueeze(0)
|
||||||
warped_big_frame = frame_prepare(big_frame, big_tfm).unsqueeze(0)
|
warped_big_frame = frame_prepare(big_frame, big_tfm).unsqueeze(0)
|
||||||
@@ -186,10 +195,10 @@ def make_run_policy(model_runner, model_metadata, frame_skip):
|
|||||||
sample_desire_fn = partial(sample_desire, frame_skip=frame_skip)
|
sample_desire_fn = partial(sample_desire, frame_skip=frame_skip)
|
||||||
sample_skip_fn = partial(sample_skip, frame_skip=frame_skip)
|
sample_skip_fn = partial(sample_skip, frame_skip=frame_skip)
|
||||||
npy_shapes, npy_sizes = get_policy_npy_shapes(model_metadata['input_shapes'])
|
npy_shapes, npy_sizes = get_policy_npy_shapes(model_metadata['input_shapes'])
|
||||||
model_input_dtypes = {name: spec.dtype for name, spec in model_runner.graph_inputs.items()}
|
|
||||||
|
|
||||||
def run_policy(warped, img_q, big_img_q, feat_q, desire_q, packed_npy_inputs):
|
def run_policy(warped, img_q, big_img_q, feat_q, desire_q, packed_npy_inputs):
|
||||||
packed_npy_inputs = packed_npy_inputs.to(Device.DEFAULT)
|
packed_npy_inputs = packed_npy_inputs.to(Device.DEFAULT)
|
||||||
|
warped = warped.to(Device.DEFAULT)
|
||||||
Tensor.realize(packed_npy_inputs, warped)
|
Tensor.realize(packed_npy_inputs, warped)
|
||||||
|
|
||||||
img = shift_and_sample(img_q, warped[0:1], sample_skip_fn)
|
img = shift_and_sample(img_q, warped[0:1], sample_skip_fn)
|
||||||
@@ -202,50 +211,33 @@ def make_run_policy(model_runner, model_metadata, frame_skip):
|
|||||||
inputs = {
|
inputs = {
|
||||||
'img': img,
|
'img': img,
|
||||||
'big_img': big_img,
|
'big_img': big_img,
|
||||||
'features_buffer': feat_buf.reshape(model_metadata['input_shapes']['features_buffer']),
|
'features_buffer': feat_buf,
|
||||||
'desire_pulse': desire_buf,
|
'desire_pulse': desire_buf,
|
||||||
'traffic_convention': traffic_convention,
|
'traffic_convention': traffic_convention,
|
||||||
'action_t': action_t,
|
'action_t': action_t,
|
||||||
}
|
}
|
||||||
inputs = {name: value.cast(model_input_dtypes[name]) for name, value in inputs.items()}
|
|
||||||
out = next(iter(model_runner(inputs).values())).cast('float32')
|
out = next(iter(model_runner(inputs).values())).cast('float32')
|
||||||
return out,
|
return out,
|
||||||
return run_policy
|
return run_policy
|
||||||
|
|
||||||
|
|
||||||
def make_run_model(warp, run_policy, model_metadata, frame_copy_size):
|
def compile_jit(jit, make_random_inputs, input_keys, make_queues):
|
||||||
_, policy_sizes = get_policy_npy_shapes(model_metadata['input_shapes'])
|
|
||||||
packed_npy_size = (18 + sum(policy_sizes)) * np.dtype(np.float32).itemsize
|
|
||||||
|
|
||||||
def run_model(img_q, big_img_q, feat_q, desire_q, packed_npy_inputs):
|
|
||||||
packed_input = packed_npy_inputs.to(Device.DEFAULT)
|
|
||||||
Tensor.realize(packed_input)
|
|
||||||
packed_npy_inputs = packed_input[:packed_npy_size].bitcast('float32')
|
|
||||||
frame = packed_input[packed_npy_size:packed_npy_size + frame_copy_size]
|
|
||||||
big_frame = packed_input[packed_npy_size + frame_copy_size:]
|
|
||||||
tfm, big_tfm, policy_inputs = packed_npy_inputs.split([9, 9, sum(policy_sizes)])
|
|
||||||
warped = warp(tfm.reshape(3, 3), big_tfm.reshape(3, 3), frame, big_frame)
|
|
||||||
return run_policy(warped, img_q, big_img_q, feat_q, desire_q, policy_inputs)
|
|
||||||
return run_model
|
|
||||||
|
|
||||||
|
|
||||||
def compile_jit(jit, input_keys, make_queues, benchmark_runs):
|
|
||||||
if benchmark_runs < 1:
|
|
||||||
raise ValueError("benchmark_runs must be at least 1")
|
|
||||||
|
|
||||||
SEED = 42
|
SEED = 42
|
||||||
def random_inputs_run(fn, seed, n_runs, test_val=None, test_buffers=None, expect_match=True):
|
def random_inputs_run(fn, seed, test_val=None, test_buffers=None, expect_match=True):
|
||||||
input_queues, npy, frame_views = make_queues(Device.DEFAULT)
|
input_queues, npy = make_queues(Device.DEFAULT)
|
||||||
rng = np.random.default_rng(seed)
|
rng = np.random.default_rng(seed)
|
||||||
|
Tensor.manual_seed(seed)
|
||||||
|
|
||||||
|
testing = test_val is not None or test_buffers is not None
|
||||||
|
n_runs = 1 if testing else 3
|
||||||
|
|
||||||
for i in range(n_runs):
|
for i in range(n_runs):
|
||||||
for v in npy.values():
|
for v in npy.values():
|
||||||
v[:] = rng.standard_normal(v.shape).astype(v.dtype)
|
v[:] = rng.standard_normal(v.shape).astype(v.dtype)
|
||||||
for v in frame_views.values():
|
|
||||||
v[:] = rng.integers(0, 256, size=v.shape, dtype=np.uint8)
|
|
||||||
Device.default.synchronize()
|
Device.default.synchronize()
|
||||||
|
random_inputs = make_random_inputs()
|
||||||
st = time.perf_counter()
|
st = time.perf_counter()
|
||||||
outs = fn(**{k: input_queues[k] for k in input_keys})
|
outs = fn(**{k: input_queues[k] for k in input_keys}, **random_inputs)
|
||||||
mt = time.perf_counter()
|
mt = time.perf_counter()
|
||||||
Device.default.synchronize()
|
Device.default.synchronize()
|
||||||
et = time.perf_counter()
|
et = time.perf_counter()
|
||||||
@@ -264,15 +256,14 @@ def compile_jit(jit, input_keys, make_queues, benchmark_runs):
|
|||||||
return val, buffers
|
return val, buffers
|
||||||
|
|
||||||
print('capture + replay')
|
print('capture + replay')
|
||||||
test_val, test_buffers = random_inputs_run(jit, SEED, 3)
|
test_val, test_buffers = random_inputs_run(jit, SEED)
|
||||||
print(f'pickle round trip ({benchmark_runs} runs per seed)')
|
print('pickle round trip')
|
||||||
with tempfile.TemporaryFile(dir=".") as f:
|
with tempfile.TemporaryFile(dir=".") as f:
|
||||||
dump_oob(jit, f)
|
dump_oob(jit, f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
loaded_jit = load_oob(f)
|
jit = load_oob(f)
|
||||||
random_inputs_run(loaded_jit, SEED, benchmark_runs, test_val, test_buffers, expect_match=True)
|
random_inputs_run(jit, SEED, test_val, test_buffers, expect_match=True)
|
||||||
random_inputs_run(loaded_jit, SEED+1, benchmark_runs, test_val, test_buffers, expect_match=False)
|
random_inputs_run(jit, SEED+1, test_val, test_buffers, expect_match=False)
|
||||||
# Keep the original so per-resolution JITs share model weight buffers in the final pickle.
|
|
||||||
return jit
|
return jit
|
||||||
|
|
||||||
|
|
||||||
@@ -301,31 +292,27 @@ if __name__ == "__main__":
|
|||||||
p.add_argument('--onnx', required=True)
|
p.add_argument('--onnx', required=True)
|
||||||
p.add_argument('--output', required=True)
|
p.add_argument('--output', required=True)
|
||||||
p.add_argument('--frame-skip', type=int, required=True)
|
p.add_argument('--frame-skip', type=int, required=True)
|
||||||
p.add_argument('--benchmark-runs', type=int, default=1,
|
|
||||||
help='timed loaded-JIT runs for each correctness seed')
|
|
||||||
args = p.parse_args()
|
args = p.parse_args()
|
||||||
|
|
||||||
model_path = read_file_chunked_to_disk(args.onnx)
|
model_path = read_file_chunked_to_disk(args.onnx)
|
||||||
model_w, model_h = args.model_size
|
model_w, model_h = args.model_size
|
||||||
|
|
||||||
model_runner = OnnxRunner(model_path)
|
model_runner = OnnxRunner(model_path)
|
||||||
out = {
|
out = {'metadata': make_metadata_dict(model_path)}
|
||||||
'metadata': make_metadata_dict(model_path),
|
|
||||||
'input_devices': {'model': Device.DEFAULT},
|
|
||||||
'run_model': {},
|
|
||||||
}
|
|
||||||
|
|
||||||
run_policy = make_run_policy(model_runner, out['metadata'], args.frame_skip)
|
run_policy_jit = TinyJit(make_run_policy(model_runner, out['metadata'], args.frame_skip), prune=True)
|
||||||
|
|
||||||
|
make_policy_queues = partial(make_input_queues, out['metadata']['input_shapes'], args.frame_skip)
|
||||||
|
make_random_model_inputs = partial(make_random_images, keys=['warped'], shape=(2, 6, *out['metadata']['input_shapes']['img'][2:]), device=WARP_DEV)
|
||||||
|
out['run_policy'] = compile_jit(run_policy_jit, make_random_model_inputs, POLICY_INPUTS,
|
||||||
|
make_policy_queues)
|
||||||
|
|
||||||
for cam_w, cam_h in args.camera_resolutions:
|
for cam_w, cam_h in args.camera_resolutions:
|
||||||
nv12 = NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
|
nv12 = NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
|
||||||
frame_copy_size = nv12_copy_size(nv12.stride, nv12.y_height, nv12.uv_height)
|
make_random_warp_inputs = partial(make_random_images, keys=['frame', 'big_frame'], shape=nv12.size, device=WARP_DEV)
|
||||||
make_model_queues = partial(make_input_queues, out['metadata']['input_shapes'], args.frame_skip,
|
warp = TinyJit(make_warp(nv12, model_w, model_h, args.frame_skip), prune=True)
|
||||||
frame_copy_size=frame_copy_size)
|
make_warp_queues = partial(make_warp_input_queues, out['metadata']['input_shapes'], args.frame_skip)
|
||||||
warp = make_warp(nv12, model_w, model_h)
|
out[(cam_w,cam_h)] = compile_jit(warp, make_random_warp_inputs, WARP_INPUTS, make_warp_queues)
|
||||||
run_model_jit = TinyJit(make_run_model(warp, run_policy, out['metadata'], frame_copy_size), prune=True)
|
|
||||||
out['run_model'][(cam_w,cam_h)] = compile_jit(run_model_jit, MODELD_INPUTS, make_model_queues,
|
|
||||||
args.benchmark_runs)
|
|
||||||
|
|
||||||
with open(args.output, "wb") as f:
|
with open(args.output, "wb") as f:
|
||||||
dump_oob(out, f)
|
dump_oob(out, f)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class ModelState:
|
|||||||
output: np.ndarray
|
output: np.ndarray
|
||||||
|
|
||||||
def __init__(self, cam_w: int, cam_h: int):
|
def __init__(self, cam_w: int, cam_h: int):
|
||||||
self.DEV = get_tg_input_devices(PROCESS_NAME, chestnut=False)['DEV']
|
self.DEV = get_tg_input_devices(PROCESS_NAME, usbgpu=False)['DEV']
|
||||||
with open(METADATA_PATH, 'rb') as f:
|
with open(METADATA_PATH, 'rb') as f:
|
||||||
model_metadata = pickle.load(f)
|
model_metadata = pickle.load(f)
|
||||||
self.input_shapes = model_metadata['input_shapes']
|
self.input_shapes = model_metadata['input_shapes']
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ def fill_driving_model_data(msg: capnp._DynamicStructBuilder, modelv2_send: capn
|
|||||||
driving_model_data.frameIdExtra = modelV2.frameIdExtra
|
driving_model_data.frameIdExtra = modelV2.frameIdExtra
|
||||||
driving_model_data.frameDropPerc = modelV2.frameDropPerc
|
driving_model_data.frameDropPerc = modelV2.frameDropPerc
|
||||||
driving_model_data.modelExecutionTime = modelV2.modelExecutionTime
|
driving_model_data.modelExecutionTime = modelV2.modelExecutionTime
|
||||||
driving_model_data.big = modelV2.big
|
|
||||||
driving_model_data.action = modelV2.action
|
driving_model_data.action = modelV2.action
|
||||||
driving_model_data.meta.laneChangeState = modelV2.meta.laneChangeState
|
driving_model_data.meta.laneChangeState = modelV2.meta.laneChangeState
|
||||||
driving_model_data.meta.laneChangeDirection = modelV2.meta.laneChangeDirection
|
driving_model_data.meta.laneChangeDirection = modelV2.meta.laneChangeDirection
|
||||||
|
|||||||
@@ -7,20 +7,18 @@ import tempfile
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from openpilot.common.file_chunker import get_manifest_path
|
from openpilot.common.file_chunker import get_manifest_path
|
||||||
from openpilot.common.hardware.usb import CHESTNUT_USB_PRODUCT, USB_DEVICES_PATH, is_chestnut_usb_id
|
from openpilot.common.hardware.usb import CHESTNUT_FW_VERSION, CHESTNUT_USB_IDS, USB_DEVICES_PATH
|
||||||
|
|
||||||
MODELS_DIR = Path(__file__).resolve().parent / 'models'
|
MODELS_DIR = Path(__file__).resolve().parent / 'models'
|
||||||
TG_INPUT_DEVICES_PATH = MODELS_DIR / 'tg_input_devices.json'
|
TG_INPUT_DEVICES_PATH = MODELS_DIR / 'tg_input_devices.json'
|
||||||
CHESTNUT_POWERED_VOLTAGE = 5000
|
|
||||||
CHESTNUT_PCIE_READY = 0x78
|
|
||||||
|
|
||||||
|
|
||||||
def get_tg_input_devices(process_name: str, chestnut: bool):
|
def get_tg_input_devices(process_name: str, usbgpu: bool):
|
||||||
with open(TG_INPUT_DEVICES_PATH) as f:
|
with open(TG_INPUT_DEVICES_PATH) as f:
|
||||||
return json.load(f)[process_name]['default' if not chestnut else 'chestnut']
|
return json.load(f)[process_name]['default' if not usbgpu else 'usbgpu']
|
||||||
|
|
||||||
def modeld_pkl_path(chestnut: bool):
|
def modeld_pkl_path(usbgpu: bool):
|
||||||
prefix = 'big_' if chestnut else ''
|
prefix = 'big_' if usbgpu else ''
|
||||||
return MODELS_DIR / f'{prefix}driving_tinygrad.pkl'
|
return MODELS_DIR / f'{prefix}driving_tinygrad.pkl'
|
||||||
|
|
||||||
def dump_oob(obj, f):
|
def dump_oob(obj, f):
|
||||||
@@ -47,20 +45,16 @@ def load_oob(f):
|
|||||||
yield pb
|
yield pb
|
||||||
return pickle.load(io.BytesIO(opcodes), buffers=buffers())
|
return pickle.load(io.BytesIO(opcodes), buffers=buffers())
|
||||||
|
|
||||||
def chestnut_present() -> bool:
|
def usbgpu_present() -> bool:
|
||||||
for d in USB_DEVICES_PATH.glob("*"):
|
for d in USB_DEVICES_PATH.glob("*"):
|
||||||
try:
|
try:
|
||||||
usb_id = (int((d / "idVendor").read_text(), 16), int((d / "idProduct").read_text(), 16))
|
usb_id = (int((d / "idVendor").read_text(), 16), int((d / "idProduct").read_text(), 16))
|
||||||
product = (d / "product").read_text().strip()
|
product = (d / "product").read_text().strip()
|
||||||
if is_chestnut_usb_id(*usb_id) and product == CHESTNUT_USB_PRODUCT:
|
if usb_id in CHESTNUT_USB_IDS and product == f"custom {CHESTNUT_FW_VERSION}-CLEAN":
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def chestnut_compiled() -> bool:
|
def usbgpu_compiled() -> bool:
|
||||||
return Path(get_manifest_path(modeld_pkl_path(chestnut=True))).is_file()
|
return Path(get_manifest_path(modeld_pkl_path(usbgpu=True))).is_file()
|
||||||
|
|
||||||
|
|
||||||
def chestnut_ready(state) -> bool:
|
|
||||||
return state.supplyVoltage >= CHESTNUT_POWERED_VOLTAGE and not state.supplyFault and state.pcieLtssm == CHESTNUT_PCIE_READY
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from collections.abc import Callable
|
|
||||||
import ctypes
|
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
import os
|
import os
|
||||||
os.environ['GMMU'] = '0' # for chestnut fast loading, noop for qcom
|
os.environ['GMMU'] = '0' # for usbgpu fast loading, noop for qcom
|
||||||
|
from tinygrad.tensor import Tensor
|
||||||
from tinygrad.device import Device
|
from tinygrad.device import Device
|
||||||
import usb1
|
|
||||||
import struct
|
import struct
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -28,17 +26,17 @@ from openpilot.common.transformations.model import get_warp_matrix
|
|||||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||||
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, should_stop, smooth_value, get_curvature_from_plan
|
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, should_stop, smooth_value, get_curvature_from_plan
|
||||||
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
|
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
|
||||||
from openpilot.selfdrive.modeld.compile_modeld import make_input_queues, nv12_copy_size, MODELD_INPUTS
|
from openpilot.selfdrive.modeld.compile_modeld import make_input_queues, WARP_INPUTS, POLICY_INPUTS
|
||||||
from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_driving_model_data, fill_pose_msg, PublishState
|
from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_driving_model_data, fill_pose_msg, PublishState
|
||||||
from openpilot.common.file_chunker import open_file_chunked
|
from openpilot.common.file_chunker import open_file_chunked
|
||||||
from openpilot.common.hardware.usb import CHESTNUT_USB_IDS
|
|
||||||
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
||||||
from openpilot.selfdrive.modeld.helpers import chestnut_present, chestnut_compiled, chestnut_ready, modeld_pkl_path, load_oob
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present, usbgpu_compiled, modeld_pkl_path, get_tg_input_devices, load_oob
|
||||||
|
|
||||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||||
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
||||||
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
|
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
|
||||||
|
|
||||||
|
PROCESS_NAME = "openpilot.selfdrive.modeld.modeld"
|
||||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||||
|
|
||||||
LAT_SMOOTH_SECONDS = 0.0
|
LAT_SMOOTH_SECONDS = 0.0
|
||||||
@@ -83,37 +81,6 @@ class ChestnutState:
|
|||||||
self.valid = True
|
self.valid = True
|
||||||
self.sends = 0
|
self.sends = 0
|
||||||
self.metrics = {}
|
self.metrics = {}
|
||||||
self._asm_usb = None
|
|
||||||
|
|
||||||
def _close_asm_usb(self) -> None:
|
|
||||||
if self._asm_usb is not None:
|
|
||||||
self._asm_usb.close()
|
|
||||||
self._asm_usb = None
|
|
||||||
|
|
||||||
def _open_asm_usb(self):
|
|
||||||
context = usb1.USBContext()
|
|
||||||
for vendor_id, product_id in CHESTNUT_USB_IDS:
|
|
||||||
if (handle := context.openByVendorIDAndProductID(vendor_id, product_id, skip_on_error=True)) is not None:
|
|
||||||
return handle
|
|
||||||
context.close()
|
|
||||||
|
|
||||||
def _read_ina(self) -> tuple[int, int, bool]:
|
|
||||||
if "AMD" in Device._opened_devices and self._asm_usb is None:
|
|
||||||
try:
|
|
||||||
raw = Device["AMD"].iface.pci_dev.usb.usb.control_read(0xC0, 5)
|
|
||||||
return struct.unpack('<Hh?', bytes(raw))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if self._asm_usb is None:
|
|
||||||
self._asm_usb = self._open_asm_usb()
|
|
||||||
if self._asm_usb is None:
|
|
||||||
raise usb1.USBErrorNoDevice
|
|
||||||
try:
|
|
||||||
raw = self._asm_usb.controlRead(0xC0, 0xC0, 0, 0, 5, timeout=100)
|
|
||||||
except usb1.USBError:
|
|
||||||
self._close_asm_usb()
|
|
||||||
raise
|
|
||||||
return struct.unpack('<Hh?', bytes(raw))
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def power_limit(self) -> int:
|
def power_limit(self) -> int:
|
||||||
@@ -127,10 +94,8 @@ class ChestnutState:
|
|||||||
if self.big and "AMD" in Device._opened_devices and self.sends % 100 == 1:
|
if self.big and "AMD" in Device._opened_devices and self.sends % 100 == 1:
|
||||||
try:
|
try:
|
||||||
smu = Device["AMD"].iface.dev_impl.smu
|
smu = Device["AMD"].iface.dev_impl.smu
|
||||||
metrics_t = smu.smu_mod.SmuMetricsExternal_t
|
|
||||||
smu._send_msg(smu.smu_mod.PPSMC_MSG_TransferTableSmu2Dram, smu.smu_mod.TABLE_SMU_METRICS, timeout=100)
|
smu._send_msg(smu.smu_mod.PPSMC_MSG_TransferTableSmu2Dram, smu.smu_mod.TABLE_SMU_METRICS, timeout=100)
|
||||||
metrics_buf = bytearray(smu.adev.vram.view(smu.driver_table_paddr, ctypes.sizeof(metrics_t))[:])
|
metrics = smu.read_table(smu.smu_mod.SmuMetricsExternal_t, smu.smu_mod.TABLE_SMU_METRICS).SmuMetrics
|
||||||
metrics = metrics_t.from_buffer(metrics_buf).SmuMetrics
|
|
||||||
self.metrics = {'tempC': metrics.AvgTemperature[smu.smu_mod.TEMP_HOTSPOT],
|
self.metrics = {'tempC': metrics.AvgTemperature[smu.smu_mod.TEMP_HOTSPOT],
|
||||||
'memoryTempC': metrics.AvgTemperature[smu.smu_mod.TEMP_MEM],
|
'memoryTempC': metrics.AvgTemperature[smu.smu_mod.TEMP_MEM],
|
||||||
'powerDrawW': metrics.AverageSocketPower,
|
'powerDrawW': metrics.AverageSocketPower,
|
||||||
@@ -149,15 +114,13 @@ class ChestnutState:
|
|||||||
setattr(state, k, v)
|
setattr(state, k, v)
|
||||||
|
|
||||||
asm_valid = False
|
asm_valid = False
|
||||||
try:
|
|
||||||
# ASM runs on USB-C power, these still read without a gpu
|
|
||||||
state.supplyVoltage, state.supplyCurrent, state.supplyFault = self._read_ina()
|
|
||||||
asm_valid = True
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if "AMD" in Device._opened_devices:
|
if "AMD" in Device._opened_devices:
|
||||||
try:
|
try:
|
||||||
state.pcieLtssm = Device["AMD"].iface.pci_dev.usb.read(0xB450, 1)[0]
|
# ASM runs on USB-C power, these still read without a gpu
|
||||||
|
asm = Device["AMD"].iface.pci_dev.usb
|
||||||
|
state.pcieLtssm = asm.read(0xB450, 1)[0]
|
||||||
|
state.supplyVoltage, state.supplyCurrent = struct.unpack('<Hh', bytes(asm.usb.control_read(0xC0, 5))[:4])
|
||||||
|
asm_valid = True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -178,34 +141,42 @@ class FrameMeta:
|
|||||||
class ModelState(ModelStateBase):
|
class ModelState(ModelStateBase):
|
||||||
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
||||||
|
|
||||||
def __init__(self, cam_w: int, cam_h: int, chestnut: bool):
|
def __init__(self, cam_w: int, cam_h: int, usbgpu: bool):
|
||||||
ModelStateBase.__init__(self)
|
ModelStateBase.__init__(self)
|
||||||
jits = load_oob(open_file_chunked(modeld_pkl_path(chestnut)))
|
input_devices = get_tg_input_devices(PROCESS_NAME, usbgpu)
|
||||||
input_devices = jits['input_devices']
|
self.WARP_DEV, self.QUEUE_DEV = input_devices['WARP_DEV'], input_devices['QUEUE_DEV']
|
||||||
self.model_device = input_devices['model']
|
jits = load_oob(open_file_chunked(modeld_pkl_path(usbgpu)))
|
||||||
metadata = jits['metadata']
|
metadata = jits['metadata']
|
||||||
self.input_shapes = metadata['input_shapes']
|
self.input_shapes = metadata['input_shapes']
|
||||||
self.vision_input_names = [k for k in self.input_shapes if 'img' in k]
|
self.vision_input_names = [k for k in self.input_shapes if 'img' in k]
|
||||||
self.output_slices = metadata['output_slices']
|
self.output_slices = metadata['output_slices']
|
||||||
|
|
||||||
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
||||||
self.chestnut = chestnut
|
self.usbgpu = usbgpu
|
||||||
|
|
||||||
self.frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
self.frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
||||||
self.frame_copy_size = nv12_copy_size(*get_nv12_info(cam_w, cam_h)[:3])
|
self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
|
||||||
self.input_queues, self.npy, self.frame_views = make_input_queues(
|
self.full_frames: dict[str, Tensor] = {}
|
||||||
self.input_shapes, self.frame_skip, device=self.model_device, frame_copy_size=self.frame_copy_size)
|
self._blob_cache: dict[tuple[str, int], Tensor] = {}
|
||||||
self.parser = Parser()
|
self.parser = Parser()
|
||||||
self.run_model = jits['run_model'][(cam_w,cam_h)]
|
self.frame_buf_params = {k: get_nv12_info(cam_w, cam_h) for k in ('img', 'big_img')}
|
||||||
|
self.run_policy = jits['run_policy']
|
||||||
|
self.warp = jits[(cam_w,cam_h)]
|
||||||
|
|
||||||
def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]:
|
def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]:
|
||||||
parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()}
|
parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()}
|
||||||
return parsed_model_outputs
|
return parsed_model_outputs
|
||||||
|
|
||||||
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
||||||
inputs: dict[str, np.ndarray], after_enqueue: Callable[[], None] | None = None) -> dict[str, np.ndarray]:
|
inputs: dict[str, np.ndarray]) -> dict[str, np.ndarray] | None:
|
||||||
for key, buf in bufs.items():
|
for key in bufs.keys():
|
||||||
np.copyto(self.frame_views[key], np.frombuffer(buf.data, dtype=np.uint8, count=self.frame_copy_size))
|
ptr = np.frombuffer(bufs[key].data, dtype=np.uint8).ctypes.data
|
||||||
|
yuv_size = self.frame_buf_params[key][3]
|
||||||
|
# There is a ringbuffer of imgs, just cache tensors pointing to all of them
|
||||||
|
cache_key = (key, ptr)
|
||||||
|
if cache_key not in self._blob_cache:
|
||||||
|
self._blob_cache[cache_key] = Tensor.from_blob(ptr, (yuv_size,), dtype='uint8', device=self.WARP_DEV)
|
||||||
|
self.full_frames[key] = self._blob_cache[cache_key]
|
||||||
|
|
||||||
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge
|
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge
|
||||||
inputs['desire_pulse'][0] = 0
|
inputs['desire_pulse'][0] = 0
|
||||||
@@ -216,12 +187,16 @@ class ModelState(ModelStateBase):
|
|||||||
self.npy['tfm'][:,:] = transforms['img'][:,:]
|
self.npy['tfm'][:,:] = transforms['img'][:,:]
|
||||||
self.npy['big_tfm'][:,:] = transforms['big_img'][:,:]
|
self.npy['big_tfm'][:,:] = transforms['big_img'][:,:]
|
||||||
|
|
||||||
outs, = self.run_model(**{k: self.input_queues[k] for k in MODELD_INPUTS})
|
warped = self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=self.full_frames['img'], big_frame=self.full_frames['big_img'])
|
||||||
if after_enqueue is not None:
|
|
||||||
after_enqueue()
|
outs, = self.run_policy(
|
||||||
|
**{k: self.input_queues[k] for k in POLICY_INPUTS if k in self.input_queues}, warped=warped
|
||||||
|
)
|
||||||
model_output = outs.numpy()[0]
|
model_output = outs.numpy()[0]
|
||||||
if self.chestnut and not np.all(np.isfinite(model_output)):
|
if self.usbgpu and not np.all(np.isfinite(model_output)):
|
||||||
raise RuntimeError("model output not finite")
|
# TODO remove with prev_feat
|
||||||
|
cloudlog.error("model output not finite, dropping frame")
|
||||||
|
return None
|
||||||
outputs_dict = self.parser.parse_outputs(self.slice_outputs(model_output, self.output_slices))
|
outputs_dict = self.parser.parse_outputs(self.slice_outputs(model_output, self.output_slices))
|
||||||
self.npy['prev_feat'][:] = model_output[self.output_slices['hidden_state']]
|
self.npy['prev_feat'][:] = model_output[self.output_slices['hidden_state']]
|
||||||
|
|
||||||
@@ -230,37 +205,25 @@ class ModelState(ModelStateBase):
|
|||||||
return outputs_dict
|
return outputs_dict
|
||||||
|
|
||||||
def warmup(self) -> None:
|
def warmup(self) -> None:
|
||||||
dummy_frames = {k: np.zeros(self.frame_copy_size, dtype=np.uint8) for k in self.vision_input_names}
|
dummy_frames = {k: np.zeros(self.frame_buf_params[k][3], dtype=np.uint8) for k in self.vision_input_names}
|
||||||
eye = np.eye(3, dtype=np.float32)
|
eye = np.eye(3, dtype=np.float32)
|
||||||
dims = {'desire_pulse': ModelConstants.DESIRE_LEN, 'traffic_convention': 2, 'action_t': 2}
|
dims = {'desire_pulse': ModelConstants.DESIRE_LEN, 'traffic_convention': 2, 'action_t': 2}
|
||||||
self.run(dummy_frames, dict.fromkeys(self.vision_input_names, eye), {k: np.zeros(v, dtype=np.float32) for k, v in dims.items()})
|
self.run(dummy_frames, dict.fromkeys(self.vision_input_names, eye), {k: np.zeros(v, dtype=np.float32) for k, v in dims.items()})
|
||||||
self.input_queues, self.npy, self.frame_views = make_input_queues(
|
self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
|
||||||
self.input_shapes, self.frame_skip, device=self.model_device, frame_copy_size=self.frame_copy_size)
|
|
||||||
self.prev_desire[:] = 0
|
self.prev_desire[:] = 0
|
||||||
|
self.full_frames.clear()
|
||||||
|
self._blob_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
def main(demo=False):
|
def main(demo=False):
|
||||||
cloudlog.warning("modeld init")
|
cloudlog.warning("modeld init")
|
||||||
|
|
||||||
chestnut_available = chestnut_present() and chestnut_compiled()
|
USBGPU = usbgpu_present() and usbgpu_compiled()
|
||||||
CHESTNUT = False
|
if USBGPU:
|
||||||
if chestnut_available:
|
|
||||||
poller = messaging.Poller()
|
|
||||||
sock = messaging.sub_sock("chestnutState", poller=poller, conflate=True)
|
|
||||||
deadline = time.monotonic() + 4. / SERVICE_LIST['deviceState'].frequency
|
|
||||||
while not CHESTNUT and (remaining := deadline - time.monotonic()) > 0.:
|
|
||||||
if not poller.poll(round(remaining * 1000)):
|
|
||||||
break
|
|
||||||
msg = messaging.recv_one_or_none(sock)
|
|
||||||
CHESTNUT = msg is not None and msg.valid and chestnut_ready(msg.chestnutState)
|
|
||||||
if CHESTNUT:
|
|
||||||
os.environ['HCQDEV_WAIT_TIMEOUT_MS'] = '3000'
|
os.environ['HCQDEV_WAIT_TIMEOUT_MS'] = '3000'
|
||||||
params = Params()
|
params = Params()
|
||||||
params.put_bool("ChestnutLoading", CHESTNUT)
|
params.put_bool("UsbGpuLoading", USBGPU)
|
||||||
if chestnut_available and not CHESTNUT:
|
params.remove("UsbGpuActive")
|
||||||
params.put_bool("ChestnutActive", False)
|
|
||||||
else:
|
|
||||||
params.remove("ChestnutActive")
|
|
||||||
|
|
||||||
config_realtime_process(7, 54)
|
config_realtime_process(7, 54)
|
||||||
|
|
||||||
@@ -290,7 +253,7 @@ def main(demo=False):
|
|||||||
st = time.monotonic()
|
st = time.monotonic()
|
||||||
cloudlog.warning("loading model")
|
cloudlog.warning("loading model")
|
||||||
model = None
|
model = None
|
||||||
if CHESTNUT:
|
if USBGPU:
|
||||||
big_model = None
|
big_model = None
|
||||||
def load_big():
|
def load_big():
|
||||||
nonlocal big_model
|
nonlocal big_model
|
||||||
@@ -304,27 +267,23 @@ def main(demo=False):
|
|||||||
loader.start()
|
loader.start()
|
||||||
loader.join(BIG_MODEL_TIMEOUT)
|
loader.join(BIG_MODEL_TIMEOUT)
|
||||||
model = big_model
|
model = big_model
|
||||||
if model is None:
|
params.put_bool("UsbGpuActive", model is not None)
|
||||||
params.put_bool("ChestnutModelError", True)
|
|
||||||
params.put_bool("ChestnutActive", model is not None)
|
|
||||||
if model is not None:
|
|
||||||
params.remove("ChestnutModelError")
|
|
||||||
|
|
||||||
small_model = ModelState(vipc_client_main.width, vipc_client_main.height, False) if model is None or CHESTNUT else None
|
small_model = ModelState(vipc_client_main.width, vipc_client_main.height, False) if model is None or USBGPU else None
|
||||||
if model is None:
|
if model is None:
|
||||||
model = small_model
|
model = small_model
|
||||||
params.put_bool("ChestnutLoading", False)
|
params.put_bool("UsbGpuLoading", False)
|
||||||
assert model is not None
|
assert model is not None
|
||||||
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
||||||
|
|
||||||
# messaging
|
# messaging
|
||||||
pub_socks = ["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"] + (["chestnutState"] if CHESTNUT else [])
|
pub_socks = ["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"] + (["chestnutState"] if USBGPU else [])
|
||||||
pm = PubMaster(pub_socks)
|
pm = PubMaster(pub_socks)
|
||||||
sm = SubMaster(["deviceState", "carState", "narrowRoadCameraState", "extrinsicsCalibration", "driverMonitoringState", "carControl", "lateralDelay"])
|
sm = SubMaster(["deviceState", "carState", "narrowRoadCameraState", "extrinsicsCalibration", "driverMonitoringState", "carControl", "lateralDelay"])
|
||||||
|
|
||||||
publish_state = PublishState()
|
publish_state = PublishState()
|
||||||
params = Params()
|
params = Params()
|
||||||
chestnut_state = ChestnutState(pm, model.chestnut) if CHESTNUT else None
|
chestnut_state = ChestnutState(pm, model.usbgpu) if USBGPU else None
|
||||||
|
|
||||||
# setup filter to track dropped frames
|
# setup filter to track dropped frames
|
||||||
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_RUN_FREQ)
|
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_RUN_FREQ)
|
||||||
@@ -434,16 +393,13 @@ def main(demo=False):
|
|||||||
|
|
||||||
mt1 = time.perf_counter()
|
mt1 = time.perf_counter()
|
||||||
try:
|
try:
|
||||||
send_chestnut = (chestnut_state is not None and
|
model_output = model.run(bufs, transforms, inputs)
|
||||||
run_count % round(ModelConstants.MODEL_RUN_FREQ / SERVICE_LIST['chestnutState'].frequency) == 0)
|
|
||||||
model_output = model.run(bufs, transforms, inputs, chestnut_state.send if send_chestnut else None)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
if not params.get_bool("ChestnutActive"):
|
if not params.get_bool("UsbGpuActive"):
|
||||||
raise
|
raise
|
||||||
# fallback to small model
|
# fallback to small model
|
||||||
cloudlog.exception("big model failed, fall back to small")
|
cloudlog.exception("big model failed, fall back to small")
|
||||||
params.put_bool("ChestnutModelError", True)
|
params.put_bool("UsbGpuActive", False)
|
||||||
params.put_bool("ChestnutActive", False)
|
|
||||||
assert small_model is not None
|
assert small_model is not None
|
||||||
model = small_model
|
model = small_model
|
||||||
if chestnut_state is not None:
|
if chestnut_state is not None:
|
||||||
@@ -463,17 +419,18 @@ def main(demo=False):
|
|||||||
fill_model_msg(modelv2_send, model_output, action,
|
fill_model_msg(modelv2_send, model_output, action,
|
||||||
publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
|
publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
|
||||||
frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, extrinsics_calibration_seen)
|
frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, extrinsics_calibration_seen)
|
||||||
modelv2_send.modelV2.big = model.chestnut
|
modelv2_send.modelV2.big = model.usbgpu
|
||||||
|
|
||||||
desire_state = modelv2_send.modelV2.meta.desireState
|
desire_state = modelv2_send.modelV2.meta.desireState
|
||||||
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
|
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
|
||||||
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
|
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
|
||||||
lane_change_prob = l_lane_change_prob + r_lane_change_prob
|
lane_change_prob = l_lane_change_prob + r_lane_change_prob
|
||||||
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||||
left_edge, right_edge = RELC.update_and_fill(modelv2_send.modelV2, mdv2sp_send.modelDataV2SP, v_ego)
|
|
||||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, left_edge, right_edge)
|
|
||||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||||
|
|
||||||
|
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
||||||
|
left_edge, right_edge = RELC.update_and_fill(modelv2_send.modelV2, mdv2sp_send.modelDataV2SP, v_ego)
|
||||||
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
|
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
|
||||||
|
|
||||||
fill_driving_model_data(drivingdata_send, modelv2_send)
|
fill_driving_model_data(drivingdata_send, modelv2_send)
|
||||||
@@ -484,6 +441,10 @@ def main(demo=False):
|
|||||||
pm.send('modelDataV2SP', mdv2sp_send)
|
pm.send('modelDataV2SP', mdv2sp_send)
|
||||||
last_vipc_frame_id = meta_main.frame_id
|
last_vipc_frame_id = meta_main.frame_id
|
||||||
|
|
||||||
|
if chestnut_state is not None and run_count % round(ModelConstants.MODEL_RUN_FREQ / SERVICE_LIST['chestnutState'].frequency) == 0:
|
||||||
|
chestnut_state.send()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1791d5940b2c048d0639813426dd2cf1d6f2a6727ed51e17c8bcea8bbe754123
|
oid sha256:a501760a9d1d5fef0eab2b8c5d122d06124fc26dc8e0782e0aa94b82a208f0ff
|
||||||
size 765950064
|
size 1757355221
|
||||||
|
|||||||
@@ -123,22 +123,22 @@ void fill_panda_state(cereal::PandaState::Builder &ps, cereal::PandaState::Panda
|
|||||||
ps.setUptime(health.uptime_pkt);
|
ps.setUptime(health.uptime_pkt);
|
||||||
ps.setSafetyTxBlocked(health.safety_tx_blocked_pkt);
|
ps.setSafetyTxBlocked(health.safety_tx_blocked_pkt);
|
||||||
ps.setSafetyRxInvalid(health.safety_rx_invalid_pkt);
|
ps.setSafetyRxInvalid(health.safety_rx_invalid_pkt);
|
||||||
ps.setIgnitionLine((health.flags_pkt & HEALTH_FLAG_IGNITION_LINE) != 0U);
|
ps.setIgnitionLine(health.ignition_line_pkt);
|
||||||
ps.setIgnitionCan((health.flags_pkt & HEALTH_FLAG_IGNITION_CAN) != 0U);
|
ps.setIgnitionCan(health.ignition_can_pkt);
|
||||||
ps.setControlsAllowed((health.flags_pkt & HEALTH_FLAG_CONTROLS_ALLOWED) != 0U);
|
ps.setControlsAllowed(health.controls_allowed_pkt);
|
||||||
ps.setTxBufferOverflow(health.tx_buffer_overflow_pkt);
|
ps.setTxBufferOverflow(health.tx_buffer_overflow_pkt);
|
||||||
ps.setRxBufferOverflow(health.rx_buffer_overflow_pkt);
|
ps.setRxBufferOverflow(health.rx_buffer_overflow_pkt);
|
||||||
ps.setPandaType(hw_type);
|
ps.setPandaType(hw_type);
|
||||||
ps.setSafetyModel(cereal::CarParams::SafetyModel(health.safety_mode_pkt));
|
ps.setSafetyModel(cereal::CarParams::SafetyModel(health.safety_mode_pkt));
|
||||||
ps.setSafetyParam(health.safety_param_pkt);
|
ps.setSafetyParam(health.safety_param_pkt);
|
||||||
ps.setFaultStatus(cereal::PandaState::FaultStatus(health.fault_status_pkt));
|
ps.setFaultStatus(cereal::PandaState::FaultStatus(health.fault_status_pkt));
|
||||||
ps.setPowerSaveEnabled((health.flags_pkt & HEALTH_FLAG_POWER_SAVE_ENABLED) != 0U);
|
ps.setPowerSaveEnabled((bool)(health.power_save_enabled_pkt));
|
||||||
ps.setHeartbeatLost((health.flags_pkt & HEALTH_FLAG_HEARTBEAT_LOST) != 0U);
|
ps.setHeartbeatLost((bool)(health.heartbeat_lost_pkt));
|
||||||
ps.setAlternativeExperience(health.alternative_experience_pkt);
|
ps.setAlternativeExperience(health.alternative_experience_pkt);
|
||||||
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt));
|
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt));
|
||||||
ps.setInterruptLoad(health.interrupt_load_pkt / 255.0f);
|
ps.setInterruptLoad(health.interrupt_load_pkt);
|
||||||
ps.setFanPower(health.fan_power);
|
ps.setFanPower(health.fan_power);
|
||||||
ps.setSafetyRxChecksInvalid((health.flags_pkt & HEALTH_FLAG_SAFETY_RX_CHECKS_INVALID) != 0U);
|
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt));
|
||||||
ps.setSpiErrorCount(health.spi_error_count_pkt);
|
ps.setSpiErrorCount(health.spi_error_count_pkt);
|
||||||
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f);
|
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f);
|
||||||
ps.setSbu2Voltage(health.sbu2_voltage_mV / 1000.0f);
|
ps.setSbu2Voltage(health.sbu2_voltage_mV / 1000.0f);
|
||||||
@@ -198,10 +198,10 @@ std::optional<bool> send_panda_states(PubMaster *pm, Panda *panda, bool is_onroa
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (spoofing_started) {
|
if (spoofing_started) {
|
||||||
health.flags_pkt |= HEALTH_FLAG_IGNITION_LINE;
|
health.ignition_line_pkt = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ignition_local = ((health.flags_pkt & (HEALTH_FLAG_IGNITION_LINE | HEALTH_FLAG_IGNITION_CAN)) != 0U) && !always_offroad;
|
bool ignition_local = ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)) && !always_offroad;
|
||||||
|
|
||||||
// Make sure CAN buses are live: safety_setter_thread does not work if Panda CAN are silent and there is only one other CAN node
|
// Make sure CAN buses are live: safety_setter_thread does not work if Panda CAN are silent and there is only one other CAN node
|
||||||
if (health.safety_mode_pkt == (uint8_t)(cereal::CarParams::SafetyModel::SILENT)) {
|
if (health.safety_mode_pkt == (uint8_t)(cereal::CarParams::SafetyModel::SILENT)) {
|
||||||
@@ -209,7 +209,7 @@ std::optional<bool> send_panda_states(PubMaster *pm, Panda *panda, bool is_onroa
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool power_save_desired = !ignition_local;
|
bool power_save_desired = !ignition_local;
|
||||||
if (((health.flags_pkt & HEALTH_FLAG_POWER_SAVE_ENABLED) != 0U) != power_save_desired) {
|
if (health.power_save_enabled_pkt != power_save_desired) {
|
||||||
panda->set_power_saving(power_save_desired);
|
panda->set_power_saving(power_save_desired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ enum SpiError {
|
|||||||
|
|
||||||
const unsigned int SPI_ACK_TIMEOUT = 500; // milliseconds
|
const unsigned int SPI_ACK_TIMEOUT = 500; // milliseconds
|
||||||
const std::string SPI_DEVICE = "/dev/spidev0.0";
|
const std::string SPI_DEVICE = "/dev/spidev0.0";
|
||||||
|
// TODO: fix SPI turnaround synchronization at the protocol level.
|
||||||
|
static uint64_t spi_last_bus_activity_ns = 0; // protected by hw_lock
|
||||||
|
|
||||||
|
static void wait_for_spi_turnaround(uint64_t start_ns) {
|
||||||
|
while ((nanos_since_boot() - start_ns) < 400000) {}
|
||||||
|
}
|
||||||
|
|
||||||
class LockEx {
|
class LockEx {
|
||||||
public:
|
public:
|
||||||
@@ -319,6 +325,8 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
|||||||
assert(tx_len < SPI_BUF_SIZE);
|
assert(tx_len < SPI_BUF_SIZE);
|
||||||
assert(max_rx_len < SPI_BUF_SIZE);
|
assert(max_rx_len < SPI_BUF_SIZE);
|
||||||
|
|
||||||
|
wait_for_spi_turnaround(spi_last_bus_activity_ns);
|
||||||
|
|
||||||
xfer_count++;
|
xfer_count++;
|
||||||
header = {
|
header = {
|
||||||
.sync = SPI_SYNC,
|
.sync = SPI_SYNC,
|
||||||
@@ -347,6 +355,7 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
|||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
wait_for_spi_turnaround(nanos_since_boot());
|
||||||
|
|
||||||
// Send data
|
// Send data
|
||||||
if (tx_data != NULL) {
|
if (tx_data != NULL) {
|
||||||
@@ -389,6 +398,7 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
|||||||
memcpy(rx_data, rx_buf + 3, rx_data_len);
|
memcpy(rx_data, rx_buf + 3, rx_data_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spi_last_bus_activity_ns = nanos_since_boot();
|
||||||
return rx_data_len;
|
return rx_data_len;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
@@ -403,6 +413,7 @@ fail:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spi_last_bus_activity_ns = nanos_since_boot();
|
||||||
if (ret >= 0) ret = -1;
|
if (ret >= 0) ret = -1;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,30 +19,6 @@
|
|||||||
},
|
},
|
||||||
"Offroad_ChestnutBranch": {
|
"Offroad_ChestnutBranch": {
|
||||||
"text": "Chestnut detected! Switch to the %1 branch to use chestnut-class models.",
|
"text": "Chestnut detected! Switch to the %1 branch to use chestnut-class models.",
|
||||||
"severity": -1
|
|
||||||
},
|
|
||||||
"Offroad_ChestnutNotDetected": {
|
|
||||||
"text": "Chestnut not detected. Check USB and 12V connections.",
|
|
||||||
"severity": 0
|
|
||||||
},
|
|
||||||
"Offroad_ChestnutOverheated": {
|
|
||||||
"text": "Chestnut overheated. Ensure good airflow. Current GPU temperature is %1.",
|
|
||||||
"severity": 0
|
|
||||||
},
|
|
||||||
"Offroad_ChestnutPcieUnavailable": {
|
|
||||||
"text": "%1",
|
|
||||||
"severity": 0
|
|
||||||
},
|
|
||||||
"Offroad_ChestnutUncompiled": {
|
|
||||||
"text": "Chestnut model not compiled. Keep ignition on and reboot the comma.",
|
|
||||||
"severity": 0
|
|
||||||
},
|
|
||||||
"Offroad_ChestnutUpdateFailed": {
|
|
||||||
"text": "Chestnut update failed. Check the USB cable.",
|
|
||||||
"severity": 0
|
|
||||||
},
|
|
||||||
"Offroad_ChestnutUsbSlow": {
|
|
||||||
"text": "Chestnut USB link is slow. Check the USB cable. The current speed is %1.",
|
|
||||||
"severity": 0
|
"severity": 0
|
||||||
},
|
},
|
||||||
"Offroad_UnregisteredHardware": {
|
"Offroad_UnregisteredHardware": {
|
||||||
|
|||||||
@@ -195,18 +195,17 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.events.add(EventName.joystickDebug)
|
self.events.add(EventName.joystickDebug)
|
||||||
self.startup_event = None
|
self.startup_event = None
|
||||||
|
|
||||||
loading = self.params.get_bool("ChestnutLoading")
|
loading = self.params.get_bool("UsbGpuLoading")
|
||||||
if self.big_model_loading and not loading:
|
if self.big_model_loading and not loading:
|
||||||
self.big_model_ready_t = time.monotonic()
|
self.big_model_ready_t = time.monotonic()
|
||||||
self.events_sp.add(custom.OnroadEventSP.EventName.bigModelReady)
|
|
||||||
self.big_model_loading = loading
|
self.big_model_loading = loading
|
||||||
if self.big_model_loading:
|
if self.big_model_loading:
|
||||||
self.events.add(EventName.bigModelLoading)
|
self.events.add(EventName.bigModelLoading)
|
||||||
|
|
||||||
big_active = self.params.get("ChestnutActive")
|
big_active = self.params.get("UsbGpuActive")
|
||||||
chestnut_present = self.sm['deviceState'].chestnutPresent
|
usbgpu_present = self.sm['deviceState'].chestnutPresent
|
||||||
model_unavailable = big_active is True and self.sm.seen['modelV2'] and not self.sm.alive['modelV2']
|
model_unavailable = big_active is True and self.sm.seen['modelV2'] and not self.sm.alive['modelV2']
|
||||||
big_failed = big_active is False or model_unavailable or (self.big_model_active and not chestnut_present)
|
big_failed = big_active is False or model_unavailable or (self.big_model_active and not usbgpu_present)
|
||||||
if big_failed and not self.big_model_failed:
|
if big_failed and not self.big_model_failed:
|
||||||
self.events.add(EventName.bigModelFailed)
|
self.events.add(EventName.bigModelFailed)
|
||||||
self.big_model_failed = big_failed
|
self.big_model_failed = big_failed
|
||||||
|
|||||||
@@ -11,15 +11,6 @@ from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPl
|
|||||||
from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU
|
from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU
|
||||||
|
|
||||||
|
|
||||||
class PlannerSM(dict):
|
|
||||||
def __init__(self, radar_frame: int, services: dict):
|
|
||||||
super().__init__(services)
|
|
||||||
self.frame = radar_frame
|
|
||||||
self.logMonoTime = {"radarState": radar_frame}
|
|
||||||
self.valid = {"radarState": True}
|
|
||||||
self.alive = {"radarState": True}
|
|
||||||
|
|
||||||
|
|
||||||
class Plant:
|
class Plant:
|
||||||
messaging_initialized = False
|
messaging_initialized = False
|
||||||
|
|
||||||
@@ -141,7 +132,7 @@ class Plant:
|
|||||||
car_control.carControl.orientationNED = [0., float(pitch), 0.]
|
car_control.carControl.orientationNED = [0., float(pitch), 0.]
|
||||||
|
|
||||||
# ******** get controlsState messages for plotting ***
|
# ******** get controlsState messages for plotting ***
|
||||||
sm = PlannerSM(self.rk.frame, {'radarState': radar.radarState,
|
sm = {'radarState': radar.radarState,
|
||||||
'carState': car_state.carState,
|
'carState': car_state.carState,
|
||||||
'carControl': car_control.carControl,
|
'carControl': car_control.carControl,
|
||||||
'controlsState': control.controlsState,
|
'controlsState': control.controlsState,
|
||||||
@@ -150,7 +141,7 @@ class Plant:
|
|||||||
'modelV2': model.modelV2,
|
'modelV2': model.modelV2,
|
||||||
'carStateSP': car_state_sp.carStateSP,
|
'carStateSP': car_state_sp.carStateSP,
|
||||||
'liveMapDataSP': live_map_data_sp.liveMapDataSP,
|
'liveMapDataSP': live_map_data_sp.liveMapDataSP,
|
||||||
'gpsLocation': gps_data.gpsLocation})
|
'gpsLocation': gps_data.gpsLocation}
|
||||||
self.planner.update(sm)
|
self.planner.update(sm)
|
||||||
self.acceleration = self.planner.output_a_target
|
self.acceleration = self.planner.output_a_target
|
||||||
if self.planner.output_should_stop:
|
if self.planner.output_should_stop:
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ def migrate_drivingModelData(msgs):
|
|||||||
add_ops = []
|
add_ops = []
|
||||||
for _, msg in msgs:
|
for _, msg in msgs:
|
||||||
dmd = messaging.new_message('drivingModelData', valid=msg.valid, logMonoTime=msg.logMonoTime)
|
dmd = messaging.new_message('drivingModelData', valid=msg.valid, logMonoTime=msg.logMonoTime)
|
||||||
for field in ["frameId", "frameIdExtra", "frameDropPerc", "modelExecutionTime", "big", "action"]:
|
for field in ["frameId", "frameIdExtra", "frameDropPerc", "modelExecutionTime", "action"]:
|
||||||
setattr(dmd.drivingModelData, field, getattr(msg.modelV2, field))
|
setattr(dmd.drivingModelData, field, getattr(msg.modelV2, field))
|
||||||
for meta_field in ["laneChangeState", "laneChangeState"]:
|
for meta_field in ["laneChangeState", "laneChangeState"]:
|
||||||
setattr(dmd.drivingModelData.meta, meta_field, getattr(msg.modelV2.meta, meta_field))
|
setattr(dmd.drivingModelData.meta, meta_field, getattr(msg.modelV2.meta, meta_field))
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ MODEL_REPLAY_BUCKET="model_replay_master"
|
|||||||
GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN)
|
GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN)
|
||||||
|
|
||||||
EXEC_TIMINGS = [
|
EXEC_TIMINGS = [
|
||||||
# model, instant max, average max, chestnut average max
|
# model, instant max, average max
|
||||||
("modelV2", 0.05, 0.03, 0.05),
|
("modelV2", 0.05, 0.028),
|
||||||
("driverStateV2", 0.05, 0.018, 0.018),
|
("driverStateV2", 0.05, 0.018),
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_log_fn(test_route, ref="master"):
|
def get_log_fn(test_route, ref="master"):
|
||||||
@@ -169,13 +169,11 @@ def model_replay(lr, frs):
|
|||||||
dmonitoringmodeld_msgs = replay_process(dmonitoringmodeld, dmodeld_logs, frs)
|
dmonitoringmodeld_msgs = replay_process(dmonitoringmodeld, dmodeld_logs, frs)
|
||||||
|
|
||||||
msgs = modeld_msgs + dmonitoringmodeld_msgs
|
msgs = modeld_msgs + dmonitoringmodeld_msgs
|
||||||
chestnut = any(m.modelV2.big for m in modeld_msgs if m.which() == "modelV2")
|
|
||||||
|
|
||||||
header = ['model', 'max instant', 'max instant allowed', 'average', 'max average allowed', 'test result']
|
header = ['model', 'max instant', 'max instant allowed', 'average', 'max average allowed', 'test result']
|
||||||
rows = []
|
rows = []
|
||||||
timings_ok = True
|
timings_ok = True
|
||||||
for (s, instant_max, avg_max, chestnut_avg_max) in EXEC_TIMINGS:
|
for (s, instant_max, avg_max) in EXEC_TIMINGS:
|
||||||
avg_max = chestnut_avg_max if chestnut else avg_max
|
|
||||||
ts = [getattr(m, s).modelExecutionTime for m in msgs if m.which() == s]
|
ts = [getattr(m, s).modelExecutionTime for m in msgs if m.which() == s]
|
||||||
# TODO some init can happen in first iteration
|
# TODO some init can happen in first iteration
|
||||||
ts = ts[1:]
|
ts = ts[1:]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import time
|
import time
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
|
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, TextAlignment, TextAlignmentVertical
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
@@ -26,8 +26,8 @@ class BodyLayout(Widget):
|
|||||||
self._last_input_time = time.monotonic()
|
self._last_input_time = time.monotonic()
|
||||||
self._was_active = False
|
self._was_active = False
|
||||||
self._offroad_label = UnifiedLabel("turn on ignition to use", 95 if gui_app.big_ui() else 45, FontWeight.DISPLAY,
|
self._offroad_label = UnifiedLabel("turn on ignition to use", 95 if gui_app.big_ui() else 45, FontWeight.DISPLAY,
|
||||||
alignment=TextAlignment.CENTER,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
alignment_vertical=TextAlignmentVertical.MIDDLE)
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||||
|
|
||||||
def draw_dot_grid(self, rect: rl.Rectangle, dots: list[tuple[int, int]], color: rl.Color):
|
def draw_dot_grid(self, rect: rl.Rectangle, dots: list[tuple[int, int]], color: rl.Color):
|
||||||
spacing = min(rect.height / GRID_ROWS, rect.width / GRID_COLS)
|
spacing = min(rect.height / GRID_ROWS, rect.width / GRID_COLS)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from openpilot.selfdrive.ui.widgets.exp_mode_button import ExperimentalModeButto
|
|||||||
from openpilot.selfdrive.ui.widgets.prime import PrimeWidget
|
from openpilot.selfdrive.ui.widgets.prime import PrimeWidget
|
||||||
from openpilot.selfdrive.ui.widgets.setup import SetupWidget
|
from openpilot.selfdrive.ui.widgets.setup import SetupWidget
|
||||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, TextAlignment
|
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||||
from openpilot.system.ui.lib.multilang import tr, trn
|
from openpilot.system.ui.lib.multilang import tr, trn
|
||||||
from openpilot.system.ui.widgets.label import gui_label
|
from openpilot.system.ui.widgets.label import gui_label
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
@@ -178,7 +178,7 @@ class HomeLayout(Widget):
|
|||||||
|
|
||||||
version_rect = rl.Rectangle(self.header_rect.x + self.header_rect.width - version_text_width, self.header_rect.y,
|
version_rect = rl.Rectangle(self.header_rect.x + self.header_rect.width - version_text_width, self.header_rect.y,
|
||||||
version_text_width, self.header_rect.height)
|
version_text_width, self.header_rect.height)
|
||||||
gui_label(version_rect, self._version_text, 48, rl.WHITE, alignment=TextAlignment.RIGHT)
|
gui_label(version_rect, self._version_text, 48, rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT)
|
||||||
|
|
||||||
def _render_home_content(self):
|
def _render_home_content(self):
|
||||||
self._render_left_column()
|
self._render_left_column()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from enum import IntEnum
|
|||||||
|
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
from openpilot.common.basedir import BASEDIR
|
from openpilot.common.basedir import BASEDIR
|
||||||
from openpilot.system.ui.lib.application import FontWeight, TextAlignment, gui_app
|
from openpilot.system.ui.lib.application import FontWeight, gui_app
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
from openpilot.system.ui.lib.multilang import tr
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||||
@@ -115,9 +115,9 @@ class TermsPage(Widget):
|
|||||||
self._on_accept = on_accept
|
self._on_accept = on_accept
|
||||||
self._on_decline = on_decline
|
self._on_decline = on_decline
|
||||||
|
|
||||||
self._title = Label(tr("Welcome to sunnypilot"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=TextAlignment.LEFT)
|
self._title = Label(tr("Welcome to sunnypilot"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT)
|
||||||
self._desc = Label(tr("You must accept the Terms of Service to use sunnypilot. Read the latest terms at https://sunnypilot.ai/terms before continuing."),
|
self._desc = Label(tr("You must accept the Terms of Service to use sunnypilot. Read the latest terms at https://sunnypilot.ai/terms before continuing."),
|
||||||
font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=TextAlignment.LEFT)
|
font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT)
|
||||||
|
|
||||||
self._decline_btn = Button(tr("Decline"), click_callback=on_decline)
|
self._decline_btn = Button(tr("Decline"), click_callback=on_decline)
|
||||||
self._accept_btn = Button(tr("Agree"), button_style=ButtonStyle.PRIMARY, click_callback=on_accept)
|
self._accept_btn = Button(tr("Agree"), button_style=ButtonStyle.PRIMARY, click_callback=on_accept)
|
||||||
@@ -150,7 +150,7 @@ class DeclinePage(Widget):
|
|||||||
def __init__(self, back_callback=None):
|
def __init__(self, back_callback=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._text = Label(tr("You must accept the Terms of Service in order to use sunnypilot."),
|
self._text = Label(tr("You must accept the Terms of Service in order to use sunnypilot."),
|
||||||
font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=TextAlignment.LEFT)
|
font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT)
|
||||||
self._back_btn = Button(tr("Back"), click_callback=back_callback)
|
self._back_btn = Button(tr("Back"), click_callback=back_callback)
|
||||||
self._uninstall_btn = Button(tr("Decline, uninstall sunnypilot"), button_style=ButtonStyle.DANGER,
|
self._uninstall_btn = Button(tr("Decline, uninstall sunnypilot"), button_style=ButtonStyle.DANGER,
|
||||||
click_callback=self._on_uninstall_clicked)
|
click_callback=self._on_uninstall_clicked)
|
||||||
|
|||||||
@@ -199,9 +199,6 @@ class SoftwareLayout(Widget):
|
|||||||
selection = self._branch_dialog.selection
|
selection = self._branch_dialog.selection
|
||||||
ui_state.params.put("UpdaterTargetBranch", selection, block=True)
|
ui_state.params.put("UpdaterTargetBranch", selection, block=True)
|
||||||
self._branch_btn.action_item.set_value(selection)
|
self._branch_btn.action_item.set_value(selection)
|
||||||
self._download_btn.action_item.set_enabled(False)
|
|
||||||
self._waiting_for_updater = True
|
|
||||||
self._waiting_start_ts = time.monotonic()
|
|
||||||
subprocess.run("pkill -SIGUSR1 -f openpilot.system.updated.updated", shell=True)
|
subprocess.run("pkill -SIGUSR1 -f openpilot.system.updated.updated", shell=True)
|
||||||
self._branch_dialog = None
|
self._branch_dialog = None
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,6 @@ DESCRIPTIONS = {
|
|||||||
"In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " +
|
"In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " +
|
||||||
"your steering wheel distance button."
|
"your steering wheel distance button."
|
||||||
),
|
),
|
||||||
"AccelPersonalityEnabled": tr_noop(
|
|
||||||
"Lets you choose how sunnypilot starts, catches up, and settles at the cruise speed. Emergency braking and stopping are unchanged."
|
|
||||||
),
|
|
||||||
"AccelPersonality": tr_noop(
|
|
||||||
"Eco is gentlest, Normal balances a prompt start with smooth catch-up, and Sport is more responsive."
|
|
||||||
),
|
|
||||||
"IsLdwEnabled": tr_noop(
|
"IsLdwEnabled": tr_noop(
|
||||||
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line " +
|
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line " +
|
||||||
"without a turn signal activated while driving over 31 mph (50 km/h)."
|
"without a turn signal activated while driving over 31 mph (50 km/h)."
|
||||||
@@ -112,24 +106,6 @@ class TogglesLayout(Widget):
|
|||||||
icon="speed_limit.png"
|
icon="speed_limit.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
self._accel_controller_enabled = toggle_item(
|
|
||||||
lambda: tr("Enable Accel Controller"),
|
|
||||||
lambda: tr(DESCRIPTIONS["AccelPersonalityEnabled"]),
|
|
||||||
self._params.get_bool("AccelPersonalityEnabled"),
|
|
||||||
callback=self._set_accel_controller_enabled,
|
|
||||||
icon="speed_limit.png",
|
|
||||||
)
|
|
||||||
|
|
||||||
self._accel_personality_setting = multiple_button_item(
|
|
||||||
lambda: tr("Acceleration Profile"),
|
|
||||||
lambda: tr(DESCRIPTIONS["AccelPersonality"]),
|
|
||||||
buttons=[lambda: tr("Eco"), lambda: tr("Normal"), lambda: tr("Sport")],
|
|
||||||
button_width=300,
|
|
||||||
callback=self._set_accel_personality,
|
|
||||||
selected_index=self._params.get("AccelPersonality", return_default=True),
|
|
||||||
icon="speed_limit.png"
|
|
||||||
)
|
|
||||||
|
|
||||||
self._toggles = {}
|
self._toggles = {}
|
||||||
self._locked_toggles = set()
|
self._locked_toggles = set()
|
||||||
for param, (title, desc, icon, needs_restart) in self._toggle_defs.items():
|
for param, (title, desc, icon, needs_restart) in self._toggle_defs.items():
|
||||||
@@ -159,11 +135,9 @@ class TogglesLayout(Widget):
|
|||||||
|
|
||||||
self._toggles[param] = toggle
|
self._toggles[param] = toggle
|
||||||
|
|
||||||
# insert longitudinal personality and Accel Controller settings after NDOG toggle
|
# insert longitudinal personality after NDOG toggle
|
||||||
if param == "DisengageOnAccelerator":
|
if param == "DisengageOnAccelerator":
|
||||||
self._toggles["LongitudinalPersonality"] = self._long_personality_setting
|
self._toggles["LongitudinalPersonality"] = self._long_personality_setting
|
||||||
self._toggles["AccelPersonalityEnabled"] = self._accel_controller_enabled
|
|
||||||
self._toggles["AccelPersonality"] = self._accel_personality_setting
|
|
||||||
|
|
||||||
self._update_experimental_mode_icon()
|
self._update_experimental_mode_icon()
|
||||||
self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0)
|
self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0)
|
||||||
@@ -184,7 +158,6 @@ class TogglesLayout(Widget):
|
|||||||
|
|
||||||
def _update_toggles(self):
|
def _update_toggles(self):
|
||||||
ui_state.update_params()
|
ui_state.update_params()
|
||||||
accel_controller_enabled = self._params.get_bool("AccelPersonalityEnabled")
|
|
||||||
|
|
||||||
e2e_description = tr(
|
e2e_description = tr(
|
||||||
"sunnypilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " +
|
"sunnypilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " +
|
||||||
@@ -203,15 +176,11 @@ class TogglesLayout(Widget):
|
|||||||
self._toggles["ExperimentalMode"].action_item.set_enabled(True)
|
self._toggles["ExperimentalMode"].action_item.set_enabled(True)
|
||||||
self._toggles["ExperimentalMode"].set_description(e2e_description)
|
self._toggles["ExperimentalMode"].set_description(e2e_description)
|
||||||
self._long_personality_setting.action_item.set_enabled(True)
|
self._long_personality_setting.action_item.set_enabled(True)
|
||||||
self._accel_controller_enabled.action_item.set_enabled(True)
|
|
||||||
self._accel_personality_setting.action_item.set_enabled(True)
|
|
||||||
else:
|
else:
|
||||||
# no long for now
|
# no long for now
|
||||||
self._toggles["ExperimentalMode"].action_item.set_enabled(False)
|
self._toggles["ExperimentalMode"].action_item.set_enabled(False)
|
||||||
self._toggles["ExperimentalMode"].action_item.set_state(False)
|
self._toggles["ExperimentalMode"].action_item.set_state(False)
|
||||||
self._long_personality_setting.action_item.set_enabled(False)
|
self._long_personality_setting.action_item.set_enabled(False)
|
||||||
self._accel_controller_enabled.action_item.set_enabled(False)
|
|
||||||
self._accel_personality_setting.action_item.set_enabled(False)
|
|
||||||
self._params.remove("ExperimentalMode")
|
self._params.remove("ExperimentalMode")
|
||||||
|
|
||||||
unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.")
|
unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.")
|
||||||
@@ -234,8 +203,6 @@ class TogglesLayout(Widget):
|
|||||||
# refresh toggles from params to mirror external changes
|
# refresh toggles from params to mirror external changes
|
||||||
for param in self._toggle_defs:
|
for param in self._toggle_defs:
|
||||||
self._toggles[param].action_item.set_state(self._params.get_bool(param))
|
self._toggles[param].action_item.set_state(self._params.get_bool(param))
|
||||||
self._accel_controller_enabled.action_item.set_state(accel_controller_enabled)
|
|
||||||
self._accel_personality_setting.action_item.set_selected_button(self._params.get("AccelPersonality", return_default=True))
|
|
||||||
|
|
||||||
# these toggles need restart, block while engaged
|
# these toggles need restart, block while engaged
|
||||||
for toggle_def in self._toggle_defs:
|
for toggle_def in self._toggle_defs:
|
||||||
@@ -280,9 +247,3 @@ class TogglesLayout(Widget):
|
|||||||
|
|
||||||
def _set_longitudinal_personality(self, button_index: int):
|
def _set_longitudinal_personality(self, button_index: int):
|
||||||
self._params.put("LongitudinalPersonality", button_index, block=True)
|
self._params.put("LongitudinalPersonality", button_index, block=True)
|
||||||
|
|
||||||
def _set_accel_personality(self, button_index: int):
|
|
||||||
self._params.put("AccelPersonality", button_index, block=True)
|
|
||||||
|
|
||||||
def _set_accel_controller_enabled(self, state: bool):
|
|
||||||
self._params.put_bool("AccelPersonalityEnabled", state, block=True)
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import math
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from openpilot.cereal import log
|
from openpilot.cereal import log
|
||||||
@@ -9,8 +8,8 @@ from openpilot.system.ui.widgets import Widget
|
|||||||
from openpilot.system.ui.widgets.layouts import HBoxLayout
|
from openpilot.system.ui.widgets.layouts import HBoxLayout
|
||||||
from openpilot.system.ui.widgets.icon_widget import IconWidget
|
from openpilot.system.ui.widgets.icon_widget import IconWidget
|
||||||
from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label
|
from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, TextAlignment, TextAlignmentVertical
|
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state, ChestnutState
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.common.version import RELEASE_BRANCHES
|
from openpilot.common.version import RELEASE_BRANCHES
|
||||||
|
|
||||||
HEAD_BUTTON_FONT_SIZE = 40
|
HEAD_BUTTON_FONT_SIZE = 40
|
||||||
@@ -70,8 +69,8 @@ class AlertsPill(Widget):
|
|||||||
|
|
||||||
count_rect = rl.Rectangle(self.rect.x + self.COUNT_OFFSET, self.rect.y, pill_w - self.COUNT_OFFSET, pill_h)
|
count_rect = rl.Rectangle(self.rect.x + self.COUNT_OFFSET, self.rect.y, pill_w - self.COUNT_OFFSET, pill_h)
|
||||||
gui_label(count_rect, str(alert_count), font_size=36,
|
gui_label(count_rect, str(alert_count), font_size=36,
|
||||||
alignment=TextAlignment.CENTER,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
alignment_vertical=TextAlignmentVertical.MIDDLE)
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||||
|
|
||||||
|
|
||||||
class NetworkIcon(Widget):
|
class NetworkIcon(Widget):
|
||||||
@@ -140,10 +139,8 @@ class MiciHomeLayout(Widget):
|
|||||||
self._version_text = self._get_version_text()
|
self._version_text = self._get_version_text()
|
||||||
|
|
||||||
self._experimental_icon = IconWidget("icons_mici/experimental_mode.png", (48, 48))
|
self._experimental_icon = IconWidget("icons_mici/experimental_mode.png", (48, 48))
|
||||||
self._usb_icon = IconWidget("icons_mici/usb.png", (62, 40))
|
self._egpu_icon = IconWidget("icons_mici/egpu_green.png", (50, 37))
|
||||||
self._chestnut_icon = IconWidget("icons_mici/chestnut_green.png", (68, 40))
|
self._egpu_icon_gray = IconWidget("icons_mici/egpu_gray.png", (50, 37))
|
||||||
self._chestnut_loading_icon = IconWidget("icons_mici/chestnut.png", (68, 40))
|
|
||||||
self._chestnut_failed_icon = IconWidget("icons_mici/chestnut_orange.png", (68, 40))
|
|
||||||
self._mic_icon = IconWidget("icons_mici/microphone.png", (32, 46))
|
self._mic_icon = IconWidget("icons_mici/microphone.png", (32, 46))
|
||||||
self._body_icon = IconWidget("icons_mici/body.png", (54, 37))
|
self._body_icon = IconWidget("icons_mici/body.png", (54, 37))
|
||||||
|
|
||||||
@@ -153,15 +150,13 @@ class MiciHomeLayout(Widget):
|
|||||||
IconWidget("icons_mici/settings.png", (48, 48), opacity=0.9),
|
IconWidget("icons_mici/settings.png", (48, 48), opacity=0.9),
|
||||||
NetworkIcon(),
|
NetworkIcon(),
|
||||||
self._experimental_icon,
|
self._experimental_icon,
|
||||||
self._usb_icon,
|
self._egpu_icon,
|
||||||
self._chestnut_icon,
|
self._egpu_icon_gray,
|
||||||
self._chestnut_loading_icon,
|
|
||||||
self._chestnut_failed_icon,
|
|
||||||
self._body_icon,
|
self._body_icon,
|
||||||
self._mic_icon,
|
self._mic_icon,
|
||||||
], spacing=18)
|
], spacing=18)
|
||||||
|
|
||||||
self._openpilot_label = UnifiedLabel("openpilot", font_size=96, font_weight=FontWeight.DISPLAY, max_width=480, wrap_text=False)
|
self._openpilot_label = UnifiedLabel("sunnypilot", font_size=96, font_weight=FontWeight.DISPLAY, max_width=480, wrap_text=False)
|
||||||
self._version_label = UnifiedLabel("", font_size=36, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False)
|
self._version_label = UnifiedLabel("", font_size=36, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False)
|
||||||
self._large_version_label = UnifiedLabel("", font_size=64, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False)
|
self._large_version_label = UnifiedLabel("", font_size=64, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False)
|
||||||
self._date_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False)
|
self._date_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False)
|
||||||
@@ -252,20 +247,12 @@ class MiciHomeLayout(Widget):
|
|||||||
self._version_commit_label.render()
|
self._version_commit_label.render()
|
||||||
|
|
||||||
# ***** Center-aligned bottom section icons *****
|
# ***** Center-aligned bottom section icons *****
|
||||||
usb_connected = ui_state.usb_connected
|
|
||||||
usb_unknown = ui_state.usb_unknown
|
|
||||||
chestnut_state = ui_state.chestnut_state
|
|
||||||
self._experimental_icon.set_visible(ui_state.experimental_mode)
|
self._experimental_icon.set_visible(ui_state.experimental_mode)
|
||||||
if gui_app.sunnypilot_ui():
|
if gui_app.sunnypilot_ui():
|
||||||
self._set_chestnut_visibility()
|
self._set_egpu_visibility()
|
||||||
else:
|
else:
|
||||||
self._usb_icon.set_visible(usb_connected and usb_unknown)
|
self._egpu_icon.set_visible(ui_state.sm["deviceState"].chestnutPresent and ui_state.usbgpu_compiled)
|
||||||
self._chestnut_icon.set_visible(not usb_unknown and chestnut_state not in
|
self._egpu_icon_gray.set_visible(ui_state.sm["deviceState"].chestnutPresent and not ui_state.usbgpu_compiled)
|
||||||
(ChestnutState.LOADING, ChestnutState.UNCOMPILED, ChestnutState.FAILED) and
|
|
||||||
(usb_connected or chestnut_state in (ChestnutState.READY, ChestnutState.ACTIVE)))
|
|
||||||
self._chestnut_loading_icon.set_visible(not usb_unknown and chestnut_state == ChestnutState.LOADING)
|
|
||||||
self._chestnut_loading_icon.set_opacity(0.35 + 0.65 * (0.5 - 0.5 * math.cos(rl.get_time() * 6.0)))
|
|
||||||
self._chestnut_failed_icon.set_visible(not usb_unknown and chestnut_state in (ChestnutState.UNCOMPILED, ChestnutState.FAILED))
|
|
||||||
self._mic_icon.set_visible(ui_state.recording_audio)
|
self._mic_icon.set_visible(ui_state.recording_audio)
|
||||||
self._body_icon.set_visible(bool(ui_state.is_body))
|
self._body_icon.set_visible(bool(ui_state.is_body))
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from openpilot.system.ui.lib.application import gui_app
|
|||||||
if gui_app.sunnypilot_ui():
|
if gui_app.sunnypilot_ui():
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout
|
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.home import MiciHomeLayoutSP as MiciHomeLayout
|
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.home import MiciHomeLayoutSP as MiciHomeLayout
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onroad import OnroadViewContainerSP as AugmentedRoadView
|
|
||||||
|
|
||||||
ONROAD_DELAY = 2.5 # seconds
|
ONROAD_DELAY = 2.5 # seconds
|
||||||
|
|
||||||
@@ -73,9 +72,6 @@ class MiciMainLayout(Scroller):
|
|||||||
# For scroll_to
|
# For scroll_to
|
||||||
return self._body_onroad_layout if ui_state.is_body else self._car_onroad_layout
|
return self._body_onroad_layout if ui_state.is_body else self._car_onroad_layout
|
||||||
|
|
||||||
def _should_auto_scroll_to_onroad(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _setup_callbacks(self):
|
def _setup_callbacks(self):
|
||||||
self._home_layout.set_callbacks(
|
self._home_layout.set_callbacks(
|
||||||
on_settings=lambda: gui_app.push_widget(self._settings_layout),
|
on_settings=lambda: gui_app.push_widget(self._settings_layout),
|
||||||
@@ -126,15 +122,13 @@ class MiciMainLayout(Scroller):
|
|||||||
|
|
||||||
# FIXME: these two pops can interrupt user interacting in the settings
|
# FIXME: these two pops can interrupt user interacting in the settings
|
||||||
if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY:
|
if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY:
|
||||||
if not gui_app.sunnypilot_ui() or self._should_auto_scroll_to_onroad():
|
gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout))
|
||||||
gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout))
|
|
||||||
self._onroad_time_delay = None
|
self._onroad_time_delay = None
|
||||||
|
|
||||||
# When car leaves standstill, pop nav stack and scroll to onroad
|
# When car leaves standstill, pop nav stack and scroll to onroad
|
||||||
CS = ui_state.sm["carState"]
|
CS = ui_state.sm["carState"]
|
||||||
if not CS.standstill and self._prev_standstill:
|
if not CS.standstill and self._prev_standstill:
|
||||||
if not gui_app.sunnypilot_ui() or self._should_auto_scroll_to_onroad():
|
gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout))
|
||||||
gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout))
|
|
||||||
self._prev_standstill = CS.standstill
|
self._prev_standstill = CS.standstill
|
||||||
|
|
||||||
def _on_interactive_timeout(self):
|
def _on_interactive_timeout(self):
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from openpilot.common.hardware import HARDWARE
|
|||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||||
from openpilot.system.ui.widgets.scroller import Scroller
|
from openpilot.system.ui.widgets.scroller import Scroller
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, TextAlignment, TextAlignmentVertical
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
from openpilot.system.ui.lib.multilang import tr
|
||||||
|
|
||||||
REFRESH_INTERVAL = 5.0 # seconds
|
REFRESH_INTERVAL = 5.0 # seconds
|
||||||
@@ -62,12 +62,12 @@ class AlertItem(Widget):
|
|||||||
self._icon_green = gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", self.ICON_SIZE, self.ICON_SIZE)
|
self._icon_green = gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", self.ICON_SIZE, self.ICON_SIZE)
|
||||||
|
|
||||||
self._title_label = UnifiedLabel(text="", font_size=32, font_weight=FontWeight.SEMI_BOLD, text_color=self.TEXT_COLOR,
|
self._title_label = UnifiedLabel(text="", font_size=32, font_weight=FontWeight.SEMI_BOLD, text_color=self.TEXT_COLOR,
|
||||||
alignment=TextAlignment.LEFT,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
|
||||||
alignment_vertical=TextAlignmentVertical.TOP, line_height=0.95)
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, line_height=0.95)
|
||||||
|
|
||||||
self._body_label = UnifiedLabel(text="", font_size=28, font_weight=FontWeight.ROMAN, text_color=self.TEXT_COLOR,
|
self._body_label = UnifiedLabel(text="", font_size=28, font_weight=FontWeight.ROMAN, text_color=self.TEXT_COLOR,
|
||||||
alignment=TextAlignment.LEFT,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
|
||||||
alignment_vertical=TextAlignmentVertical.BOTTOM, line_height=0.95)
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, line_height=0.95)
|
||||||
|
|
||||||
self._title_text = ""
|
self._title_text = ""
|
||||||
self._body_text = ""
|
self._body_text = ""
|
||||||
@@ -200,8 +200,8 @@ class MiciOffroadAlerts(Scroller):
|
|||||||
|
|
||||||
# Create empty state label
|
# Create empty state label
|
||||||
self._empty_label = UnifiedLabel(tr("no alerts"), 65, FontWeight.DISPLAY, rl.WHITE,
|
self._empty_label = UnifiedLabel(tr("no alerts"), 65, FontWeight.DISPLAY, rl.WHITE,
|
||||||
alignment=TextAlignment.CENTER,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
alignment_vertical=TextAlignmentVertical.MIDDLE)
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||||
|
|
||||||
# Build initial alert list
|
# Build initial alert list
|
||||||
self._build_alerts()
|
self._build_alerts()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import pyray as rl
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from openpilot.common.filter_simple import FirstOrderFilter
|
from openpilot.common.filter_simple import FirstOrderFilter
|
||||||
from openpilot.common.qrcode import make_texture
|
from openpilot.common.qrcode import make_texture
|
||||||
from openpilot.system.ui.lib.application import FontWeight, gui_app, TextAlignment
|
from openpilot.system.ui.lib.application import FontWeight, gui_app
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
from openpilot.system.ui.widgets.button import SmallCircleIconButton
|
from openpilot.system.ui.widgets.button import SmallCircleIconButton
|
||||||
from openpilot.system.ui.widgets.scroller import NavScroller, Scroller
|
from openpilot.system.ui.widgets.scroller import NavScroller, Scroller
|
||||||
@@ -35,7 +35,7 @@ class DriverCameraSetupDialog(BaseCabinCameraDialog):
|
|||||||
|
|
||||||
if not self._camera_view.frame:
|
if not self._camera_view.frame:
|
||||||
gui_label(rect, tr("camera starting"), font_size=64, font_weight=FontWeight.BOLD,
|
gui_label(rect, tr("camera starting"), font_size=64, font_weight=FontWeight.BOLD,
|
||||||
alignment=TextAlignment.CENTER)
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||||
rl.end_scissor_mode()
|
rl.end_scissor_mode()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -74,10 +74,6 @@ class SoftwareInfoLayoutMici(Widget):
|
|||||||
|
|
||||||
|
|
||||||
class CheckUpdateButton(BigButton):
|
class CheckUpdateButton(BigButton):
|
||||||
UPDATER_PROC = "openpilot.system.updated.updated"
|
|
||||||
CHECK_FOR_UPDATE = "SIGUSR1"
|
|
||||||
DOWNLOAD_UPDATE = "SIGHUP"
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 75)
|
self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 75)
|
||||||
self._txt_up_to_date_icon = gui_app.texture("icons_mici/settings/device/up_to_date.png", 64, 64)
|
self._txt_up_to_date_icon = gui_app.texture("icons_mici/settings/device/up_to_date.png", 64, 64)
|
||||||
@@ -101,20 +97,15 @@ class CheckUpdateButton(BigButton):
|
|||||||
gui_app.push_widget(dlg)
|
gui_app.push_widget(dlg)
|
||||||
return
|
return
|
||||||
|
|
||||||
self._signal_updater(self.DOWNLOAD_UPDATE if self.get_value() == "download update" else self.CHECK_FOR_UPDATE)
|
|
||||||
|
|
||||||
def check_for_update(self):
|
|
||||||
self._signal_updater(self.CHECK_FOR_UPDATE)
|
|
||||||
|
|
||||||
def _signal_updater(self, sig: str):
|
|
||||||
self.set_enabled(False)
|
self.set_enabled(False)
|
||||||
self._state = UpdaterState.WAITING_FOR_UPDATER
|
self._state = UpdaterState.WAITING_FOR_UPDATER
|
||||||
self._hide_value_t = None
|
|
||||||
self.set_value("")
|
|
||||||
self.set_icon(self._txt_update_icon)
|
self.set_icon(self._txt_update_icon)
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
subprocess.run(f"pkill -{sig} -f {self.UPDATER_PROC}", shell=True)
|
if self.get_value() == "download update":
|
||||||
|
subprocess.run("pkill -SIGHUP -f openpilot.system.updated.updated", shell=True)
|
||||||
|
else:
|
||||||
|
subprocess.run("pkill -SIGUSR1 -f openpilot.system.updated.updated", shell=True)
|
||||||
|
|
||||||
threading.Thread(target=run, daemon=True).start()
|
threading.Thread(target=run, daemon=True).start()
|
||||||
|
|
||||||
@@ -193,7 +184,7 @@ class CheckUpdateButton(BigButton):
|
|||||||
|
|
||||||
class InstallUpdateButton(BigButton):
|
class InstallUpdateButton(BigButton):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("install now", "", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70))
|
super().__init__("install update", "", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70))
|
||||||
self.set_visible(lambda: ui_state.is_offroad() and ui_state.params.get_bool("UpdateAvailable"))
|
self.set_visible(lambda: ui_state.is_offroad() and ui_state.params.get_bool("UpdateAvailable"))
|
||||||
|
|
||||||
def _update_state(self):
|
def _update_state(self):
|
||||||
@@ -241,9 +232,8 @@ class BranchSelectPage(NavScroller):
|
|||||||
|
|
||||||
|
|
||||||
class TargetBranchButton(BigButton):
|
class TargetBranchButton(BigButton):
|
||||||
def __init__(self, check_update_btn: CheckUpdateButton):
|
def __init__(self):
|
||||||
super().__init__("target branch", ui_state.params.get("UpdaterTargetBranch") or "")
|
super().__init__("target branch", ui_state.params.get("UpdaterTargetBranch") or "")
|
||||||
self._check_update_btn = check_update_btn
|
|
||||||
self.set_click_callback(self._on_click)
|
self.set_click_callback(self._on_click)
|
||||||
self.set_visible(not ui_state.params.get_bool("IsTestedBranch"))
|
self.set_visible(not ui_state.params.get_bool("IsTestedBranch"))
|
||||||
self.set_enabled(lambda: ui_state.is_offroad())
|
self.set_enabled(lambda: ui_state.is_offroad())
|
||||||
@@ -256,15 +246,12 @@ class TargetBranchButton(BigButton):
|
|||||||
self.set_value(target)
|
self.set_value(target)
|
||||||
|
|
||||||
def _on_click(self):
|
def _on_click(self):
|
||||||
if not ui_state.params.get("UpdaterAvailableBranches"):
|
|
||||||
gui_app.push_widget(BigDialog("", tr("Failed to get available branches. Ensure you're connected to the internet and try again.")))
|
|
||||||
return
|
|
||||||
gui_app.push_widget(BranchSelectPage(self._on_select))
|
gui_app.push_widget(BranchSelectPage(self._on_select))
|
||||||
|
|
||||||
def _on_select(self, branch: str):
|
def _on_select(self, branch: str):
|
||||||
ui_state.params.put("UpdaterTargetBranch", branch, block=True)
|
ui_state.params.put("UpdaterTargetBranch", branch, block=True)
|
||||||
self.set_value(branch)
|
self.set_value(branch)
|
||||||
self._check_update_btn.check_for_update()
|
subprocess.run("pkill -SIGUSR1 -f openpilot.system.updated.updated", shell=True)
|
||||||
|
|
||||||
|
|
||||||
class SoftwareLayoutMici(NavScroller):
|
class SoftwareLayoutMici(NavScroller):
|
||||||
@@ -278,11 +265,10 @@ class SoftwareLayoutMici(NavScroller):
|
|||||||
gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64),
|
gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64),
|
||||||
uninstall_openpilot_callback, exit_on_confirm=False)
|
uninstall_openpilot_callback, exit_on_confirm=False)
|
||||||
|
|
||||||
check_update_btn = CheckUpdateButton()
|
|
||||||
self._scroller.add_widgets([
|
self._scroller.add_widgets([
|
||||||
SoftwareInfoLayoutMici(),
|
SoftwareInfoLayoutMici(),
|
||||||
check_update_btn,
|
CheckUpdateButton(),
|
||||||
InstallUpdateButton(),
|
InstallUpdateButton(),
|
||||||
TargetBranchButton(check_update_btn),
|
TargetBranchButton(),
|
||||||
uninstall_openpilot_btn,
|
uninstall_openpilot_btn,
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ class TogglesLayoutMici(NavScroller):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"])
|
self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"])
|
||||||
self._accel_controller_enabled = BigParamControl("enable accel controller", "AccelPersonalityEnabled")
|
|
||||||
self._accel_personality_toggle = BigMultiParamToggle("acceleration profile", "AccelPersonality", ["eco", "normal", "sport"])
|
|
||||||
self._experimental_btn = BigToggle("experimental mode", initial_state=ui_state.params.get_bool("ExperimentalMode"),
|
self._experimental_btn = BigToggle("experimental mode", initial_state=ui_state.params.get_bool("ExperimentalMode"),
|
||||||
toggle_callback=self._on_experimental_mode)
|
toggle_callback=self._on_experimental_mode)
|
||||||
is_metric_toggle = BigParamControl("use metric units", "IsMetric")
|
is_metric_toggle = BigParamControl("use metric units", "IsMetric")
|
||||||
@@ -55,8 +53,6 @@ class TogglesLayoutMici(NavScroller):
|
|||||||
|
|
||||||
self._scroller.add_widgets([
|
self._scroller.add_widgets([
|
||||||
self._personality_toggle,
|
self._personality_toggle,
|
||||||
self._accel_controller_enabled,
|
|
||||||
self._accel_personality_toggle,
|
|
||||||
self._experimental_btn,
|
self._experimental_btn,
|
||||||
is_metric_toggle,
|
is_metric_toggle,
|
||||||
ldw_toggle,
|
ldw_toggle,
|
||||||
@@ -69,7 +65,6 @@ class TogglesLayoutMici(NavScroller):
|
|||||||
# Toggle lists
|
# Toggle lists
|
||||||
self._refresh_toggles = (
|
self._refresh_toggles = (
|
||||||
("ExperimentalMode", self._experimental_btn),
|
("ExperimentalMode", self._experimental_btn),
|
||||||
("AccelPersonalityEnabled", self._accel_controller_enabled),
|
|
||||||
("IsMetric", is_metric_toggle),
|
("IsMetric", is_metric_toggle),
|
||||||
("IsLdwEnabled", ldw_toggle),
|
("IsLdwEnabled", ldw_toggle),
|
||||||
("AlwaysOnDM", always_on_dm_toggle),
|
("AlwaysOnDM", always_on_dm_toggle),
|
||||||
@@ -109,23 +104,17 @@ class TogglesLayoutMici(NavScroller):
|
|||||||
if ui_state.has_longitudinal_control:
|
if ui_state.has_longitudinal_control:
|
||||||
self._experimental_btn.set_visible(True)
|
self._experimental_btn.set_visible(True)
|
||||||
self._personality_toggle.set_visible(True)
|
self._personality_toggle.set_visible(True)
|
||||||
self._accel_controller_enabled.set_visible(True)
|
|
||||||
self._accel_personality_toggle.set_visible(True)
|
|
||||||
else:
|
else:
|
||||||
# no long for now
|
# no long for now
|
||||||
self._experimental_btn.set_visible(False)
|
self._experimental_btn.set_visible(False)
|
||||||
self._experimental_btn.set_checked(False)
|
self._experimental_btn.set_checked(False)
|
||||||
self._personality_toggle.set_visible(False)
|
self._personality_toggle.set_visible(False)
|
||||||
self._accel_controller_enabled.set_visible(False)
|
|
||||||
self._accel_personality_toggle.set_visible(False)
|
|
||||||
ui_state.params.remove("ExperimentalMode")
|
ui_state.params.remove("ExperimentalMode")
|
||||||
|
|
||||||
# Refresh toggles from params to mirror external changes
|
# Refresh toggles from params to mirror external changes
|
||||||
for key, item in self._refresh_toggles:
|
for key, item in self._refresh_toggles:
|
||||||
item.set_checked(ui_state.params.get_bool(key))
|
item.set_checked(ui_state.params.get_bool(key))
|
||||||
|
|
||||||
self._accel_personality_toggle.refresh()
|
|
||||||
|
|
||||||
def _on_experimental_mode(self, state: bool):
|
def _on_experimental_mode(self, state: bool):
|
||||||
if state and not ui_state.params.get_bool("ExperimentalModeConfirmed"):
|
if state and not ui_state.params.get_bool("ExperimentalModeConfirmed"):
|
||||||
# Don't show enabled state until confirm
|
# Don't show enabled state until confirm
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from opendbc.car.structs import car
|
|||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter
|
from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter
|
||||||
from openpilot.common.hardware import COMMA_HARDWARE
|
from openpilot.common.hardware import COMMA_HARDWARE
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, TextAlignment
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ class AlertRenderer(Widget, SpeedLimitAlertRenderer):
|
|||||||
self._alert_text1_label.set_text(alert_text1)
|
self._alert_text1_label.set_text(alert_text1)
|
||||||
self._alert_text1_label.set_text_color(color)
|
self._alert_text1_label.set_text_color(color)
|
||||||
self._alert_text1_label.set_font_size(font_size)
|
self._alert_text1_label.set_font_size(font_size)
|
||||||
self._alert_text1_label.set_alignment(TextAlignment.LEFT if icon_side != 'left' else TextAlignment.RIGHT)
|
self._alert_text1_label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_LEFT if icon_side != 'left' else rl.GuiTextAlignment.TEXT_ALIGN_RIGHT)
|
||||||
self._alert_text1_label.render(text_rect1)
|
self._alert_text1_label.render(text_rect1)
|
||||||
|
|
||||||
alert_text2 = alert.text2.lower()
|
alert_text2 = alert.text2.lower()
|
||||||
@@ -365,5 +365,5 @@ class AlertRenderer(Widget, SpeedLimitAlertRenderer):
|
|||||||
self._alert_text2_label.set_text(alert_text2)
|
self._alert_text2_label.set_text(alert_text2)
|
||||||
self._alert_text2_label.set_text_color(color)
|
self._alert_text2_label.set_text_color(color)
|
||||||
self._alert_text2_label.set_font_size(small_font_size)
|
self._alert_text2_label.set_font_size(small_font_size)
|
||||||
self._alert_text2_label.set_alignment(TextAlignment.LEFT if icon_side != 'left' else TextAlignment.RIGHT)
|
self._alert_text2_label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_LEFT if icon_side != 'left' else rl.GuiTextAlignment.TEXT_ALIGN_RIGHT)
|
||||||
self._alert_text2_label.render(text_rect2)
|
self._alert_text2_label.render(text_rect2)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer
|
|||||||
from openpilot.selfdrive.ui.mici.onroad.model_renderer import ModelRenderer
|
from openpilot.selfdrive.ui.mici.onroad.model_renderer import ModelRenderer
|
||||||
from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall
|
from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall
|
||||||
from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView
|
from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView
|
||||||
from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent, TextAlignment, TextAlignmentVertical
|
from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent
|
||||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
from openpilot.common.filter_simple import BounceFilter
|
from openpilot.common.filter_simple import BounceFilter
|
||||||
@@ -158,8 +158,8 @@ class AugmentedRoadView(CameraView):
|
|||||||
self._confidence_ball = ConfidenceBall()
|
self._confidence_ball = ConfidenceBall()
|
||||||
self._offroad_label = UnifiedLabel("start the car to\nuse sunnypilot", 54, FontWeight.DISPLAY,
|
self._offroad_label = UnifiedLabel("start the car to\nuse sunnypilot", 54, FontWeight.DISPLAY,
|
||||||
text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
||||||
alignment=TextAlignment.CENTER,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
alignment_vertical=TextAlignmentVertical.MIDDLE)
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||||
|
|
||||||
self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png")
|
self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png")
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from openpilot.cereal.visionipc import VisionStreamType
|
|||||||
from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView
|
from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView
|
||||||
from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer
|
from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state, device
|
from openpilot.selfdrive.ui.ui_state import ui_state, device
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, TextAlignment, TextAlignmentVertical
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
from openpilot.system.ui.lib.multilang import tr
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
from openpilot.system.ui.widgets.nav_widget import NavWidget
|
from openpilot.system.ui.widgets.nav_widget import NavWidget
|
||||||
@@ -76,7 +76,7 @@ class BaseCabinCameraDialog(Widget):
|
|||||||
|
|
||||||
if not self._camera_view.frame:
|
if not self._camera_view.frame:
|
||||||
gui_label(rect, tr("camera starting"), font_size=54, font_weight=FontWeight.BOLD,
|
gui_label(rect, tr("camera starting"), font_size=54, font_weight=FontWeight.BOLD,
|
||||||
alignment=TextAlignment.CENTER)
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||||
rl.end_scissor_mode()
|
rl.end_scissor_mode()
|
||||||
self._publish_alert_sound(None)
|
self._publish_alert_sound(None)
|
||||||
return
|
return
|
||||||
@@ -124,12 +124,12 @@ class BaseCabinCameraDialog(Widget):
|
|||||||
awareness_pct = dm_state.visionPolicyState.awarenessPercent if is_vision else dm_state.wheeltouchPolicyState.awarenessPercent
|
awareness_pct = dm_state.visionPolicyState.awarenessPercent if is_vision else dm_state.wheeltouchPolicyState.awarenessPercent
|
||||||
gui_label(rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height),
|
gui_label(rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height),
|
||||||
f"Awareness: {awareness_pct:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM,
|
f"Awareness: {awareness_pct:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM,
|
||||||
alignment=TextAlignment.RIGHT,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
|
||||||
alignment_vertical=TextAlignmentVertical.TOP,
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
|
||||||
color=rl.Color(0, 0, 0, 180))
|
color=rl.Color(0, 0, 0, 180))
|
||||||
gui_label(rect, f"Awareness: {awareness_pct:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM,
|
gui_label(rect, f"Awareness: {awareness_pct:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM,
|
||||||
alignment=TextAlignment.RIGHT,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
|
||||||
alignment_vertical=TextAlignmentVertical.TOP,
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
|
||||||
color=rl.Color(255, 255, 255, int(255 * 0.9)))
|
color=rl.Color(255, 255, 255, int(255 * 0.9)))
|
||||||
|
|
||||||
if dm_state.alertLevel == log.DriverMonitoringState.AlertLevel.none:
|
if dm_state.alertLevel == log.DriverMonitoringState.AlertLevel.none:
|
||||||
@@ -137,16 +137,16 @@ class BaseCabinCameraDialog(Widget):
|
|||||||
|
|
||||||
# Show alert level
|
# Show alert level
|
||||||
alert_level_str = f"{'Pay Attention' if is_vision else 'Touch Wheel'} - level {dm_state.alertLevel}"
|
alert_level_str = f"{'Pay Attention' if is_vision else 'Touch Wheel'} - level {dm_state.alertLevel}"
|
||||||
alignment = TextAlignment.RIGHT if self.driver_state_renderer.is_rhd else TextAlignment.LEFT
|
alignment = rl.GuiTextAlignment.TEXT_ALIGN_RIGHT if self.driver_state_renderer.is_rhd else rl.GuiTextAlignment.TEXT_ALIGN_LEFT
|
||||||
|
|
||||||
shadow_rect = rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height)
|
shadow_rect = rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height)
|
||||||
gui_label(shadow_rect, alert_level_str, font_size=40, font_weight=FontWeight.BOLD,
|
gui_label(shadow_rect, alert_level_str, font_size=40, font_weight=FontWeight.BOLD,
|
||||||
alignment=alignment,
|
alignment=alignment,
|
||||||
alignment_vertical=TextAlignmentVertical.BOTTOM,
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM,
|
||||||
color=rl.Color(0, 0, 0, 180))
|
color=rl.Color(0, 0, 0, 180))
|
||||||
gui_label(rect, alert_level_str, font_size=40, font_weight=FontWeight.BOLD,
|
gui_label(rect, alert_level_str, font_size=40, font_weight=FontWeight.BOLD,
|
||||||
alignment=alignment,
|
alignment=alignment,
|
||||||
alignment_vertical=TextAlignmentVertical.BOTTOM,
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM,
|
||||||
color=rl.Color(255, 255, 255, int(255 * 0.9)))
|
color=rl.Color(255, 255, 255, int(255 * 0.9)))
|
||||||
|
|
||||||
def _load_eye_textures(self):
|
def _load_eye_textures(self):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import pyray as rl
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from openpilot.common.constants import CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar
|
from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus, ChestnutState
|
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
from openpilot.system.ui.lib.multilang import tr
|
||||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||||
@@ -107,7 +107,8 @@ class HudRenderer(Widget):
|
|||||||
self.speed: float = 0.0
|
self.speed: float = 0.0
|
||||||
self.v_ego_cluster_seen: bool = False
|
self.v_ego_cluster_seen: bool = False
|
||||||
self._engaged: bool = False
|
self._engaged: bool = False
|
||||||
self._chestnut_fade_time: float = 0
|
self._small_model_engaged: bool = False
|
||||||
|
self._egpu_fade_time: float = 0
|
||||||
|
|
||||||
self._can_draw_top_icons = True
|
self._can_draw_top_icons = True
|
||||||
self._show_wheel_critical = False
|
self._show_wheel_critical = False
|
||||||
@@ -123,15 +124,17 @@ class HudRenderer(Widget):
|
|||||||
self._txt_wheel: rl.Texture = gui_app.texture('icons_mici/wheel.png', 50, 50)
|
self._txt_wheel: rl.Texture = gui_app.texture('icons_mici/wheel.png', 50, 50)
|
||||||
self._txt_wheel_critical: rl.Texture = gui_app.texture('icons_mici/wheel_critical.png', 50, 50)
|
self._txt_wheel_critical: rl.Texture = gui_app.texture('icons_mici/wheel_critical.png', 50, 50)
|
||||||
self._txt_exclamation_point: rl.Texture = gui_app.texture('icons_mici/exclamation_point.png', 9, 44)
|
self._txt_exclamation_point: rl.Texture = gui_app.texture('icons_mici/exclamation_point.png', 9, 44)
|
||||||
self._txt_chestnut: rl.Texture = gui_app.texture('icons_mici/chestnut.png', 60, 44)
|
self._txt_egpu: rl.Texture = gui_app.texture('icons_mici/egpu.png', 60, 44)
|
||||||
self._txt_chestnut_green: rl.Texture = gui_app.texture('icons_mici/chestnut_green.png', 60, 44)
|
self._txt_egpu_green: rl.Texture = gui_app.texture('icons_mici/egpu_green.png', 60, 44)
|
||||||
self._txt_chestnut_orange: rl.Texture = gui_app.texture('icons_mici/chestnut_orange.png', 75, 44)
|
self._txt_egpu_orange: rl.Texture = gui_app.texture('icons_mici/egpu_orange.png', 60, 44)
|
||||||
self._chestnut_icon: rl.Texture | None = None
|
self._txt_egpu_crossed: rl.Texture = gui_app.texture('icons_mici/egpu_crossed.png', 60, 52)
|
||||||
|
self._egpu_icon: rl.Texture | None = None
|
||||||
|
|
||||||
self._wheel_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps)
|
self._wheel_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps)
|
||||||
self._wheel_y_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps)
|
self._wheel_y_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps)
|
||||||
|
|
||||||
self._set_speed_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
|
self._set_speed_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
|
||||||
self._chestnut_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
|
self._egpu_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
|
||||||
|
|
||||||
def set_wheel_critical_icon(self, critical: bool):
|
def set_wheel_critical_icon(self, critical: bool):
|
||||||
"""Set the wheel icon to critical or normal state."""
|
"""Set the wheel icon to critical or normal state."""
|
||||||
@@ -162,10 +165,13 @@ class HudRenderer(Widget):
|
|||||||
controls_state.deprecated.vCruise if v_cruise_cluster == 0.0 else v_cruise_cluster
|
controls_state.deprecated.vCruise if v_cruise_cluster == 0.0 else v_cruise_cluster
|
||||||
)
|
)
|
||||||
engaged = sm['selfdriveState'].enabled
|
engaged = sm['selfdriveState'].enabled
|
||||||
|
if (engaged and not self._engaged and not ui_state.usbgpu_loading and ui_state.usbgpu_active is not True and
|
||||||
|
ui_state.sm.recv_frame['modelV2'] > ui_state.started_frame):
|
||||||
|
self._small_model_engaged = True
|
||||||
|
if engaged != self._engaged:
|
||||||
|
self._egpu_fade_time = rl.get_time() if engaged else 0
|
||||||
if (set_speed != self.set_speed and engaged) or (engaged and not self._engaged):
|
if (set_speed != self.set_speed and engaged) or (engaged and not self._engaged):
|
||||||
self._set_speed_changed_time = rl.get_time()
|
self._set_speed_changed_time = rl.get_time()
|
||||||
if engaged != self._engaged:
|
|
||||||
self._chestnut_fade_time = rl.get_time() if engaged else 0
|
|
||||||
self._engaged = engaged
|
self._engaged = engaged
|
||||||
self.set_speed = set_speed
|
self.set_speed = set_speed
|
||||||
self.is_cruise_set = 0 < self.set_speed < SET_SPEED_NA
|
self.is_cruise_set = 0 < self.set_speed < SET_SPEED_NA
|
||||||
@@ -185,7 +191,8 @@ class HudRenderer(Widget):
|
|||||||
if self.is_cruise_set:
|
if self.is_cruise_set:
|
||||||
self._draw_set_speed(rect)
|
self._draw_set_speed(rect)
|
||||||
|
|
||||||
self._draw_model_source(rect)
|
if ui_state.usbgpu and ui_state.usbgpu_compiled:
|
||||||
|
self._draw_model_source(rect)
|
||||||
|
|
||||||
self._draw_steering_wheel(rect)
|
self._draw_steering_wheel(rect)
|
||||||
|
|
||||||
@@ -193,24 +200,30 @@ class HudRenderer(Widget):
|
|||||||
if ui_state.sm.recv_frame['selfdriveState'] < ui_state.started_frame:
|
if ui_state.sm.recv_frame['selfdriveState'] < ui_state.started_frame:
|
||||||
return
|
return
|
||||||
|
|
||||||
loading = ui_state.chestnut_state == ChestnutState.LOADING
|
big_failed = (ui_state.usbgpu_active is False or not ui_state.sm['deviceState'].chestnutPresent or
|
||||||
|
(ui_state.usbgpu_active is True and ui_state.sm.recv_frame['modelV2'] > ui_state.started_frame and
|
||||||
|
not ui_state.sm.alive['modelV2']) or
|
||||||
|
(ui_state.usbgpu_active is None and ui_state.sm.recv_frame['modelV2'] > ui_state.started_frame))
|
||||||
|
self._small_model_engaged &= big_failed
|
||||||
|
loading = ui_state.usbgpu_loading or (ui_state.usbgpu_active is None and not big_failed)
|
||||||
if loading:
|
if loading:
|
||||||
icon = self._txt_chestnut
|
pulse = 0.5 - 0.5 * math.cos(rl.get_time() * 6.0)
|
||||||
opacity = 0.35 + 0.65 * (0.5 - 0.5 * math.cos(rl.get_time() * 6.0))
|
icon = self._txt_egpu
|
||||||
elif ui_state.chestnut_state in (ChestnutState.UNCOMPILED, ChestnutState.FAILED):
|
opacity = 0.35 + 0.65 * pulse
|
||||||
icon = self._txt_chestnut_orange
|
elif self._small_model_engaged:
|
||||||
opacity = 1.0
|
icon = self._txt_egpu_crossed
|
||||||
elif ui_state.chestnut_state == ChestnutState.ACTIVE:
|
opacity = 0.65
|
||||||
icon = self._txt_chestnut_green
|
elif big_failed:
|
||||||
|
icon = self._txt_egpu_orange
|
||||||
opacity = 1.0
|
opacity = 1.0
|
||||||
else:
|
else:
|
||||||
return
|
icon = self._txt_egpu_green
|
||||||
|
opacity = 1.0
|
||||||
|
|
||||||
if icon is not self._chestnut_icon:
|
if icon is not self._egpu_icon:
|
||||||
self._chestnut_fade_time = rl.get_time()
|
self._egpu_fade_time = rl.get_time()
|
||||||
self._chestnut_icon = icon
|
self._egpu_icon = icon
|
||||||
visible = loading or rl.get_time() - self._chestnut_fade_time < SET_SPEED_PERSISTENCE
|
alpha = self._egpu_alpha_filter.update(loading or 0 < rl.get_time() - self._egpu_fade_time < SET_SPEED_PERSISTENCE)
|
||||||
alpha = self._chestnut_alpha_filter.update(visible)
|
|
||||||
if alpha < 1e-2:
|
if alpha < 1e-2:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -154,8 +154,8 @@ class ModelRenderer(Widget, ModelRendererSP):
|
|||||||
self._draw_lane_lines()
|
self._draw_lane_lines()
|
||||||
self._draw_path(sm)
|
self._draw_path(sm)
|
||||||
|
|
||||||
if render_lead_indicator and radar_state:
|
# if render_lead_indicator and radar_state:
|
||||||
self._draw_lead_indicator()
|
# self._draw_lead_indicator()
|
||||||
|
|
||||||
def _update_raw_points(self, model):
|
def _update_raw_points(self, model):
|
||||||
"""Update raw 3D points from model data"""
|
"""Update raw 3D points from model data"""
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from collections.abc import Callable
|
|||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||||
from openpilot.system.ui.widgets.scroller import DO_ZOOM
|
from openpilot.system.ui.widgets.scroller import DO_ZOOM
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, TextAlignmentVertical
|
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||||
from openpilot.common.filter_simple import BounceFilter
|
from openpilot.common.filter_simple import BounceFilter
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -125,10 +125,10 @@ class BigButton(Widget):
|
|||||||
self._rotate_icon_t: float | None = None
|
self._rotate_icon_t: float | None = None
|
||||||
|
|
||||||
self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.BOLD,
|
self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.BOLD,
|
||||||
text_color=LABEL_COLOR, alignment_vertical=TextAlignmentVertical.BOTTOM, scroll=scroll,
|
text_color=LABEL_COLOR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, scroll=scroll,
|
||||||
line_height=0.9)
|
line_height=0.9)
|
||||||
self._sub_label = UnifiedLabel(value, font_size=COMPLICATION_SIZE, font_weight=FontWeight.ROMAN,
|
self._sub_label = UnifiedLabel(value, font_size=COMPLICATION_SIZE, font_weight=FontWeight.ROMAN,
|
||||||
text_color=COMPLICATION_GREY, alignment_vertical=TextAlignmentVertical.BOTTOM)
|
text_color=COMPLICATION_GREY, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM)
|
||||||
self._update_label_layout()
|
self._update_label_layout()
|
||||||
|
|
||||||
self._load_images()
|
self._load_images()
|
||||||
@@ -167,9 +167,9 @@ class BigButton(Widget):
|
|||||||
def _update_label_layout(self):
|
def _update_label_layout(self):
|
||||||
self._label.set_font_size(self._get_label_font_size())
|
self._label.set_font_size(self._get_label_font_size())
|
||||||
if self.value:
|
if self.value:
|
||||||
self._label.set_alignment_vertical(TextAlignmentVertical.TOP)
|
self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP)
|
||||||
else:
|
else:
|
||||||
self._label.set_alignment_vertical(TextAlignmentVertical.BOTTOM)
|
self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM)
|
||||||
|
|
||||||
def set_text(self, text: str):
|
def set_text(self, text: str):
|
||||||
self.text = text
|
self.text = text
|
||||||
@@ -356,8 +356,8 @@ class GreyBigButton(BigButton):
|
|||||||
self._sub_label.set_font_size(36)
|
self._sub_label.set_font_size(36)
|
||||||
self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9)))
|
self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9)))
|
||||||
self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR)
|
self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR)
|
||||||
self._sub_label.set_alignment_vertical(TextAlignmentVertical.MIDDLE if not self._label.text else
|
self._sub_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE if not self._label.text else
|
||||||
TextAlignmentVertical.BOTTOM)
|
rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM)
|
||||||
self._sub_label.set_line_height(0.95)
|
self._sub_label.set_line_height(0.95)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -383,18 +383,13 @@ class BigMultiParamToggle(BigMultiToggle):
|
|||||||
self._load_value()
|
self._load_value()
|
||||||
|
|
||||||
def _load_value(self):
|
def _load_value(self):
|
||||||
value = self._params.get(self._param, return_default=True)
|
self.set_value(self._options[self._params.get(self._param) or 0])
|
||||||
index = value if isinstance(value, int) else 0
|
|
||||||
self.set_value(self._options[max(0, min(index, len(self._options) - 1))])
|
|
||||||
|
|
||||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||||
super()._handle_mouse_release(mouse_pos)
|
super()._handle_mouse_release(mouse_pos)
|
||||||
new_idx = self._options.index(self.value)
|
new_idx = self._options.index(self.value)
|
||||||
self._params.put(self._param, new_idx)
|
self._params.put(self._param, new_idx)
|
||||||
|
|
||||||
def refresh(self):
|
|
||||||
self._load_value()
|
|
||||||
|
|
||||||
|
|
||||||
class BigParamControl(BigToggle):
|
class BigParamControl(BigToggle):
|
||||||
def __init__(self, text: str, param: str, toggle_callback: Callable | None = None):
|
def __init__(self, text: str, param: str, toggle_callback: Callable | None = None):
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
|||||||
from openpilot.cereal import messaging, log
|
from openpilot.cereal import messaging, log
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.common.hardware import COMMA_HARDWARE
|
from openpilot.common.hardware import COMMA_HARDWARE
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, TextAlignment, TextAlignmentVertical
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
from openpilot.system.ui.lib.multilang import tr
|
||||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
@@ -76,10 +76,10 @@ class AlertRenderer(Widget):
|
|||||||
self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
|
self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
|
||||||
|
|
||||||
# font size is set dynamically
|
# font size is set dynamically
|
||||||
self._full_text1_label = Label("", font_size=0, font_weight=FontWeight.BOLD, text_alignment=TextAlignment.CENTER,
|
self._full_text1_label = Label("", font_size=0, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
text_alignment_vertical=TextAlignmentVertical.TOP)
|
text_alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP)
|
||||||
self._full_text2_label = Label("", font_size=ALERT_FONT_BIG, text_alignment=TextAlignment.CENTER,
|
self._full_text2_label = Label("", font_size=ALERT_FONT_BIG, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
text_alignment_vertical=TextAlignmentVertical.TOP)
|
text_alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP)
|
||||||
|
|
||||||
def get_alert(self, sm: messaging.SubMaster) -> Alert | None:
|
def get_alert(self, sm: messaging.SubMaster) -> Alert | None:
|
||||||
"""Generate the current alert based on selfdrive state."""
|
"""Generate the current alert based on selfdrive state."""
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from openpilot.cereal.visionipc import VisionStreamType
|
|||||||
from openpilot.selfdrive.ui.onroad.cameraview import CameraView
|
from openpilot.selfdrive.ui.onroad.cameraview import CameraView
|
||||||
from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer
|
from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state, device
|
from openpilot.selfdrive.ui.ui_state import ui_state, device
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, TextAlignment
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
from openpilot.system.ui.lib.multilang import tr
|
||||||
from openpilot.system.ui.widgets.label import gui_label
|
from openpilot.system.ui.widgets.label import gui_label
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class CabinCameraDialog(CameraView):
|
|||||||
tr("camera starting"),
|
tr("camera starting"),
|
||||||
font_size=100,
|
font_size=100,
|
||||||
font_weight=FontWeight.BOLD,
|
font_weight=FontWeight.BOLD,
|
||||||
alignment=TextAlignment.CENTER,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
)
|
)
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ See the LICENSE.md file in the root directory for more details.
|
|||||||
"""
|
"""
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
from openpilot.selfdrive.ui.layouts.home import HomeLayout, HomeLayoutState, HEAD_BUTTON_FONT_SIZE, SPACING
|
from openpilot.selfdrive.ui.layouts.home import HomeLayout, HomeLayoutState, HEAD_BUTTON_FONT_SIZE, SPACING
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, TextAlignment
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||||
from openpilot.system.ui.lib.multilang import tr, trn
|
from openpilot.system.ui.lib.multilang import tr, trn
|
||||||
from openpilot.system.ui.widgets.label import gui_label
|
from openpilot.system.ui.widgets.label import gui_label
|
||||||
@@ -59,7 +59,7 @@ class HomeLayoutSP(HomeLayout):
|
|||||||
desc_size = measure_text_cached(gui_app.font(FontWeight.NORMAL), description, BRAND_FONT_SIZE)
|
desc_size = measure_text_cached(gui_app.font(FontWeight.NORMAL), description, BRAND_FONT_SIZE)
|
||||||
desc_width = desc_size.x
|
desc_width = desc_size.x
|
||||||
desc_rect = rl.Rectangle(version_right - desc_width, self.header_rect.y, desc_width, self.header_rect.height)
|
desc_rect = rl.Rectangle(version_right - desc_width, self.header_rect.y, desc_width, self.header_rect.height)
|
||||||
gui_label(desc_rect, description, BRAND_FONT_SIZE, rl.WHITE, alignment=TextAlignment.RIGHT)
|
gui_label(desc_rect, description, BRAND_FONT_SIZE, rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT)
|
||||||
|
|
||||||
brand_size = measure_text_cached(gui_app.font(FontWeight.AUDIOWIDE), brand, BRAND_FONT_SIZE)
|
brand_size = measure_text_cached(gui_app.font(FontWeight.AUDIOWIDE), brand, BRAND_FONT_SIZE)
|
||||||
spacing = BRAND_DESC_SPACING if description else 0
|
spacing = BRAND_DESC_SPACING if description else 0
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ See the LICENSE.md file in the root directory for more details.
|
|||||||
"""
|
"""
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.system.ui.lib.application import FontWeight, TextAlignment
|
from openpilot.system.ui.lib.application import FontWeight
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
from openpilot.system.ui.lib.multilang import tr
|
||||||
from openpilot.system.ui.widgets import Widget
|
from openpilot.system.ui.widgets import Widget
|
||||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||||
@@ -20,7 +20,7 @@ class SunnylinkConsentPage(Widget):
|
|||||||
self._done_callback = done_callback
|
self._done_callback = done_callback
|
||||||
self._step = 0
|
self._step = 0
|
||||||
|
|
||||||
self._title = self._child(Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.AUDIOWIDE, text_alignment=TextAlignment.LEFT))
|
self._title = self._child(Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.AUDIOWIDE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT))
|
||||||
|
|
||||||
self._content = [
|
self._content = [
|
||||||
{
|
{
|
||||||
@@ -43,7 +43,7 @@ class SunnylinkConsentPage(Widget):
|
|||||||
self._primary_btn = self._child(Button("", button_style=ButtonStyle.PRIMARY, click_callback=lambda: self._handle_choice("enable")))
|
self._primary_btn = self._child(Button("", button_style=ButtonStyle.PRIMARY, click_callback=lambda: self._handle_choice("enable")))
|
||||||
self._secondary_btn = self._child(Button("", button_style=ButtonStyle.NORMAL, click_callback=lambda: self._handle_choice("secondary")))
|
self._secondary_btn = self._child(Button("", button_style=ButtonStyle.NORMAL, click_callback=lambda: self._handle_choice("secondary")))
|
||||||
self._danger_btn = self._child(Button("", button_style=ButtonStyle.DANGER, click_callback=lambda: self._handle_choice("disable")))
|
self._danger_btn = self._child(Button("", button_style=ButtonStyle.DANGER, click_callback=lambda: self._handle_choice("disable")))
|
||||||
self._desc = self._child(Label("", font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=TextAlignment.LEFT))
|
self._desc = self._child(Label("", font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT))
|
||||||
|
|
||||||
def _handle_choice(self, choice):
|
def _handle_choice(self, choice):
|
||||||
if choice == "enable":
|
if choice == "enable":
|
||||||
|
|||||||
@@ -143,8 +143,7 @@ class CruiseLayout(Widget):
|
|||||||
self.icbm_toggle.show_description(True)
|
self.icbm_toggle.show_description(True)
|
||||||
|
|
||||||
if has_long or has_icbm:
|
if has_long or has_icbm:
|
||||||
software_cruise_speed = has_long and (not ui_state.CP.pcmCruise or not ui_state.CP_SP.pcmCruiseSpeed)
|
self.custom_acc_toggle.action_item.set_enabled(((has_long and not ui_state.CP.pcmCruise) or has_icbm) and ui_state.is_offroad())
|
||||||
self.custom_acc_toggle.action_item.set_enabled((software_cruise_speed or has_icbm) and ui_state.is_offroad())
|
|
||||||
self.dec_toggle.action_item.set_enabled(has_long)
|
self.dec_toggle.action_item.set_enabled(has_long)
|
||||||
self.scc_v_toggle.action_item.set_enabled(True)
|
self.scc_v_toggle.action_item.set_enabled(True)
|
||||||
self.scc_m_toggle.action_item.set_enabled(True)
|
self.scc_m_toggle.action_item.set_enabled(True)
|
||||||
@@ -170,7 +169,7 @@ class CruiseLayout(Widget):
|
|||||||
show_custom_acc_desc = True
|
show_custom_acc_desc = True
|
||||||
else:
|
else:
|
||||||
if has_long or has_icbm:
|
if has_long or has_icbm:
|
||||||
if has_long and ui_state.CP.pcmCruise and ui_state.CP_SP.pcmCruiseSpeed:
|
if has_long and ui_state.CP.pcmCruise:
|
||||||
new_custom_acc_desc = tr(ACC_PCMCRUISE_DISABLED_DESCRIPTION)
|
new_custom_acc_desc = tr(ACC_PCMCRUISE_DISABLED_DESCRIPTION)
|
||||||
show_custom_acc_desc = True
|
show_custom_acc_desc = True
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -62,14 +62,14 @@ class ModelsLayout(Widget):
|
|||||||
self.big_model_item = ListItemSP(
|
self.big_model_item = ListItemSP(
|
||||||
title=tr("Big Model"),
|
title=tr("Big Model"),
|
||||||
action_item=ScrollingButtonAction(tr("SELECT")),
|
action_item=ScrollingButtonAction(tr("SELECT")),
|
||||||
callback=lambda: self._open_source_dialog("chestnut")
|
callback=lambda: self._open_source_dialog("usbgpu")
|
||||||
)
|
)
|
||||||
|
|
||||||
self.download_item = download_status_item(lambda: tr("Download") if self._downloading else tr("Model Status"))
|
self.download_item = download_status_item(lambda: tr("Download") if self._downloading else tr("Model Status"))
|
||||||
|
|
||||||
self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "",
|
self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "",
|
||||||
lambda: (ui_state.params.put("ModelManager_LastSyncTime", 0),
|
lambda: (ui_state.params.put("ModelManager_LastSyncTime", 0),
|
||||||
ui_state.params.put("ModelManager_LastSyncTime_Chestnut", 0),
|
ui_state.params.put("ModelManager_LastSyncTime_USBGPU", 0),
|
||||||
gui_app.push_widget(alert_dialog(tr("Fetching Latest Models")))))
|
gui_app.push_widget(alert_dialog(tr("Fetching Latest Models")))))
|
||||||
|
|
||||||
self.clear_cache_item = ListItemSP(
|
self.clear_cache_item = ListItemSP(
|
||||||
@@ -177,14 +177,14 @@ class ModelsLayout(Widget):
|
|||||||
big_state = big_model_state()
|
big_state = big_model_state()
|
||||||
carry_source, carry_internal, _ = carrying_model()
|
carry_source, carry_internal, _ = carrying_model()
|
||||||
segments = []
|
segments = []
|
||||||
for source, label in (("qcom", tr("small")), ("chestnut", tr("big"))):
|
for source, label in (("qcom", tr("small")), ("usbgpu", tr("big"))):
|
||||||
if segments:
|
if segments:
|
||||||
segments.append(("|", rl.GRAY, None, None))
|
segments.append(("|", rl.GRAY, None, None))
|
||||||
bundle = get_selected_bundle(ui_state.params, source)
|
bundle = get_selected_bundle(ui_state.params, source)
|
||||||
name = bundle.internalName if bundle else default_model_name(source)
|
name = bundle.internalName if bundle else default_model_name(source)
|
||||||
color = ON_COLOR if (source == carry_source and name == carry_internal) else rl.LIGHTGRAY
|
color = ON_COLOR if (source == carry_source and name == carry_internal) else rl.LIGHTGRAY
|
||||||
name = "● " + name
|
name = "● " + name
|
||||||
if source == "chestnut":
|
if source == "usbgpu":
|
||||||
if big_state == 'failed':
|
if big_state == 'failed':
|
||||||
color = rl.RED
|
color = rl.RED
|
||||||
elif big_state == 'loading':
|
elif big_state == 'loading':
|
||||||
@@ -208,10 +208,10 @@ class ModelsLayout(Widget):
|
|||||||
"""The failover story for the Model Status row. One-way big -> small, and the
|
"""The failover story for the Model Status row. One-way big -> small, and the
|
||||||
fallback is runner-matched: a Default big can only fall back to the Default
|
fallback is runner-matched: a Default big can only fall back to the Default
|
||||||
small (stock modeld), a custom big has no automatic fallback yet."""
|
small (stock modeld), a custom big has no automatic fallback yet."""
|
||||||
if not ui_state.chestnut_present:
|
if not ui_state.usbgpu:
|
||||||
return ""
|
return ""
|
||||||
big_bundle = get_selected_bundle(ui_state.params, "chestnut")
|
big_bundle = get_selected_bundle(ui_state.params, "usbgpu")
|
||||||
big_name = big_bundle.internalName if big_bundle else default_model_name("chestnut")
|
big_name = big_bundle.internalName if big_bundle else default_model_name("usbgpu")
|
||||||
big_is_default = big_bundle is None
|
big_is_default = big_bundle is None
|
||||||
fallback_name = default_model_name("qcom")
|
fallback_name = default_model_name("qcom")
|
||||||
state = big_model_state()
|
state = big_model_state()
|
||||||
@@ -225,7 +225,7 @@ class ModelsLayout(Widget):
|
|||||||
return tr("Getting the big model ready.")
|
return tr("Getting the big model ready.")
|
||||||
if big_is_default:
|
if big_is_default:
|
||||||
return tr("{} will drive. If it fails during a drive, {} takes over until the next drive.").format(big_name, fallback_name)
|
return tr("{} will drive. If it fails during a drive, {} takes over until the next drive.").format(big_name, fallback_name)
|
||||||
return tr("{} will drive when the chestnut is ready.").format(big_name)
|
return tr("{} will drive when the eGPU is ready.").format(big_name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _download_row_state(progresses, name: str) -> dict:
|
def _download_row_state(progresses, name: str) -> dict:
|
||||||
@@ -261,7 +261,7 @@ class ModelsLayout(Widget):
|
|||||||
ui_state.params.put("ModelManager_DownloadRef", selected_bundle.ref)
|
ui_state.params.put("ModelManager_DownloadRef", selected_bundle.ref)
|
||||||
|
|
||||||
def _resolve_selected_bundle(self, ref):
|
def _resolve_selected_bundle(self, ref):
|
||||||
source_bundles = {source: bundles_for_source(source) for source in ("qcom", "chestnut")}
|
source_bundles = {source: bundles_for_source(source) for source in ("qcom", "usbgpu")}
|
||||||
resolved = resolve_bundle_by_ref(ref, source_bundles)
|
resolved = resolve_bundle_by_ref(ref, source_bundles)
|
||||||
return resolved[0] if resolved else None
|
return resolved[0] if resolved else None
|
||||||
|
|
||||||
@@ -329,7 +329,7 @@ class ModelsLayout(Widget):
|
|||||||
self._handle_bundle_download_progress()
|
self._handle_bundle_download_progress()
|
||||||
|
|
||||||
carry_source, _, carry_display = carrying_model()
|
carry_source, _, carry_display = carrying_model()
|
||||||
for item, item_source in ((self.small_model_item, "qcom"), (self.big_model_item, "chestnut")):
|
for item, item_source in ((self.small_model_item, "qcom"), (self.big_model_item, "usbgpu")):
|
||||||
bundle = get_selected_bundle(ui_state.params, item_source)
|
bundle = get_selected_bundle(ui_state.params, item_source)
|
||||||
name = bundle.displayName if bundle else default_model_name(item_source)
|
name = bundle.displayName if bundle else default_model_name(item_source)
|
||||||
color = ON_COLOR if (item_source == carry_source and name == carry_display) else style.ITEM_TEXT_VALUE_COLOR
|
color = ON_COLOR if (item_source == carry_source and name == carry_display) else style.ITEM_TEXT_VALUE_COLOR
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from openpilot.cereal import custom
|
|||||||
from openpilot.selfdrive.ui.sunnypilot.layouts.onboarding import SunnylinkConsentPage
|
from openpilot.selfdrive.ui.sunnypilot.layouts.onboarding import SunnylinkConsentPage
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID
|
from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, TextAlignment, TextAlignmentVertical
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
from openpilot.system.ui.lib.multilang import tr
|
||||||
from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp
|
from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp
|
||||||
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
|
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
|
||||||
@@ -32,8 +32,8 @@ class SunnylinkHeader(Widget):
|
|||||||
font_size=90,
|
font_size=90,
|
||||||
font_weight=FontWeight.AUDIOWIDE,
|
font_weight=FontWeight.AUDIOWIDE,
|
||||||
text_color=rl.WHITE,
|
text_color=rl.WHITE,
|
||||||
alignment=TextAlignment.CENTER,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
alignment_vertical=TextAlignmentVertical.TOP,
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
|
||||||
wrap_text=False,
|
wrap_text=False,
|
||||||
elide=False
|
elide=False
|
||||||
)
|
)
|
||||||
@@ -43,8 +43,8 @@ class SunnylinkHeader(Widget):
|
|||||||
font_size=40,
|
font_size=40,
|
||||||
font_weight=FontWeight.NORMAL,
|
font_weight=FontWeight.NORMAL,
|
||||||
text_color=rl.Color(0, 255, 0, 255), # Green
|
text_color=rl.Color(0, 255, 0, 255), # Green
|
||||||
alignment=TextAlignment.CENTER,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
alignment_vertical=TextAlignmentVertical.TOP,
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
|
||||||
wrap_text=True,
|
wrap_text=True,
|
||||||
elide=False
|
elide=False
|
||||||
)
|
)
|
||||||
@@ -55,8 +55,8 @@ class SunnylinkHeader(Widget):
|
|||||||
font_size=35,
|
font_size=35,
|
||||||
font_weight=FontWeight.NORMAL,
|
font_weight=FontWeight.NORMAL,
|
||||||
text_color=rl.Color(255, 165, 0, 255), # Orange
|
text_color=rl.Color(255, 165, 0, 255), # Orange
|
||||||
alignment=TextAlignment.CENTER,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
alignment_vertical=TextAlignmentVertical.TOP,
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
|
||||||
wrap_text=True,
|
wrap_text=True,
|
||||||
elide=False
|
elide=False
|
||||||
)
|
)
|
||||||
@@ -109,8 +109,8 @@ class SunnylinkDescriptionItem(Widget):
|
|||||||
font_size=40,
|
font_size=40,
|
||||||
font_weight=FontWeight.NORMAL,
|
font_weight=FontWeight.NORMAL,
|
||||||
text_color=rl.WHITE,
|
text_color=rl.WHITE,
|
||||||
alignment=TextAlignment.LEFT,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
|
||||||
alignment_vertical=TextAlignmentVertical.TOP,
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
|
||||||
wrap_text=True,
|
wrap_text=True,
|
||||||
elide=False,
|
elide=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ DESCRIPTIONS = {
|
|||||||
'stop_and_go_hack': tr_noop(
|
'stop_and_go_hack': tr_noop(
|
||||||
'sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. ' +
|
'sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. ' +
|
||||||
'This feature is only applicable to certain models that are able to use longitudinal control. This is an alpha feature. Use at your own risk.'
|
'This feature is only applicable to certain models that are able to use longitudinal control. This is an alpha feature. Use at your own risk.'
|
||||||
),
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import math
|
|||||||
import pyray as rl
|
import pyray as rl
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state, ChestnutState
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID
|
from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID
|
||||||
from openpilot.system.ui.lib.application import gui_app
|
from openpilot.system.ui.lib.application import gui_app
|
||||||
from openpilot.system.ui.lib.multilang import tr_noop
|
from openpilot.system.ui.lib.multilang import tr_noop
|
||||||
@@ -21,8 +21,8 @@ METRIC_MARGIN = 30
|
|||||||
METRIC_START_Y = 300
|
METRIC_START_Y = 300
|
||||||
HOME_BTN = rl.Rectangle(60, 860, 180, 180)
|
HOME_BTN = rl.Rectangle(60, 860, 180, 180)
|
||||||
|
|
||||||
CHESTNUT_ICON_WIDTH = 180
|
EGPU_ICON_WIDTH = 180
|
||||||
CHESTNUT_ICON_HEIGHT = 133
|
EGPU_ICON_HEIGHT = 133
|
||||||
|
|
||||||
|
|
||||||
# Color scheme
|
# Color scheme
|
||||||
@@ -59,9 +59,10 @@ class MetricData:
|
|||||||
class SidebarSP:
|
class SidebarSP:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._sunnylink_status = MetricData(tr_noop("SUNNYLINK"), tr_noop("OFFLINE"), Colors.WARNING)
|
self._sunnylink_status = MetricData(tr_noop("SUNNYLINK"), tr_noop("OFFLINE"), Colors.WARNING)
|
||||||
self._chestnut_green_img = gui_app.texture("icons_mici/chestnut_green.png", CHESTNUT_ICON_WIDTH, CHESTNUT_ICON_HEIGHT)
|
self._egpu_green_img = gui_app.texture("icons_mici/egpu_green.png", EGPU_ICON_WIDTH, EGPU_ICON_HEIGHT)
|
||||||
self._chestnut_default_img = gui_app.texture("icons_mici/chestnut.png", CHESTNUT_ICON_WIDTH, CHESTNUT_ICON_HEIGHT)
|
self._egpu_default_img = gui_app.texture("icons_mici/egpu.png", EGPU_ICON_WIDTH, EGPU_ICON_HEIGHT)
|
||||||
self._chestnut_orange_img = gui_app.texture("icons_mici/chestnut_orange.png", CHESTNUT_ICON_WIDTH, CHESTNUT_ICON_HEIGHT)
|
self._egpu_orange_img = gui_app.texture("icons_mici/egpu_orange.png", EGPU_ICON_WIDTH, EGPU_ICON_HEIGHT)
|
||||||
|
self._egpu_gray_img = gui_app.texture("icons_mici/egpu_gray.png", EGPU_ICON_WIDTH, EGPU_ICON_HEIGHT)
|
||||||
|
|
||||||
def _update_sunnylink_status(self):
|
def _update_sunnylink_status(self):
|
||||||
if not ui_state.params.get_bool("SunnylinkEnabled"):
|
if not ui_state.params.get_bool("SunnylinkEnabled"):
|
||||||
@@ -89,17 +90,22 @@ class SidebarSP:
|
|||||||
|
|
||||||
def _get_home_icon(self, default_img: rl.Texture) -> tuple[rl.Texture, rl.Vector2, float]:
|
def _get_home_icon(self, default_img: rl.Texture) -> tuple[rl.Texture, rl.Vector2, float]:
|
||||||
default_pos = rl.Vector2(HOME_BTN.x, HOME_BTN.y)
|
default_pos = rl.Vector2(HOME_BTN.x, HOME_BTN.y)
|
||||||
state = ui_state.chestnut_state
|
if not ui_state.sm["deviceState"].chestnutPresent:
|
||||||
if state == ChestnutState.DISCONNECTED:
|
|
||||||
return default_img, default_pos, 1.0
|
return default_img, default_pos, 1.0
|
||||||
|
|
||||||
if state == ChestnutState.LOADING:
|
big_model_selected = ui_state.usbgpu_compiled or ui_state.model_runner_tinygrad
|
||||||
icon = self._chestnut_default_img
|
big_model_failed = ui_state.started and ui_state.big_model_failed
|
||||||
|
loading = ui_state.usbgpu_loading or (big_model_selected and ui_state.started and ui_state.usbgpu_active is None)
|
||||||
|
|
||||||
|
if loading:
|
||||||
|
icon = self._egpu_default_img
|
||||||
opacity = 0.35 + 0.65 * (0.5 - 0.5 * math.cos(rl.get_time() * 6.0))
|
opacity = 0.35 + 0.65 * (0.5 - 0.5 * math.cos(rl.get_time() * 6.0))
|
||||||
elif state in (ChestnutState.UNCOMPILED, ChestnutState.FAILED):
|
elif big_model_selected and big_model_failed:
|
||||||
icon, opacity = self._chestnut_orange_img, 1.0
|
icon, opacity = self._egpu_orange_img, 1.0
|
||||||
|
elif big_model_selected:
|
||||||
|
icon, opacity = self._egpu_green_img, 1.0
|
||||||
else:
|
else:
|
||||||
icon, opacity = self._chestnut_green_img, 1.0
|
icon, opacity = self._egpu_gray_img, 1.0
|
||||||
|
|
||||||
x = HOME_BTN.x + (HOME_BTN.width - icon.width) / 2
|
x = HOME_BTN.x + (HOME_BTN.width - icon.width) / 2
|
||||||
y = HOME_BTN.y + (HOME_BTN.height - icon.height) / 2
|
y = HOME_BTN.y + (HOME_BTN.height - icon.height) / 2
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import math
|
|||||||
import pyray as rl
|
import pyray as rl
|
||||||
|
|
||||||
from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout
|
from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state, ChestnutState
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.system.ui.lib.application import FontWeight
|
from openpilot.system.ui.lib.application import FontWeight
|
||||||
|
from openpilot.system.ui.widgets.icon_widget import IconWidget
|
||||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||||
|
|
||||||
|
|
||||||
@@ -18,16 +19,35 @@ class MiciHomeLayoutSP(MiciHomeLayout):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._openpilot_label = UnifiedLabel("sunnypilot", font_size=88, font_weight=FontWeight.AUDIOWIDE, max_width=480, wrap_text=False)
|
self._openpilot_label = UnifiedLabel("sunnypilot", font_size=88, font_weight=FontWeight.AUDIOWIDE, max_width=480, wrap_text=False)
|
||||||
|
self._egpu_icon_default = IconWidget("icons_mici/egpu.png", (50, 37))
|
||||||
|
self._egpu_icon_default.set_visible(False)
|
||||||
|
self._egpu_icon_orange = IconWidget("icons_mici/egpu_orange.png", (50, 37))
|
||||||
|
self._egpu_icon_orange.set_visible(False)
|
||||||
|
gray_idx = self._status_bar_layout.widgets.index(self._egpu_icon_gray)
|
||||||
|
self._status_bar_layout.widgets.insert(gray_idx + 1, self._egpu_icon_default)
|
||||||
|
self._status_bar_layout.widgets.insert(gray_idx + 2, self._egpu_icon_orange)
|
||||||
|
|
||||||
def _set_chestnut_visibility(self):
|
def _set_egpu_visibility(self):
|
||||||
usb_connected = ui_state.usb_connected
|
chestnut = ui_state.sm["deviceState"].chestnutPresent
|
||||||
usb_unknown = ui_state.usb_unknown
|
if not chestnut:
|
||||||
chestnut_state = ui_state.chestnut_state
|
self._egpu_icon.set_visible(False)
|
||||||
loading = chestnut_state == ChestnutState.LOADING
|
self._egpu_icon_default.set_visible(False)
|
||||||
|
self._egpu_icon_orange.set_visible(False)
|
||||||
|
self._egpu_icon_gray.set_visible(False)
|
||||||
|
return
|
||||||
|
|
||||||
self._usb_icon.set_visible(usb_connected and usb_unknown)
|
big_model_selected = ui_state.usbgpu_compiled or ui_state.model_runner_tinygrad
|
||||||
self._chestnut_loading_icon.set_opacity(0.35 + 0.65 * (0.5 - 0.5 * math.cos(rl.get_time() * 6.0)))
|
big_model_failed = ui_state.started and ui_state.big_model_failed
|
||||||
self._chestnut_loading_icon.set_visible(not usb_unknown and loading)
|
loading = ui_state.usbgpu_loading or (big_model_selected and ui_state.started and ui_state.usbgpu_active is None)
|
||||||
self._chestnut_icon.set_visible(not usb_unknown and not loading and
|
|
||||||
chestnut_state in (ChestnutState.READY, ChestnutState.ACTIVE))
|
if loading:
|
||||||
self._chestnut_failed_icon.set_visible(not usb_unknown and chestnut_state in (ChestnutState.UNCOMPILED, ChestnutState.FAILED))
|
self._egpu_icon_default._opacity = 0.35 + 0.65 * (0.5 - 0.5 * math.cos(rl.get_time() * 6.0))
|
||||||
|
self._egpu_icon_default.set_visible(True)
|
||||||
|
self._egpu_icon.set_visible(False)
|
||||||
|
self._egpu_icon_orange.set_visible(False)
|
||||||
|
self._egpu_icon_gray.set_visible(False)
|
||||||
|
else:
|
||||||
|
self._egpu_icon_default.set_visible(False)
|
||||||
|
self._egpu_icon.set_visible(big_model_selected and not big_model_failed)
|
||||||
|
self._egpu_icon_orange.set_visible(big_model_selected and big_model_failed)
|
||||||
|
self._egpu_icon_gray.set_visible(not big_model_selected)
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
|
||||||
|
|
||||||
This file is part of sunnypilot and is licensed under the MIT License.
|
|
||||||
See the LICENSE.md file in the root directory for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout
|
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroll_panel_sp import GuiScrollPanel2SP
|
|
||||||
|
|
||||||
|
|
||||||
class MiciMainLayoutSP(MiciMainLayout):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
scroller = self._scroller
|
|
||||||
scroller.scroll_panel = GuiScrollPanel2SP(scroller._horizontal, handle_out_of_bounds=not scroller._snap_items)
|
|
||||||
|
|
||||||
def _should_auto_scroll_to_onroad(self) -> bool:
|
|
||||||
return not self._onroad_layout.is_on_info_panel()
|
|
||||||
@@ -27,14 +27,14 @@ def _model_info() -> tuple[str, str, str]:
|
|||||||
state = big_model_state()
|
state = big_model_state()
|
||||||
_, _, carry_display = carrying_model()
|
_, _, carry_display = carrying_model()
|
||||||
if carry_display is None:
|
if carry_display is None:
|
||||||
big = get_selected_bundle(ui_state.params, "chestnut")
|
big = get_selected_bundle(ui_state.params, "usbgpu")
|
||||||
carry_display = big.displayName if big else default_model_name("chestnut")
|
carry_display = big.displayName if big else default_model_name("usbgpu")
|
||||||
active_text = (carry_display or active_name).lower()
|
active_text = (carry_display or active_name).lower()
|
||||||
if state == 'failed':
|
if state == 'failed':
|
||||||
return active_text, tr("big model"), tr("unavailable")
|
return active_text, tr("big model"), tr("unavailable")
|
||||||
if state == 'loading':
|
if state == 'loading':
|
||||||
return active_text, tr("big model"), tr("getting ready")
|
return active_text, tr("big model"), tr("getting ready")
|
||||||
header = tr("small model") if source == "chestnut" else tr("big model")
|
header = tr("small model") if source == "usbgpu" else tr("big model")
|
||||||
return active_text, header, other_name.lower()
|
return active_text, header, other_name.lower()
|
||||||
|
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ class ModelsLayoutMici(NavScroller):
|
|||||||
|
|
||||||
hardware_btns = []
|
hardware_btns = []
|
||||||
active = active_source()
|
active = active_source()
|
||||||
for source, label in (("qcom", tr("small models")), ("chestnut", tr("big models"))):
|
for source, label in (("qcom", tr("small models")), ("usbgpu", tr("big models"))):
|
||||||
bundle = get_selected_bundle(ui_state.params, source)
|
bundle = get_selected_bundle(ui_state.params, source)
|
||||||
value = (bundle.internalName if bundle else default_model_name(source)).lower()
|
value = (bundle.internalName if bundle else default_model_name(source)).lower()
|
||||||
if source == active:
|
if source == active:
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
|
||||||
|
|
||||||
This file is part of sunnypilot and is licensed under the MIT License.
|
|
||||||
See the LICENSE.md file in the root directory for more details.
|
|
||||||
"""
|
|
||||||
from collections.abc import Callable
|
|
||||||
import pyray as rl
|
|
||||||
from openpilot.system.ui.lib.application import gui_app
|
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroller_sp import ScrollerSP
|
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.augmented_road_view import AugmentedRoadViewSP
|
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onroad_info_panel import OnroadInfoPanel
|
|
||||||
|
|
||||||
CONFIDENCE_BALL_VISIBLE_RATIO = 0.4
|
|
||||||
HORIZONTAL_SETTLE_PX = 5
|
|
||||||
HORIZONTAL_RESET_RATIO = 0.5
|
|
||||||
|
|
||||||
|
|
||||||
class OnroadViewContainerSP(ScrollerSP):
|
|
||||||
def __init__(self, bookmark_callback=None):
|
|
||||||
super().__init__(horizontal=False, snap_items=True, spacing=0, pad=0, scroll_indicator=False, edge_shadows=False)
|
|
||||||
self.road_view = AugmentedRoadViewSP(bookmark_callback=bookmark_callback)
|
|
||||||
self.onroad_info_panel = OnroadInfoPanel(bookmark_callback=bookmark_callback)
|
|
||||||
|
|
||||||
self._scroller.add_widgets([
|
|
||||||
self.road_view,
|
|
||||||
self.onroad_info_panel,
|
|
||||||
])
|
|
||||||
self._scroller.set_reset_scroll_at_show(False)
|
|
||||||
self._scroller.set_scrolling_enabled(lambda: abs(self.rect.x) < HORIZONTAL_SETTLE_PX)
|
|
||||||
|
|
||||||
for child in (self.road_view, self.onroad_info_panel):
|
|
||||||
inner_touch_valid = child._touch_valid_callback
|
|
||||||
child.set_touch_valid_callback(
|
|
||||||
lambda inner=inner_touch_valid: self._touch_valid() and (inner() if inner else True)
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_rect(self, rect: rl.Rectangle):
|
|
||||||
super().set_rect(rect)
|
|
||||||
self.road_view.set_rect(rect)
|
|
||||||
self.onroad_info_panel.set_rect(rect)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def is_swiping_left(self) -> bool:
|
|
||||||
return self.road_view.is_swiping_left() or self.onroad_info_panel.is_swiping_left()
|
|
||||||
|
|
||||||
def set_click_callback(self, click_callback: Callable[[], None] | None) -> None:
|
|
||||||
self.road_view.set_click_callback(click_callback)
|
|
||||||
self.onroad_info_panel.set_click_callback(click_callback)
|
|
||||||
|
|
||||||
def is_on_info_panel(self) -> bool:
|
|
||||||
"""True when scrolled past halfway toward onroad_info_panel (used by main layout
|
|
||||||
to skip auto-pop-back-to-camera while user is reading the info panel)."""
|
|
||||||
return abs(self._scroller.scroll_panel.get_offset()) > self._rect.height / 2
|
|
||||||
|
|
||||||
def _render(self, rect: rl.Rectangle):
|
|
||||||
if abs(self.rect.x) > gui_app.width * HORIZONTAL_RESET_RATIO:
|
|
||||||
self._scroller.scroll_panel.set_offset(0)
|
|
||||||
|
|
||||||
vertical_offset = self._scroller.scroll_panel.get_offset()
|
|
||||||
show_ball = abs(vertical_offset) < rect.height * CONFIDENCE_BALL_VISIBLE_RATIO
|
|
||||||
self.road_view.set_show_confidence_ball(show_ball)
|
|
||||||
|
|
||||||
super()._render(rect)
|
|
||||||
@@ -1,403 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
|
||||||
|
|
||||||
This file is part of sunnypilot and is licensed under the MIT License.
|
|
||||||
See the LICENSE.md file in the root directory for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pyray as rl
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from openpilot.common.constants import CV
|
|
||||||
from openpilot.common.filter_simple import FirstOrderFilter
|
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
|
||||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
|
||||||
from openpilot.system.ui.widgets import Widget
|
|
||||||
from openpilot.selfdrive.ui.mici.onroad.alert_renderer import AlertRenderer
|
|
||||||
from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import BookmarkIcon
|
|
||||||
|
|
||||||
METER_TO_KM = 0.001
|
|
||||||
METER_TO_MILE = 0.000621371
|
|
||||||
|
|
||||||
CONTENT_MARGIN = 16
|
|
||||||
SPEED_LIMIT_SIGN_WIDTH = 146
|
|
||||||
VIENNA_SIGN_SIZE = 146
|
|
||||||
MUTCD_SIGN_HEIGHT = 178
|
|
||||||
OFFSET_BADGE_SIZE = 50
|
|
||||||
OFFSET_BADGE_PANEL_PADDING = 4
|
|
||||||
MUTCD_OFFSET_SIGN_Y_SHIFT = 6
|
|
||||||
VIENNA_BADGE_X_RATIO = 0.80
|
|
||||||
VIENNA_BADGE_UPCOMING_X_RATIO = 0.70
|
|
||||||
VIENNA_BADGE_Y_RATIO = -0.82
|
|
||||||
UPCOMING_SIGN_SIZE_RATIO = 0.76
|
|
||||||
UPCOMING_SIGN_OVERLAP_RATIO = 0.05
|
|
||||||
UNIT_FONT_SIZE = 40
|
|
||||||
SPEED_FONT_SIZE = 114
|
|
||||||
ROAD_FONT_SIZE = 32
|
|
||||||
SCC_TAG_WIDTH = 78
|
|
||||||
SCC_TAG_HEIGHT = 30
|
|
||||||
SCC_TAG_GAP = 5
|
|
||||||
COLUMN_GAP = 12
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class OnroadInfoPanelColors:
|
|
||||||
white: rl.Color = rl.WHITE
|
|
||||||
black: rl.Color = rl.BLACK
|
|
||||||
red: rl.Color = field(default_factory=lambda: rl.Color(255, 0, 0, 255))
|
|
||||||
green: rl.Color = field(default_factory=lambda: rl.Color(0, 255, 0, 255))
|
|
||||||
grey: rl.Color = field(default_factory=lambda: rl.Color(190, 195, 190, 255))
|
|
||||||
light_grey: rl.Color = field(default_factory=lambda: rl.Color(200, 200, 200, 255))
|
|
||||||
dark_grey: rl.Color = field(default_factory=lambda: rl.Color(100, 100, 100, 255))
|
|
||||||
bg_dark: rl.Color = field(default_factory=lambda: rl.Color(0, 0, 0, 255))
|
|
||||||
card_bg: rl.Color = field(default_factory=lambda: rl.Color(50, 50, 50, 200))
|
|
||||||
badge_bg: rl.Color = field(default_factory=lambda: rl.Color(60, 60, 60, 255))
|
|
||||||
|
|
||||||
|
|
||||||
COLORS = OnroadInfoPanelColors()
|
|
||||||
|
|
||||||
|
|
||||||
class OnroadInfoPanel(Widget):
|
|
||||||
def __init__(self, bookmark_callback=None):
|
|
||||||
super().__init__()
|
|
||||||
self.speed_limit: float = 0.0
|
|
||||||
self.speed_limit_valid: bool = False
|
|
||||||
self.speed_limit_offset: float = 0.0
|
|
||||||
self.next_speed_limit: float = 0.0
|
|
||||||
self.next_speed_limit_distance: float = 0.0
|
|
||||||
self.road_name: str = ""
|
|
||||||
self.current_speed: float = 0.0
|
|
||||||
self.set_speed: float = 0.0
|
|
||||||
self.cruise_enabled: bool = False
|
|
||||||
|
|
||||||
self._sign_slide: float = 0.0
|
|
||||||
|
|
||||||
self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
|
|
||||||
self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD)
|
|
||||||
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
|
|
||||||
|
|
||||||
self._marquee_offset: float = 0.0
|
|
||||||
self._marquee_direction: int = 1
|
|
||||||
self._marquee_pause_timer: float = 0.0
|
|
||||||
self._marquee_speed: float = 40.0
|
|
||||||
self._marquee_pause_duration: float = 1.5
|
|
||||||
|
|
||||||
self._alert_renderer = AlertRenderer()
|
|
||||||
self._alert_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps)
|
|
||||||
|
|
||||||
self._bookmark_icon = BookmarkIcon(bookmark_callback)
|
|
||||||
|
|
||||||
def is_swiping_left(self) -> bool:
|
|
||||||
return self._bookmark_icon.is_swiping_left()
|
|
||||||
|
|
||||||
def _handle_mouse_release(self, mouse_pos: MousePos) -> None:
|
|
||||||
# Mirror stock AugmentedRoadView: suppress click while bookmark gesture active
|
|
||||||
if not self._bookmark_icon.interacting():
|
|
||||||
super()._handle_mouse_release(mouse_pos)
|
|
||||||
|
|
||||||
def _update_state(self) -> None:
|
|
||||||
sm = ui_state.sm
|
|
||||||
speed_conv = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
|
|
||||||
|
|
||||||
if sm.valid["longitudinalPlanSP"]:
|
|
||||||
lp_sp = sm["longitudinalPlanSP"]
|
|
||||||
resolver = lp_sp.speedLimit.resolver
|
|
||||||
self.speed_limit = resolver.speedLimit * speed_conv
|
|
||||||
self.speed_limit_valid = resolver.speedLimitValid
|
|
||||||
self.speed_limit_offset = resolver.speedLimitOffset * speed_conv
|
|
||||||
|
|
||||||
if sm.valid["liveMapDataSP"]:
|
|
||||||
lmd = sm["liveMapDataSP"]
|
|
||||||
self.next_speed_limit = lmd.speedLimitAhead * speed_conv
|
|
||||||
self.next_speed_limit_distance = lmd.speedLimitAheadDistance
|
|
||||||
self.road_name = lmd.roadName
|
|
||||||
|
|
||||||
if sm.updated["carState"]:
|
|
||||||
self.current_speed = sm["carState"].vEgo * speed_conv
|
|
||||||
|
|
||||||
if sm.valid["carState"] and sm.valid["controlsState"]:
|
|
||||||
self.cruise_enabled = sm["carState"].cruiseState.enabled
|
|
||||||
v_cruise_cluster = sm["carState"].vCruiseCluster
|
|
||||||
set_speed_kph = sm["controlsState"].vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster
|
|
||||||
self.set_speed = set_speed_kph * (METER_TO_MILE / METER_TO_KM) if not ui_state.is_metric else set_speed_kph
|
|
||||||
|
|
||||||
def _render(self, rect: rl.Rectangle) -> None:
|
|
||||||
self._update_state()
|
|
||||||
|
|
||||||
rl.draw_rectangle(int(rect.x), int(rect.y), int(rect.width), int(rect.height), COLORS.bg_dark)
|
|
||||||
|
|
||||||
left_x = rect.x + CONTENT_MARGIN
|
|
||||||
|
|
||||||
if self.cruise_enabled:
|
|
||||||
unit = tr("MAX")
|
|
||||||
display_speed = self.set_speed
|
|
||||||
else:
|
|
||||||
unit = tr("km/h") if ui_state.is_metric else tr("MPH")
|
|
||||||
display_speed = self.current_speed
|
|
||||||
|
|
||||||
display_speed_text = str(round(display_speed))
|
|
||||||
if self.speed_limit_valid and display_speed > self.speed_limit:
|
|
||||||
speed_color = COLORS.red
|
|
||||||
else:
|
|
||||||
speed_color = COLORS.white
|
|
||||||
|
|
||||||
sign_width = min(SPEED_LIMIT_SIGN_WIDTH, rect.width * 0.30)
|
|
||||||
sign_height = VIENNA_SIGN_SIZE if ui_state.is_metric else MUTCD_SIGN_HEIGHT
|
|
||||||
|
|
||||||
has_upcoming_limit = self.next_speed_limit > 0 and self.next_speed_limit != self.speed_limit
|
|
||||||
target_sign_slide = 1.0 if has_upcoming_limit else 0.0
|
|
||||||
slide_speed = 3.0 * rl.get_frame_time()
|
|
||||||
if self._sign_slide < target_sign_slide:
|
|
||||||
self._sign_slide = min(self._sign_slide + slide_speed, target_sign_slide)
|
|
||||||
elif self._sign_slide > target_sign_slide:
|
|
||||||
self._sign_slide = max(self._sign_slide - slide_speed, target_sign_slide)
|
|
||||||
|
|
||||||
upcoming_width = int(sign_width * UPCOMING_SIGN_SIZE_RATIO)
|
|
||||||
upcoming_height = int(sign_height * UPCOMING_SIGN_SIZE_RATIO)
|
|
||||||
upcoming_reserved_width = int(upcoming_width * 0.85) + 5
|
|
||||||
sign_x_without_upcoming = rect.x + rect.width - sign_width - CONTENT_MARGIN
|
|
||||||
sign_x_with_upcoming = rect.x + rect.width - sign_width - CONTENT_MARGIN - upcoming_reserved_width
|
|
||||||
sign_x = sign_x_without_upcoming + (sign_x_with_upcoming - sign_x_without_upcoming) * self._sign_slide
|
|
||||||
sign_y = rect.y + (rect.height - sign_height) / 2
|
|
||||||
if not ui_state.is_metric and self.speed_limit_offset != 0 and self.speed_limit_valid:
|
|
||||||
sign_y += MUTCD_OFFSET_SIGN_Y_SHIFT
|
|
||||||
|
|
||||||
readout_right = sign_x - COLUMN_GAP
|
|
||||||
readout_width = max(1, readout_right - left_x)
|
|
||||||
road_y = rect.y + rect.height - 44
|
|
||||||
|
|
||||||
unit_font_size = self._fit_font_size(self._font_semi_bold, unit, readout_width, 46, UNIT_FONT_SIZE, 28)
|
|
||||||
speed_font_size = self._fit_font_size(self._font_bold, display_speed_text, readout_width, road_y - (rect.y + 54) - 8,
|
|
||||||
SPEED_FONT_SIZE, 76)
|
|
||||||
speed_size = measure_text_cached(self._font_bold, display_speed_text, speed_font_size)
|
|
||||||
speed_y = min(rect.y + 54, road_y - speed_size.y - 8)
|
|
||||||
unit_y = max(rect.y + 14, speed_y - unit_font_size - 6)
|
|
||||||
|
|
||||||
rl.draw_text_ex(self._font_semi_bold, unit, rl.Vector2(left_x, unit_y), unit_font_size, 0, COLORS.grey)
|
|
||||||
rl.draw_text_ex(self._font_bold, display_speed_text, rl.Vector2(left_x, speed_y), speed_font_size, 0, speed_color)
|
|
||||||
self._draw_road_name(left_x, road_y, readout_width)
|
|
||||||
|
|
||||||
if has_upcoming_limit and self._sign_slide > 0.01:
|
|
||||||
upcoming_speed_text = str(round(self.next_speed_limit))
|
|
||||||
distance_text = self._format_distance(self.next_speed_limit_distance)
|
|
||||||
upcoming_x = sign_x + sign_width - int(upcoming_width * UPCOMING_SIGN_OVERLAP_RATIO)
|
|
||||||
upcoming_y = sign_y + (sign_height - upcoming_height) / 2
|
|
||||||
|
|
||||||
upcoming_speed_color = COLORS.black
|
|
||||||
if ui_state.is_metric:
|
|
||||||
self._draw_vienna_sign(upcoming_x, upcoming_y, upcoming_width, upcoming_height, upcoming_speed_text, upcoming_speed_color, is_upcoming=True)
|
|
||||||
else:
|
|
||||||
self._draw_mutcd_sign(upcoming_x, upcoming_y, upcoming_width, upcoming_height, upcoming_speed_text, upcoming_speed_color, is_upcoming=True)
|
|
||||||
|
|
||||||
distance_font_size = self._fit_font_size(self._font_medium, distance_text, upcoming_width, 30, 24, 16)
|
|
||||||
distance_size = measure_text_cached(self._font_medium, distance_text, distance_font_size)
|
|
||||||
rl.draw_text_ex(self._font_medium, distance_text, rl.Vector2(upcoming_x + upcoming_width / 2 - distance_size.x / 2, upcoming_y + upcoming_height),
|
|
||||||
distance_font_size, 0, COLORS.grey)
|
|
||||||
|
|
||||||
self._draw_speed_limit_sign(sign_x, sign_y, sign_width, sign_height)
|
|
||||||
|
|
||||||
if self.speed_limit_offset != 0 and self.speed_limit_valid:
|
|
||||||
offset_text = str(abs(round(self.speed_limit_offset)))
|
|
||||||
badge_size = OFFSET_BADGE_SIZE
|
|
||||||
badge_rect = self._offset_badge_rect(rect, sign_x, sign_y, sign_width, sign_height, badge_size, has_upcoming_limit)
|
|
||||||
|
|
||||||
if ui_state.is_metric:
|
|
||||||
badge_radius = badge_size / 2
|
|
||||||
badge_center_x = badge_rect.x + badge_radius
|
|
||||||
badge_center_y = badge_rect.y + badge_radius
|
|
||||||
rl.draw_circle(int(badge_center_x), int(badge_center_y), badge_radius + 2, COLORS.dark_grey)
|
|
||||||
rl.draw_circle(int(badge_center_x), int(badge_center_y), badge_radius, COLORS.badge_bg)
|
|
||||||
self._draw_text_centered_fit(self._font_bold, offset_text, 32, rl.Vector2(badge_center_x, badge_center_y), COLORS.white,
|
|
||||||
badge_size - 10, badge_size - 8, min_size=24)
|
|
||||||
else:
|
|
||||||
rl.draw_rectangle_rounded(badge_rect, 0.25, 10, COLORS.badge_bg)
|
|
||||||
rl.draw_rectangle_rounded_lines_ex(badge_rect, 0.25, 10, 2, COLORS.dark_grey)
|
|
||||||
self._draw_text_centered_fit(self._font_bold, offset_text, 32, rl.Vector2(badge_rect.x + badge_size / 2, badge_rect.y + badge_size / 2),
|
|
||||||
COLORS.white, badge_size - 10, badge_size - 8, min_size=24)
|
|
||||||
|
|
||||||
scc_tag_x = min(left_x + speed_size.x + COLUMN_GAP, readout_right - SCC_TAG_WIDTH)
|
|
||||||
scc_tag_y = speed_y + (speed_size.y - (SCC_TAG_HEIGHT * 2 + SCC_TAG_GAP)) / 2
|
|
||||||
if scc_tag_x >= left_x + speed_size.x + 8:
|
|
||||||
self._draw_scc_icons(scc_tag_x, scc_tag_y, readout_right)
|
|
||||||
|
|
||||||
self._bookmark_icon.render(rect)
|
|
||||||
|
|
||||||
if ui_state.started:
|
|
||||||
alert_obj, no_alert = self._alert_renderer.will_render()
|
|
||||||
self._alert_alpha_filter.update(0 if no_alert else 1)
|
|
||||||
alpha = self._alert_alpha_filter.x
|
|
||||||
if alpha > 0.01:
|
|
||||||
rl.draw_rectangle(int(rect.x), int(rect.y), int(rect.width), int(rect.height), rl.Color(0, 0, 0, int(150 * alpha)))
|
|
||||||
self._alert_renderer.render(rect)
|
|
||||||
|
|
||||||
def _draw_scc_icons(self, x: float, y: float, right_limit: float) -> None:
|
|
||||||
sm = ui_state.sm
|
|
||||||
if not sm.valid["longitudinalPlanSP"]:
|
|
||||||
return
|
|
||||||
scc = sm["longitudinalPlanSP"].smartCruiseControl
|
|
||||||
|
|
||||||
drawn = 0
|
|
||||||
|
|
||||||
for label, active in [("SCC-V", scc.vision.active), ("SCC-M", scc.map.active)]:
|
|
||||||
if not active:
|
|
||||||
continue
|
|
||||||
tag_x = x
|
|
||||||
if tag_x + SCC_TAG_WIDTH > right_limit:
|
|
||||||
return
|
|
||||||
tag_y = y + drawn * (SCC_TAG_HEIGHT + SCC_TAG_GAP)
|
|
||||||
rl.draw_rectangle_rounded(rl.Rectangle(tag_x, tag_y, SCC_TAG_WIDTH, SCC_TAG_HEIGHT), 0.3, 10, COLORS.green)
|
|
||||||
self._draw_text_centered_fit(self._font_bold, label, 18, rl.Vector2(tag_x + SCC_TAG_WIDTH / 2, tag_y + SCC_TAG_HEIGHT / 2), COLORS.black,
|
|
||||||
SCC_TAG_WIDTH - 10, SCC_TAG_HEIGHT - 4, min_size=14)
|
|
||||||
drawn += 1
|
|
||||||
|
|
||||||
def _draw_speed_limit_sign(self, x: float, y: float, sign_width: float, sign_height: float) -> None:
|
|
||||||
speed_str = str(round(self.speed_limit)) if self.speed_limit_valid and self.speed_limit > 0 else "--"
|
|
||||||
speed_color = COLORS.black if not self.speed_limit_valid or self.current_speed <= self.speed_limit else COLORS.red
|
|
||||||
|
|
||||||
if ui_state.is_metric:
|
|
||||||
self._draw_vienna_sign(x, y, sign_width, sign_height, speed_str, speed_color, is_upcoming=False)
|
|
||||||
else:
|
|
||||||
self._draw_mutcd_sign(x, y, sign_width, sign_height, speed_str, speed_color, is_upcoming=False)
|
|
||||||
|
|
||||||
def _draw_road_name(self, x: float, y: float, width: float) -> None:
|
|
||||||
if width <= 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
road_display = self.road_name if self.road_name else "--"
|
|
||||||
font_size = self._fit_font_size(self._font_semi_bold, road_display, width, 38, ROAD_FONT_SIZE, 28)
|
|
||||||
road_size = measure_text_cached(self._font_semi_bold, road_display, font_size)
|
|
||||||
text_width = road_size.x
|
|
||||||
|
|
||||||
if text_width <= width:
|
|
||||||
self._marquee_offset = 0.0
|
|
||||||
self._marquee_direction = 1
|
|
||||||
self._marquee_pause_timer = 0.0
|
|
||||||
rl.draw_text_ex(self._font_semi_bold, road_display, rl.Vector2(x, y), font_size, 0, COLORS.white)
|
|
||||||
else:
|
|
||||||
overflow = text_width - width
|
|
||||||
dt = rl.get_frame_time()
|
|
||||||
|
|
||||||
if self._marquee_pause_timer > 0:
|
|
||||||
self._marquee_pause_timer -= dt
|
|
||||||
else:
|
|
||||||
self._marquee_offset += self._marquee_direction * self._marquee_speed * dt
|
|
||||||
|
|
||||||
if self._marquee_offset >= overflow:
|
|
||||||
self._marquee_offset = overflow
|
|
||||||
self._marquee_direction = -1
|
|
||||||
self._marquee_pause_timer = self._marquee_pause_duration
|
|
||||||
elif self._marquee_offset <= 0:
|
|
||||||
self._marquee_offset = 0
|
|
||||||
self._marquee_direction = 1
|
|
||||||
self._marquee_pause_timer = self._marquee_pause_duration
|
|
||||||
|
|
||||||
rl.begin_scissor_mode(int(x), int(y), int(width), int(road_size.y + 4))
|
|
||||||
text_pos = rl.Vector2(x - self._marquee_offset, y)
|
|
||||||
rl.draw_text_ex(self._font_semi_bold, road_display, text_pos, font_size, 0, COLORS.white)
|
|
||||||
rl.end_scissor_mode()
|
|
||||||
|
|
||||||
def _draw_vienna_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color, is_upcoming: bool = False) -> None:
|
|
||||||
center = rl.Vector2(x + width / 2, y + height / 2)
|
|
||||||
outer_radius = min(width, height) / 2
|
|
||||||
|
|
||||||
rl.draw_circle_v(center, outer_radius, COLORS.white)
|
|
||||||
ring_width = outer_radius * 0.18
|
|
||||||
rl.draw_ring(center, outer_radius - ring_width, outer_radius, 0, 360, 36, COLORS.red)
|
|
||||||
|
|
||||||
font_size = outer_radius * (0.7 if len(speed_str) >= 3 else 0.9)
|
|
||||||
self._draw_text_centered_fit(self._font_bold, speed_str, int(font_size), center, speed_color, width * 0.72, height * 0.50, min_size=24)
|
|
||||||
|
|
||||||
def _draw_mutcd_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color, is_upcoming: bool = False) -> None:
|
|
||||||
sign_rect = rl.Rectangle(x, y, width, height)
|
|
||||||
rl.draw_rectangle_rounded(sign_rect, 0.35, 10, COLORS.white)
|
|
||||||
|
|
||||||
inset = max(4, width * 0.05)
|
|
||||||
inner_rect = rl.Rectangle(x + inset, y + inset, width - inset * 2, height - inset * 2)
|
|
||||||
outer_radius = 0.35 * width / 2.0
|
|
||||||
inner_radius = outer_radius - inset
|
|
||||||
inner_roundness = inner_radius / (inner_rect.width / 2.0)
|
|
||||||
rl.draw_rectangle_rounded_lines_ex(inner_rect, inner_roundness, 10, 3, COLORS.black)
|
|
||||||
|
|
||||||
mid_x = x + width / 2
|
|
||||||
label_size = max(18, int(width * 0.26))
|
|
||||||
if is_upcoming:
|
|
||||||
self._draw_text_centered_fit(self._font_bold, tr("AHEAD"), int(width * 0.34), rl.Vector2(mid_x, y + height * 0.28), COLORS.black,
|
|
||||||
width * 0.94, height * 0.32, min_size=20)
|
|
||||||
else:
|
|
||||||
self._draw_text_centered_fit(self._font_bold, tr("SPEED"), label_size, rl.Vector2(mid_x, y + height * 0.20), COLORS.black,
|
|
||||||
width * 0.84, height * 0.24, min_size=16)
|
|
||||||
self._draw_text_centered_fit(self._font_bold, tr("LIMIT"), label_size, rl.Vector2(mid_x, y + height * 0.40), COLORS.black,
|
|
||||||
width * 0.84, height * 0.24, min_size=16)
|
|
||||||
|
|
||||||
speed_font_size = int(width * 0.60) if len(speed_str) >= 3 else int(width * 0.72)
|
|
||||||
self._draw_text_centered_fit(self._font_bold, speed_str, speed_font_size, rl.Vector2(mid_x, y + height * 0.72), speed_color,
|
|
||||||
width * 0.90, height * 0.52, min_size=32)
|
|
||||||
|
|
||||||
def _draw_text_centered(self, font, text, size, pos_center, color):
|
|
||||||
sz = measure_text_cached(font, text, size)
|
|
||||||
rl.draw_text_ex(font, text, rl.Vector2(pos_center.x - sz.x / 2, pos_center.y - sz.y / 2), size, 0, color)
|
|
||||||
|
|
||||||
def _draw_text_centered_fit(self, font, text, size, pos_center, color, max_width: float, max_height: float, min_size: int = 10):
|
|
||||||
size = self._fit_font_size(font, text, max_width, max_height, size, min_size)
|
|
||||||
self._draw_text_centered(font, text, size, pos_center, color)
|
|
||||||
|
|
||||||
def _fit_font_size(self, font, text: str, max_width: float, max_height: float, max_size: int | float, min_size: int) -> int:
|
|
||||||
size = int(max_size)
|
|
||||||
while size > min_size:
|
|
||||||
text_size = measure_text_cached(font, text, size)
|
|
||||||
if text_size.x <= max_width and text_size.y <= max_height:
|
|
||||||
return size
|
|
||||||
size -= 2
|
|
||||||
return min_size
|
|
||||||
|
|
||||||
def _offset_badge_rect(self, panel_rect: rl.Rectangle, sign_x: float, sign_y: float, sign_width: float, sign_height: float,
|
|
||||||
badge_size: float, has_upcoming_limit: bool) -> rl.Rectangle:
|
|
||||||
if ui_state.is_metric:
|
|
||||||
radius = min(sign_width, sign_height) / 2
|
|
||||||
center_x = sign_x + sign_width / 2
|
|
||||||
center_y = sign_y + sign_height / 2
|
|
||||||
badge_x_ratio = VIENNA_BADGE_UPCOMING_X_RATIO if has_upcoming_limit else VIENNA_BADGE_X_RATIO
|
|
||||||
badge_center_x = center_x + radius * badge_x_ratio
|
|
||||||
badge_center_y = center_y + radius * VIENNA_BADGE_Y_RATIO
|
|
||||||
badge_x = badge_center_x - badge_size / 2
|
|
||||||
badge_y = badge_center_y - badge_size / 2
|
|
||||||
else:
|
|
||||||
badge_x = sign_x + sign_width - badge_size * 0.45
|
|
||||||
badge_y = sign_y - badge_size * 0.75
|
|
||||||
|
|
||||||
return rl.Rectangle(
|
|
||||||
self._clamp(
|
|
||||||
badge_x,
|
|
||||||
panel_rect.x + OFFSET_BADGE_PANEL_PADDING,
|
|
||||||
panel_rect.x + panel_rect.width - badge_size - OFFSET_BADGE_PANEL_PADDING,
|
|
||||||
),
|
|
||||||
self._clamp(
|
|
||||||
badge_y,
|
|
||||||
panel_rect.y + OFFSET_BADGE_PANEL_PADDING,
|
|
||||||
panel_rect.y + panel_rect.height - badge_size - OFFSET_BADGE_PANEL_PADDING,
|
|
||||||
),
|
|
||||||
badge_size,
|
|
||||||
badge_size,
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _clamp(value: float, min_value: float, max_value: float) -> float:
|
|
||||||
return max(min_value, min(max_value, value))
|
|
||||||
|
|
||||||
def _format_distance(self, distance: float) -> str:
|
|
||||||
if ui_state.is_metric:
|
|
||||||
if distance < 50:
|
|
||||||
return tr("Near")
|
|
||||||
if distance >= 1000:
|
|
||||||
return f"{distance * METER_TO_KM:.1f}" + tr("km")
|
|
||||||
if distance < 200:
|
|
||||||
rounded = max(10, int(distance / 10) * 10)
|
|
||||||
else:
|
|
||||||
rounded = int(distance / 100) * 100
|
|
||||||
return str(rounded) + tr("m")
|
|
||||||
else:
|
|
||||||
distance_mi = distance * METER_TO_MILE
|
|
||||||
if distance_mi < 0.1:
|
|
||||||
return tr("Near")
|
|
||||||
return f"{distance_mi:.1f}" + tr("mi")
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
|
||||||
|
|
||||||
This file is part of sunnypilot and is licensed under the MIT License.
|
|
||||||
See the LICENSE.md file in the root directory for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView
|
|
||||||
|
|
||||||
|
|
||||||
class _SuppressedConfidenceBall:
|
|
||||||
def render(self, *_):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AugmentedRoadViewSP(AugmentedRoadView):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self._show_confidence_ball: bool = True
|
|
||||||
self._real_confidence_ball = self._confidence_ball
|
|
||||||
self._confidence_ball = _SuppressedConfidenceBall()
|
|
||||||
|
|
||||||
def set_show_confidence_ball(self, show: bool) -> None:
|
|
||||||
self._show_confidence_ball = show
|
|
||||||
|
|
||||||
def _render(self, _) -> None:
|
|
||||||
super()._render(_)
|
|
||||||
if self._show_confidence_ball:
|
|
||||||
self._real_confidence_ball.render(self.rect)
|
|
||||||
@@ -7,6 +7,7 @@ See the LICENSE.md file in the root directory for more details.
|
|||||||
import pyray as rl
|
import pyray as rl
|
||||||
|
|
||||||
from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer
|
from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer
|
||||||
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.selfdrive.ui.sunnypilot.onroad.blind_spot_indicators import BlindSpotIndicators
|
from openpilot.selfdrive.ui.sunnypilot.onroad.blind_spot_indicators import BlindSpotIndicators
|
||||||
|
|
||||||
|
|
||||||
@@ -21,6 +22,8 @@ class HudRendererSP(HudRenderer):
|
|||||||
|
|
||||||
def _render(self, rect: rl.Rectangle) -> None:
|
def _render(self, rect: rl.Rectangle) -> None:
|
||||||
super()._render(rect)
|
super()._render(rect)
|
||||||
|
if ui_state.usbgpu and not ui_state.usbgpu_compiled and ui_state.model_runner_tinygrad:
|
||||||
|
self._draw_model_source(rect)
|
||||||
self.blind_spot_indicators.render(rect)
|
self.blind_spot_indicators.render(rect)
|
||||||
|
|
||||||
def _has_blind_spot_detected(self) -> bool:
|
def _has_blind_spot_detected(self) -> bool:
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
import pyray as rl
|
|
||||||
|
|
||||||
from openpilot.common.test import OpenpilotTestCase
|
|
||||||
from openpilot.system.ui.lib.application import MouseEvent, MousePos, gui_app
|
|
||||||
from openpilot.system.ui.lib.scroll_panel2 import ScrollState
|
|
||||||
from openpilot.system.ui.widgets import Widget
|
|
||||||
from openpilot.system.ui.widgets import scroller as scroller_mod
|
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroll_panel_sp import GuiScrollPanel2SP
|
|
||||||
|
|
||||||
|
|
||||||
class DummyScrollIndicator:
|
|
||||||
def update(self, *_) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def render(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DummyWidget(Widget):
|
|
||||||
def __init__(self, rect: rl.Rectangle):
|
|
||||||
super().__init__()
|
|
||||||
self.set_rect(rect)
|
|
||||||
|
|
||||||
def _render(self, _) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _mouse_event(x: float, y: float, *, pressed: bool = False, released: bool = False,
|
|
||||||
down: bool = True, t: float = 0.0) -> MouseEvent:
|
|
||||||
return MouseEvent(MousePos(x, y), 0, pressed, released, down, t)
|
|
||||||
|
|
||||||
|
|
||||||
class TestScrollerSP(OpenpilotTestCase):
|
|
||||||
def test_vertical_snap_items_are_supported(self, monkeypatch):
|
|
||||||
monkeypatch.setattr(scroller_mod, "ScrollIndicator", DummyScrollIndicator)
|
|
||||||
|
|
||||||
scroller = scroller_mod._Scroller([], horizontal=False, snap_items=True, scroll_indicator=False)
|
|
||||||
scroller.set_rect(rl.Rectangle(0, 0, 100, 100))
|
|
||||||
scroller.scroll_panel.set_offset(-60)
|
|
||||||
|
|
||||||
captured_snap_target = None
|
|
||||||
|
|
||||||
def update(_, __, snap_target=None):
|
|
||||||
nonlocal captured_snap_target
|
|
||||||
captured_snap_target = snap_target
|
|
||||||
return scroller.scroll_panel.get_offset()
|
|
||||||
|
|
||||||
monkeypatch.setattr(scroller.scroll_panel, "update", update)
|
|
||||||
|
|
||||||
visible_items: list[Widget] = [
|
|
||||||
DummyWidget(rl.Rectangle(0, -60, 100, 100)),
|
|
||||||
DummyWidget(rl.Rectangle(0, 40, 100, 100)),
|
|
||||||
]
|
|
||||||
scroller._get_scroll(visible_items, 200)
|
|
||||||
|
|
||||||
assert captured_snap_target == -100
|
|
||||||
|
|
||||||
def test_scroll_panel_sp_rejects_orthogonal_drags(self, monkeypatch):
|
|
||||||
panel = GuiScrollPanel2SP(horizontal=True)
|
|
||||||
bounds = rl.Rectangle(0, 0, 100, 100)
|
|
||||||
|
|
||||||
monkeypatch.setattr(gui_app, "_mouse_events", [_mouse_event(10, 10, pressed=True, t=1.0)])
|
|
||||||
panel.update(bounds, 200)
|
|
||||||
assert panel.state == ScrollState.PRESSED
|
|
||||||
|
|
||||||
monkeypatch.setattr(gui_app, "_mouse_events", [_mouse_event(23, 60, t=1.1)])
|
|
||||||
panel.update(bounds, 200)
|
|
||||||
|
|
||||||
assert panel.state == ScrollState.STEADY
|
|
||||||
assert panel.get_offset() == 0
|
|
||||||
|
|
||||||
def test_scroll_panel_sp_can_disable_out_of_bounds_handling(self, monkeypatch):
|
|
||||||
panel = GuiScrollPanel2SP(horizontal=False, handle_out_of_bounds=False)
|
|
||||||
bounds = rl.Rectangle(0, 0, 100, 100)
|
|
||||||
monkeypatch.setattr(gui_app, "_mouse_events", [])
|
|
||||||
|
|
||||||
panel.set_offset(20)
|
|
||||||
panel.update(bounds, 200)
|
|
||||||
assert panel.get_offset() == 0
|
|
||||||
|
|
||||||
panel.set_offset(-150)
|
|
||||||
panel.update(bounds, 200)
|
|
||||||
assert panel.get_offset() == -100
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
|
||||||
|
|
||||||
This file is part of sunnypilot and is licensed under the MIT License.
|
|
||||||
See the LICENSE.md file in the root directory for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pyray as rl
|
|
||||||
from openpilot.system.ui.lib.application import MouseEvent
|
|
||||||
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2, ScrollState
|
|
||||||
|
|
||||||
|
|
||||||
class GuiScrollPanel2SP(GuiScrollPanel2):
|
|
||||||
"""Scroll panel behavior for nested Mici pagers."""
|
|
||||||
|
|
||||||
def __init__(self, horizontal: bool = True, handle_out_of_bounds: bool = True) -> None:
|
|
||||||
super().__init__(horizontal, handle_out_of_bounds=handle_out_of_bounds)
|
|
||||||
|
|
||||||
def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, bounds_size: float,
|
|
||||||
content_size: float) -> None:
|
|
||||||
state_before_update = self._state
|
|
||||||
super()._handle_mouse_event(mouse_event, bounds, bounds_size, content_size)
|
|
||||||
|
|
||||||
if self._state == ScrollState.MANUAL_SCROLL and state_before_update == ScrollState.PRESSED and \
|
|
||||||
self._initial_click_event is not None:
|
|
||||||
drag_x = abs(mouse_event.pos.x - self._initial_click_event.pos.x)
|
|
||||||
drag_y = abs(mouse_event.pos.y - self._initial_click_event.pos.y)
|
|
||||||
primary_drag = drag_x if self._horizontal else drag_y
|
|
||||||
cross_drag = drag_y if self._horizontal else drag_x
|
|
||||||
if cross_drag > primary_drag:
|
|
||||||
self._state = ScrollState.STEADY
|
|
||||||
self._velocity = 0.0
|
|
||||||
self._velocity_buffer.clear()
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
|
||||||
|
|
||||||
This file is part of sunnypilot and is licensed under the MIT License.
|
|
||||||
See the LICENSE.md file in the root directory for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from openpilot.system.ui.widgets.scroller import Scroller
|
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroll_panel_sp import GuiScrollPanel2SP
|
|
||||||
|
|
||||||
|
|
||||||
class ScrollerSP(Scroller):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
inner = self._scroller
|
|
||||||
inner.scroll_panel = GuiScrollPanel2SP(inner._horizontal, handle_out_of_bounds=not inner._snap_items)
|
|
||||||
@@ -4,15 +4,15 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
|||||||
This file is part of sunnypilot and is licensed under the MIT License.
|
This file is part of sunnypilot and is licensed under the MIT License.
|
||||||
See the LICENSE.md file in the root directory for more details.
|
See the LICENSE.md file in the root directory for more details.
|
||||||
"""
|
"""
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state, ChestnutState
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.sunnypilot.models.fetcher import get_cached_bundles
|
from openpilot.sunnypilot.models.fetcher import get_cached_bundles
|
||||||
from openpilot.sunnypilot.models.helpers import get_active_source, get_selected_bundle, resolve_bundle_by_ref
|
from openpilot.sunnypilot.models.helpers import get_active_source, get_selected_bundle, resolve_bundle_by_ref
|
||||||
from openpilot.sunnypilot.models.model_name import DEFAULT_BIG_MODEL, DEFAULT_MODEL
|
from openpilot.sunnypilot.models.model_name import DEFAULT_BIG_MODEL, DEFAULT_MODEL
|
||||||
|
|
||||||
|
|
||||||
def active_source() -> str:
|
def active_source() -> str:
|
||||||
return get_active_source(chestnut=ui_state.chestnut_present,
|
return get_active_source(usbgpu=ui_state.usbgpu,
|
||||||
chestnut_active=ui_state.chestnut_active, chestnut_loading=ui_state.chestnut_loading,
|
usbgpu_active=ui_state.usbgpu_active, usbgpu_loading=ui_state.usbgpu_loading,
|
||||||
offroad=ui_state.is_offroad())
|
offroad=ui_state.is_offroad())
|
||||||
|
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ def bundles_for_source(source: str):
|
|||||||
|
|
||||||
|
|
||||||
def default_model(source: str) -> str:
|
def default_model(source: str) -> str:
|
||||||
return DEFAULT_BIG_MODEL if source == 'chestnut' else DEFAULT_MODEL
|
return DEFAULT_BIG_MODEL if source == 'usbgpu' else DEFAULT_MODEL
|
||||||
|
|
||||||
|
|
||||||
def default_model_name(source: str) -> str:
|
def default_model_name(source: str) -> str:
|
||||||
@@ -31,10 +31,13 @@ def default_model_name(source: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def big_model_state() -> str | None:
|
def big_model_state() -> str | None:
|
||||||
"""'failed' | 'loading' | None, from the same state the icons render."""
|
"""'failed' | 'loading' | None, mirroring the sidebar's detection (#1969)."""
|
||||||
return {ChestnutState.UNCOMPILED: 'failed',
|
if ui_state.started and ui_state.usbgpu and ui_state.big_model_failed:
|
||||||
ChestnutState.FAILED: 'failed',
|
return 'failed'
|
||||||
ChestnutState.LOADING: 'loading'}.get(ui_state.chestnut_state)
|
big_selected = ui_state.usbgpu_compiled or ui_state.model_runner_tinygrad
|
||||||
|
if ui_state.usbgpu_loading or (big_selected and ui_state.started and ui_state.usbgpu_active is None):
|
||||||
|
return 'loading'
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def carrying_model() -> tuple[str | None, str | None, str | None]:
|
def carrying_model() -> tuple[str | None, str | None, str | None]:
|
||||||
@@ -42,14 +45,14 @@ def carrying_model() -> tuple[str | None, str | None, str | None]:
|
|||||||
when a Default big cannot carry, stock modeld runs the Default small, never the
|
when a Default big cannot carry, stock modeld runs the Default small, never the
|
||||||
small slot's pick; a custom big has no automatic fallback yet -> (None, None, None)."""
|
small slot's pick; a custom big has no automatic fallback yet -> (None, None, None)."""
|
||||||
source = active_source()
|
source = active_source()
|
||||||
if source == "chestnut":
|
if source == "usbgpu":
|
||||||
bundle = get_selected_bundle(ui_state.params, "chestnut")
|
bundle = get_selected_bundle(ui_state.params, "usbgpu")
|
||||||
if bundle:
|
if bundle:
|
||||||
return "chestnut", bundle.internalName, bundle.displayName
|
return "usbgpu", bundle.internalName, bundle.displayName
|
||||||
name = default_model_name("chestnut")
|
name = default_model_name("usbgpu")
|
||||||
return "chestnut", name, name
|
return "usbgpu", name, name
|
||||||
if ui_state.chestnut_present:
|
if ui_state.usbgpu:
|
||||||
if get_selected_bundle(ui_state.params, "chestnut") is None:
|
if get_selected_bundle(ui_state.params, "usbgpu") is None:
|
||||||
name = default_model_name("qcom")
|
name = default_model_name("qcom")
|
||||||
return "qcom", name, name
|
return "qcom", name, name
|
||||||
return None, None, None
|
return None, None, None
|
||||||
@@ -63,7 +66,7 @@ def carrying_model() -> tuple[str | None, str | None, str | None]:
|
|||||||
def queued_name(current_ref) -> str | None:
|
def queued_name(current_ref) -> str | None:
|
||||||
ref = ui_state.params.get("ModelManager_DownloadRef")
|
ref = ui_state.params.get("ModelManager_DownloadRef")
|
||||||
if ref and ref != current_ref:
|
if ref and ref != current_ref:
|
||||||
source_bundles = {source: bundles_for_source(source) for source in ("qcom", "chestnut")}
|
source_bundles = {source: bundles_for_source(source) for source in ("qcom", "usbgpu")}
|
||||||
if resolved := resolve_bundle_by_ref(ref, source_bundles):
|
if resolved := resolve_bundle_by_ref(ref, source_bundles):
|
||||||
return resolved[0].internalName
|
return resolved[0].internalName
|
||||||
return None
|
return None
|
||||||
@@ -76,7 +79,7 @@ def model_info() -> tuple[str, str, str]:
|
|||||||
manager republishes a tick after a chestnut change, so the stale bundle
|
manager republishes a tick after a chestnut change, so the stale bundle
|
||||||
would flash the wrong model."""
|
would flash the wrong model."""
|
||||||
source = active_source()
|
source = active_source()
|
||||||
other = "qcom" if source == "chestnut" else "chestnut"
|
other = "qcom" if source == "usbgpu" else "usbgpu"
|
||||||
active_bundle = get_selected_bundle(ui_state.params, source)
|
active_bundle = get_selected_bundle(ui_state.params, source)
|
||||||
other_bundle = get_selected_bundle(ui_state.params, other)
|
other_bundle = get_selected_bundle(ui_state.params, other)
|
||||||
|
|
||||||
|
|||||||
@@ -152,13 +152,10 @@ class UIStateSP:
|
|||||||
self.has_icbm = self.CP_SP.intelligentCruiseButtonManagementAvailable and self.params.get_bool("IntelligentCruiseButtonManagement")
|
self.has_icbm = self.CP_SP.intelligentCruiseButtonManagementAvailable and self.params.get_bool("IntelligentCruiseButtonManagement")
|
||||||
|
|
||||||
self._enforce_constraints()
|
self._enforce_constraints()
|
||||||
source = get_active_source(chestnut=self.chestnut_present, chestnut_active=self.chestnut_active,
|
source = get_active_source(usbgpu=self.usbgpu, usbgpu_active=self.usbgpu_active,
|
||||||
chestnut_loading=self.chestnut_loading, offroad=self.is_offroad())
|
usbgpu_loading=self.usbgpu_loading, offroad=self.is_offroad())
|
||||||
self.active_bundle = self.params.get(ACTIVE_BUNDLE_KEYS[source])
|
self.active_bundle = self.params.get(ACTIVE_BUNDLE_KEYS[source])
|
||||||
self.model_runner_tinygrad = self.active_bundle is not None and self.active_bundle.get("runner") == "tinygrad"
|
self.model_runner_tinygrad = self.active_bundle is not None and self.active_bundle.get("runner") == "tinygrad"
|
||||||
# stock only counts the default big model's compiled pkl. a downloaded big bundle runs on the
|
|
||||||
# chestnut just the same, so ChestnutState has to see it as available too.
|
|
||||||
self.chestnut_compiled = self.chestnut_compiled or self.model_runner_tinygrad
|
|
||||||
self.blindspot = self.params.get_bool("BlindSpot")
|
self.blindspot = self.params.get_bool("BlindSpot")
|
||||||
self.chevron_metrics = self.params.get("ChevronInfo")
|
self.chevron_metrics = self.params.get("ChevronInfo")
|
||||||
self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True)
|
self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True)
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ from openpilot.selfdrive.ui.layouts.main import MainLayout
|
|||||||
from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout
|
from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
|
|
||||||
if gui_app.sunnypilot_ui():
|
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.main import MiciMainLayoutSP as MiciMainLayout
|
|
||||||
|
|
||||||
BIG_UI = gui_app.big_ui()
|
BIG_UI = gui_app.big_ui()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ from openpilot.common.swaglog import cloudlog
|
|||||||
from openpilot.selfdrive.ui.lib.prime_state import PrimeState
|
from openpilot.selfdrive.ui.lib.prime_state import PrimeState
|
||||||
from openpilot.system.ui.lib.application import gui_app
|
from openpilot.system.ui.lib.application import gui_app
|
||||||
from openpilot.common.hardware import HARDWARE, PC
|
from openpilot.common.hardware import HARDWARE, PC
|
||||||
from openpilot.common.hardware.usb import TYPEC_CC_ORIENTATION_PATH, get_usb_state, is_chestnut_usb_id, read_int
|
from openpilot.selfdrive.modeld.helpers import usbgpu_compiled
|
||||||
from openpilot.selfdrive.modeld.helpers import chestnut_compiled
|
|
||||||
|
|
||||||
from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP, DeviceSP
|
from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP, DeviceSP
|
||||||
|
|
||||||
@@ -29,15 +28,6 @@ class UIStatus(Enum):
|
|||||||
LONG_ONLY = "long_only"
|
LONG_ONLY = "long_only"
|
||||||
|
|
||||||
|
|
||||||
class ChestnutState(Enum):
|
|
||||||
DISCONNECTED = "disconnected"
|
|
||||||
UNCOMPILED = "uncompiled"
|
|
||||||
READY = "ready"
|
|
||||||
LOADING = "loading"
|
|
||||||
ACTIVE = "active"
|
|
||||||
FAILED = "failed"
|
|
||||||
|
|
||||||
|
|
||||||
class UIState(UIStateSP):
|
class UIState(UIStateSP):
|
||||||
_instance: 'UIState | None' = None
|
_instance: 'UIState | None' = None
|
||||||
|
|
||||||
@@ -92,15 +82,10 @@ class UIState(UIStateSP):
|
|||||||
self.always_on_dm: bool = self.params.get_bool("AlwaysOnDM")
|
self.always_on_dm: bool = self.params.get_bool("AlwaysOnDM")
|
||||||
self.experimental_mode: bool = self.params.get_bool("ExperimentalMode")
|
self.experimental_mode: bool = self.params.get_bool("ExperimentalMode")
|
||||||
self.experimental_mode_confirmed: bool = self.params.get_bool("ExperimentalModeConfirmed")
|
self.experimental_mode_confirmed: bool = self.params.get_bool("ExperimentalModeConfirmed")
|
||||||
self.chestnut_present: bool = False
|
self.usbgpu: bool = False
|
||||||
self.chestnut_compiled: bool = chestnut_compiled()
|
self.usbgpu_compiled: bool = usbgpu_compiled()
|
||||||
self.chestnut_active: bool | None = None
|
self.usbgpu_active: bool | None = self.params.get("UsbGpuActive")
|
||||||
self.chestnut_loading: bool = False
|
self.usbgpu_loading: bool = self.params.get_bool("UsbGpuLoading")
|
||||||
self.usb_connected: bool = False
|
|
||||||
self.usb_connected_ts: float | None = None
|
|
||||||
self.usb_disconnected_ts: float | None = None
|
|
||||||
self.usb_unknown: bool = False
|
|
||||||
self.chestnut_state = ChestnutState.DISCONNECTED
|
|
||||||
self.started: bool = False
|
self.started: bool = False
|
||||||
self.ignition: bool = False
|
self.ignition: bool = False
|
||||||
self.recording_audio: bool = False
|
self.recording_audio: bool = False
|
||||||
@@ -127,6 +112,15 @@ class UIState(UIStateSP):
|
|||||||
def add_on_body_changed_callbacks(self, callback: Callable[[], None]):
|
def add_on_body_changed_callbacks(self, callback: Callable[[], None]):
|
||||||
self._on_body_changed_callbacks.append(callback)
|
self._on_body_changed_callbacks.append(callback)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def big_model_failed(self) -> bool:
|
||||||
|
# Mirrors the onroad HUD's four-condition check so sidebar and home icons reflect the same failure states
|
||||||
|
return (self.usbgpu_active is False or
|
||||||
|
not self.sm['deviceState'].chestnutPresent or
|
||||||
|
(self.usbgpu_active is True and self.sm.recv_frame['modelV2'] > self.started_frame and
|
||||||
|
not self.sm.alive['modelV2']) or
|
||||||
|
(self.usbgpu_active is None and self.sm.recv_frame['modelV2'] > self.started_frame))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def engaged(self) -> bool:
|
def engaged(self) -> bool:
|
||||||
return self.started and (self.sm["selfdriveState"].enabled or self.sm["selfdriveStateSP"].mads.enabled)
|
return self.started and (self.sm["selfdriveState"].enabled or self.sm["selfdriveStateSP"].mads.enabled)
|
||||||
@@ -146,7 +140,6 @@ class UIState(UIStateSP):
|
|||||||
self.sm.update(0)
|
self.sm.update(0)
|
||||||
self._update_state()
|
self._update_state()
|
||||||
self._update_status()
|
self._update_status()
|
||||||
self._update_chestnut_state()
|
|
||||||
device.update()
|
device.update()
|
||||||
UIStateSP.update(self)
|
UIStateSP.update(self)
|
||||||
|
|
||||||
@@ -210,35 +203,12 @@ class UIState(UIStateSP):
|
|||||||
self.status = UIStatus.DISENGAGED
|
self.status = UIStatus.DISENGAGED
|
||||||
self.started_frame = self.sm.frame
|
self.started_frame = self.sm.frame
|
||||||
self.started_time = time.monotonic()
|
self.started_time = time.monotonic()
|
||||||
self.chestnut_present = self.sm["deviceState"].chestnutPresent
|
|
||||||
|
|
||||||
for callback in self._offroad_transition_callbacks:
|
for callback in self._offroad_transition_callbacks:
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
self._started_prev = self.started
|
self._started_prev = self.started
|
||||||
|
|
||||||
def _update_chestnut_state(self) -> None:
|
|
||||||
detected = self.sm["deviceState"].chestnutPresent
|
|
||||||
if not self.started:
|
|
||||||
self.chestnut_present = detected
|
|
||||||
self.chestnut_state = (ChestnutState.READY if detected and self.chestnut_compiled else
|
|
||||||
ChestnutState.UNCOMPILED if detected else ChestnutState.DISCONNECTED)
|
|
||||||
return
|
|
||||||
|
|
||||||
model_seen = self.sm.recv_frame["modelV2"] > self.started_frame
|
|
||||||
if not self.chestnut_present:
|
|
||||||
self.chestnut_state = ChestnutState.DISCONNECTED
|
|
||||||
elif not self.chestnut_compiled:
|
|
||||||
self.chestnut_state = ChestnutState.UNCOMPILED
|
|
||||||
elif self.chestnut_state == ChestnutState.FAILED or not detected or (model_seen and (not self.sm.alive["modelV2"] or not self.sm["modelV2"].big)):
|
|
||||||
self.chestnut_state = ChestnutState.FAILED
|
|
||||||
elif self.chestnut_loading or not model_seen:
|
|
||||||
self.chestnut_state = ChestnutState.LOADING
|
|
||||||
elif self.chestnut_active is False:
|
|
||||||
self.chestnut_state = ChestnutState.FAILED
|
|
||||||
else:
|
|
||||||
self.chestnut_state = ChestnutState.ACTIVE
|
|
||||||
|
|
||||||
def update_params(self) -> None:
|
def update_params(self) -> None:
|
||||||
# For slower operations
|
# For slower operations
|
||||||
# Update longitudinal control state
|
# Update longitudinal control state
|
||||||
@@ -255,27 +225,12 @@ class UIState(UIStateSP):
|
|||||||
self.always_on_dm = self.params.get_bool("AlwaysOnDM")
|
self.always_on_dm = self.params.get_bool("AlwaysOnDM")
|
||||||
self.experimental_mode = self.params.get_bool("ExperimentalMode")
|
self.experimental_mode = self.params.get_bool("ExperimentalMode")
|
||||||
self.experimental_mode_confirmed = self.params.get_bool("ExperimentalModeConfirmed")
|
self.experimental_mode_confirmed = self.params.get_bool("ExperimentalModeConfirmed")
|
||||||
if not self.chestnut_compiled:
|
# keep usbgpu UI active until offroad transition when gpu disappears
|
||||||
self.chestnut_compiled = chestnut_compiled()
|
self.usbgpu = self.sm["deviceState"].chestnutPresent or (self.usbgpu and self.started)
|
||||||
self.chestnut_active = self.params.get("ChestnutActive")
|
if not self.usbgpu_compiled:
|
||||||
self.chestnut_loading = self.params.get_bool("ChestnutLoading")
|
self.usbgpu_compiled = usbgpu_compiled()
|
||||||
now = time.monotonic()
|
self.usbgpu_active = self.params.get("UsbGpuActive")
|
||||||
if read_int(TYPEC_CC_ORIENTATION_PATH) != 0:
|
self.usbgpu_loading = self.params.get_bool("UsbGpuLoading")
|
||||||
self.usb_disconnected_ts = None
|
|
||||||
if not self.usb_connected:
|
|
||||||
self.usb_connected = True
|
|
||||||
self.usb_connected_ts = now
|
|
||||||
self.usb_unknown = False
|
|
||||||
elif self.usb_connected_ts is not None and now - self.usb_connected_ts > 10.:
|
|
||||||
self.usb_unknown = not any(is_chestnut_usb_id(d["vendorId"], d["productId"], True) for d in get_usb_state())
|
|
||||||
self.usb_connected_ts = None
|
|
||||||
elif self.usb_connected:
|
|
||||||
if self.usb_disconnected_ts is None:
|
|
||||||
self.usb_disconnected_ts = now
|
|
||||||
elif now - self.usb_disconnected_ts > PARAM_UPDATE_TIME:
|
|
||||||
self.usb_connected = False
|
|
||||||
self.usb_connected_ts = None
|
|
||||||
self.usb_unknown = False
|
|
||||||
|
|
||||||
UIStateSP.update_params(self)
|
UIStateSP.update_params(self)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
MODEL_PATH = Path(__file__).parent / 'models/supercombo.onnx'
|
||||||
|
MODEL_PKL_PATH = Path(__file__).parent / 'models/supercombo_tinygrad.pkl'
|
||||||
|
METADATA_PATH = Path(__file__).parent / 'models/supercombo_metadata.pkl'
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def _patch_tinygrad_fetch_fw():
|
|||||||
helpers.fetch_fw = fetch_fw
|
helpers.fetch_fw = fetch_fw
|
||||||
_patch_tinygrad_fetch_fw()
|
_patch_tinygrad_fetch_fw()
|
||||||
|
|
||||||
import openpilot.selfdrive.modeld.compile_modeld as stock
|
from openpilot.selfdrive.modeld.compile_modeld import NV12Frame, make_frame_prepare, sample_desire, sample_skip, shift_and_sample
|
||||||
from tinygrad import dtypes
|
from tinygrad import dtypes
|
||||||
from tinygrad.device import Device
|
from tinygrad.device import Device
|
||||||
from tinygrad.engine.jit import TinyJit
|
from tinygrad.engine.jit import TinyJit
|
||||||
@@ -41,7 +41,8 @@ from tinygrad.tensor import Tensor
|
|||||||
MODEL_TYPES = ('vision_policy', 'supercombo', 'vision_multi_policy')
|
MODEL_TYPES = ('vision_policy', 'supercombo', 'vision_multi_policy')
|
||||||
WARP_INPUTS = ['tfm', 'big_tfm']
|
WARP_INPUTS = ['tfm', 'big_tfm']
|
||||||
POLICY_INPUTS = ['img_q', 'big_img_q', 'feat_q', 'desire_q', 'packed_npy_inputs']
|
POLICY_INPUTS = ['img_q', 'big_img_q', 'feat_q', 'desire_q', 'packed_npy_inputs']
|
||||||
nv12_copy_size = stock.nv12_copy_size
|
WARP_DEV = os.getenv('WARP_DEV')
|
||||||
|
|
||||||
|
|
||||||
def _detect_desire_key(shapes: dict) -> str | None:
|
def _detect_desire_key(shapes: dict) -> str | None:
|
||||||
return next((key for key in shapes if key.startswith('desire')), None)
|
return next((key for key in shapes if key.startswith('desire')), None)
|
||||||
@@ -138,7 +139,7 @@ def make_supercombo_input_queues(input_shapes: dict, frame_skip: int,
|
|||||||
return generate_queues_and_npy(input_shapes, frame_skip, device, is_supercombo=True)
|
return generate_queues_and_npy(input_shapes, frame_skip, device, is_supercombo=True)
|
||||||
|
|
||||||
|
|
||||||
def make_random_images(keys, shape, device, rng=None):
|
def make_random_images(keys, shape, device):
|
||||||
return {k: Tensor.randint(shape, low=0, high=256, dtype=dtypes.uint8, device=device).realize() for k in keys}
|
return {k: Tensor.randint(shape, low=0, high=256, dtype=dtypes.uint8, device=device).realize() for k in keys}
|
||||||
|
|
||||||
|
|
||||||
@@ -151,9 +152,24 @@ def make_warp_queues(device=Device.DEFAULT):
|
|||||||
return queues, npy
|
return queues, npy
|
||||||
|
|
||||||
|
|
||||||
|
def make_warp(nv12: NV12Frame, model_w: int, model_h: int):
|
||||||
|
frame_prepare = make_frame_prepare(nv12, model_w, model_h)
|
||||||
|
WARP_DEV = os.getenv('WARP_DEV', Device.DEFAULT)
|
||||||
|
|
||||||
|
def warp(tfm, big_tfm, frame, big_frame):
|
||||||
|
tfm = tfm.to(WARP_DEV)
|
||||||
|
big_tfm = big_tfm.to(WARP_DEV)
|
||||||
|
Tensor.realize(tfm, big_tfm)
|
||||||
|
|
||||||
|
warped_frame = frame_prepare(frame, tfm).unsqueeze(0)
|
||||||
|
warped_big_frame = frame_prepare(big_frame, big_tfm).unsqueeze(0)
|
||||||
|
return Tensor.cat(warped_frame, warped_big_frame)
|
||||||
|
return warp
|
||||||
|
|
||||||
|
|
||||||
def make_run_policy(vision_runner, policy_runners: list, features_slice: slice, frame_skip: int, input_shapes: dict):
|
def make_run_policy(vision_runner, policy_runners: list, features_slice: slice, frame_skip: int, input_shapes: dict):
|
||||||
sample_skip_fn = partial(stock.sample_skip, frame_skip=frame_skip)
|
sample_skip_fn = partial(sample_skip, frame_skip=frame_skip)
|
||||||
sample_desire_fn = partial(stock.sample_desire, frame_skip=frame_skip)
|
sample_desire_fn = partial(sample_desire, frame_skip=frame_skip)
|
||||||
|
|
||||||
desire_key = _detect_desire_key(input_shapes)
|
desire_key = _detect_desire_key(input_shapes)
|
||||||
road_key, wide_key = _detect_vision_keys(input_shapes)
|
road_key, wide_key = _detect_vision_keys(input_shapes)
|
||||||
@@ -170,14 +186,14 @@ def make_run_policy(vision_runner, policy_runners: list, features_slice: slice,
|
|||||||
warped_dev = warped.to(Device.DEFAULT)
|
warped_dev = warped.to(Device.DEFAULT)
|
||||||
Tensor.realize(packed_npy_inputs_dev, warped_dev)
|
Tensor.realize(packed_npy_inputs_dev, warped_dev)
|
||||||
|
|
||||||
img = stock.shift_and_sample(img_q, warped_dev[0:1], sample_skip_fn)
|
img = shift_and_sample(img_q, warped_dev[0:1], sample_skip_fn)
|
||||||
big_img = stock.shift_and_sample(big_img_q, warped_dev[1:2], sample_skip_fn)
|
big_img = shift_and_sample(big_img_q, warped_dev[1:2], sample_skip_fn)
|
||||||
|
|
||||||
unpacked_tensors = [tensor.reshape(shape) for tensor, shape in zip(packed_npy_inputs_dev.split(npy_sizes), npy_shapes.values(), strict=True)]
|
unpacked_tensors = [tensor.reshape(shape) for tensor, shape in zip(packed_npy_inputs_dev.split(npy_sizes), npy_shapes.values(), strict=True)]
|
||||||
unpacked_dict = dict(zip(npy_shapes.keys(), unpacked_tensors, strict=True))
|
unpacked_dict = dict(zip(npy_shapes.keys(), unpacked_tensors, strict=True))
|
||||||
|
|
||||||
desire_dev = unpacked_dict['desire']
|
desire_dev = unpacked_dict['desire']
|
||||||
desire_buf = stock.shift_and_sample(desire_q, desire_dev.reshape(1, 1, -1), sample_desire_fn)
|
desire_buf = shift_and_sample(desire_q, desire_dev.reshape(1, 1, -1), sample_desire_fn)
|
||||||
|
|
||||||
inputs = {desire_key: desire_buf}
|
inputs = {desire_key: desire_buf}
|
||||||
for key, tensor_val in unpacked_dict.items():
|
for key, tensor_val in unpacked_dict.items():
|
||||||
@@ -186,13 +202,13 @@ def make_run_policy(vision_runner, policy_runners: list, features_slice: slice,
|
|||||||
|
|
||||||
if 'prev_feat' in unpacked_dict:
|
if 'prev_feat' in unpacked_dict:
|
||||||
prev_feat_dev = unpacked_dict['prev_feat']
|
prev_feat_dev = unpacked_dict['prev_feat']
|
||||||
inputs['features_buffer'] = stock.shift_and_sample(feat_q, prev_feat_dev.reshape(1, 1, -1), sample_skip_fn).reshape(input_shapes['features_buffer'])
|
inputs['features_buffer'] = shift_and_sample(feat_q, prev_feat_dev.reshape(1, 1, -1), sample_skip_fn).reshape(input_shapes['features_buffer'])
|
||||||
|
|
||||||
if vision_runner:
|
if vision_runner:
|
||||||
vision_out_cast = next(iter(vision_runner({road_key: img, wide_key: big_img}).values())).cast('float32').realize()
|
vision_out_cast = next(iter(vision_runner({road_key: img, wide_key: big_img}).values())).cast('float32').realize()
|
||||||
if 'features_buffer' not in inputs:
|
if 'features_buffer' not in inputs:
|
||||||
new_feat = vision_out_cast[:, features_slice].reshape(1, -1).unsqueeze(0)
|
new_feat = vision_out_cast[:, features_slice].reshape(1, -1).unsqueeze(0)
|
||||||
inputs['features_buffer'] = stock.shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
|
inputs['features_buffer'] = shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
|
||||||
policy_outs = [next(iter(pol_runner(inputs).values())).cast('float32').realize() for pol_runner in policy_runners]
|
policy_outs = [next(iter(pol_runner(inputs).values())).cast('float32').realize() for pol_runner in policy_runners]
|
||||||
return (vision_out_cast, *policy_outs) if len(policy_outs) > 1 else (vision_out_cast, policy_outs[0])
|
return (vision_out_cast, *policy_outs) if len(policy_outs) > 1 else (vision_out_cast, policy_outs[0])
|
||||||
|
|
||||||
@@ -203,28 +219,27 @@ def make_run_policy(vision_runner, policy_runners: list, features_slice: slice,
|
|||||||
policy_out = next(iter(policy_runners[0](inputs).values())).cast('float32').realize()
|
policy_out = next(iter(policy_runners[0](inputs).values())).cast('float32').realize()
|
||||||
if 'features_buffer' not in inputs and features_slice is not None:
|
if 'features_buffer' not in inputs and features_slice is not None:
|
||||||
new_feat = policy_out[:, features_slice].reshape(1, -1).unsqueeze(0)
|
new_feat = policy_out[:, features_slice].reshape(1, -1).unsqueeze(0)
|
||||||
stock.shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
|
shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
|
||||||
return policy_out
|
return policy_out
|
||||||
|
|
||||||
return run_policy
|
return run_policy
|
||||||
|
|
||||||
|
|
||||||
def compile_jit(jit, input_keys, make_queues, make_random_inputs=None, benchmark_runs: int = 1):
|
def compile_jit(jit, make_random_inputs, input_keys, make_queues):
|
||||||
SEED = 42
|
SEED = 42
|
||||||
def random_inputs_run(fn, seed, n_runs, test_val=None, test_buffers=None, expect_match=True):
|
def random_inputs_run(fn, seed, test_val=None, test_buffers=None, expect_match=True):
|
||||||
queues_res = make_queues(Device.DEFAULT)
|
input_queues, npy = make_queues(Device.DEFAULT)
|
||||||
input_queues, npy = queues_res[0], queues_res[1]
|
|
||||||
frame_views = queues_res[2] if len(queues_res) > 2 else {}
|
|
||||||
rng = np.random.default_rng(seed)
|
rng = np.random.default_rng(seed)
|
||||||
Tensor.manual_seed(seed)
|
Tensor.manual_seed(seed)
|
||||||
|
|
||||||
|
testing = test_val is not None or test_buffers is not None
|
||||||
|
n_runs = 1 if testing else 3
|
||||||
|
|
||||||
for i in range(n_runs):
|
for i in range(n_runs):
|
||||||
for v in npy.values():
|
for v in npy.values():
|
||||||
v[:] = rng.standard_normal(v.shape).astype(v.dtype)
|
v[:] = rng.standard_normal(v.shape).astype(v.dtype)
|
||||||
for v in frame_views.values():
|
|
||||||
v[:] = rng.integers(0, 256, size=v.shape, dtype=np.uint8)
|
|
||||||
Device.default.synchronize()
|
Device.default.synchronize()
|
||||||
random_inputs = make_random_inputs(rng=rng) if make_random_inputs is not None else {}
|
random_inputs = make_random_inputs()
|
||||||
st = time.perf_counter()
|
st = time.perf_counter()
|
||||||
outs = fn(**{k: input_queues[k] for k in input_keys if k in input_queues}, **random_inputs)
|
outs = fn(**{k: input_queues[k] for k in input_keys if k in input_queues}, **random_inputs)
|
||||||
mt = time.perf_counter()
|
mt = time.perf_counter()
|
||||||
@@ -245,15 +260,14 @@ def compile_jit(jit, input_keys, make_queues, make_random_inputs=None, benchmark
|
|||||||
return val, buffers
|
return val, buffers
|
||||||
|
|
||||||
print('capture + replay')
|
print('capture + replay')
|
||||||
test_val, test_buffers = random_inputs_run(jit, SEED, 3)
|
test_val, test_buffers = random_inputs_run(jit, SEED)
|
||||||
print(f'pickle round trip ({benchmark_runs} runs per seed)')
|
print('pickle round trip')
|
||||||
with tempfile.TemporaryFile(dir=".") as f:
|
with tempfile.TemporaryFile(dir=".") as f:
|
||||||
dump_oob(jit, f)
|
dump_oob(jit, f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
loaded_jit = load_oob(f)
|
deserialized_jit = load_oob(f)
|
||||||
random_inputs_run(loaded_jit, SEED, benchmark_runs, test_val, test_buffers, expect_match=True)
|
random_inputs_run(deserialized_jit, SEED, test_val=test_val, test_buffers=test_buffers)
|
||||||
random_inputs_run(loaded_jit, SEED+1, benchmark_runs, test_val, test_buffers, expect_match=False)
|
return deserialized_jit
|
||||||
return jit
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_size(size_str: str) -> tuple[int, int]:
|
def _parse_size(size_str: str) -> tuple[int, int]:
|
||||||
@@ -284,7 +298,7 @@ def _load_policy_runners(args: argparse.Namespace) -> tuple[list, list]:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if 'USB' in os.getenv('DEV', '') or os.getenv('CHESTNUT'):
|
if 'USB' in os.getenv('DEV', '') or os.getenv('USBGPU'):
|
||||||
from openpilot.system.hardware.chestnut.flash import link_up
|
from openpilot.system.hardware.chestnut.flash import link_up
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
if link_up():
|
if link_up():
|
||||||
@@ -303,7 +317,6 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument('--model-size', type=_parse_size, required=True, help='model input WxH')
|
parser.add_argument('--model-size', type=_parse_size, required=True, help='model input WxH')
|
||||||
parser.add_argument('--camera-resolutions', type=_parse_size, nargs='+', required=True)
|
parser.add_argument('--camera-resolutions', type=_parse_size, nargs='+', required=True)
|
||||||
parser.add_argument('--frame-skip', type=int, default=None, help='frame skip value (auto-derived if not provided)')
|
parser.add_argument('--frame-skip', type=int, default=None, help='frame skip value (auto-derived if not provided)')
|
||||||
parser.add_argument('--benchmark-runs', type=int, default=1, help='benchmark runs')
|
|
||||||
parser.add_argument('--output', required=True)
|
parser.add_argument('--output', required=True)
|
||||||
|
|
||||||
parser.add_argument('--vision-onnx', help='vision ONNX (for split models)')
|
parser.add_argument('--vision-onnx', help='vision ONNX (for split models)')
|
||||||
@@ -322,64 +335,48 @@ if __name__ == "__main__":
|
|||||||
args.on_policy_onnx = read_file_chunked_to_disk(args.on_policy_onnx)
|
args.on_policy_onnx = read_file_chunked_to_disk(args.on_policy_onnx)
|
||||||
args.supercombo_onnx = read_file_chunked_to_disk(args.supercombo_onnx)
|
args.supercombo_onnx = read_file_chunked_to_disk(args.supercombo_onnx)
|
||||||
|
|
||||||
if args.model_type == 'supercombo':
|
vision_runner = OnnxRunner(args.vision_onnx) if args.vision_onnx else None
|
||||||
|
|
||||||
|
if args.model_type == 'vision_policy':
|
||||||
|
assert vision_runner and args.policy_onnx
|
||||||
|
policy_runners = [OnnxRunner(args.policy_onnx)]
|
||||||
|
output_data['metadata'] = {'vision': make_metadata_dict(args.vision_onnx), 'policy': make_metadata_dict(args.policy_onnx)}
|
||||||
|
elif args.model_type == 'supercombo':
|
||||||
assert args.supercombo_onnx
|
assert args.supercombo_onnx
|
||||||
model_metadata = make_metadata_dict(args.supercombo_onnx)
|
policy_runners = [OnnxRunner(args.supercombo_onnx)]
|
||||||
output_data['metadata'] = {'model': model_metadata, **model_metadata}
|
output_data['metadata'] = {'model': make_metadata_dict(args.supercombo_onnx)}
|
||||||
output_data['input_devices'] = {'model': Device.DEFAULT}
|
elif args.model_type == 'vision_multi_policy':
|
||||||
output_data['run_model'] = {}
|
assert vision_runner
|
||||||
derived_frame_skip = args.frame_skip or derive_frame_skip({}, model_metadata['input_shapes'])
|
policy_runners, policy_names = _load_policy_runners(args)
|
||||||
model_runner = OnnxRunner(args.supercombo_onnx)
|
output_data['metadata'] = {'vision': make_metadata_dict(args.vision_onnx)}
|
||||||
run_policy = stock.make_run_policy(model_runner, model_metadata, derived_frame_skip)
|
for name in policy_names:
|
||||||
for cam_w, cam_h in args.camera_resolutions:
|
runner_arg = getattr(args, f"{name}_onnx")
|
||||||
print(f"Compiling unified run_model JIT for {cam_w}x{cam_h}...")
|
output_data['metadata'][name] = make_metadata_dict(runner_arg)
|
||||||
nv12 = stock.NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
|
|
||||||
frame_copy_size = stock.nv12_copy_size(nv12.stride, nv12.y_height, nv12.uv_height)
|
|
||||||
make_model_queues = partial(stock.make_input_queues, model_metadata['input_shapes'], derived_frame_skip,
|
|
||||||
frame_copy_size=frame_copy_size)
|
|
||||||
warp = stock.make_warp(nv12, model_w, model_h)
|
|
||||||
run_model_jit = TinyJit(stock.make_run_model(warp, run_policy, model_metadata, frame_copy_size), prune=True)
|
|
||||||
output_data['run_model'][(cam_w, cam_h)] = compile_jit(run_model_jit, stock.MODELD_INPUTS, make_model_queues, benchmark_runs=args.benchmark_runs)
|
|
||||||
else:
|
|
||||||
vision_runner = OnnxRunner(args.vision_onnx) if args.vision_onnx else None
|
|
||||||
if args.model_type == 'vision_policy':
|
|
||||||
assert vision_runner and args.policy_onnx
|
|
||||||
policy_runners = [OnnxRunner(args.policy_onnx)]
|
|
||||||
output_data['metadata'] = {'vision': make_metadata_dict(args.vision_onnx), 'policy': make_metadata_dict(args.policy_onnx)}
|
|
||||||
elif args.model_type == 'vision_multi_policy':
|
|
||||||
assert vision_runner
|
|
||||||
policy_runners, policy_names = _load_policy_runners(args)
|
|
||||||
output_data['metadata'] = {'vision': make_metadata_dict(args.vision_onnx)}
|
|
||||||
for name in policy_names:
|
|
||||||
runner_arg = getattr(args, f"{name}_onnx")
|
|
||||||
output_data['metadata'][name] = make_metadata_dict(runner_arg)
|
|
||||||
|
|
||||||
policy_keys = [key for key in output_data['metadata'].keys() if key != 'vision']
|
policy_keys = [key for key in output_data['metadata'].keys() if key != 'vision']
|
||||||
first_policy_meta = output_data['metadata'][policy_keys[0]] if policy_keys else {}
|
first_policy_meta = output_data['metadata'][policy_keys[0]] if policy_keys else {}
|
||||||
vision_meta = output_data['metadata'].get('vision', {})
|
vision_meta = output_data['metadata'].get('vision', {})
|
||||||
|
|
||||||
derived_frame_skip = args.frame_skip or derive_frame_skip(vision_meta.get('input_shapes', {}), first_policy_meta.get('input_shapes', {}))
|
derived_frame_skip = args.frame_skip or derive_frame_skip(vision_meta.get('input_shapes', {}), first_policy_meta.get('input_shapes', {}))
|
||||||
all_shapes = {key: value for meta in output_data['metadata'].values() for key, value in meta['input_shapes'].items()}
|
all_shapes = {key: value for meta in output_data['metadata'].values() for key, value in meta['input_shapes'].items()}
|
||||||
feat_meta = output_data['metadata'].get('vision') or output_data['metadata'].get('policy')
|
feat_meta = output_data['metadata'].get('vision') or output_data['metadata'].get('model') or output_data['metadata'].get('policy')
|
||||||
assert feat_meta is not None
|
assert feat_meta is not None
|
||||||
features_slice = feat_meta['output_slices']['hidden_state']
|
features_slice = feat_meta['output_slices']['hidden_state']
|
||||||
|
is_supercombo = vision_runner is None
|
||||||
|
|
||||||
print(f"Compiling run_policy JIT (model_size={model_w}x{model_h}, frame_skip={derived_frame_skip})...")
|
print(f"Compiling run_policy JIT (model_size={model_w}x{model_h}, frame_skip={derived_frame_skip})...")
|
||||||
run_policy_func = make_run_policy(vision_runner, policy_runners, features_slice, derived_frame_skip, all_shapes)
|
run_policy_func = make_run_policy(vision_runner, policy_runners, features_slice, derived_frame_skip, all_shapes)
|
||||||
run_policy_jit = TinyJit(run_policy_func, prune=True)
|
run_policy_jit = TinyJit(run_policy_func, prune=True)
|
||||||
make_policy_queues = partial(generate_queues_and_npy, all_shapes, derived_frame_skip, is_supercombo=False)
|
make_policy_queues = partial(generate_queues_and_npy, all_shapes, derived_frame_skip, is_supercombo=is_supercombo)
|
||||||
make_random_model_inputs = partial(make_random_images, keys=['warped'], shape=(2, 6, model_h // 2, model_w // 2), device=Device.DEFAULT)
|
make_random_model_inputs = partial(make_random_images, keys=['warped'], shape=(2, 6, model_h // 2, model_w // 2), device=WARP_DEV)
|
||||||
output_data['run_policy'] = compile_jit(run_policy_jit, POLICY_INPUTS, make_policy_queues, make_random_inputs=make_random_model_inputs)
|
output_data['run_policy'] = compile_jit(run_policy_jit, make_random_model_inputs, POLICY_INPUTS, make_policy_queues)
|
||||||
|
|
||||||
for cam_w, cam_h in args.camera_resolutions:
|
for cam_w, cam_h in args.camera_resolutions:
|
||||||
print(f"Compiling warp JIT for {cam_w}x{cam_h}...")
|
print(f"Compiling warp JIT for {cam_w}x{cam_h}...")
|
||||||
nv12 = stock.NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
|
nv12 = NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
|
||||||
frame_copy_size = stock.nv12_copy_size(nv12.stride, nv12.y_height, nv12.uv_height)
|
make_random_warp_inputs = partial(make_random_images, keys=['frame', 'big_frame'], shape=nv12.size, device=WARP_DEV)
|
||||||
make_random_warp_inputs = partial(make_random_images, keys=['frame', 'big_frame'], shape=frame_copy_size, device=Device.DEFAULT)
|
warp = TinyJit(make_warp(nv12, model_w, model_h), prune=True)
|
||||||
warp = TinyJit(stock.make_warp(nv12, model_w, model_h), prune=True)
|
output_data[(cam_w, cam_h)] = compile_jit(warp, make_random_warp_inputs, WARP_INPUTS, make_warp_queues)
|
||||||
output_data[(cam_w, cam_h)] = compile_jit(warp, WARP_INPUTS, make_warp_queues, make_random_inputs=make_random_warp_inputs)
|
|
||||||
|
|
||||||
output_data['metadata']['warp_dev'] = Device.DEFAULT
|
|
||||||
|
|
||||||
with open(args.output, "wb") as file:
|
with open(args.output, "wb") as file:
|
||||||
dump_oob(output_data, file)
|
dump_oob(output_data, file)
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ class ModelConstants:
|
|||||||
|
|
||||||
# model inputs constants
|
# model inputs constants
|
||||||
MODEL_FREQ = 20
|
MODEL_FREQ = 20
|
||||||
MODEL_RUN_FREQ = 20
|
|
||||||
MODEL_CONTEXT_FREQ = 5
|
|
||||||
FEATURE_LEN = 512
|
FEATURE_LEN = 512
|
||||||
FULL_HISTORY_BUFFER_LEN = 99
|
FULL_HISTORY_BUFFER_LEN = 99
|
||||||
DESIRE_LEN = 8
|
DESIRE_LEN = 8
|
||||||
@@ -37,7 +35,6 @@ class ModelConstants:
|
|||||||
LANE_LINES_WIDTH = 2
|
LANE_LINES_WIDTH = 2
|
||||||
ROAD_EDGES_WIDTH = 2
|
ROAD_EDGES_WIDTH = 2
|
||||||
PLAN_WIDTH = 15
|
PLAN_WIDTH = 15
|
||||||
ACTION_WIDTH = 2
|
|
||||||
DESIRE_PRED_WIDTH = 8
|
DESIRE_PRED_WIDTH = 8
|
||||||
LAT_PLANNER_SOLUTION_WIDTH = 4
|
LAT_PLANNER_SOLUTION_WIDTH = 4
|
||||||
DESIRED_CURV_WIDTH = 1
|
DESIRED_CURV_WIDTH = 1
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
from openpilot.sunnypilot.modeld_v2.constants import Meta
|
from openpilot.sunnypilot.modeld_v2.constants import Meta
|
||||||
|
from openpilot.cereal import custom
|
||||||
from openpilot.sunnypilot.modeld_v2.meta_20hz import Meta20hz
|
from openpilot.sunnypilot.modeld_v2.meta_20hz import Meta20hz
|
||||||
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
||||||
|
|
||||||
|
ModelBundle = custom.ModelManagerSP.ModelBundle
|
||||||
|
|
||||||
|
|
||||||
def load_meta_constants():
|
def load_meta_constants():
|
||||||
|
"""
|
||||||
|
Determines and loads the appropriate meta model class based on the metadata provided. The function checks
|
||||||
|
specific keys and conditions within the provided metadata dictionary to identify the corresponding meta
|
||||||
|
model class to return.
|
||||||
|
|
||||||
|
:param model_metadata: Dictionary containing metadata about the model. It includes
|
||||||
|
details such as input shapes, output slices, and other configurations for identifying
|
||||||
|
metadata-dependent meta model classes.
|
||||||
|
:type model_metadata: dict
|
||||||
|
:return: The appropriate meta model class (Meta, MetaSimPose, or MetaTombRaider)
|
||||||
|
based on the conditions and metadata provided.
|
||||||
|
:rtype: type
|
||||||
|
"""
|
||||||
if (bundle := get_active_bundle()) and bundle.is20hz:
|
if (bundle := get_active_bundle()) and bundle.is20hz:
|
||||||
return Meta20hz
|
return Meta20hz
|
||||||
return Meta
|
|
||||||
|
return Meta # Default
|
||||||
|
|||||||
@@ -6,25 +6,24 @@ This file is part of sunnypilot and is licensed under the MIT License.
|
|||||||
See the LICENSE.md file in the root directory for more details.
|
See the LICENSE.md file in the root directory for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import Callable
|
|
||||||
import os
|
import os
|
||||||
os.environ['GMMU'] = '0'
|
os.environ['GMMU'] = '0'
|
||||||
import numpy as np
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
from setproctitle import setproctitle
|
|
||||||
from tinygrad.tensor import Tensor
|
|
||||||
|
|
||||||
import openpilot.cereal.messaging as messaging
|
|
||||||
from openpilot.common.hardware import COMMA_HARDWARE
|
from openpilot.common.hardware import COMMA_HARDWARE
|
||||||
from openpilot.selfdrive.modeld.helpers import chestnut_present, load_oob
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present, load_oob
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
import openpilot.cereal.messaging as messaging
|
||||||
from openpilot.cereal import log
|
from openpilot.cereal import log
|
||||||
from opendbc.car.structs import car
|
from opendbc.car.structs import car
|
||||||
from openpilot.cereal.services import SERVICE_LIST
|
from openpilot.cereal.services import SERVICE_LIST
|
||||||
|
from setproctitle import setproctitle
|
||||||
from openpilot.cereal.messaging import PubMaster, SubMaster
|
from openpilot.cereal.messaging import PubMaster, SubMaster
|
||||||
from openpilot.cereal.visionipc import VisionStreamType
|
from openpilot.cereal.visionipc import VisionStreamType
|
||||||
from msgq.visionipc import VisionIpcClient, VisionBuf
|
from msgq.visionipc import VisionIpcClient, VisionBuf
|
||||||
from opendbc.car.car_helpers import get_demo_car_params
|
from opendbc.car.car_helpers import get_demo_car_params
|
||||||
|
|
||||||
|
from tinygrad.tensor import Tensor
|
||||||
|
|
||||||
from openpilot.common.file_chunker import open_file_chunked
|
from openpilot.common.file_chunker import open_file_chunked
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
@@ -38,25 +37,18 @@ from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
|||||||
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value
|
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value
|
||||||
from openpilot.selfdrive.modeld.modeld import ChestnutState
|
from openpilot.selfdrive.modeld.modeld import ChestnutState
|
||||||
|
|
||||||
from openpilot.selfdrive.modeld.compile_modeld import (
|
|
||||||
MODELD_INPUTS,
|
|
||||||
make_input_queues as make_stock_input_queues,
|
|
||||||
)
|
|
||||||
from openpilot.sunnypilot.modeld_v2.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState, get_curvature_from_output
|
from openpilot.sunnypilot.modeld_v2.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState, get_curvature_from_output
|
||||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser
|
from openpilot.sunnypilot.modeld_v2.constants import Plan
|
||||||
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants, Plan
|
|
||||||
from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants
|
from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants
|
||||||
from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper
|
from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import (derive_frame_skip, make_split_input_queues,
|
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, make_split_input_queues, make_supercombo_input_queues, WARP_INPUTS, POLICY_INPUTS
|
||||||
make_supercombo_input_queues, nv12_copy_size,
|
|
||||||
WARP_INPUTS, POLICY_INPUTS)
|
|
||||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||||
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
||||||
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
||||||
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
|
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
|
||||||
|
|
||||||
PROCESS_NAME = "openpilot.selfdrive.modeld.modeld_tinygrad"
|
PROCESS_NAME = "openpilot.selfdrive.modeld.modeld_tinygrad"
|
||||||
BIG_MODEL_TIMEOUT = 60
|
|
||||||
|
|
||||||
|
|
||||||
def _pkl_exists(path):
|
def _pkl_exists(path):
|
||||||
@@ -76,7 +68,6 @@ def _find_driving_pkl(bundle):
|
|||||||
pkl_path = os.path.join(model_root, pkl_name)
|
pkl_path = os.path.join(model_root, pkl_name)
|
||||||
if _pkl_exists(pkl_path):
|
if _pkl_exists(pkl_path):
|
||||||
return pkl_path
|
return pkl_path
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class FrameMeta:
|
class FrameMeta:
|
||||||
@@ -93,14 +84,14 @@ class ModelState(ModelStateBase):
|
|||||||
inputs: dict[str, np.ndarray]
|
inputs: dict[str, np.ndarray]
|
||||||
prev_desire: np.ndarray
|
prev_desire: np.ndarray
|
||||||
|
|
||||||
def __init__(self, cam_w: int, cam_h: int, chestnut: bool = False):
|
def __init__(self, cam_w: int, cam_h: int, usbgpu: bool = False):
|
||||||
ModelStateBase.__init__(self)
|
ModelStateBase.__init__(self)
|
||||||
|
|
||||||
env_pkl = os.environ.get('COMBINED_MODEL_PKL')
|
env_pkl = os.environ.get('COMBINED_MODEL_PKL')
|
||||||
if env_pkl and os.path.exists(env_pkl):
|
if env_pkl and os.path.exists(env_pkl):
|
||||||
model_bundle = None
|
model_bundle = None
|
||||||
else:
|
else:
|
||||||
model_bundle = get_active_bundle(chestnut=chestnut)
|
model_bundle = get_active_bundle(usbgpu=usbgpu)
|
||||||
self.generation = model_bundle.generation if model_bundle is not None else None
|
self.generation = model_bundle.generation if model_bundle is not None else None
|
||||||
overrides = {override.key: override.value for override in model_bundle.overrides} if model_bundle else {}
|
overrides = {override.key: override.value for override in model_bundle.overrides} if model_bundle else {}
|
||||||
|
|
||||||
@@ -108,50 +99,46 @@ class ModelState(ModelStateBase):
|
|||||||
self.LONG_SMOOTH_SECONDS = float(overrides.get('long', ".0"))
|
self.LONG_SMOOTH_SECONDS = float(overrides.get('long', ".0"))
|
||||||
self.MIN_LAT_CONTROL_SPEED = 0.3
|
self.MIN_LAT_CONTROL_SPEED = 0.3
|
||||||
self.PLANPLUS_CONTROL: float = 1.0
|
self.PLANPLUS_CONTROL: float = 1.0
|
||||||
self.chestnut = chestnut
|
self.usbgpu = usbgpu
|
||||||
|
|
||||||
pkl_path = _find_driving_pkl(model_bundle)
|
pkl_path = _find_driving_pkl(model_bundle)
|
||||||
assert pkl_path is not None, f"No driving pkl found for {'chestnut' if chestnut else 'small model'} — all models must be compiled with compile_modeld.py"
|
assert pkl_path is not None, "No driving pkl found — all models must be compiled with compile_modeld.py"
|
||||||
self._init_combined(pkl_path, cam_w, cam_h, model_bundle)
|
self._init_combined(pkl_path, cam_w, cam_h, model_bundle)
|
||||||
|
|
||||||
def _init_combined(self, pkl_path, cam_w, cam_h, bundle):
|
def _init_combined(self, pkl_path, cam_w, cam_h, bundle):
|
||||||
cloudlog.warning(f"loading combined pkl: {pkl_path}")
|
cloudlog.warning(f"loading combined pkl: {pkl_path}")
|
||||||
jits = load_oob(open_file_chunked(pkl_path))
|
jits = load_oob(open_file_chunked(pkl_path))
|
||||||
|
|
||||||
metadata = jits['metadata']
|
self.WARP_DEV = 'QCOM' if COMMA_HARDWARE else 'CPU'
|
||||||
self.WARP_DEV = metadata.get('warp_dev', 'QCOM') if COMMA_HARDWARE else 'CPU'
|
self.DEV = 'AMD' if self.usbgpu else self.WARP_DEV
|
||||||
self.DEV = ('AMD' if self.chestnut else 'QCOM') if COMMA_HARDWARE else 'CPU'
|
|
||||||
self.QUEUE_DEV = self.DEV
|
self.QUEUE_DEV = self.DEV
|
||||||
self.is_run_model = 'run_model' in jits
|
metadata = jits['metadata']
|
||||||
|
|
||||||
nv12_info = get_nv12_info(cam_w, cam_h)
|
self.is_legacy_model = 'run_policy' not in jits # remove after next recompile
|
||||||
self.frame_copy_size = nv12_copy_size(*nv12_info[:3])
|
if self.is_legacy_model:
|
||||||
self.full_frames: dict = {}
|
self.warp = jits[(cam_w, cam_h)]['warp_enqueue']
|
||||||
self._blob_cache: dict = {}
|
self.run_policy = jits[(cam_w, cam_h)]['run_policy']
|
||||||
self.frame_buffers: dict = {}
|
else:
|
||||||
|
self.run_policy = jits['run_policy']
|
||||||
|
self.warp = jits[(cam_w, cam_h)]
|
||||||
|
|
||||||
if self.is_run_model or 'model' in metadata:
|
if 'model' in metadata:
|
||||||
model_metadata = metadata.get('model', metadata)
|
model_metadata = metadata['model']
|
||||||
self.input_shapes = model_metadata['input_shapes']
|
|
||||||
self.vision_output_slices = model_metadata['output_slices']
|
self.vision_output_slices = model_metadata['output_slices']
|
||||||
self.policy_output_slices = {}
|
self.policy_output_slices = {}
|
||||||
self._policy_slices_list = []
|
self._policy_slices_list = []
|
||||||
self._combined_model_type = 'supercombo'
|
self._combined_model_type = 'supercombo'
|
||||||
self._vision_input_names = [key for key in self.input_shapes if 'img' in key]
|
self._vision_input_names = [key for key in model_metadata['input_shapes'] if 'img' in key]
|
||||||
self.frame_skip = derive_frame_skip({}, self.input_shapes)
|
frame_skip = derive_frame_skip({}, model_metadata['input_shapes'])
|
||||||
if self.is_run_model:
|
self.input_queues, self.numpy_inputs = make_supercombo_input_queues(model_metadata['input_shapes'],
|
||||||
self.input_queues, self.numpy_inputs, self.frame_buffers = make_stock_input_queues(
|
frame_skip, device=self.QUEUE_DEV)
|
||||||
self.input_shapes, self.frame_skip, device=self.DEV, frame_copy_size=self.frame_copy_size)
|
|
||||||
self.frame_views, self.npy = self.frame_buffers, self.numpy_inputs
|
|
||||||
self.run_model, self.run_policy, self.warp = jits['run_model'][(cam_w, cam_h)], None, None
|
|
||||||
else:
|
|
||||||
self.input_queues, self.numpy_inputs = make_supercombo_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
|
|
||||||
self.run_model, self.run_policy, self.warp = None, jits['run_policy'], jits[(cam_w, cam_h)]
|
|
||||||
else:
|
else:
|
||||||
self.run_model, self.run_policy, self.warp = None, jits['run_policy'], jits[(cam_w, cam_h)]
|
|
||||||
vision_metadata = metadata['vision']
|
vision_metadata = metadata['vision']
|
||||||
policy_keys = [k for k in metadata if k not in ('vision', 'warp_dev')]
|
policy_keys = [k for k in metadata if k != 'vision']
|
||||||
self._combined_model_type = 'split' if policy_keys == ['policy'] else 'multi_policy'
|
if policy_keys == ['policy']:
|
||||||
|
self._combined_model_type = 'split'
|
||||||
|
else:
|
||||||
|
self._combined_model_type = 'multi_policy'
|
||||||
self.vision_output_slices = vision_metadata['output_slices']
|
self.vision_output_slices = vision_metadata['output_slices']
|
||||||
self._policy_keys = policy_keys
|
self._policy_keys = policy_keys
|
||||||
self._policy_slices_list = [metadata[k]['output_slices'] for k in policy_keys]
|
self._policy_slices_list = [metadata[k]['output_slices'] for k in policy_keys]
|
||||||
@@ -167,39 +154,57 @@ class ModelState(ModelStateBase):
|
|||||||
self._desire_key = next(key for key in self.numpy_inputs if key.startswith('desire'))
|
self._desire_key = next(key for key in self.numpy_inputs if key.startswith('desire'))
|
||||||
self._road_key = next(key for key in self._vision_input_names if 'big' not in key)
|
self._road_key = next(key for key in self._vision_input_names if 'big' not in key)
|
||||||
self._wide_key = next(key for key in self._vision_input_names if 'big' in key)
|
self._wide_key = next(key for key in self._vision_input_names if 'big' in key)
|
||||||
self.frame_buf_params = dict.fromkeys(self._vision_input_names, nv12_info)
|
|
||||||
|
|
||||||
is_20hz = bundle.is20hz if bundle else self._combined_model_type in ('split', 'multi_policy')
|
is_20hz = bundle.is20hz if bundle else self._combined_model_type in ('split', 'multi_policy')
|
||||||
if is_20hz:
|
if is_20hz:
|
||||||
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
|
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
|
||||||
self.constants = SplitModelConstants()
|
self.constants = SplitModelConstants()
|
||||||
else:
|
else:
|
||||||
|
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
|
||||||
self.constants = ModelConstants()
|
self.constants = ModelConstants()
|
||||||
|
|
||||||
self.parser = Parser()
|
if self._combined_model_type != 'supercombo':
|
||||||
self.prev_desire = np.zeros(self.constants.DESIRE_LEN, dtype=np.float32)
|
from openpilot.sunnypilot.modeld_v2.parse_model_outputs_split import Parser as SplitParser
|
||||||
|
self.parser = SplitParser()
|
||||||
|
else:
|
||||||
|
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser as CombinedParser
|
||||||
|
self.parser = CombinedParser()
|
||||||
|
|
||||||
if self.warp is not None:
|
self.prev_desire = np.zeros(self.constants.DESIRE_LEN, dtype=np.float32)
|
||||||
self.full_frames = {k: Tensor(np.zeros(nv12_info[3], dtype=np.uint8), device=self.WARP_DEV).contiguous().realize() for k in self._vision_input_names}
|
self.full_frames: dict = {}
|
||||||
self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=self.full_frames[self._road_key], big_frame=self.full_frames[self._wide_key])
|
self._blob_cache: dict = {}
|
||||||
|
nv12_info = get_nv12_info(cam_w, cam_h)
|
||||||
|
self.frame_buf_params = dict.fromkeys(self._vision_input_names, nv12_info)
|
||||||
|
|
||||||
|
yuv_size = self.frame_buf_params[self._road_key][3]
|
||||||
|
frame_tensor = Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize()
|
||||||
|
big_frame_tensor = Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize()
|
||||||
|
|
||||||
|
if self.is_legacy_model: # Remove this conditional hack after recompile
|
||||||
|
self.warp(**self.input_queues, frame=frame_tensor, big_frame=big_frame_tensor)
|
||||||
|
else:
|
||||||
|
self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=frame_tensor, big_frame=big_frame_tensor)
|
||||||
|
|
||||||
|
if self.usbgpu:
|
||||||
|
self.warmup()
|
||||||
|
|
||||||
def warmup(self) -> None:
|
def warmup(self) -> None:
|
||||||
dummy_size = self.frame_copy_size if self.is_run_model else self.frame_buf_params[self._road_key][3]
|
dummy_frames = {k: np.zeros(self.frame_buf_params[k][3], dtype=np.uint8) for k in self._vision_input_names}
|
||||||
dummy_frames = {k: np.zeros(dummy_size, dtype=np.uint8) for k in self._vision_input_names}
|
|
||||||
transforms = {k: np.eye(3, dtype=np.float32) for k in [self._road_key, self._wide_key] if k}
|
transforms = {k: np.eye(3, dtype=np.float32) for k in [self._road_key, self._wide_key] if k}
|
||||||
dummy_inputs = {k: np.zeros(v.shape, dtype=v.dtype) for k, v in self.numpy_inputs.items() if k not in ['tfm', 'big_tfm', 'prev_feat']}
|
|
||||||
self.run(dummy_frames, transforms, dummy_inputs)
|
dummy_inputs = {}
|
||||||
if self.is_run_model:
|
for k, v in self.numpy_inputs.items():
|
||||||
self.input_queues, self.numpy_inputs, self.frame_buffers = make_stock_input_queues(
|
if k not in ['tfm', 'big_tfm', 'prev_feat']:
|
||||||
self.input_shapes, self.frame_skip, device=self.DEV, frame_copy_size=self.frame_copy_size)
|
dummy_inputs[k] = np.zeros(v.shape, dtype=v.dtype)
|
||||||
self.frame_views = self.frame_buffers
|
|
||||||
self.npy = self.numpy_inputs
|
self.run(dummy_frames, transforms, dummy_inputs, prepare_only=False)
|
||||||
else:
|
|
||||||
for v in self.numpy_inputs.values():
|
for v in self.numpy_inputs.values():
|
||||||
v[:] = 0
|
v[:] = 0
|
||||||
self.full_frames.clear()
|
|
||||||
self._blob_cache.clear()
|
|
||||||
self.prev_desire[:] = 0
|
self.prev_desire[:] = 0
|
||||||
|
self.full_frames.clear()
|
||||||
|
self._blob_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mlsim(self) -> bool:
|
def mlsim(self) -> bool:
|
||||||
@@ -214,50 +219,45 @@ class ModelState(ModelStateBase):
|
|||||||
return self._desire_key
|
return self._desire_key
|
||||||
|
|
||||||
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
||||||
inputs: dict[str, np.ndarray],
|
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
|
||||||
after_enqueue: Callable[[], None] | None = None) -> dict[str, np.ndarray] | None:
|
for key in bufs.keys():
|
||||||
if self.is_run_model:
|
ptr = np.frombuffer(bufs[key].data, dtype=np.uint8).ctypes.data
|
||||||
for key, buf in bufs.items():
|
yuv_size = self.frame_buf_params[key][3]
|
||||||
data = buf.data if hasattr(buf, 'data') else buf
|
cache_key = (key, ptr)
|
||||||
np.copyto(self.frame_buffers[key], np.frombuffer(data, dtype=np.uint8, count=self.frame_copy_size))
|
if cache_key not in self._blob_cache:
|
||||||
else:
|
self._blob_cache[cache_key] = Tensor.from_blob(ptr, (yuv_size,), dtype='uint8', device=self.WARP_DEV)
|
||||||
for key, buf in bufs.items():
|
self.full_frames[key] = self._blob_cache[cache_key]
|
||||||
ptr = np.frombuffer(buf.data, dtype=np.uint8).ctypes.data
|
|
||||||
cache_key = (key, ptr)
|
|
||||||
if cache_key not in self._blob_cache:
|
|
||||||
self._blob_cache[cache_key] = Tensor.from_blob(ptr, (self.frame_buf_params[key][3],), dtype='uint8', device=self.WARP_DEV)
|
|
||||||
self.full_frames[key] = self._blob_cache[cache_key]
|
|
||||||
|
|
||||||
desire_key = self.desire_key
|
desire_key = self.desire_key
|
||||||
inputs[desire_key][0] = 0
|
inputs[desire_key][0] = 0
|
||||||
self.numpy_inputs[desire_key][:] = np.where(inputs[desire_key] - self.prev_desire > .99, inputs[desire_key], 0)
|
self.numpy_inputs[desire_key][:] = np.where(inputs[desire_key] - self.prev_desire > .99, inputs[desire_key], 0)
|
||||||
self.prev_desire[:] = inputs[desire_key]
|
self.prev_desire[:] = inputs[desire_key]
|
||||||
|
|
||||||
for key in ('traffic_convention', 'lateral_control_params', 'action_t'):
|
for key in ('traffic_convention', 'lateral_control_params', 'action_t'):
|
||||||
if key in self.numpy_inputs and key in inputs:
|
if key in self.numpy_inputs and key in inputs:
|
||||||
self.numpy_inputs[key][:] = inputs[key]
|
self.numpy_inputs[key][:] = inputs[key]
|
||||||
|
|
||||||
self.numpy_inputs['tfm'][:, :] = transforms[self._road_key].reshape(3, 3)
|
road_key = self._road_key
|
||||||
self.numpy_inputs['big_tfm'][:, :] = transforms[self._wide_key].reshape(3, 3)
|
wide_key = self._wide_key
|
||||||
|
self.numpy_inputs['tfm'][:, :] = transforms[road_key].reshape(3, 3)
|
||||||
|
self.numpy_inputs['big_tfm'][:, :] = transforms[wide_key].reshape(3, 3)
|
||||||
|
|
||||||
if self.run_model is not None:
|
if self.is_legacy_model: # remove after next recompile
|
||||||
outs, = self.run_model(**{k: self.input_queues[k] for k in MODELD_INPUTS})
|
if prepare_only:
|
||||||
raw_outputs = outs
|
self.warp(**self.input_queues, frame=self.full_frames[road_key], big_frame=self.full_frames[wide_key])
|
||||||
|
return None
|
||||||
|
raw_outputs = self.run_policy(**self.input_queues, frame=self.full_frames[road_key], big_frame=self.full_frames[wide_key])
|
||||||
else:
|
else:
|
||||||
assert self.warp is not None and self.run_policy is not None
|
if prepare_only:
|
||||||
warped = self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=self.full_frames[self._road_key], big_frame=self.full_frames[self._wide_key])
|
self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=self.full_frames[road_key], big_frame=self.full_frames[wide_key])
|
||||||
|
return None
|
||||||
|
warped = self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=self.full_frames[road_key], big_frame=self.full_frames[wide_key])
|
||||||
raw_outputs = self.run_policy(**{k: self.input_queues[k] for k in POLICY_INPUTS if k in self.input_queues}, warped=warped)
|
raw_outputs = self.run_policy(**{k: self.input_queues[k] for k in POLICY_INPUTS if k in self.input_queues}, warped=warped)
|
||||||
|
|
||||||
if after_enqueue is not None:
|
|
||||||
after_enqueue()
|
|
||||||
|
|
||||||
if self._combined_model_type == 'supercombo':
|
if self._combined_model_type == 'supercombo':
|
||||||
model_output = raw_outputs.numpy().flatten()
|
model_output = raw_outputs.numpy().flatten()
|
||||||
if self.chestnut and not np.all(np.isfinite(model_output)):
|
|
||||||
raise RuntimeError("model output not finite")
|
|
||||||
sliced = {k: model_output[np.newaxis, v] for k, v in self.vision_output_slices.items()}
|
sliced = {k: model_output[np.newaxis, v] for k, v in self.vision_output_slices.items()}
|
||||||
outputs = self.parser.parse_outputs(sliced)
|
outputs = self.parser.parse_outputs(sliced)
|
||||||
if 'prev_feat' in self.numpy_inputs and 'hidden_state' in self.vision_output_slices:
|
if 'prev_feat' in self.numpy_inputs:
|
||||||
self.numpy_inputs['prev_feat'][:] = model_output[self.vision_output_slices['hidden_state']]
|
self.numpy_inputs['prev_feat'][:] = model_output[self.vision_output_slices['hidden_state']]
|
||||||
else:
|
else:
|
||||||
vision_output = raw_outputs[0].numpy().flatten()
|
vision_output = raw_outputs[0].numpy().flatten()
|
||||||
@@ -287,6 +287,10 @@ class ModelState(ModelStateBase):
|
|||||||
buf[0, :-1] = buf[0, 1:]
|
buf[0, :-1] = buf[0, 1:]
|
||||||
buf[0, -1, :] = outputs['desired_curvature'][0, :] if not self.mlsim else 0
|
buf[0, -1, :] = outputs['desired_curvature'][0, :] if not self.mlsim else 0
|
||||||
|
|
||||||
|
if self.usbgpu and not np.all(np.isfinite(outputs.get('plan', np.array([0.])))):
|
||||||
|
cloudlog.error("model output not finite, dropping frame")
|
||||||
|
return None
|
||||||
|
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
def get_action_from_model(self, model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
|
def get_action_from_model(self, model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
|
||||||
@@ -323,13 +327,13 @@ def main(demo=False):
|
|||||||
setproctitle(PROCESS_NAME)
|
setproctitle(PROCESS_NAME)
|
||||||
config_realtime_process(7, 54)
|
config_realtime_process(7, 54)
|
||||||
|
|
||||||
CHESTNUT = chestnut_present()
|
USBGPU = usbgpu_present()
|
||||||
if CHESTNUT:
|
if USBGPU:
|
||||||
os.environ['HCQDEV_WAIT_TIMEOUT_MS'] = '3000'
|
os.environ['HCQDEV_WAIT_TIMEOUT_MS'] = '3000'
|
||||||
|
|
||||||
params = Params()
|
params = Params()
|
||||||
params.put_bool("ChestnutLoading", CHESTNUT)
|
params.put_bool("UsbGpuLoading", USBGPU)
|
||||||
params.remove("ChestnutActive")
|
params.remove("UsbGpuActive")
|
||||||
|
|
||||||
# visionipc clients
|
# visionipc clients
|
||||||
while True:
|
while True:
|
||||||
@@ -358,40 +362,31 @@ def main(demo=False):
|
|||||||
st = time.monotonic()
|
st = time.monotonic()
|
||||||
|
|
||||||
model = None
|
model = None
|
||||||
if CHESTNUT:
|
if USBGPU:
|
||||||
big_model = None
|
import threading
|
||||||
def load_big():
|
def load():
|
||||||
nonlocal big_model
|
nonlocal model
|
||||||
try:
|
model = ModelState(cam_w=vipc_client_main.width, cam_h=vipc_client_main.height, usbgpu=True)
|
||||||
m = ModelState(cam_w=vipc_client_main.width, cam_h=vipc_client_main.height, chestnut=True)
|
t = threading.Thread(target=load, daemon=True)
|
||||||
m.warmup()
|
t.start()
|
||||||
big_model = m
|
t.join(60)
|
||||||
except Exception:
|
|
||||||
cloudlog.exception("chestnut load failed")
|
|
||||||
loader = threading.Thread(target=load_big, daemon=True)
|
|
||||||
loader.start()
|
|
||||||
loader.join(BIG_MODEL_TIMEOUT)
|
|
||||||
model = big_model
|
|
||||||
if model is None:
|
if model is None:
|
||||||
params.put_bool("ChestnutModelError", True)
|
params.put_bool("UsbGpuActive", False)
|
||||||
params.put_bool("ChestnutActive", model is not None)
|
raise RuntimeError("eGPU model load failed or timed out (60s)")
|
||||||
if model is not None:
|
params.put_bool("UsbGpuActive", True)
|
||||||
params.remove("ChestnutModelError")
|
else:
|
||||||
|
model = ModelState(cam_w=vipc_client_main.width, cam_h=vipc_client_main.height, usbgpu=False)
|
||||||
|
|
||||||
small_model = ModelState(cam_w=vipc_client_main.width, cam_h=vipc_client_main.height, chestnut=False) if model is None or CHESTNUT else None
|
params.put_bool("UsbGpuLoading", False)
|
||||||
if model is None:
|
|
||||||
model = small_model
|
|
||||||
params.put_bool("ChestnutLoading", False)
|
|
||||||
assert model is not None
|
|
||||||
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
||||||
|
|
||||||
# messaging
|
# messaging
|
||||||
pub_socks = ["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"] + (["chestnutState"] if CHESTNUT else [])
|
pub_socks = ["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"] + (["chestnutState"] if USBGPU else [])
|
||||||
pm = PubMaster(pub_socks)
|
pm = PubMaster(pub_socks)
|
||||||
sm = SubMaster(["deviceState", "carState", "narrowRoadCameraState", "extrinsicsCalibration", "driverMonitoringState", "carControl", "lateralDelay"])
|
sm = SubMaster(["deviceState", "carState", "narrowRoadCameraState", "extrinsicsCalibration", "driverMonitoringState", "carControl", "lateralDelay"])
|
||||||
|
|
||||||
publish_state = PublishState()
|
publish_state = PublishState()
|
||||||
chestnut_state = ChestnutState(pm, model.chestnut) if CHESTNUT else None
|
chestnut_state = ChestnutState(pm, USBGPU) if USBGPU else None
|
||||||
|
|
||||||
# setup filter to track dropped frames
|
# setup filter to track dropped frames
|
||||||
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / model.constants.MODEL_FREQ)
|
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / model.constants.MODEL_FREQ)
|
||||||
@@ -490,6 +485,9 @@ def main(demo=False):
|
|||||||
run_count = run_count + 1
|
run_count = run_count + 1
|
||||||
|
|
||||||
frame_drop_ratio = frames_dropped / (1 + frames_dropped)
|
frame_drop_ratio = frames_dropped / (1 + frames_dropped)
|
||||||
|
prepare_only = vipc_dropped_frames > 0
|
||||||
|
if prepare_only:
|
||||||
|
cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames")
|
||||||
|
|
||||||
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
|
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
|
||||||
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
|
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
|
||||||
@@ -511,22 +509,7 @@ def main(demo=False):
|
|||||||
inputs['action_t'] = np.array([lat_action_t, long_action_t], dtype=np.float32)
|
inputs['action_t'] = np.array([lat_action_t, long_action_t], dtype=np.float32)
|
||||||
|
|
||||||
mt1 = time.perf_counter()
|
mt1 = time.perf_counter()
|
||||||
try:
|
model_output = model.run(bufs, transforms, inputs, prepare_only)
|
||||||
send_chestnut = (chestnut_state is not None and
|
|
||||||
run_count % round(model.constants.MODEL_FREQ / SERVICE_LIST['chestnutState'].frequency) == 0)
|
|
||||||
model_output = model.run(bufs, transforms, inputs, chestnut_state.send if send_chestnut else None)
|
|
||||||
except Exception:
|
|
||||||
if not params.get_bool("ChestnutActive"):
|
|
||||||
raise
|
|
||||||
cloudlog.exception("chestnut failed, falling back to small")
|
|
||||||
params.put_bool("ChestnutModelError", True)
|
|
||||||
params.put_bool("ChestnutActive", False)
|
|
||||||
assert small_model is not None
|
|
||||||
model = small_model
|
|
||||||
if chestnut_state is not None:
|
|
||||||
chestnut_state.big = False
|
|
||||||
run_count = 0
|
|
||||||
model_output = None
|
|
||||||
mt2 = time.perf_counter()
|
mt2 = time.perf_counter()
|
||||||
model_execution_time = mt2 - mt1
|
model_execution_time = mt2 - mt1
|
||||||
|
|
||||||
@@ -541,7 +524,7 @@ def main(demo=False):
|
|||||||
fill_model_msg(drivingdata_send, modelv2_send, model_output, action,
|
fill_model_msg(drivingdata_send, modelv2_send, model_output, action,
|
||||||
publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
|
publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
|
||||||
frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, live_calib_seen, meta_constants)
|
frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, live_calib_seen, meta_constants)
|
||||||
modelv2_send.modelV2.big = model.chestnut
|
modelv2_send.modelV2.big = model.usbgpu
|
||||||
|
|
||||||
desire_state = modelv2_send.modelV2.meta.desireState
|
desire_state = modelv2_send.modelV2.meta.desireState
|
||||||
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
|
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
|
||||||
@@ -562,6 +545,9 @@ def main(demo=False):
|
|||||||
pm.send('modelDataV2SP', mdv2sp_send)
|
pm.send('modelDataV2SP', mdv2sp_send)
|
||||||
last_vipc_frame_id = meta_main.frame_id
|
last_vipc_frame_id = meta_main.frame_id
|
||||||
|
|
||||||
|
if chestnut_state is not None and run_count % round(model.constants.MODEL_FREQ / SERVICE_LIST['chestnutState'].frequency) == 0:
|
||||||
|
chestnut_state.send()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -115,41 +115,22 @@ class Parser:
|
|||||||
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
|
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
|
||||||
|
|
||||||
def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
||||||
if 'plan' in outs:
|
# supercombo (4955 / 102) and newer variants (e.g. 990 / 144).
|
||||||
self.parse_mdn('plan', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH))
|
self.parse_mdn('plan', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH))
|
||||||
if 'planplus' in outs:
|
self.parse_mdn('lane_lines', outs, out_shape=(ModelConstants.NUM_LANE_LINES, ModelConstants.IDX_N, ModelConstants.LANE_LINES_WIDTH))
|
||||||
self.parse_mdn('planplus', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH))
|
self.parse_mdn('road_edges', outs, out_shape=(ModelConstants.NUM_ROAD_EDGES, ModelConstants.IDX_N, ModelConstants.LANE_LINES_WIDTH))
|
||||||
if 'lane_lines' in outs:
|
self.parse_mdn('pose', outs, out_shape=(ModelConstants.POSE_WIDTH,))
|
||||||
self.parse_mdn('lane_lines', outs, out_shape=(ModelConstants.NUM_LANE_LINES, ModelConstants.IDX_N, ModelConstants.LANE_LINES_WIDTH))
|
self.parse_mdn('road_transform', outs, out_shape=(ModelConstants.POSE_WIDTH,))
|
||||||
if 'road_edges' in outs:
|
|
||||||
self.parse_mdn('road_edges', outs, out_shape=(ModelConstants.NUM_ROAD_EDGES, ModelConstants.IDX_N, ModelConstants.LANE_LINES_WIDTH))
|
|
||||||
if 'pose' in outs:
|
|
||||||
self.parse_mdn('pose', outs, out_shape=(ModelConstants.POSE_WIDTH,))
|
|
||||||
if 'road_transform' in outs:
|
|
||||||
self.parse_mdn('road_transform', outs, out_shape=(ModelConstants.POSE_WIDTH,))
|
|
||||||
if 'sim_pose' in outs:
|
if 'sim_pose' in outs:
|
||||||
self.parse_mdn('sim_pose', outs, out_shape=(ModelConstants.POSE_WIDTH,))
|
self.parse_mdn('sim_pose', outs, out_shape=(ModelConstants.POSE_WIDTH,))
|
||||||
if 'wide_from_device_euler' in outs:
|
self.parse_mdn('wide_from_device_euler', outs, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,))
|
||||||
self.parse_mdn('wide_from_device_euler', outs, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,))
|
self.parse_mdn('lead', outs, out_shape=(ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH))
|
||||||
if 'lead' in outs:
|
|
||||||
self.parse_mdn('lead', outs, out_shape=(ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH))
|
|
||||||
if 'lat_planner_solution' in outs:
|
if 'lat_planner_solution' in outs:
|
||||||
self.parse_mdn('lat_planner_solution', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
|
self.parse_mdn('lat_planner_solution', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
|
||||||
if 'desired_curvature' in outs:
|
if 'desired_curvature' in outs:
|
||||||
self.parse_mdn('desired_curvature', outs, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
|
self.parse_mdn('desired_curvature', outs, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
|
||||||
if 'action' in outs:
|
|
||||||
self.parse_mdn('action', outs, out_shape=(ModelConstants.ACTION_WIDTH,))
|
|
||||||
for k in ['lead_prob', 'lane_lines_prob', 'meta']:
|
for k in ['lead_prob', 'lane_lines_prob', 'meta']:
|
||||||
if k in outs:
|
self.parse_binary_crossentropy(k, outs)
|
||||||
self.parse_binary_crossentropy(k, outs)
|
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
|
||||||
if 'desire_state' in outs:
|
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN, ModelConstants.DESIRE_PRED_WIDTH))
|
||||||
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
|
|
||||||
if 'desire_pred' in outs:
|
|
||||||
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN, ModelConstants.DESIRE_PRED_WIDTH))
|
|
||||||
return outs
|
return outs
|
||||||
|
|
||||||
def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
|
||||||
return self.parse_outputs(outs)
|
|
||||||
|
|
||||||
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
|
||||||
return self.parse_outputs(outs)
|
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import numpy as np
|
||||||
|
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
|
||||||
|
|
||||||
|
|
||||||
|
def safe_exp(x, out=None):
|
||||||
|
# -11 is around 10**14, more causes float16 overflow
|
||||||
|
return np.exp(np.clip(x, -np.inf, 11), out=out)
|
||||||
|
|
||||||
|
|
||||||
|
def sigmoid(x):
|
||||||
|
return 1. / (1. + safe_exp(-x))
|
||||||
|
|
||||||
|
|
||||||
|
def softmax(x, axis=-1):
|
||||||
|
x -= np.max(x, axis=axis, keepdims=True)
|
||||||
|
if x.dtype == np.float32 or x.dtype == np.float64:
|
||||||
|
safe_exp(x, out=x)
|
||||||
|
else:
|
||||||
|
x = safe_exp(x)
|
||||||
|
x /= np.sum(x, axis=axis, keepdims=True)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class Parser:
|
||||||
|
def __init__(self, ignore_missing=False):
|
||||||
|
self.ignore_missing = ignore_missing
|
||||||
|
|
||||||
|
def check_missing(self, outs, name):
|
||||||
|
if name not in outs and not self.ignore_missing:
|
||||||
|
raise ValueError(f"Missing output {name}")
|
||||||
|
return name not in outs
|
||||||
|
|
||||||
|
def parse_categorical_crossentropy(self, name, outs, out_shape=None):
|
||||||
|
if self.check_missing(outs, name):
|
||||||
|
return
|
||||||
|
raw = outs[name]
|
||||||
|
if out_shape is not None:
|
||||||
|
raw = raw.reshape((raw.shape[0],) + out_shape)
|
||||||
|
outs[name] = softmax(raw, axis=-1)
|
||||||
|
|
||||||
|
def parse_binary_crossentropy(self, name, outs):
|
||||||
|
if self.check_missing(outs, name):
|
||||||
|
return
|
||||||
|
raw = outs[name]
|
||||||
|
outs[name] = sigmoid(raw)
|
||||||
|
|
||||||
|
def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=None):
|
||||||
|
if self.check_missing(outs, name):
|
||||||
|
return
|
||||||
|
raw = outs[name]
|
||||||
|
raw = raw.reshape((raw.shape[0], max(in_N, 1), -1))
|
||||||
|
|
||||||
|
n_values = (raw.shape[2] - out_N)//2
|
||||||
|
pred_mu = raw[:,:,:n_values]
|
||||||
|
pred_std = safe_exp(raw[:,:,n_values: 2*n_values])
|
||||||
|
|
||||||
|
if in_N > 1:
|
||||||
|
weights = np.zeros((raw.shape[0], in_N, out_N), dtype=raw.dtype)
|
||||||
|
for i in range(out_N):
|
||||||
|
weights[:,:,i - out_N] = softmax(raw[:,:,i - out_N], axis=-1)
|
||||||
|
|
||||||
|
if out_N == 1:
|
||||||
|
for fidx in range(weights.shape[0]):
|
||||||
|
idxs = np.argsort(weights[fidx][:,0])[::-1]
|
||||||
|
weights[fidx] = weights[fidx][idxs]
|
||||||
|
pred_mu[fidx] = pred_mu[fidx][idxs]
|
||||||
|
pred_std[fidx] = pred_std[fidx][idxs]
|
||||||
|
assert out_shape is not None
|
||||||
|
full_shape = tuple([raw.shape[0], in_N] + list(out_shape))
|
||||||
|
outs[name + '_weights'] = weights
|
||||||
|
outs[name + '_hypotheses'] = pred_mu.reshape(full_shape)
|
||||||
|
outs[name + '_stds_hypotheses'] = pred_std.reshape(full_shape)
|
||||||
|
|
||||||
|
pred_mu_final = np.zeros((raw.shape[0], out_N, n_values), dtype=raw.dtype)
|
||||||
|
pred_std_final = np.zeros((raw.shape[0], out_N, n_values), dtype=raw.dtype)
|
||||||
|
for fidx in range(weights.shape[0]):
|
||||||
|
for hidx in range(out_N):
|
||||||
|
idxs = np.argsort(weights[fidx,:,hidx])[::-1]
|
||||||
|
pred_mu_final[fidx, hidx] = pred_mu[fidx, idxs[0]]
|
||||||
|
pred_std_final[fidx, hidx] = pred_std[fidx, idxs[0]]
|
||||||
|
else:
|
||||||
|
pred_mu_final = pred_mu
|
||||||
|
pred_std_final = pred_std
|
||||||
|
|
||||||
|
if out_N > 1:
|
||||||
|
assert out_shape is not None
|
||||||
|
final_shape = tuple([raw.shape[0], out_N] + list(out_shape))
|
||||||
|
else:
|
||||||
|
assert out_shape is not None
|
||||||
|
final_shape = tuple([raw.shape[0],] + list(out_shape))
|
||||||
|
outs[name] = pred_mu_final.reshape(final_shape)
|
||||||
|
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
|
||||||
|
|
||||||
|
def is_mhp(self, outs, name, shape):
|
||||||
|
if self.check_missing(outs, name):
|
||||||
|
return False
|
||||||
|
if outs[name].shape[1] == 2 * shape:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def parse_dynamic_outputs(self, outs: dict[str, np.ndarray]) -> None:
|
||||||
|
if 'lead' in outs:
|
||||||
|
lead_mhp = self.is_mhp(outs, 'lead',
|
||||||
|
SplitModelConstants.LEAD_MHP_SELECTION * SplitModelConstants.LEAD_TRAJ_LEN * SplitModelConstants.LEAD_WIDTH)
|
||||||
|
lead_in_N, lead_out_N = (SplitModelConstants.LEAD_MHP_N, SplitModelConstants.LEAD_MHP_SELECTION) if lead_mhp else (0, 0)
|
||||||
|
lead_out_shape = (SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH) if lead_mhp else \
|
||||||
|
(SplitModelConstants.LEAD_MHP_SELECTION, SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH)
|
||||||
|
self.parse_mdn('lead', outs, in_N=lead_in_N, out_N=lead_out_N, out_shape=lead_out_shape)
|
||||||
|
if 'plan' in outs:
|
||||||
|
plan_mhp = self.is_mhp(outs, 'plan', SplitModelConstants.IDX_N * SplitModelConstants.PLAN_WIDTH)
|
||||||
|
plan_in_N, plan_out_N = (SplitModelConstants.PLAN_MHP_N, SplitModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0)
|
||||||
|
self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N,
|
||||||
|
out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH))
|
||||||
|
if 'planplus' in outs:
|
||||||
|
self.parse_mdn('planplus', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH))
|
||||||
|
|
||||||
|
def split_outputs(self, outs: dict[str, np.ndarray]) -> None:
|
||||||
|
if 'desired_curvature' in outs:
|
||||||
|
self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.DESIRED_CURV_WIDTH,))
|
||||||
|
if 'desire_pred' in outs:
|
||||||
|
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(SplitModelConstants.DESIRE_PRED_LEN,SplitModelConstants.DESIRE_PRED_WIDTH))
|
||||||
|
if 'desire_state' in outs:
|
||||||
|
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(SplitModelConstants.DESIRE_PRED_WIDTH,))
|
||||||
|
if 'lane_lines' in outs:
|
||||||
|
self.parse_mdn('lane_lines', outs, in_N=0, out_N=0,
|
||||||
|
out_shape=(SplitModelConstants.NUM_LANE_LINES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
|
||||||
|
if 'lane_lines_prob' in outs:
|
||||||
|
self.parse_binary_crossentropy('lane_lines_prob', outs)
|
||||||
|
if 'lead_prob' in outs:
|
||||||
|
self.parse_binary_crossentropy('lead_prob', outs)
|
||||||
|
if 'lat_planner_solution' in outs:
|
||||||
|
self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
|
||||||
|
if 'meta' in outs:
|
||||||
|
self.parse_binary_crossentropy('meta', outs)
|
||||||
|
if 'road_edges' in outs:
|
||||||
|
self.parse_mdn('road_edges', outs, in_N=0, out_N=0,
|
||||||
|
out_shape=(SplitModelConstants.NUM_ROAD_EDGES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
|
||||||
|
if 'sim_pose' in outs:
|
||||||
|
self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
|
||||||
|
if 'action' in outs:
|
||||||
|
self.parse_mdn('action', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.ACTION_WIDTH,))
|
||||||
|
|
||||||
|
def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
||||||
|
self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
|
||||||
|
self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.WIDE_FROM_DEVICE_WIDTH,))
|
||||||
|
self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
|
||||||
|
self.parse_dynamic_outputs(outs)
|
||||||
|
self.split_outputs(outs)
|
||||||
|
return outs
|
||||||
|
|
||||||
|
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
||||||
|
self.parse_dynamic_outputs(outs)
|
||||||
|
self.split_outputs(outs)
|
||||||
|
return outs
|
||||||
|
|
||||||
|
def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
||||||
|
outs = self.parse_vision_outputs(outs)
|
||||||
|
outs = self.parse_policy_outputs(outs)
|
||||||
|
return outs
|
||||||
@@ -117,7 +117,7 @@ ARCHETYPES = {
|
|||||||
is_20hz=True,
|
is_20hz=True,
|
||||||
expected_model_type='split',
|
expected_model_type='split',
|
||||||
expected_constants_class=SplitModelConstants,
|
expected_constants_class=SplitModelConstants,
|
||||||
expected_parser_module='parse_model_outputs',
|
expected_parser_module='parse_model_outputs_split',
|
||||||
expected_desire_key='desire',
|
expected_desire_key='desire',
|
||||||
),
|
),
|
||||||
'vision_multi_policy': Archetype(
|
'vision_multi_policy': Archetype(
|
||||||
@@ -130,7 +130,7 @@ ARCHETYPES = {
|
|||||||
is_20hz=True,
|
is_20hz=True,
|
||||||
expected_model_type='multi_policy',
|
expected_model_type='multi_policy',
|
||||||
expected_constants_class=SplitModelConstants,
|
expected_constants_class=SplitModelConstants,
|
||||||
expected_parser_module='parse_model_outputs',
|
expected_parser_module='parse_model_outputs_split',
|
||||||
expected_desire_key='desire',
|
expected_desire_key='desire',
|
||||||
),
|
),
|
||||||
'tri_policy': Archetype(
|
'tri_policy': Archetype(
|
||||||
@@ -144,7 +144,7 @@ ARCHETYPES = {
|
|||||||
is_20hz=True,
|
is_20hz=True,
|
||||||
expected_model_type='multi_policy',
|
expected_model_type='multi_policy',
|
||||||
expected_constants_class=SplitModelConstants,
|
expected_constants_class=SplitModelConstants,
|
||||||
expected_parser_module='parse_model_outputs',
|
expected_parser_module='parse_model_outputs_split',
|
||||||
expected_desire_key='desire',
|
expected_desire_key='desire',
|
||||||
),
|
),
|
||||||
'supercombo_non20hz': Archetype(
|
'supercombo_non20hz': Archetype(
|
||||||
@@ -190,8 +190,8 @@ def tmp_path():
|
|||||||
|
|
||||||
def patch_modeld(monkeypatch):
|
def patch_modeld(monkeypatch):
|
||||||
def _patch(bundle):
|
def _patch(bundle):
|
||||||
monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None, *, chestnut=None: bundle)
|
monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None, *, usbgpu=None: bundle)
|
||||||
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None, *, chestnut=None: bundle)
|
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None, *, usbgpu=None: bundle)
|
||||||
|
|
||||||
return _patch
|
return _patch
|
||||||
|
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ class TestFindDrivingPkl(OpenpilotTestCase):
|
|||||||
class TestModelStateCombinedInit(OpenpilotTestCase):
|
class TestModelStateCombinedInit(OpenpilotTestCase):
|
||||||
def test_asserts_when_no_pkl(self, monkeypatch):
|
def test_asserts_when_no_pkl(self, monkeypatch):
|
||||||
bundle = DummyBundle(models=[], is_20hz=True)
|
bundle = DummyBundle(models=[], is_20hz=True)
|
||||||
monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None, *, chestnut=None: bundle)
|
monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None, *, usbgpu=None: bundle)
|
||||||
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None, *, chestnut=None: bundle)
|
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None, *, usbgpu=None: bundle)
|
||||||
with self.assertRaisesRegex(AssertionError, "No driving pkl found"):
|
with self.assertRaisesRegex(AssertionError, "No driving pkl found"):
|
||||||
ModelState(cam_w=CAM_W, cam_h=CAM_H)
|
ModelState(cam_w=CAM_W, cam_h=CAM_H)
|
||||||
|
|
||||||
@@ -75,11 +75,11 @@ class TestStockEquivalence(OpenpilotTestCase):
|
|||||||
|
|
||||||
frame_skip = derive_frame_skip(SPLIT_VISION_INPUT_SHAPES, SPLIT_POLICY_INPUT_SHAPES)
|
frame_skip = derive_frame_skip(SPLIT_VISION_INPUT_SHAPES, SPLIT_POLICY_INPUT_SHAPES)
|
||||||
stock_shapes = {**SPLIT_VISION_INPUT_SHAPES, **SPLIT_POLICY_INPUT_SHAPES, 'action_t': (1, 2)}
|
stock_shapes = {**SPLIT_VISION_INPUT_SHAPES, **SPLIT_POLICY_INPUT_SHAPES, 'action_t': (1, 2)}
|
||||||
stock_queues, stock_npy, _frame_views = make_input_queues(stock_shapes, frame_skip, device='NPY', frame_copy_size=49152)
|
stock_queues, stock_npy = make_input_queues(stock_shapes, frame_skip, device='NPY')
|
||||||
|
|
||||||
# sunnypilot split pipeline has tfm/big_tfm as queues (stock has them in npy only)
|
assert set(state.input_queues.keys()) == set(stock_queues.keys())
|
||||||
assert set(stock_queues.keys()) <= set(state.input_queues.keys())
|
|
||||||
assert {'desire', 'traffic_convention'} <= set(state.numpy_inputs.keys())
|
assert {'desire', 'traffic_convention'} <= set(state.numpy_inputs.keys())
|
||||||
|
assert set(state.numpy_inputs.keys()) == set(stock_npy.keys()) - {'action_t', 'prev_feat'}
|
||||||
|
|
||||||
def test_split_queue_keys_work_with_desire_key(self, model_state_factory):
|
def test_split_queue_keys_work_with_desire_key(self, model_state_factory):
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, make_split_input_queues
|
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, make_split_input_queues
|
||||||
@@ -103,23 +103,6 @@ class TestStockEquivalence(OpenpilotTestCase):
|
|||||||
assert state.vision_output_slices == arch.metadata_structure['vision']['output_slices']
|
assert state.vision_output_slices == arch.metadata_structure['vision']['output_slices']
|
||||||
assert state.policy_output_slices == arch.metadata_structure['policy']['output_slices']
|
assert state.policy_output_slices == arch.metadata_structure['policy']['output_slices']
|
||||||
|
|
||||||
def test_unified_run_model(self, tmp_path, monkeypatch, patch_modeld):
|
|
||||||
from openpilot.common.hardware import hw
|
|
||||||
from openpilot.selfdrive.modeld.helpers import dump_oob
|
|
||||||
shapes = {'img': (1, 12, 128, 256), 'big_img': (1, 12, 128, 256), 'features_buffer': (1, 24, 32, 512),
|
|
||||||
'desire_pulse': (1, 25, 8), 'traffic_convention': (1, 2), 'action_t': (1, 2)}
|
|
||||||
pkl_data = {'metadata': {'model': {'input_shapes': shapes, 'output_slices': {}}},
|
|
||||||
'run_model': {(CAM_W, CAM_H): tests_helpers._noop_jit}}
|
|
||||||
with open(tmp_path / 'driving_test_tinygrad.pkl', 'wb') as f:
|
|
||||||
dump_oob(pkl_data, f)
|
|
||||||
bundle = DummyBundle(models=[DummyModel('supercombo', 'driving_test_tinygrad.pkl')])
|
|
||||||
patch_modeld(bundle)
|
|
||||||
monkeypatch.setattr(hw.Paths, 'model_root', staticmethod(lambda: str(tmp_path)))
|
|
||||||
state = ModelState(cam_w=CAM_W, cam_h=CAM_H)
|
|
||||||
assert state.is_run_model and state.run_model is not None
|
|
||||||
assert state.run_policy is None and state.warp is None
|
|
||||||
assert 'img' in state.frame_views and 'big_img' in state.frame_views
|
|
||||||
|
|
||||||
|
|
||||||
ARCHETYPE_NAMES = list(ARCHETYPES.keys())
|
ARCHETYPE_NAMES = list(ARCHETYPES.keys())
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ See the LICENSE.md file in the root directory for more details.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -249,27 +248,23 @@ class TestStockCompileModeldEquivalence(OpenpilotTestCase):
|
|||||||
input_shapes = {
|
input_shapes = {
|
||||||
'img': (1, 12, 128, 256),
|
'img': (1, 12, 128, 256),
|
||||||
'desire_pulse': (1, 25, 8),
|
'desire_pulse': (1, 25, 8),
|
||||||
'features_buffer': (1, 24, 32, 512),
|
'features_buffer': (1, 24, 512), # when https://github.com/commaai/openpilot/pull/38681 merges, update to 1,24,32,512
|
||||||
'traffic_convention': (1, 2),
|
'traffic_convention': (1, 2),
|
||||||
'action_t': (1, 2),
|
'action_t': (1, 2),
|
||||||
}
|
}
|
||||||
frame_skip = 4
|
frame_skip = 4
|
||||||
|
|
||||||
stock_queues, stock_npy, _frame_views = stock_make_input_queues(input_shapes, frame_skip, device='NPY', frame_copy_size=49152)
|
stock_queues, stock_npy = stock_make_input_queues(input_shapes, frame_skip, device='NPY')
|
||||||
sunny_queues, sunny_npy = sunny_make_supercombo_input_queues(input_shapes, frame_skip, device='NPY')
|
sunny_queues, sunny_npy = sunny_make_supercombo_input_queues(input_shapes, frame_skip, device='NPY')
|
||||||
# sunnypilot split pipeline has tfm/big_tfm as queues; packed_npy_inputs size differs (different frame packing)
|
assert set(sunny_queues.keys()) == set(stock_queues.keys())
|
||||||
assert set(stock_queues.keys()) <= set(sunny_queues.keys())
|
|
||||||
for key in stock_queues:
|
for key in stock_queues:
|
||||||
if key == 'packed_npy_inputs':
|
|
||||||
continue
|
|
||||||
assert sunny_queues[key].shape == stock_queues[key].shape, \
|
assert sunny_queues[key].shape == stock_queues[key].shape, \
|
||||||
f"Queue shape mismatch for {key}: sunny {sunny_queues[key].shape} != stock {stock_queues[key].shape}"
|
f"Queue shape mismatch for {key}: sunny {sunny_queues[key].shape} != stock {stock_queues[key].shape}"
|
||||||
assert set(stock_npy.keys()) <= set(sunny_npy.keys())
|
assert set(sunny_npy.keys()) == set(stock_npy.keys())
|
||||||
for key in stock_npy:
|
for key in stock_npy:
|
||||||
assert sunny_npy[key].shape == stock_npy[key].shape, \
|
assert sunny_npy[key].shape == stock_npy[key].shape, \
|
||||||
f"Numpy array shape mismatch for {key}: sunny {sunny_npy[key].shape} != stock {stock_npy[key].shape}"
|
f"Numpy array shape mismatch for {key}: sunny {sunny_npy[key].shape} != stock {stock_npy[key].shape}"
|
||||||
|
|
||||||
@unittest.skip("upstream removed make_warp_input_queues — warp merged into run_model")
|
|
||||||
def test_make_warp_queues_stock_equivalence(self):
|
def test_make_warp_queues_stock_equivalence(self):
|
||||||
from openpilot.selfdrive.modeld.compile_modeld import make_warp_input_queues as stock_make_warp_queues
|
from openpilot.selfdrive.modeld.compile_modeld import make_warp_input_queues as stock_make_warp_queues
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import make_warp_queues as sunny_make_warp_queues
|
from openpilot.sunnypilot.modeld_v2.compile_modeld import make_warp_queues as sunny_make_warp_queues
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
|
||||||
|
|
||||||
This file is part of sunnypilot and is licensed under the MIT License.
|
|
||||||
See the LICENSE.md file in the root directory for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import io
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from openpilot.common.file_chunker import get_chunk_name
|
|
||||||
from openpilot.common.hardware import hw
|
|
||||||
from openpilot.common.test import OpenpilotTestCase
|
|
||||||
from openpilot.selfdrive.modeld.helpers import dump_oob
|
|
||||||
import openpilot.sunnypilot.modeld_v2.modeld as modeld_module
|
|
||||||
from openpilot.sunnypilot.modeld_v2.tests import helpers as tests_helpers
|
|
||||||
from openpilot.sunnypilot.modeld_v2.tests.helpers import DummyModel, DummyBundle, CAM_W, CAM_H
|
|
||||||
from openpilot.sunnypilot.models.fetcher import ModelParser, ModelFetcher
|
|
||||||
|
|
||||||
tmp_path = tests_helpers.tmp_path
|
|
||||||
|
|
||||||
|
|
||||||
class TestFallback(OpenpilotTestCase):
|
|
||||||
def test_find_dual_model_in_bundle(self, tmp_path, monkeypatch):
|
|
||||||
lebowski_file = 'driving_lebowski.pkl'
|
|
||||||
tsfdo_file = 'driving_tsfdo.pkl'
|
|
||||||
(tmp_path / lebowski_file).write_bytes(b'fkasdjfkljf')
|
|
||||||
(tmp_path / tsfdo_file).write_bytes(b'dskfajklsdjlsfka')
|
|
||||||
|
|
||||||
monkeypatch.setattr(hw.Paths, 'model_root', staticmethod(lambda: str(tmp_path)))
|
|
||||||
big_bundle = DummyBundle(models=[DummyModel('supercombo', lebowski_file)])
|
|
||||||
small_bundle = DummyBundle(models=[DummyModel('supercombo', tsfdo_file)])
|
|
||||||
big_pkl = modeld_module._find_driving_pkl(big_bundle)
|
|
||||||
small_pkl = modeld_module._find_driving_pkl(small_bundle)
|
|
||||||
|
|
||||||
assert big_pkl is not None and lebowski_file in big_pkl
|
|
||||||
assert small_pkl is not None and tsfdo_file in small_pkl
|
|
||||||
|
|
||||||
def test_download_models_and_init_modelstate_fallback(self, tmp_path, monkeypatch):
|
|
||||||
monkeypatch.setattr(hw.Paths, 'model_root', staticmethod(lambda: str(tmp_path)))
|
|
||||||
big_json = requests.get(ModelFetcher.MODEL_URL_CHESTNUT).json()
|
|
||||||
big_bundle = ModelParser.parse_models(big_json)[-1]
|
|
||||||
small_json = requests.get(ModelFetcher.MODEL_URL).json()
|
|
||||||
small_bundle = ModelParser.parse_models(small_json)[-1]
|
|
||||||
|
|
||||||
buf = io.BytesIO()
|
|
||||||
dump_oob(tests_helpers.make_pkl_data(tests_helpers.ARCHETYPES['supercombo_non20hz']), buf)
|
|
||||||
oob_bytes = buf.getvalue()
|
|
||||||
|
|
||||||
for bundle in (big_bundle, small_bundle):
|
|
||||||
artifact = bundle.models[0].artifact
|
|
||||||
for i in range(len(artifact.chunks)):
|
|
||||||
(tmp_path / get_chunk_name(artifact.fileName, i, len(artifact.chunks))).write_bytes(oob_bytes if i == 0 else b"")
|
|
||||||
|
|
||||||
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None, *, chestnut=None: small_bundle)
|
|
||||||
assert modeld_module.ModelState(CAM_W, CAM_H, chestnut=False).chestnut is False
|
|
||||||
|
|
||||||
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None, *, chestnut=None: big_bundle)
|
|
||||||
try:
|
|
||||||
assert modeld_module.ModelState(CAM_W, CAM_H, chestnut=True).chestnut is True
|
|
||||||
except Exception as e:
|
|
||||||
assert "AMD" in str(e) or "device" in str(e).lower()
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import numpy as np
|
|
||||||
from openpilot.common.test import OpenpilotTestCase
|
|
||||||
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
|
|
||||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser, _infer_mhp, sigmoid, softmax
|
|
||||||
|
|
||||||
|
|
||||||
class TestParseModelOutputs(OpenpilotTestCase):
|
|
||||||
def test_infer_mhp_lead(self):
|
|
||||||
in_hypotheses, out_selections = _infer_mhp(102, 24)
|
|
||||||
assert in_hypotheses == 2
|
|
||||||
assert out_selections == 3
|
|
||||||
|
|
||||||
def test_infer_mhp_plan(self):
|
|
||||||
in_hypotheses, out_selections = _infer_mhp(4955, 495)
|
|
||||||
assert in_hypotheses == 5
|
|
||||||
assert out_selections == 1
|
|
||||||
|
|
||||||
def test_infer_mhp_non_mdn(self):
|
|
||||||
in_hypotheses, out_selections = _infer_mhp(48, 24)
|
|
||||||
assert in_hypotheses == 1
|
|
||||||
assert out_selections == 0
|
|
||||||
|
|
||||||
def test_check_missing_raises(self):
|
|
||||||
parser = Parser(ignore_missing=False)
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
parser.check_missing({}, "missing_key")
|
|
||||||
|
|
||||||
def test_check_missing_ignored(self):
|
|
||||||
parser = Parser(ignore_missing=True)
|
|
||||||
assert parser.check_missing({}, "missing_key") is True
|
|
||||||
|
|
||||||
def test_binary_crossentropy(self):
|
|
||||||
parser = Parser()
|
|
||||||
raw_logits = np.array([[-10.0, 0.0, 10.0]], dtype=np.float32)
|
|
||||||
outs = {"meta": raw_logits.copy()}
|
|
||||||
parser.parse_binary_crossentropy("meta", outs)
|
|
||||||
expected_probabilities = sigmoid(raw_logits)
|
|
||||||
np.testing.assert_allclose(outs["meta"], expected_probabilities, rtol=1e-5, atol=1e-6)
|
|
||||||
|
|
||||||
def test_categorical_crossentropy(self):
|
|
||||||
parser = Parser()
|
|
||||||
raw_logits = np.array([[1.0, 2.0, 3.0]], dtype=np.float32)
|
|
||||||
outs = {"desire_state": raw_logits.copy()}
|
|
||||||
parser.parse_categorical_crossentropy("desire_state", outs)
|
|
||||||
expected_probabilities = softmax(raw_logits)
|
|
||||||
np.testing.assert_allclose(outs["desire_state"], expected_probabilities, rtol=1e-5, atol=1e-6)
|
|
||||||
|
|
||||||
def test_parse_vision_outputs(self):
|
|
||||||
parser = Parser()
|
|
||||||
pose_raw = np.zeros((1, ModelConstants.POSE_WIDTH * 2), dtype=np.float32)
|
|
||||||
road_transform_raw = np.zeros((1, ModelConstants.POSE_WIDTH * 2), dtype=np.float32)
|
|
||||||
lead_raw = np.zeros((1, 102), dtype=np.float32)
|
|
||||||
meta_raw = np.zeros((1, 55), dtype=np.float32)
|
|
||||||
vision_outputs = {"pose": pose_raw, "road_transform": road_transform_raw, "lead": lead_raw, "meta": meta_raw}
|
|
||||||
parsed = parser.parse_vision_outputs(vision_outputs)
|
|
||||||
assert "pose" in parsed
|
|
||||||
assert "road_transform" in parsed
|
|
||||||
assert "lead" in parsed
|
|
||||||
assert "meta" in parsed
|
|
||||||
assert parsed["pose"].shape == (1, ModelConstants.POSE_WIDTH)
|
|
||||||
assert parsed["lead"].shape == (1, ModelConstants.LEAD_MHP_SELECTION, ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH)
|
|
||||||
|
|
||||||
def test_parse_policy_outputs(self):
|
|
||||||
parser = Parser()
|
|
||||||
plan_raw = np.zeros((1, 4955), dtype=np.float32)
|
|
||||||
desire_state_raw = np.zeros((1, ModelConstants.DESIRE_PRED_WIDTH), dtype=np.float32)
|
|
||||||
action_raw = np.zeros((1, ModelConstants.ACTION_WIDTH * 2), dtype=np.float32)
|
|
||||||
policy_outputs = {"plan": plan_raw, "desire_state": desire_state_raw, "action": action_raw}
|
|
||||||
parsed = parser.parse_policy_outputs(policy_outputs)
|
|
||||||
assert parsed["plan"].shape == (1, ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)
|
|
||||||
assert parsed["action"].shape == (1, ModelConstants.ACTION_WIDTH)
|
|
||||||
assert parsed["desire_state"].shape == (1, ModelConstants.DESIRE_PRED_WIDTH)
|
|
||||||
|
|
||||||
def test_parse_outputs_combined(self):
|
|
||||||
parser = Parser()
|
|
||||||
outputs = {"plan": np.zeros((1, 4955), dtype=np.float32), "pose": np.zeros((1, ModelConstants.POSE_WIDTH * 2),
|
|
||||||
dtype=np.float32), "meta": np.zeros((1, 55), dtype=np.float32)}
|
|
||||||
parsed = parser.parse_outputs(outputs)
|
|
||||||
assert parsed["plan"].shape == (1, ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)
|
|
||||||
assert parsed["pose"].shape == (1, ModelConstants.POSE_WIDTH)
|
|
||||||
assert parsed["meta"].shape == (1, 55)
|
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def index_function(idx, max_val=192, max_idx=32):
|
||||||
|
return max_val * ((idx/max_idx)**2)
|
||||||
|
|
||||||
|
|
||||||
|
class ModelConstants:
|
||||||
|
# time and distance indices
|
||||||
|
IDX_N = 33
|
||||||
|
T_IDXS = [index_function(idx, max_val=10.0) for idx in range(IDX_N)]
|
||||||
|
X_IDXS = [index_function(idx, max_val=192.0) for idx in range(IDX_N)]
|
||||||
|
LEAD_T_IDXS = [0., 2., 4., 6., 8., 10.]
|
||||||
|
LEAD_T_OFFSETS = [0., 2., 4.]
|
||||||
|
META_T_IDXS = [2., 4., 6., 8., 10.]
|
||||||
|
|
||||||
|
# model inputs constants
|
||||||
|
MODEL_FREQ = 20
|
||||||
|
FEATURE_LEN = 512
|
||||||
|
HISTORY_BUFFER_LEN = 99
|
||||||
|
DESIRE_LEN = 8
|
||||||
|
TRAFFIC_CONVENTION_LEN = 2
|
||||||
|
NAV_FEATURE_LEN = 256
|
||||||
|
NAV_INSTRUCTION_LEN = 150
|
||||||
|
LAT_PLANNER_STATE_LEN = 4
|
||||||
|
LATERAL_CONTROL_PARAMS_LEN = 2
|
||||||
|
PREV_DESIRED_CURV_LEN = 1
|
||||||
|
|
||||||
|
# model outputs constants
|
||||||
|
FCW_THRESHOLDS_5MS2 = np.array([.05, .05, .15, .15, .15], dtype=np.float32)
|
||||||
|
FCW_THRESHOLDS_3MS2 = np.array([.7, .7], dtype=np.float32)
|
||||||
|
FCW_5MS2_PROBS_WIDTH = 5
|
||||||
|
FCW_3MS2_PROBS_WIDTH = 2
|
||||||
|
|
||||||
|
DISENGAGE_WIDTH = 5
|
||||||
|
POSE_WIDTH = 6
|
||||||
|
WIDE_FROM_DEVICE_WIDTH = 3
|
||||||
|
SIM_POSE_WIDTH = 6
|
||||||
|
LEAD_WIDTH = 4
|
||||||
|
LANE_LINES_WIDTH = 2
|
||||||
|
ROAD_EDGES_WIDTH = 2
|
||||||
|
PLAN_WIDTH = 15
|
||||||
|
DESIRE_PRED_WIDTH = 8
|
||||||
|
LAT_PLANNER_SOLUTION_WIDTH = 4
|
||||||
|
DESIRED_CURV_WIDTH = 1
|
||||||
|
|
||||||
|
NUM_LANE_LINES = 4
|
||||||
|
NUM_ROAD_EDGES = 2
|
||||||
|
|
||||||
|
LEAD_TRAJ_LEN = 6
|
||||||
|
DESIRE_PRED_LEN = 4
|
||||||
|
|
||||||
|
PLAN_MHP_N = 5
|
||||||
|
LEAD_MHP_N = 2
|
||||||
|
PLAN_MHP_SELECTION = 1
|
||||||
|
LEAD_MHP_SELECTION = 3
|
||||||
|
|
||||||
|
FCW_THRESHOLD_5MS2_HIGH = 0.15
|
||||||
|
FCW_THRESHOLD_5MS2_LOW = 0.05
|
||||||
|
FCW_THRESHOLD_3MS2 = 0.7
|
||||||
|
|
||||||
|
CONFIDENCE_BUFFER_LEN = 5
|
||||||
|
RYG_GREEN = 0.01165
|
||||||
|
RYG_YELLOW = 0.06157
|
||||||
|
|
||||||
|
POLY_PATH_DEGREE = 4
|
||||||
|
|
||||||
|
|
||||||
|
# model outputs slices
|
||||||
|
class Plan:
|
||||||
|
POSITION = slice(0, 3)
|
||||||
|
VELOCITY = slice(3, 6)
|
||||||
|
ACCELERATION = slice(6, 9)
|
||||||
|
T_FROM_CURRENT_EULER = slice(9, 12)
|
||||||
|
ORIENTATION_RATE = slice(12, 15)
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ENGAGED = slice(0, 1)
|
||||||
|
# next 2, 4, 6, 8, 10 seconds
|
||||||
|
GAS_DISENGAGE = slice(1, 31, 6)
|
||||||
|
BRAKE_DISENGAGE = slice(2, 31, 6)
|
||||||
|
STEER_OVERRIDE = slice(3, 31, 6)
|
||||||
|
HARD_BRAKE_3 = slice(4, 31, 6)
|
||||||
|
HARD_BRAKE_4 = slice(5, 31, 6)
|
||||||
|
HARD_BRAKE_5 = slice(6, 31, 6)
|
||||||
|
# next 0, 2, 4, 6, 8, 10 seconds
|
||||||
|
GAS_PRESS = slice(31, 55, 4)
|
||||||
|
BRAKE_PRESS = slice(32, 55, 4)
|
||||||
|
LEFT_BLINKER = slice(33, 55, 4)
|
||||||
|
RIGHT_BLINKER = slice(34, 55, 4)
|
||||||
|
|
||||||
|
|
||||||
|
class MetaTombRaider:
|
||||||
|
ENGAGED = slice(0, 1)
|
||||||
|
# next 2, 4, 6, 8, 10 seconds
|
||||||
|
GAS_DISENGAGE = slice(1, 41, 8)
|
||||||
|
BRAKE_DISENGAGE = slice(2, 41, 8)
|
||||||
|
STEER_OVERRIDE = slice(3, 41, 8)
|
||||||
|
HARD_BRAKE_3 = slice(4, 41, 8)
|
||||||
|
HARD_BRAKE_4 = slice(5, 41, 8)
|
||||||
|
HARD_BRAKE_5 = slice(6, 41, 8)
|
||||||
|
GAS_PRESS = slice(7, 41, 8)
|
||||||
|
BRAKE_PRESS = slice(8, 41, 8)
|
||||||
|
# next 0, 2, 4, 6, 8, 10 seconds
|
||||||
|
LEFT_BLINKER = slice(41, 53, 2)
|
||||||
|
RIGHT_BLINKER = slice(42, 53, 2)
|
||||||
|
|
||||||
|
|
||||||
|
class MetaSimPose:
|
||||||
|
ENGAGED = slice(0, 1)
|
||||||
|
# next 2, 4, 6, 8, 10 seconds
|
||||||
|
GAS_DISENGAGE = slice(1, 36, 7)
|
||||||
|
BRAKE_DISENGAGE = slice(2, 36, 7)
|
||||||
|
STEER_OVERRIDE = slice(3, 36, 7)
|
||||||
|
HARD_BRAKE_3 = slice(4, 36, 7)
|
||||||
|
HARD_BRAKE_4 = slice(5, 36, 7)
|
||||||
|
HARD_BRAKE_5 = slice(6, 36, 7)
|
||||||
|
GAS_PRESS = slice(7, 36, 7)
|
||||||
|
# next 0, 2, 4, 6, 8, 10 seconds
|
||||||
|
LEFT_BLINKER = slice(36, 48, 2)
|
||||||
|
RIGHT_BLINKER = slice(37, 48, 2)
|
||||||
@@ -1,89 +1,43 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import requests
|
|
||||||
import re
|
|
||||||
|
|
||||||
from openpilot.common.basedir import BASEDIR
|
from openpilot.common.basedir import BASEDIR
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.sunnypilot import get_file_hash
|
from openpilot.sunnypilot import get_file_hash
|
||||||
from openpilot.sunnypilot.models.model_name import DEFAULT_MODEL, DEFAULT_BIG_MODEL
|
from openpilot.sunnypilot.models.model_name import DEFAULT_MODEL, DEFAULT_BIG_MODEL
|
||||||
from openpilot.sunnypilot.models.fetcher import ModelFetcher
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_model() -> str:
|
def get_default_model() -> str:
|
||||||
show_big_model = (ui_state.chestnut_present
|
show_big_model = (ui_state.usbgpu
|
||||||
and (ui_state.chestnut_active or ui_state.chestnut_loading or ui_state.is_offroad()))
|
and (ui_state.usbgpu_active or ui_state.usbgpu_loading or ui_state.is_offroad()))
|
||||||
|
|
||||||
return DEFAULT_BIG_MODEL if show_big_model else DEFAULT_MODEL
|
return DEFAULT_BIG_MODEL if show_big_model else DEFAULT_MODEL
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_MODEL_NAME_PATH = os.path.join(BASEDIR, "openpilot", "sunnypilot", "models", "model_name.py")
|
DEFAULT_MODEL_NAME_PATH = os.path.join(BASEDIR, "openpilot", "sunnypilot", "models", "model_name.py")
|
||||||
MODEL_HASH_PATH = os.path.join(BASEDIR, "openpilot", "sunnypilot", "models", "tests", "model_hash")
|
MODEL_HASH_PATH = os.path.join(BASEDIR, "openpilot", "sunnypilot", "models", "tests", "model_hash")
|
||||||
BIG_MODEL_HASH_PATH = os.path.join(BASEDIR, "openpilot", "sunnypilot", "models", "tests", "big_model_hash")
|
|
||||||
SUPERCOMBO_ONNX_PATH = os.path.join(BASEDIR, "openpilot", "selfdrive", "modeld", "models", "driving_supercombo.onnx")
|
SUPERCOMBO_ONNX_PATH = os.path.join(BASEDIR, "openpilot", "selfdrive", "modeld", "models", "driving_supercombo.onnx")
|
||||||
BIG_SUPERCOMBO_ONNX_PATH = os.path.join(BASEDIR, "openpilot", "selfdrive", "modeld", "models", "big_driving_supercombo.onnx")
|
|
||||||
|
|
||||||
|
|
||||||
def _read_model_name_fields():
|
|
||||||
with open(DEFAULT_MODEL_NAME_PATH) as f:
|
|
||||||
content = f.read()
|
|
||||||
fields = {}
|
|
||||||
for line in content.splitlines():
|
|
||||||
if "=" in line:
|
|
||||||
key, val = line.split("=", 1)
|
|
||||||
fields[key.strip()] = val.strip().strip('"')
|
|
||||||
return fields
|
|
||||||
|
|
||||||
|
|
||||||
def update_model_hash():
|
def update_model_hash():
|
||||||
fields = _read_model_name_fields()
|
|
||||||
supercombo_hash = get_file_hash(SUPERCOMBO_ONNX_PATH)
|
supercombo_hash = get_file_hash(SUPERCOMBO_ONNX_PATH)
|
||||||
fingerprint = f"{supercombo_hash}:{fields.get('DEFAULT_MODEL', '')}:{fields.get('DEFAULT_MODEL_REF', '')}"
|
combined_hash = hashlib.sha256(supercombo_hash.encode()).hexdigest()
|
||||||
combined_hash = hashlib.sha256(fingerprint.encode()).hexdigest()
|
|
||||||
|
|
||||||
with open(MODEL_HASH_PATH, "w") as f:
|
with open(MODEL_HASH_PATH, "w") as f:
|
||||||
f.write(combined_hash)
|
f.write(combined_hash)
|
||||||
|
|
||||||
print(f"Generated and updated new combined model hash to {MODEL_HASH_PATH}")
|
print(f"Generated and updated new combined model hash to {MODEL_HASH_PATH}")
|
||||||
|
|
||||||
if os.path.exists(BIG_SUPERCOMBO_ONNX_PATH):
|
|
||||||
import subprocess
|
|
||||||
rel = os.path.relpath(BIG_SUPERCOMBO_ONNX_PATH, os.getcwd())
|
|
||||||
pointer = subprocess.check_output(["git", "show", f"HEAD:{rel}"], text=True)
|
|
||||||
oid = next(l.split(":", 1)[1] for l in pointer.splitlines() if l.startswith("oid sha256:"))
|
|
||||||
big_fingerprint = f"{oid}:{fields.get('DEFAULT_BIG_MODEL', '')}:{fields.get('DEFAULT_BIG_MODEL_REF', '')}"
|
|
||||||
big_combined_hash = hashlib.sha256(big_fingerprint.encode()).hexdigest()
|
|
||||||
|
|
||||||
with open(BIG_MODEL_HASH_PATH, "w") as f:
|
|
||||||
f.write(big_combined_hash)
|
|
||||||
|
|
||||||
print(f"Generated and updated new big model hash to {BIG_MODEL_HASH_PATH}")
|
|
||||||
|
|
||||||
|
|
||||||
def get_ref_for_name(url: str, name: str) -> str:
|
|
||||||
response = requests.get(url, timeout=10)
|
|
||||||
if response.status_code == 200:
|
|
||||||
bundles = response.json()["bundles"]
|
|
||||||
matching = [b for b in bundles if re.search(name, f"{b['short_name']} {b['display_name']}", re.IGNORECASE)]
|
|
||||||
if matching:
|
|
||||||
return max(matching, key=lambda b: int(b["index"]))["ref"]
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def update_default_model_names(default_model_name: str, default_big_model_name: str):
|
def update_default_model_names(default_model_name: str, default_big_model_name: str):
|
||||||
print("[CHANGE DEFAULT MODEL NAMES]")
|
print("[CHANGE DEFAULT MODEL NAMES]")
|
||||||
small_ref = get_ref_for_name(ModelFetcher.MODEL_URL, default_model_name)
|
|
||||||
big_ref = get_ref_for_name(ModelFetcher.MODEL_URL_CHESTNUT, default_big_model_name)
|
|
||||||
|
|
||||||
with open(DEFAULT_MODEL_NAME_PATH, "w") as f:
|
with open(DEFAULT_MODEL_NAME_PATH, "w") as f:
|
||||||
f.write(f'DEFAULT_MODEL = "{default_model_name}"\n')
|
f.write(f'DEFAULT_MODEL = "{default_model_name}"\n')
|
||||||
f.write(f'DEFAULT_MODEL_REF = "{small_ref}"\n')
|
|
||||||
f.write(f'DEFAULT_BIG_MODEL = "{default_big_model_name}"\n')
|
f.write(f'DEFAULT_BIG_MODEL = "{default_big_model_name}"\n')
|
||||||
f.write(f'DEFAULT_BIG_MODEL_REF = "{big_ref}"\n')
|
|
||||||
|
|
||||||
print(f'New default small model name: "{default_model_name}" (ref: {small_ref})')
|
print(f'New default small model name: "{default_model_name}"')
|
||||||
print(f'New default big model name: "{default_big_model_name}" (ref: {big_ref})')
|
print(f'New default big model name: "{default_big_model_name}"')
|
||||||
print("[DONE]")
|
print("[DONE]")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -138,12 +138,12 @@ class ModelCache:
|
|||||||
|
|
||||||
class ModelFetcher:
|
class ModelFetcher:
|
||||||
"""Handles fetching and caching of model data from remote source"""
|
"""Handles fetching and caching of model data from remote source"""
|
||||||
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v22.json"
|
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v21.json"
|
||||||
MODEL_URL_CHESTNUT = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_chestnut_v25.json"
|
MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v22.json"
|
||||||
|
|
||||||
MODEL_SOURCES = {
|
MODEL_SOURCES = {
|
||||||
"qcom": (MODEL_URL, ""),
|
"qcom": (MODEL_URL, ""),
|
||||||
"chestnut": (MODEL_URL_CHESTNUT, "_Chestnut"),
|
"usbgpu": (MODEL_URL_USBGPU, "_USBGPU"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, params: Params):
|
def __init__(self, params: Params):
|
||||||
@@ -156,12 +156,12 @@ class ModelFetcher:
|
|||||||
self._refetched: set[str] = set()
|
self._refetched: set[str] = set()
|
||||||
self.params.put("ModelManager_ActiveJson", {
|
self.params.put("ModelManager_ActiveJson", {
|
||||||
"qcom": self.MODEL_URL,
|
"qcom": self.MODEL_URL,
|
||||||
"chestnut": self.MODEL_URL_CHESTNUT,
|
"usbgpu": self.MODEL_URL_USBGPU,
|
||||||
}, block=True)
|
}, block=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def active_source(chestnut_present: bool) -> str:
|
def active_source(chestnut_present: bool) -> str:
|
||||||
return "chestnut" if chestnut_present else "qcom"
|
return "usbgpu" if chestnut_present else "qcom"
|
||||||
|
|
||||||
def _fetch_and_cache_models(self, source: str) -> list[custom.ModelManagerSP.ModelBundle] | None:
|
def _fetch_and_cache_models(self, source: str) -> list[custom.ModelManagerSP.ModelBundle] | None:
|
||||||
"""Fetches fresh model data from remote and updates cache.
|
"""Fetches fresh model data from remote and updates cache.
|
||||||
@@ -200,7 +200,7 @@ class ModelFetcher:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _cache_matches_source(source: str, cached_data: dict) -> bool:
|
def _cache_matches_source(source: str, cached_data: dict) -> bool:
|
||||||
bundles = cached_data.get("bundles", [])
|
bundles = cached_data.get("bundles", [])
|
||||||
if source == "chestnut":
|
if source == "usbgpu":
|
||||||
return any(bundle.get("is_big") is True for bundle in bundles)
|
return any(bundle.get("is_big") is True for bundle in bundles)
|
||||||
return not any(bundle.get("is_big") is True for bundle in bundles)
|
return not any(bundle.get("is_big") is True for bundle in bundles)
|
||||||
|
|
||||||
@@ -261,10 +261,10 @@ def get_cached_bundles(params: Params, source: str) -> list[custom.ModelManagerS
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from openpilot.selfdrive.modeld.helpers import chestnut_present
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present
|
||||||
params = Params()
|
params = Params()
|
||||||
model_fetcher = ModelFetcher(params)
|
model_fetcher = ModelFetcher(params)
|
||||||
bundles = model_fetcher.get_bundles_for_source(ModelFetcher.active_source(chestnut_present()))
|
bundles = model_fetcher.get_bundles_for_source(ModelFetcher.active_source(usbgpu_present()))
|
||||||
for bundle in bundles:
|
for bundle in bundles:
|
||||||
for model in bundle.models:
|
for model in bundle.models:
|
||||||
model_overrides = {override.key: override.value for override in bundle.overrides}
|
model_overrides = {override.key: override.value for override in bundle.overrides}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user