mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-21 04:53:47 +08:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 049d225d5a | |||
| c783f2225a | |||
| 53e13a7bc0 | |||
| ba29a38507 | |||
| ed35a82129 | |||
| 8edce0da44 | |||
| 3c90b66b65 | |||
| 08c83149b0 | |||
| 03711a13b0 | |||
| 9f1709a7e1 | |||
| 2b576c5fce | |||
| 20ba774eaa | |||
| 59833c500a | |||
| 3d09a47a47 | |||
| 2f4744d39b | |||
| 6dd3457f4f | |||
| b7657f6553 | |||
| b8e14d85fb |
@@ -103,10 +103,10 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
cd ${{ github.workspace }}/openpilot/openpilot
|
cd ${{ github.workspace }}/openpilot/openpilot
|
||||||
if [ "${{ inputs.target_hardware }}" != "usbgpu" ]; then
|
if [ "${{ inputs.target_hardware }}" != "usbgpu" ]; then
|
||||||
git lfs pull -X "selfdrive/modeld/models/big_*.onnx" -X "selfdrive/modeld/models/dmonitoring_*.onnx"
|
git lfs pull -X "**/selfdrive/modeld/models/big_*.onnx" -X "**/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
|
||||||
git lfs pull -I "selfdrive/modeld/models/big_*.onnx"
|
git lfs pull -I "**/selfdrive/modeld/models/big_*.onnx"
|
||||||
find selfdrive/modeld/models -name "*.onnx" ! -name "big_*.onnx" -delete
|
find selfdrive/modeld/models -name "*.onnx" ! -name "big_*.onnx" -delete
|
||||||
fi
|
fi
|
||||||
- name: 'Upload Artifact'
|
- name: 'Upload Artifact'
|
||||||
@@ -116,7 +116,7 @@ jobs:
|
|||||||
path: ${{ github.workspace }}/openpilot/openpilot/selfdrive/modeld/models/*.onnx
|
path: ${{ github.workspace }}/openpilot/openpilot/selfdrive/modeld/models/*.onnx
|
||||||
|
|
||||||
build_model:
|
build_model:
|
||||||
runs-on: [self-hosted, 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 }})
|
||||||
@@ -127,7 +127,6 @@ jobs:
|
|||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- run: git lfs pull
|
|
||||||
|
|
||||||
- name: Set environment variables
|
- name: Set environment variables
|
||||||
id: set-env
|
id: set-env
|
||||||
@@ -160,7 +159,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||||
rm -rf ${{ env.MODELS_DIR }}/*.onnx
|
rm -rf ${{ env.MODELS_DIR }}/*.onnx*
|
||||||
|
|
||||||
- name: Download model artifacts
|
- name: Download model artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
@@ -180,6 +179,7 @@ jobs:
|
|||||||
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
||||||
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"
|
||||||
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
||||||
echo "USBGPU build"
|
echo "USBGPU build"
|
||||||
export USBGPU=1
|
export USBGPU=1
|
||||||
@@ -187,27 +187,40 @@ jobs:
|
|||||||
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"
|
||||||
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
TG_FLAGS="$TG_FLAGS_QCOM"
|
||||||
OUTPUT_PKL="${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
|
OUTPUT_PKL="${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate metadata for all ONNX files
|
# Generate metadata for all ONNX files
|
||||||
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
||||||
echo "Generating metadata: $onnx_file"
|
echo "Generating metadata: $onnx_file"
|
||||||
env ${TG_FLAGS} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
env ${TG_FLAGS_QCOM} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Detect model type and build compile args
|
# Detect model type and build compile args
|
||||||
VISION_ONNX="${{ env.MODELS_DIR }}/driving_vision.onnx"
|
VISION_ONNX=""
|
||||||
POLICY_ONNX="${{ env.MODELS_DIR }}/driving_policy.onnx"
|
for f in "${{ env.MODELS_DIR }}/driving_vision.onnx" "${{ env.MODELS_DIR }}/big_driving_vision.onnx"; do
|
||||||
OFF_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_off_policy.onnx"
|
[ -f "$f" ] && VISION_ONNX="$f" && break
|
||||||
ON_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_on_policy.onnx"
|
done
|
||||||
|
|
||||||
|
POLICY_ONNX=""
|
||||||
|
for f in "${{ env.MODELS_DIR }}/driving_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_policy.onnx"; do
|
||||||
|
[ -f "$f" ] && POLICY_ONNX="$f" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
OFF_POLICY_ONNX=""
|
||||||
|
for f in "${{ env.MODELS_DIR }}/driving_off_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_off_policy.onnx"; do
|
||||||
|
[ -f "$f" ] && OFF_POLICY_ONNX="$f" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
ON_POLICY_ONNX=""
|
||||||
|
for f in "${{ env.MODELS_DIR }}/driving_on_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_on_policy.onnx"; do
|
||||||
|
[ -f "$f" ] && ON_POLICY_ONNX="$f" && break
|
||||||
|
done
|
||||||
|
|
||||||
SUPERCOMBO_ONNX=""
|
SUPERCOMBO_ONNX=""
|
||||||
for f in "${{ env.MODELS_DIR }}/supercombo.onnx" "${{ env.MODELS_DIR }}/driving_supercombo.onnx"; do
|
for f in "${{ env.MODELS_DIR }}/supercombo.onnx" "${{ env.MODELS_DIR }}/driving_supercombo.onnx" "${{ env.MODELS_DIR }}/big_supercombo.onnx" "${{ env.MODELS_DIR }}/big_driving_supercombo.onnx"; do
|
||||||
if [ -f "$f" ]; then
|
[ -f "$f" ] && SUPERCOMBO_ONNX="$f" && break
|
||||||
SUPERCOMBO_ONNX="$f"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
|
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ env:
|
|||||||
BUILD_DIR: "/data/openpilot"
|
BUILD_DIR: "/data/openpilot"
|
||||||
OUTPUT_DIR: ${{ github.workspace }}/output
|
OUTPUT_DIR: ${{ github.workspace }}/output
|
||||||
CI_DIR: ${{ github.workspace }}/release/ci
|
CI_DIR: ${{ github.workspace }}/release/ci
|
||||||
SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache
|
|
||||||
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
||||||
|
|
||||||
# Branch configurations
|
|
||||||
STAGING_SOURCE_BRANCH: 'master'
|
|
||||||
|
|
||||||
# Runtime configuration
|
# Runtime configuration
|
||||||
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
@@ -109,11 +105,6 @@ jobs:
|
|||||||
group: build-${{ github.head_ref || github.ref_name }}
|
group: build-${{ github.head_ref || github.ref_name }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
runs-on: [self-hosted, tici]
|
runs-on: [self-hosted, tici]
|
||||||
outputs:
|
|
||||||
new_branch: ${{ needs.prepare_strategy.outputs.new_branch }}
|
|
||||||
version: ${{ needs.prepare_strategy.outputs.version }}
|
|
||||||
extra_version_identifier: ${{ needs.prepare_strategy.outputs.extra_version_identifier }}
|
|
||||||
commit_sha: ${{ github.sha }}
|
|
||||||
if: ${{
|
if: ${{
|
||||||
(always() && !cancelled() && !failure()) &&
|
(always() && !cancelled() && !failure()) &&
|
||||||
needs.prepare_strategy.result == 'success' &&
|
needs.prepare_strategy.result == 'success' &&
|
||||||
@@ -129,26 +120,8 @@ jobs:
|
|||||||
repository: ${{ github.event.pull_request.head.repo.fork && github.event.pull_request.head.repo.full_name || github.repository }}
|
repository: ${{ github.event.pull_request.head.repo.fork && github.event.pull_request.head.repo.full_name || github.repository }}
|
||||||
- run: git lfs pull
|
- run: git lfs pull
|
||||||
|
|
||||||
- name: Cache SCons
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ${{env.SCONS_CACHE_DIR}}
|
|
||||||
key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}-${{ github.sha }}
|
|
||||||
# Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.)
|
|
||||||
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
|
||||||
restore-keys: |
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_SOURCE_BRANCH }}
|
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}
|
|
||||||
|
|
||||||
- name: Set environment variables
|
- name: Set environment variables
|
||||||
id: set-env
|
|
||||||
run: |
|
run: |
|
||||||
echo "new_branch=${{ needs.prepare_strategy.outputs.new_branch }}" >> $GITHUB_OUTPUT
|
|
||||||
echo "version=${{ needs.prepare_strategy.outputs.version }}" >> $GITHUB_OUTPUT
|
|
||||||
echo "extra_version_identifier=${{ needs.prepare_strategy.outputs.extra_version_identifier }}" >> $GITHUB_OUTPUT
|
|
||||||
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Set up common environment
|
# Set up common environment
|
||||||
source /etc/profile;
|
source /etc/profile;
|
||||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||||
@@ -157,9 +130,6 @@ jobs:
|
|||||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||||
uv sync
|
uv sync
|
||||||
printenv >> $GITHUB_ENV
|
printenv >> $GITHUB_ENV
|
||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
|
||||||
cat $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup build environment
|
- name: Setup build environment
|
||||||
run: |
|
run: |
|
||||||
@@ -168,7 +138,7 @@ jobs:
|
|||||||
echo "Starting build stage..."
|
echo "Starting build stage..."
|
||||||
echo "BUILD_DIR: ${BUILD_DIR}"
|
echo "BUILD_DIR: ${BUILD_DIR}"
|
||||||
echo "CI_DIR: ${CI_DIR}"
|
echo "CI_DIR: ${CI_DIR}"
|
||||||
echo "VERSION: ${{ steps.set-env.outputs.version }}"
|
echo "VERSION: ${{ needs.prepare_strategy.outputs.version }}"
|
||||||
echo "UV_PROJECT_ENVIRONMENT: ${UV_PROJECT_ENVIRONMENT}"
|
echo "UV_PROJECT_ENVIRONMENT: ${UV_PROJECT_ENVIRONMENT}"
|
||||||
echo "VIRTUAL_ENV: ${VIRTUAL_ENV}"
|
echo "VIRTUAL_ENV: ${VIRTUAL_ENV}"
|
||||||
echo "-------"
|
echo "-------"
|
||||||
@@ -180,61 +150,44 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Main Project
|
- name: Build Main Project
|
||||||
run: |
|
run: |
|
||||||
export PYTHONPATH="$BUILD_DIR"
|
export PYTHONPATH="$BUILD_DIR:$BUILD_DIR/msgq_repo:$BUILD_DIR/opendbc_repo:$BUILD_DIR/rednose_repo:$BUILD_DIR/teleoprtc_repo:$BUILD_DIR/tinygrad_repo"
|
||||||
./tools/release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/
|
./tools/release/release_files.py | xargs -0 cp -pR --parents -t "$BUILD_DIR" --
|
||||||
|
# outside the checkout, which is wiped each run. /data/scons_cache is the device's, not ours.
|
||||||
|
SCONS_CACHE="$RUNNER_WORKSPACE/scons_cache"
|
||||||
|
mkdir -p "$SCONS_CACHE"
|
||||||
cd $BUILD_DIR
|
cd $BUILD_DIR
|
||||||
ln -sfn msgq_repo/msgq msgq
|
echo "Building locationd..."
|
||||||
ln -sfn opendbc_repo/opendbc opendbc
|
# -j1: parallel rednose generators OOM the device
|
||||||
ln -sfn rednose_repo/rednose rednose
|
scons -j1 cache_dir="$SCONS_CACHE" --minimal \
|
||||||
ln -sfn teleoprtc_repo/teleoprtc teleoprtc
|
openpilot/selfdrive/locationd openpilot/sunnypilot/selfdrive/locationd
|
||||||
ln -sfn tinygrad_repo/tinygrad tinygrad
|
|
||||||
sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py
|
|
||||||
echo "Building sunnypilot's modeld_v2..."
|
|
||||||
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal openpilot/sunnypilot/modeld_v2
|
|
||||||
echo "Building sunnypilot's locationd..."
|
|
||||||
scons -j2 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal openpilot/sunnypilot/selfdrive/locationd
|
|
||||||
echo "Building openpilot's locationd..."
|
|
||||||
scons -j1 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal openpilot/selfdrive/locationd
|
|
||||||
echo "Building rest of sunnypilot"
|
echo "Building rest of sunnypilot"
|
||||||
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal
|
/usr/bin/time -v scons -j$(nproc) cache_dir="$SCONS_CACHE" --minimal
|
||||||
touch ${BUILD_DIR}/prebuilt
|
touch ${BUILD_DIR}/prebuilt
|
||||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||||
ls -la ${BUILD_DIR}
|
ls -la ${BUILD_DIR}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Prepare Output
|
- name: Strip release tree
|
||||||
run: |
|
run: |
|
||||||
sudo rm -rf ${OUTPUT_DIR}
|
cd $BUILD_DIR
|
||||||
mkdir -p ${OUTPUT_DIR}
|
find . -name '*.a' -delete
|
||||||
rsync -am${RUNNER_DEBUG:+v} \
|
find . -name '*.o' -delete
|
||||||
--exclude='.sconsign.dblite' \
|
find . -name '*.os' -delete
|
||||||
--exclude='*.a' \
|
find . -name '*.pyc' -delete
|
||||||
--exclude='*.o' \
|
find . -name 'moc_*' -delete
|
||||||
--exclude='*.os' \
|
find . -name '__pycache__' -type d -exec rm -rf {} +
|
||||||
--exclude='*.pyc' \
|
find . -name 'SConstruct' -delete
|
||||||
--exclude='moc_*' \
|
find . -name 'SConscript' -delete
|
||||||
--exclude='__pycache__' \
|
rm -rf .sconsign.dblite Jenkinsfile tools/release/ release/
|
||||||
--exclude='Jenkinsfile' \
|
rm -f openpilot/selfdrive/modeld/models/*.onnx*
|
||||||
--exclude='**/release/' \
|
rm -f openpilot/sunnypilot/modeld*/models/*.onnx*
|
||||||
--exclude='**/.github/' \
|
find openpilot/third_party/ -name '*x86*' -exec rm -r {} +
|
||||||
--exclude='**/openpilot/selfdrive/ui/replay/' \
|
find openpilot/third_party/ -name '*Darwin*' -exec rm -r {} +
|
||||||
--exclude='**/__pycache__/' \
|
cd -
|
||||||
--exclude='${{env.SCONS_CACHE_DIR}}' \
|
|
||||||
--exclude='**/.git/' \
|
|
||||||
--exclude='**/SConstruct' \
|
|
||||||
--exclude='**/SConscript' \
|
|
||||||
--exclude='**/.venv/' \
|
|
||||||
--exclude='openpilot/selfdrive/modeld/models/*.onnx*' \
|
|
||||||
--exclude='openpilot/sunnypilot/modeld*/models/*.onnx*' \
|
|
||||||
--exclude='openpilot/third_party/*x86*' \
|
|
||||||
--exclude='openpilot/third_party/*Darwin*' \
|
|
||||||
--delete-excluded \
|
|
||||||
--chown=comma:comma \
|
|
||||||
${BUILD_DIR}/ ${OUTPUT_DIR}/
|
|
||||||
|
|
||||||
- name: 'Tar.gz files'
|
- name: 'Tar.gz files'
|
||||||
run: |
|
run: |
|
||||||
tar czf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }} .
|
tar czf prebuilt.tar.gz -C ${{ env.BUILD_DIR }} .
|
||||||
ls -la prebuilt.tar.gz
|
ls -la prebuilt.tar.gz
|
||||||
|
|
||||||
- name: 'Upload Artifact'
|
- name: 'Upload Artifact'
|
||||||
@@ -242,6 +195,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: prebuilt
|
name: prebuilt
|
||||||
path: prebuilt.tar.gz
|
path: prebuilt.tar.gz
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
- name: Re-enable powersave
|
- name: Re-enable powersave
|
||||||
if: always()
|
if: always()
|
||||||
@@ -283,29 +237,22 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo '${{ toJSON(needs.build.outputs) }}'
|
echo '${{ toJSON(needs.prepare_strategy.outputs) }}'
|
||||||
ls -la ${{ env.OUTPUT_DIR }}
|
ls -la ${{ env.OUTPUT_DIR }}
|
||||||
|
|
||||||
${{ env.CI_DIR }}/publish.sh \
|
${{ env.CI_DIR }}/publish.sh \
|
||||||
"${{ github.workspace }}" \
|
"${{ github.workspace }}" \
|
||||||
"${{ env.OUTPUT_DIR }}" \
|
"${{ env.OUTPUT_DIR }}" \
|
||||||
"${{ needs.build.outputs.new_branch }}" \
|
"${{ needs.prepare_strategy.outputs.new_branch }}" \
|
||||||
"${{ needs.build.outputs.version }}" \
|
"${{ needs.prepare_strategy.outputs.version }}" \
|
||||||
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
|
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
|
||||||
"${{ needs.build.outputs.extra_version_identifier }}"
|
"${{ needs.prepare_strategy.outputs.extra_version_identifier }}"
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "---- ℹ️ To update the list of branches that auto deploy prebuilts -----"
|
|
||||||
echo ""
|
|
||||||
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES"
|
|
||||||
echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}"
|
|
||||||
echo "3. Update as needed (JSON array with no spaces)"
|
|
||||||
|
|
||||||
- name: Tag ${{ needs.prepare_strategy.outputs.environment }}
|
- name: Tag ${{ needs.prepare_strategy.outputs.environment }}
|
||||||
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
|
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ needs.prepare_strategy.outputs.environment }}/${{ needs.prepare_strategy.outputs.version }}/${{ needs.prepare_strategy.outputs.build }}"
|
TAG="${{ needs.prepare_strategy.outputs.environment }}/${{ needs.prepare_strategy.outputs.version }}/${{ needs.prepare_strategy.outputs.build }}"
|
||||||
git tag -f -a ${TAG} -m "${{ needs.prepare_strategy.outputs.environment }} @ ${{ needs.prepare_strategy.outputs.version }} of build ${{ needs.build.outputs.build }}."
|
git tag -f -a ${TAG} -m "${{ needs.prepare_strategy.outputs.environment }} @ ${{ needs.prepare_strategy.outputs.version }} of build ${{ needs.prepare_strategy.outputs.build }}."
|
||||||
git push -f origin ${TAG}
|
git push -f origin ${TAG}
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
@@ -324,7 +271,6 @@ jobs:
|
|||||||
- name: Prepare notification message
|
- name: Prepare notification message
|
||||||
id: message
|
id: message
|
||||||
run: |
|
run: |
|
||||||
TEMPLATE='${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}'
|
|
||||||
export VERSION="${{ needs.prepare_strategy.outputs.version }}"
|
export VERSION="${{ needs.prepare_strategy.outputs.version }}"
|
||||||
export branch_name="${{ env.SOURCE_BRANCH }}"
|
export branch_name="${{ env.SOURCE_BRANCH }}"
|
||||||
export new_branch="${{ needs.prepare_strategy.outputs.new_branch }}"
|
export new_branch="${{ needs.prepare_strategy.outputs.new_branch }}"
|
||||||
@@ -373,7 +319,7 @@ jobs:
|
|||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: prNumber,
|
issue_number: prNumber,
|
||||||
name: process.env.LABELf
|
name: process.env.LABEL
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Removed '${process.env.LABEL}' label from PR #${prNumber}`);
|
console.log(`Removed '${process.env.LABEL}' label from PR #${prNumber}`);
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
preview:
|
preview:
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
if: false # tmp disable due to GH API rate limiting flakiness
|
||||||
|
#if: github.repository == 'sunnypilot/sunnypilot'
|
||||||
name: preview
|
name: preview
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|||||||
+1
-1
Submodule opendbc_repo updated: 792104f458...06743dfb39
@@ -49,9 +49,8 @@ def get_cruise_accel(e2e, v_cruise, v_ego, a_cruise_prev, angle_steers, CP, dt,
|
|||||||
max_accel = min(max_accel, coast_limit)
|
max_accel = min(max_accel, coast_limit)
|
||||||
|
|
||||||
target_accel = np.clip(v_cruise - v_ego, A_CRUISE_MIN, max_accel)
|
target_accel = np.clip(v_cruise - v_ego, A_CRUISE_MIN, max_accel)
|
||||||
if not e2e:
|
j_cruise = np.interp(v_ego, A_CRUISE_MAX_BP, J_CRUISE_VALS)
|
||||||
j_cruise = np.interp(v_ego, A_CRUISE_MAX_BP, J_CRUISE_VALS)
|
target_accel = float(np.clip(target_accel, a_cruise_prev - j_cruise * dt, a_cruise_prev + j_cruise * dt))
|
||||||
target_accel = float(np.clip(target_accel, a_cruise_prev - j_cruise * dt, a_cruise_prev + j_cruise * dt))
|
|
||||||
|
|
||||||
return target_accel
|
return target_accel
|
||||||
|
|
||||||
@@ -65,10 +64,9 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
self.dt = dt
|
self.dt = dt
|
||||||
self.allow_throttle = True
|
self.allow_throttle = True
|
||||||
|
|
||||||
self.a_desired = init_a
|
|
||||||
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt)
|
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt)
|
||||||
self.a_cruise = 0.0
|
self.a_cruise = init_a
|
||||||
self.output_a_target = 0.0
|
self.output_a_target = init_a
|
||||||
self.output_should_stop = False
|
self.output_should_stop = False
|
||||||
|
|
||||||
self.v_desired_trajectory = np.zeros(CONTROL_N)
|
self.v_desired_trajectory = np.zeros(CONTROL_N)
|
||||||
@@ -105,7 +103,8 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
|
|
||||||
if reset_state:
|
if reset_state:
|
||||||
self.v_desired_filter.x = v_ego
|
self.v_desired_filter.x = v_ego
|
||||||
self.a_desired = np.clip(sm['carState'].aEgo, ACCEL_MIN, ACCEL_MAX)
|
self.output_a_target = np.clip(sm['carState'].aEgo, ACCEL_MIN, ACCEL_MAX)
|
||||||
|
self.a_cruise = self.output_a_target
|
||||||
|
|
||||||
# Prevent divergence, smooth in current v_ego
|
# Prevent divergence, smooth in current v_ego
|
||||||
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))
|
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))
|
||||||
@@ -113,11 +112,11 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
# No change cost when user is controlling the speed, or when standstill
|
# No change cost when user is controlling the speed, or when standstill
|
||||||
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
|
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
|
||||||
|
|
||||||
# Get new v_cruise and a_desired from Smart Cruise Control and Speed Limit Assist
|
# Get new v_cruise and a_target from Smart Cruise Control and Speed Limit Assist
|
||||||
v_cruise, self.a_desired = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.a_desired, v_cruise)
|
v_cruise, self.output_a_target = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.output_a_target, v_cruise)
|
||||||
|
|
||||||
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.a_desired)
|
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.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)
|
||||||
@@ -130,7 +129,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
cloudlog.info("FCW triggered")
|
cloudlog.info("FCW triggered")
|
||||||
|
|
||||||
# Save starting point for next iteration
|
# Save starting point for next iteration
|
||||||
a_prev = self.a_desired
|
a_prev = self.output_a_target
|
||||||
|
|
||||||
action_t = self.CP.longitudinalActuatorDelay + DT_MDL
|
action_t = self.CP.longitudinalActuatorDelay + DT_MDL
|
||||||
output_a_target_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
|
output_a_target_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
|
||||||
@@ -155,7 +154,6 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
self.output_should_stop = any(should_stop for _, _, should_stop in candidates)
|
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.a_desired = float(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
|
||||||
|
|
||||||
def publish(self, sm, pm):
|
def publish(self, sm, pm):
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ from openpilot.system.ui.widgets.toggle import ON_COLOR
|
|||||||
|
|
||||||
from openpilot.sunnypilot.models.runners.constants import CUSTOM_MODEL_PATH
|
from openpilot.sunnypilot.models.runners.constants import CUSTOM_MODEL_PATH
|
||||||
from openpilot.system.ui.sunnypilot.lib.styles import style
|
from openpilot.system.ui.sunnypilot.lib.styles import style
|
||||||
from openpilot.system.ui.sunnypilot.lib.utils import NoElideButtonAction
|
from openpilot.system.ui.sunnypilot.lib.utils import NoElideButtonAction, ScrollingButtonAction
|
||||||
from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP, toggle_item_sp, option_item_sp
|
from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP, toggle_item_sp, option_item_sp
|
||||||
from openpilot.system.ui.sunnypilot.widgets.progress_bar import progress_item
|
from openpilot.system.ui.sunnypilot.widgets.download_status import download_status_item
|
||||||
from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder
|
from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder
|
||||||
|
|
||||||
if gui_app.sunnypilot_ui():
|
if gui_app.sunnypilot_ui():
|
||||||
@@ -35,9 +35,8 @@ class ModelsLayout(Widget):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.model_manager = None
|
self.model_manager = None
|
||||||
self.download_status = None
|
|
||||||
self.prev_download_status = None
|
|
||||||
self.model_dialog = None
|
self.model_dialog = None
|
||||||
|
self._downloading = False
|
||||||
self.last_cache_calc_time = 0
|
self.last_cache_calc_time = 0
|
||||||
|
|
||||||
self._initialize_items()
|
self._initialize_items()
|
||||||
@@ -52,15 +51,11 @@ class ModelsLayout(Widget):
|
|||||||
self.current_model_item = ListItemSP(
|
self.current_model_item = ListItemSP(
|
||||||
title=tr("Current Model"),
|
title=tr("Current Model"),
|
||||||
description="",
|
description="",
|
||||||
action_item=NoElideButtonAction(tr("SELECT")),
|
action_item=ScrollingButtonAction(tr("SELECT")),
|
||||||
callback=self._handle_current_model_clicked
|
callback=self._handle_current_model_clicked
|
||||||
)
|
)
|
||||||
|
|
||||||
self.supercombo_label = progress_item(tr("Driving Model"))
|
self.download_item = download_status_item(lambda: tr("Download") if self._downloading else tr("Model Status"))
|
||||||
self.vision_label = progress_item(tr("Vision Model"))
|
|
||||||
self.policy_label = progress_item(tr("Policy Model"))
|
|
||||||
self.off_policy_label = progress_item(tr("Off-Policy Model"))
|
|
||||||
self.on_policy_label = progress_item(tr("On-Policy Model"))
|
|
||||||
|
|
||||||
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),
|
||||||
@@ -98,8 +93,7 @@ class ModelsLayout(Widget):
|
|||||||
1, None, True, "", style.BUTTON_ACTION_WIDTH, None, True,
|
1, None, True, "", style.BUTTON_ACTION_WIDTH, None, True,
|
||||||
lambda v: f"{v / 100:.2f} m")
|
lambda v: f"{v / 100:.2f} m")
|
||||||
|
|
||||||
self.items = [self.current_model_item, self.cancel_download_item, self.supercombo_label, self.vision_label,
|
self.items = [self.current_model_item, self.cancel_download_item, self.download_item, self.refresh_item, self.clear_cache_item,
|
||||||
self.policy_label, self.off_policy_label, self.on_policy_label, self.refresh_item, self.clear_cache_item,
|
|
||||||
self.lane_turn_desire_toggle, self.lane_turn_value_control, self.lagd_toggle, self.delay_control, self.camera_offset]
|
self.lane_turn_desire_toggle, self.lane_turn_value_control, self.lagd_toggle, self.delay_control, self.camera_offset]
|
||||||
|
|
||||||
def _update_lagd_description(self, lagd_toggle: bool):
|
def _update_lagd_description(self, lagd_toggle: bool):
|
||||||
@@ -135,14 +129,9 @@ class ModelsLayout(Widget):
|
|||||||
gui_app.push_widget(dialog)
|
gui_app.push_widget(dialog)
|
||||||
|
|
||||||
def _handle_bundle_download_progress(self):
|
def _handle_bundle_download_progress(self):
|
||||||
labels = {custom.ModelManagerSP.Model.Type.supercombo: self.supercombo_label,
|
self.download_item.set_visible(False)
|
||||||
custom.ModelManagerSP.Model.Type.vision: self.vision_label,
|
|
||||||
custom.ModelManagerSP.Model.Type.policy: self.policy_label,
|
|
||||||
custom.ModelManagerSP.Model.Type.offPolicy: self.off_policy_label,
|
|
||||||
custom.ModelManagerSP.Model.Type.onPolicy: self.on_policy_label}
|
|
||||||
for label in labels.values():
|
|
||||||
label.set_visible(False)
|
|
||||||
self.cancel_download_item.set_visible(False)
|
self.cancel_download_item.set_visible(False)
|
||||||
|
self._downloading = False
|
||||||
|
|
||||||
if not self.model_manager or (not self.model_manager.selectedBundle and not self.model_manager.activeBundle):
|
if not self.model_manager or (not self.model_manager.selectedBundle and not self.model_manager.activeBundle):
|
||||||
return
|
return
|
||||||
@@ -153,32 +142,41 @@ class ModelsLayout(Widget):
|
|||||||
if not bundle:
|
if not bundle:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.download_status = bundle.status
|
|
||||||
status_changed = self.prev_download_status != self.download_status
|
|
||||||
self.prev_download_status = self.download_status
|
|
||||||
|
|
||||||
self.cancel_download_item.set_visible(bool(self.model_manager.selectedBundle) and ui_state.params.get("ModelManager_DownloadIndex") is not None)
|
self.cancel_download_item.set_visible(bool(self.model_manager.selectedBundle) and ui_state.params.get("ModelManager_DownloadIndex") is not None)
|
||||||
|
|
||||||
if (current_time := time.monotonic()) - self.last_cache_calc_time > 0.5:
|
if (current_time := time.monotonic()) - self.last_cache_calc_time > 0.5:
|
||||||
self.last_cache_calc_time = current_time
|
self.last_cache_calc_time = current_time
|
||||||
self.clear_cache_item.action_item.set_value(f"{self.calculate_cache_size():.2f} MB")
|
self.clear_cache_item.action_item.set_value(f"{self.calculate_cache_size():.2f} MB")
|
||||||
|
|
||||||
if self.download_status == custom.ModelManagerSP.DownloadStatus.downloading:
|
if bundle.status == custom.ModelManagerSP.DownloadStatus.downloading:
|
||||||
device._reset_interactive_timeout()
|
device._reset_interactive_timeout()
|
||||||
|
|
||||||
for model in bundle.models:
|
# every bundle is a single chunked artifact now
|
||||||
if label := labels.get(getattr(model.type, 'raw', model.type)):
|
progresses = [model.artifact.downloadProgress for model in bundle.models if model.artifact.fileName]
|
||||||
label.set_visible(True)
|
if not progresses:
|
||||||
p = model.artifact.downloadProgress
|
return
|
||||||
text, show, color = f"pending - {bundle.displayName}", False, rl.GRAY
|
|
||||||
if p.status == custom.ModelManagerSP.DownloadStatus.downloading:
|
self.download_item.set_visible(True)
|
||||||
text, show = f"{int(p.progress)}% - {bundle.displayName}", True
|
self.download_item.action_item.update(**self._download_row_state(progresses, bundle.internalName))
|
||||||
elif p.status in (custom.ModelManagerSP.DownloadStatus.downloaded, custom.ModelManagerSP.DownloadStatus.cached):
|
self._downloading = self.download_item.action_item.downloading
|
||||||
status_text = tr("from cache" if p.status == custom.ModelManagerSP.DownloadStatus.cached else "downloaded")
|
|
||||||
text, color = f"{bundle.displayName} - {status_text if status_changed else tr('ready')}", ON_COLOR
|
@staticmethod
|
||||||
elif p.status == custom.ModelManagerSP.DownloadStatus.failed:
|
def _download_row_state(progresses, name: str) -> dict:
|
||||||
text, color = f"download failed - {bundle.displayName}", rl.RED
|
"""Maps a bundle's artifact progress to DownloadStatusAction.update kwargs."""
|
||||||
label.action_item.update(p.progress, text, show, color)
|
# .raw: _DynamicEnum equals its int but does not hash like it
|
||||||
|
statuses = {getattr(p.status, 'raw', p.status) for p in progresses}
|
||||||
|
progress = sum(p.progress for p in progresses) / len(progresses)
|
||||||
|
ds = custom.ModelManagerSP.DownloadStatus
|
||||||
|
|
||||||
|
if ds.failed in statuses:
|
||||||
|
# close.png is authored black and a tint cannot lift it, hence close2
|
||||||
|
return {"name": name, "status_text": tr("download failed"), "text_color": rl.RED, "icon": "icons/close2.png"}
|
||||||
|
if ds.downloading in statuses:
|
||||||
|
return {"name": name, "downloading": True, "progress": progress}
|
||||||
|
if statuses <= {ds.downloaded, ds.cached}:
|
||||||
|
return {"name": name, "text_color": ON_COLOR, "icon": "icons/checkmark.png"}
|
||||||
|
# circled_slash is authored grey; tinting it again only darkens it
|
||||||
|
return {"name": name, "text_color": rl.GRAY, "icon": "icons/circled_slash.png", "icon_color": rl.WHITE}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _show_reset_params_dialog():
|
def _show_reset_params_dialog():
|
||||||
@@ -251,7 +249,7 @@ class ModelsLayout(Widget):
|
|||||||
self._update_lagd_description(live_delay)
|
self._update_lagd_description(live_delay)
|
||||||
self.model_manager = ui_state.sm["modelManagerSP"]
|
self.model_manager = ui_state.sm["modelManagerSP"]
|
||||||
self._handle_bundle_download_progress()
|
self._handle_bundle_download_progress()
|
||||||
active_name = self.model_manager.activeBundle.internalName if self.model_manager and self.model_manager.activeBundle.ref else f"{DEFAULT_MODEL} (Default)"
|
active_name = self.model_manager.activeBundle.displayName if self.model_manager and self.model_manager.activeBundle.ref else f"{DEFAULT_MODEL} (Default)"
|
||||||
self.current_model_item.action_item.set_value(active_name)
|
self.current_model_item.action_item.set_value(active_name)
|
||||||
|
|
||||||
if not ui_state.is_offroad():
|
if not ui_state.is_offroad():
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ from openpilot.common.params import Params
|
|||||||
|
|
||||||
|
|
||||||
def get_lat_delay(params: Params, stock_lat_delay: float) -> float:
|
def get_lat_delay(params: Params, stock_lat_delay: float) -> float:
|
||||||
if params.get_bool("LagdToggle"):
|
# live learning on: use what lagd publishes.
|
||||||
return float(params.get("LagdValueCache", return_default=True))
|
# off: use the fixed steerActuatorDelay + software delay sum that LagdToggle caches.
|
||||||
|
|
||||||
return stock_lat_delay
|
if params.get_bool("LagdToggle"):
|
||||||
|
return stock_lat_delay
|
||||||
|
|
||||||
|
return float(params.get("LagdValueCache", return_default=True))
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ def generate_queues_and_npy(input_shapes: dict, frame_skip: int, device: str = D
|
|||||||
}
|
}
|
||||||
|
|
||||||
if features_buffer:
|
if features_buffer:
|
||||||
queues['feat_q'] = Tensor(np.zeros((frame_skip * (features_buffer[1] - 1) + 1, features_buffer[0], features_buffer[2]),
|
feat_q_len = frame_skip * features_buffer[1] if is_supercombo else frame_skip * (features_buffer[1] - 1) + 1
|
||||||
|
queues['feat_q'] = Tensor(np.zeros((feat_q_len, features_buffer[0], features_buffer[2]),
|
||||||
dtype=np.float32), device=device).contiguous().realize()
|
dtype=np.float32), device=device).contiguous().realize()
|
||||||
|
|
||||||
queues.update({key: Tensor(value, device='NPY').realize() for key, value in npy_arrays.items() if key in ('tfm', 'big_tfm')})
|
queues.update({key: Tensor(value, device='NPY').realize() for key, value in npy_arrays.items() if key in ('tfm', 'big_tfm')})
|
||||||
@@ -271,18 +272,17 @@ def _parse_size(size_str: str) -> tuple[int, int]:
|
|||||||
return int(width), int(height)
|
return int(width), int(height)
|
||||||
|
|
||||||
|
|
||||||
def read_file_chunked_to_shm(path):
|
def read_file_chunked_to_disk(path):
|
||||||
if not path:
|
if not path:
|
||||||
return None
|
return None
|
||||||
import atexit
|
import atexit
|
||||||
import shutil
|
import shutil
|
||||||
from openpilot.common.file_chunker import open_file_chunked
|
from openpilot.common.file_chunker import open_file_chunked
|
||||||
from openpilot.common.hardware.hw import Paths
|
tmp_path = f'{path}.unchunked'
|
||||||
shm_path = os.path.join(Paths.shm_path(), os.path.basename(path))
|
with open(tmp_path, 'wb') as f, open_file_chunked(path) as src:
|
||||||
atexit.register(lambda: os.path.exists(shm_path) and os.remove(shm_path))
|
shutil.copyfileobj(src, f)
|
||||||
with open(shm_path, 'wb') as dst, open_file_chunked(path) as src:
|
atexit.register(lambda: os.path.exists(tmp_path) and os.remove(tmp_path))
|
||||||
shutil.copyfileobj(src, dst)
|
return tmp_path
|
||||||
return shm_path
|
|
||||||
|
|
||||||
|
|
||||||
def _load_policy_runners(args: argparse.Namespace) -> tuple[list, list]:
|
def _load_policy_runners(args: argparse.Namespace) -> tuple[list, list]:
|
||||||
@@ -326,11 +326,11 @@ if __name__ == "__main__":
|
|||||||
model_w, model_h = args.model_size
|
model_w, model_h = args.model_size
|
||||||
output_data = {}
|
output_data = {}
|
||||||
|
|
||||||
args.vision_onnx = read_file_chunked_to_shm(args.vision_onnx)
|
args.vision_onnx = read_file_chunked_to_disk(args.vision_onnx)
|
||||||
args.policy_onnx = read_file_chunked_to_shm(args.policy_onnx)
|
args.policy_onnx = read_file_chunked_to_disk(args.policy_onnx)
|
||||||
args.off_policy_onnx = read_file_chunked_to_shm(args.off_policy_onnx)
|
args.off_policy_onnx = read_file_chunked_to_disk(args.off_policy_onnx)
|
||||||
args.on_policy_onnx = read_file_chunked_to_shm(args.on_policy_onnx)
|
args.on_policy_onnx = read_file_chunked_to_disk(args.on_policy_onnx)
|
||||||
args.supercombo_onnx = read_file_chunked_to_shm(args.supercombo_onnx)
|
args.supercombo_onnx = read_file_chunked_to_disk(args.supercombo_onnx)
|
||||||
|
|
||||||
vision_runner = OnnxRunner(args.vision_onnx) if args.vision_onnx else None
|
vision_runner = OnnxRunner(args.vision_onnx) if args.vision_onnx else None
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ 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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from openpilot.common.parameterized import parameterized
|
from openpilot.common.parameterized import parameterized
|
||||||
|
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, _detect_desire_key
|
from openpilot.common.file_chunker import chunk_file, get_chunk_targets
|
||||||
|
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, _detect_desire_key, read_file_chunked_to_disk
|
||||||
from openpilot.common.test import OpenpilotTestCase
|
from openpilot.common.test import OpenpilotTestCase
|
||||||
|
|
||||||
|
|
||||||
@@ -160,3 +165,33 @@ class TestOutputSlicePreservation(OpenpilotTestCase):
|
|||||||
policy_slices = {'plan': slice(0, 495), 'meta': slice(495, 550)}
|
policy_slices = {'plan': slice(0, 495), 'meta': slice(495, 550)}
|
||||||
assert set(vision_slices.keys()) & set(policy_slices.keys()) == set(), \
|
assert set(vision_slices.keys()) & set(policy_slices.keys()) == set(), \
|
||||||
"vision and policy slices should not overlap in keys"
|
"vision and policy slices should not overlap in keys"
|
||||||
|
|
||||||
|
|
||||||
|
class TestReadFileChunkedToDisk(OpenpilotTestCase):
|
||||||
|
def test_none_passthrough(self):
|
||||||
|
assert read_file_chunked_to_disk(None) is None
|
||||||
|
|
||||||
|
def test_unchunked_source_staged_on_disk(self):
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
src = Path(d) / "driving_supercombo.onnx"
|
||||||
|
payload = os.urandom(1024)
|
||||||
|
src.write_bytes(payload)
|
||||||
|
|
||||||
|
out = Path(read_file_chunked_to_disk(str(src)))
|
||||||
|
|
||||||
|
assert out.parent == Path(d)
|
||||||
|
assert out.name == "driving_supercombo.onnx.unchunked"
|
||||||
|
assert out.read_bytes() == payload
|
||||||
|
|
||||||
|
def test_chunked_source_reassembled_on_disk(self):
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
src = Path(d) / "driving_supercombo.onnx"
|
||||||
|
payload = os.urandom(4096)
|
||||||
|
src.write_bytes(payload)
|
||||||
|
chunk_file(str(src), get_chunk_targets(str(src), len(payload)))
|
||||||
|
assert not src.exists()
|
||||||
|
|
||||||
|
out = Path(read_file_chunked_to_disk(str(src)))
|
||||||
|
|
||||||
|
assert out.parent == Path(d)
|
||||||
|
assert out.read_bytes() == payload
|
||||||
|
|||||||
@@ -140,8 +140,8 @@ 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_v19.json"
|
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v20.json"
|
||||||
MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v19.json"
|
MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v20.json"
|
||||||
|
|
||||||
def __init__(self, params: Params):
|
def __init__(self, params: Params):
|
||||||
self.params = params
|
self.params = params
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from openpilot.common.utils import strip_deprecated_keys
|
|||||||
from openpilot.common.filter_simple import FirstOrderFilter
|
from openpilot.common.filter_simple import FirstOrderFilter
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.realtime import DT_HW
|
from openpilot.common.realtime import DT_HW
|
||||||
|
from openpilot.selfdrive.modeld.helpers import MODELS_DIR, usbgpu_compiled
|
||||||
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
|
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
|
||||||
from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE
|
from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE
|
||||||
from openpilot.common.basedir import BASEDIR
|
from openpilot.common.basedir import BASEDIR
|
||||||
@@ -238,8 +239,7 @@ def hardware_thread(end_event, hw_queue) -> None:
|
|||||||
|
|
||||||
fan_controller = FanController(int(1./DT_HW))
|
fan_controller = FanController(int(1./DT_HW))
|
||||||
chestnut = Chestnut()
|
chestnut = Chestnut()
|
||||||
big_model_available = os.path.isfile(os.path.join(BASEDIR, "openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx")) or \
|
big_model_available = (MODELS_DIR / 'big_driving_supercombo.onnx').is_file() or usbgpu_compiled()
|
||||||
os.path.isfile(os.path.join(BASEDIR, "openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl.chunkmanifest"))
|
|
||||||
|
|
||||||
while not end_event.is_set():
|
while not end_event.is_set():
|
||||||
sm.update(PANDA_STATES_TIMEOUT)
|
sm.update(PANDA_STATES_TIMEOUT)
|
||||||
|
|||||||
@@ -4,7 +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 collections.abc import Callable
|
||||||
|
|
||||||
|
import pyray as rl
|
||||||
|
|
||||||
|
from openpilot.system.ui.lib.application import FontWeight
|
||||||
|
from openpilot.system.ui.sunnypilot.lib.styles import style
|
||||||
from openpilot.system.ui.sunnypilot.widgets.list_view import ButtonActionSP
|
from openpilot.system.ui.sunnypilot.widgets.list_view import ButtonActionSP
|
||||||
|
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||||
|
from openpilot.system.ui.widgets.list_view import BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING, _resolve_value
|
||||||
|
|
||||||
|
|
||||||
class NoElideButtonAction(ButtonActionSP):
|
class NoElideButtonAction(ButtonActionSP):
|
||||||
@@ -12,6 +20,38 @@ class NoElideButtonAction(ButtonActionSP):
|
|||||||
return super().get_width_hint() + 1
|
return super().get_width_hint() + 1
|
||||||
|
|
||||||
|
|
||||||
|
class ScrollingButtonAction(ButtonActionSP):
|
||||||
|
"""ButtonActionSP whose value scrolls instead of eliding when it doesn't fit."""
|
||||||
|
|
||||||
|
def __init__(self, text: str | Callable[[], str], width: int = style.BUTTON_ACTION_WIDTH,
|
||||||
|
enabled: bool | Callable[[], bool] = True):
|
||||||
|
super().__init__(text=text, width=width, enabled=enabled)
|
||||||
|
self._value_label = UnifiedLabel("", font_size=style.ITEM_TEXT_FONT_SIZE, font_weight=FontWeight.NORMAL,
|
||||||
|
text_color=self._value_color, scroll=True,
|
||||||
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||||
|
|
||||||
|
def set_value(self, value: str | Callable[[], str], color: rl.Color = style.ITEM_TEXT_VALUE_COLOR):
|
||||||
|
if self.value != _resolve_value(value, ""):
|
||||||
|
self._value_label.reset_scroll()
|
||||||
|
super().set_value(value, color)
|
||||||
|
self._value_label.set_text(value)
|
||||||
|
self._value_label.set_text_color(color)
|
||||||
|
|
||||||
|
def _render(self, rect: rl.Rectangle) -> bool:
|
||||||
|
"""Duplicate of ButtonActionSP._render, with the value drawn by a scrolling label"""
|
||||||
|
self._button.set_text(self.text)
|
||||||
|
self._button.set_enabled(_resolve_value(self.enabled))
|
||||||
|
button_rect = rl.Rectangle(rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT)
|
||||||
|
self._button.render(button_rect)
|
||||||
|
|
||||||
|
if self.value:
|
||||||
|
self._value_label.render(rl.Rectangle(rect.x, rect.y, rect.width - BUTTON_WIDTH - TEXT_PADDING, rect.height))
|
||||||
|
|
||||||
|
pressed = self._pressed
|
||||||
|
self._pressed = False
|
||||||
|
return pressed
|
||||||
|
|
||||||
|
|
||||||
class AlertFadeAnimator:
|
class AlertFadeAnimator:
|
||||||
def __init__(self, target_fps: int, duration_on: float = 0.75, rc: float = 0.05):
|
def __init__(self, target_fps: int, duration_on: float = 0.75, rc: float = 0.05):
|
||||||
from openpilot.common.filter_simple import FirstOrderFilter
|
from openpilot.common.filter_simple import FirstOrderFilter
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
"""
|
||||||
|
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 math
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pyray as rl
|
||||||
|
|
||||||
|
from openpilot.common.filter_simple import FirstOrderFilter
|
||||||
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
|
from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient
|
||||||
|
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||||
|
from openpilot.system.ui.sunnypilot.lib.styles import style
|
||||||
|
from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP
|
||||||
|
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||||
|
from openpilot.system.ui.widgets.list_view import ItemAction
|
||||||
|
|
||||||
|
FONT_SIZE = style.ITEM_TEXT_FONT_SIZE
|
||||||
|
ICON_SIZE = 56
|
||||||
|
ICON_PADDING = 12
|
||||||
|
|
||||||
|
BAR_WIDTH = 1100
|
||||||
|
BAR_HEIGHT = 20
|
||||||
|
BAR_GAP = 16
|
||||||
|
BAR_RADIUS = BAR_HEIGHT / 2
|
||||||
|
CAPSULE_POINTS = 24
|
||||||
|
|
||||||
|
RAIL_COLOR = rl.Color(60, 60, 60, 255)
|
||||||
|
FILL_COLOR = rl.Color(30, 121, 232, 255)
|
||||||
|
# rl.WHITE is a tuple; the shimmer path reads .a off the color
|
||||||
|
TEXT_COLOR = rl.Color(255, 255, 255, 255)
|
||||||
|
|
||||||
|
SWEEP_SPEED = 550.0 # px/s
|
||||||
|
SWEEP_BAND = 240.0 # highlight half-width, px
|
||||||
|
SWEEP_DIM = 0.65
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadStatusAction(ItemAction):
|
||||||
|
"""Model download row: a name + percent over a progress rail while downloading, a name + icon otherwise."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(width=BAR_WIDTH)
|
||||||
|
self.name = ""
|
||||||
|
self.status_text = ""
|
||||||
|
self.downloading = False
|
||||||
|
self.text_color = rl.GRAY
|
||||||
|
self.icon: str | None = None
|
||||||
|
self.icon_color: rl.Color | None = None
|
||||||
|
self._font = gui_app.font(FontWeight.NORMAL)
|
||||||
|
# raw progress arrives in steps, one per 128KB chunk the manager publishes
|
||||||
|
self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps)
|
||||||
|
# integrated per frame; (t * speed) % span jumps whenever the fill width changes
|
||||||
|
self._sweep = 0.0
|
||||||
|
|
||||||
|
self._name_label = UnifiedLabel("", font_size=FONT_SIZE, font_weight=FontWeight.NORMAL, text_color=TEXT_COLOR,
|
||||||
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
|
||||||
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||||
|
self._percent_label = UnifiedLabel("", font_size=FONT_SIZE, font_weight=FontWeight.NORMAL, text_color=TEXT_COLOR,
|
||||||
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
|
||||||
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||||
|
|
||||||
|
def update(self, name, downloading=False, progress=0.0, status_text="", text_color=rl.GRAY, icon=None, icon_color=None):
|
||||||
|
if downloading and not self.downloading:
|
||||||
|
self._name_label.reset_shimmer()
|
||||||
|
self._progress.x = progress
|
||||||
|
self._sweep = 0.0
|
||||||
|
self.name = name
|
||||||
|
self.downloading = downloading
|
||||||
|
self.status_text = status_text
|
||||||
|
self.text_color = text_color
|
||||||
|
self.icon = icon
|
||||||
|
self.icon_color = icon_color
|
||||||
|
self._name_label._shimmer = downloading
|
||||||
|
if downloading:
|
||||||
|
self._progress.update(progress)
|
||||||
|
self._sweep += SWEEP_SPEED / gui_app.target_fps
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _idle_text(self) -> str:
|
||||||
|
return f"{self.name} - {self.status_text}" if self.status_text else self.name
|
||||||
|
|
||||||
|
def get_width_hint(self) -> float:
|
||||||
|
if self.downloading:
|
||||||
|
return BAR_WIDTH
|
||||||
|
width = measure_text_cached(self._font, self._idle_text, FONT_SIZE).x
|
||||||
|
if self.icon:
|
||||||
|
width += ICON_SIZE + ICON_PADDING
|
||||||
|
return width
|
||||||
|
|
||||||
|
def _render(self, rect: rl.Rectangle):
|
||||||
|
if self.downloading:
|
||||||
|
self._render_downloading(rect)
|
||||||
|
else:
|
||||||
|
self._render_idle(rect)
|
||||||
|
|
||||||
|
def _sweep_gradient(self, width: float) -> Gradient:
|
||||||
|
# clearance at both ends keeps the wrap offscreen
|
||||||
|
center = (self._sweep % (width + 2 * SWEEP_BAND)) - SWEEP_BAND
|
||||||
|
|
||||||
|
def band(x: float) -> float:
|
||||||
|
return max(0.0, 1.0 - abs(x - center) / SWEEP_BAND)
|
||||||
|
|
||||||
|
# sampling the corners is exact for a piecewise linear band
|
||||||
|
xs = sorted({0.0, width} | {min(max(center + o, 0.0), width) for o in (-SWEEP_BAND, 0.0, SWEEP_BAND)}, reverse=True)
|
||||||
|
# the gradient axis runs right-to-left in screen space
|
||||||
|
stops = [1.0 - x / width for x in xs]
|
||||||
|
# alpha here is the lift over the SWEEP_DIM base, not the final opacity
|
||||||
|
colors = [rl.Color(FILL_COLOR.r, FILL_COLOR.g, FILL_COLOR.b, int(255 * band(x))) for x in xs]
|
||||||
|
return Gradient(start=(0.0, 0.0), end=(1.0, 0.0), colors=colors, stops=stops)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _capsule(rect: rl.Rectangle) -> np.ndarray:
|
||||||
|
"""Rounded-end ribbon so the gradient covers the caps."""
|
||||||
|
r = rect.height / 2
|
||||||
|
cy = rect.y + r
|
||||||
|
top, bottom = [], []
|
||||||
|
for i in range(CAPSULE_POINTS):
|
||||||
|
x = rect.x + rect.width * i / (CAPSULE_POINTS - 1)
|
||||||
|
d = min(x - rect.x, rect.x + rect.width - x, r)
|
||||||
|
h = math.sqrt(max(r * r - (r - d) ** 2, 0.0))
|
||||||
|
top.append((x, cy - h))
|
||||||
|
bottom.append((x, cy + h))
|
||||||
|
return np.array(top + bottom[::-1], dtype=np.float32)
|
||||||
|
|
||||||
|
def _draw_fill(self, rail: rl.Rectangle, fill_width: float):
|
||||||
|
if fill_width <= 0:
|
||||||
|
return
|
||||||
|
fill = rl.Rectangle(rail.x, rail.y, fill_width, rail.height)
|
||||||
|
rl.draw_rectangle_rounded(fill, 1.0, 10, rl.Color(FILL_COLOR.r, FILL_COLOR.g, FILL_COLOR.b, int(255 * SWEEP_DIM)))
|
||||||
|
draw_polygon(fill, self._capsule(fill), gradient=self._sweep_gradient(fill_width))
|
||||||
|
|
||||||
|
def _render_downloading(self, rect: rl.Rectangle):
|
||||||
|
percent = f"{int(self._progress.x)}%"
|
||||||
|
text_height = measure_text_cached(self._font, percent, FONT_SIZE).y
|
||||||
|
top = rect.y + (rect.height - (text_height + BAR_GAP + BAR_HEIGHT)) / 2
|
||||||
|
|
||||||
|
text_rect = rl.Rectangle(rect.x, top, rect.width, text_height)
|
||||||
|
self._name_label.set_text(self.name)
|
||||||
|
self._name_label.render(text_rect)
|
||||||
|
self._percent_label.set_text(percent)
|
||||||
|
self._percent_label.render(text_rect)
|
||||||
|
|
||||||
|
rail = rl.Rectangle(rect.x, top + text_height + BAR_GAP, rect.width, BAR_HEIGHT)
|
||||||
|
rl.draw_rectangle_rounded(rail, 1.0, 10, RAIL_COLOR)
|
||||||
|
self._draw_fill(rail, max(0.0, min(rect.width, rect.width * (self._progress.x / 100.0))))
|
||||||
|
|
||||||
|
def _render_idle(self, rect: rl.Rectangle):
|
||||||
|
text = self._idle_text
|
||||||
|
text_size = measure_text_cached(self._font, text, FONT_SIZE)
|
||||||
|
right = rect.x + rect.width
|
||||||
|
|
||||||
|
if self.icon:
|
||||||
|
texture = gui_app.texture(self.icon, ICON_SIZE, ICON_SIZE, keep_aspect_ratio=True)
|
||||||
|
rl.draw_texture_v(texture, rl.Vector2(right - texture.width, rect.y + (rect.height - texture.height) / 2),
|
||||||
|
self.icon_color or self.text_color)
|
||||||
|
right -= texture.width + ICON_PADDING
|
||||||
|
|
||||||
|
rl.draw_text_ex(self._font, text, rl.Vector2(right - text_size.x, rect.y + (rect.height - text_size.y) / 2),
|
||||||
|
FONT_SIZE, 0, self.text_color)
|
||||||
|
|
||||||
|
|
||||||
|
def download_status_item(title):
|
||||||
|
return ListItemSP(title=title, action_item=DownloadStatusAction(), title_color=style.ITEM_TEXT_COLOR)
|
||||||
@@ -7,7 +7,7 @@ from openpilot.common.test import OpenpilotTestCase
|
|||||||
from openpilot.cereal import messaging, log
|
from openpilot.cereal import messaging, log
|
||||||
from teleoprtc.tracks import VIDEO_CLOCK_RATE
|
from teleoprtc.tracks import VIDEO_CLOCK_RATE
|
||||||
|
|
||||||
from openpilot.system.webrtc.webrtcd import CerealOutgoingMessageProxy, CerealIncomingMessageProxy
|
from openpilot.system.webrtc.webrtcd import CerealOutgoingMessageProxy, CerealIncomingMessageProxy, ServerState, handle_get_stream
|
||||||
from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack
|
from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack
|
||||||
|
|
||||||
|
|
||||||
@@ -80,3 +80,8 @@ class TestStreamSession(OpenpilotTestCase):
|
|||||||
start_pts = packet.pts
|
start_pts = packet.pts
|
||||||
assert abs(i + packet.pts - (start_pts + (((time.monotonic_ns() - start_ns) * VIDEO_CLOCK_RATE) // 1_000_000_000))) < 450 #5ms
|
assert abs(i + packet.pts - (start_pts + (((time.monotonic_ns() - start_ns) * VIDEO_CLOCK_RATE) // 1_000_000_000))) < 450 #5ms
|
||||||
assert bytes(packet) == b""
|
assert bytes(packet) == b""
|
||||||
|
|
||||||
|
def test_stream_rejects_non_json_content_type(self):
|
||||||
|
response = self.loop.run_until_complete(handle_get_stream(ServerState(), b"{}", "text/plain"))
|
||||||
|
|
||||||
|
assert response == (415, b'{"error": "unsupported media type"}', "application/json; charset=utf-8")
|
||||||
|
|||||||
@@ -395,7 +395,10 @@ def _text_response(text: str, status: int = 200) -> tuple[int, bytes, str]:
|
|||||||
return (status, text.encode(), "text/plain; charset=utf-8")
|
return (status, text.encode(), "text/plain; charset=utf-8")
|
||||||
|
|
||||||
|
|
||||||
async def handle_get_stream(state: ServerState, raw_body: bytes) -> tuple[int, bytes, str]:
|
async def handle_get_stream(state: ServerState, raw_body: bytes, content_type: str) -> tuple[int, bytes, str]:
|
||||||
|
if content_type != "application/json":
|
||||||
|
return _json_response({"error": "unsupported media type"}, status=415)
|
||||||
|
|
||||||
stream_dict = state.streams
|
stream_dict = state.streams
|
||||||
body = StreamRequestBody(**json.loads(raw_body))
|
body = StreamRequestBody(**json.loads(raw_body))
|
||||||
|
|
||||||
@@ -508,7 +511,7 @@ class WebrtcdHandler(BaseHTTPRequestHandler):
|
|||||||
services = parse_qs(parsed.query).get("services", [""])[0]
|
services = parse_qs(parsed.query).get("services", [""])[0]
|
||||||
result = self._run(handle_get_schema(self.server.state, services))
|
result = self._run(handle_get_schema(self.server.state, services))
|
||||||
elif parsed.path == "/stream":
|
elif parsed.path == "/stream":
|
||||||
result = self._run(handle_get_stream(self.server.state, self._read_body()))
|
result = self._run(handle_get_stream(self.server.state, self._read_body(), self.headers.get_content_type()))
|
||||||
else: # /notify
|
else: # /notify
|
||||||
try:
|
try:
|
||||||
payload = json.loads(self._read_body())
|
payload = json.loads(self._read_body())
|
||||||
@@ -611,7 +614,7 @@ def webrtcd_thread(host: str, port: int):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="WebRTC daemon")
|
parser = argparse.ArgumentParser(description="WebRTC daemon")
|
||||||
parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to listen on")
|
parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to listen on")
|
||||||
parser.add_argument("--port", type=int, default=5001, help="Port to listen on")
|
parser.add_argument("--port", type=int, default=5001, help="Port to listen on")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ dependencies = [
|
|||||||
"tqdm", # cars (fw_versions.py) on start + many one-off uses
|
"tqdm", # cars (fw_versions.py) on start + many one-off uses
|
||||||
|
|
||||||
# core
|
# core
|
||||||
"scons",
|
"scons==4.10.1", # 4.11 removed the qt3 tool still used to build Cabana
|
||||||
"pycapnp==2.1.0", # 2.2 introduces a memory leak due to cyclic references
|
"pycapnp==2.1.0", # 2.2 introduces a memory leak due to cyclic references
|
||||||
"numpy >=2.0",
|
"numpy >=2.0",
|
||||||
|
|
||||||
|
|||||||
@@ -53,24 +53,28 @@ def create_pkl_name(full_name: str) -> str:
|
|||||||
return pkl
|
return pkl
|
||||||
|
|
||||||
|
|
||||||
def _read_pkl_bytes(pkl_path: Path) -> bytes:
|
def _hash_pkl(pkl_path: Path) -> str:
|
||||||
manifest = Path(f"{pkl_path}.chunkmanifest")
|
manifest = Path(f"{pkl_path}.chunkmanifest")
|
||||||
if manifest.exists():
|
if manifest.exists():
|
||||||
num_chunks = int(manifest.read_text().strip())
|
num_chunks = int(manifest.read_text().strip())
|
||||||
parts = []
|
paths = [Path(f"{pkl_path}.chunk{i + 1:02d}of{num_chunks:02d}") for i in range(num_chunks)]
|
||||||
for i in range(num_chunks):
|
else:
|
||||||
chunk = Path(f"{pkl_path}.chunk{i + 1:02d}of{num_chunks:02d}")
|
paths = [pkl_path]
|
||||||
parts.append(chunk.read_bytes())
|
|
||||||
return b''.join(parts)
|
digest = hashlib.sha256()
|
||||||
return pkl_path.read_bytes()
|
for path in paths:
|
||||||
|
with path.open('rb') as f:
|
||||||
|
while block := f.read(1024 * 1024):
|
||||||
|
digest.update(block)
|
||||||
|
return digest.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def _find_driving_pkl(output_path: Path) -> Path | None:
|
def _find_driving_pkl(output_path: Path) -> Path | None:
|
||||||
for pattern in ('driving_tinygrad.pkl', 'driving_*_tinygrad.pkl'):
|
for pattern in ('*driving_tinygrad.pkl', '*driving_*_tinygrad.pkl'):
|
||||||
matches = sorted(output_path.glob(pattern))
|
matches = sorted(output_path.glob(pattern))
|
||||||
if matches:
|
if matches:
|
||||||
return matches[0]
|
return matches[0]
|
||||||
for pattern in ('driving_tinygrad.pkl.chunkmanifest', 'driving_*_tinygrad.pkl.chunkmanifest'):
|
for pattern in ('*driving_tinygrad.pkl.chunkmanifest', '*driving_*_tinygrad.pkl.chunkmanifest'):
|
||||||
matches = sorted(output_path.glob(pattern))
|
matches = sorted(output_path.glob(pattern))
|
||||||
if matches:
|
if matches:
|
||||||
return Path(str(matches[0]).removesuffix('.chunkmanifest'))
|
return Path(str(matches[0]).removesuffix('.chunkmanifest'))
|
||||||
@@ -87,7 +91,7 @@ def _rename_pkl_with_chunks(old_pkl: Path, new_pkl: Path) -> Path:
|
|||||||
|
|
||||||
|
|
||||||
def generate_chunked_model(driving_pkl: Path) -> dict:
|
def generate_chunked_model(driving_pkl: Path) -> dict:
|
||||||
tinygrad_hash = hashlib.sha256(_read_pkl_bytes(driving_pkl)).hexdigest()
|
tinygrad_hash = _hash_pkl(driving_pkl)
|
||||||
|
|
||||||
chunks_config = []
|
chunks_config = []
|
||||||
manifest_file = Path(f"{driving_pkl}.chunkmanifest")
|
manifest_file = Path(f"{driving_pkl}.chunkmanifest")
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ git fetch origin $DEV_BRANCH || (git checkout -b $DEV_BRANCH && git commit --all
|
|||||||
echo "[-] committing version $VERSION T=$SECONDS"
|
echo "[-] committing version $VERSION T=$SECONDS"
|
||||||
git add -f .
|
git add -f .
|
||||||
|
|
||||||
|
# gitlinks break the release tree on device
|
||||||
|
if git ls-files -s | awk '$1 == "160000" { found = 1; print } END { exit !found }'; then
|
||||||
|
echo "Error: submodules found in release tree."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# include source commit hash and build date in commit
|
# include source commit hash and build date in commit
|
||||||
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
|
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
|
||||||
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
|
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
|
||||||
|
|||||||
@@ -381,20 +381,20 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deepmerge"
|
name = "deepmerge"
|
||||||
version = "2.1.0"
|
version = "3.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/78/6e9e20106224083cfb817d2d3c26e80e72258d617b616721a169b87081e0/deepmerge-2.1.0.tar.gz", hash = "sha256:07ca7a7b8935df596c512fa8161877c0487ac61f691c07766e7d71d2b23bdd2f", size = 21449, upload-time = "2026-06-22T05:46:07.669Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/b7/6c/9f4577a36d5f463a3a3f8322bd65d33e1a1a6b6ba1d692a5ebc3cba19015/deepmerge-3.0.tar.gz", hash = "sha256:14ed69f063de64b7743985c732ccff5d6c34ff4560946e7fbfd99086b853b9ce", size = 22279, upload-time = "2026-08-17T05:50:53.161Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/51/25/2a75b47cb057b1e164c604fb81ab690a6cdb5e2260ce651194eae90f64a3/deepmerge-2.1.0-py3-none-any.whl", hash = "sha256:8f148339a91d680a75ecb74ade235d9e759a93df373a0b04e9d31c8666cfeb75", size = 14345, upload-time = "2026-06-22T05:46:06.742Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/d7/7f19bedd30b90b72865aeec3a29127bed6dee6c9ef0324bb5b4d424bb0e3/deepmerge-3.0-py3-none-any.whl", hash = "sha256:c8541c3e186dc88d19a5513ad3a0b2d0b22beaa780969fc0c13b995a64265365", size = 14855, upload-time = "2026-08-17T05:50:52.218Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filelock"
|
name = "filelock"
|
||||||
version = "3.32.2"
|
version = "3.32.3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/57/3ba6e6cb097f85b855b00163d169f35365f44277df044dcf96d55b8f62a3/filelock-3.32.2.tar.gz", hash = "sha256:c33351e1f49cae33414acbc6d56784e6ecee82514ec90795da1161fc4836b5b8", size = 217172, upload-time = "2026-07-29T22:46:04.895Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/7d/64/a02e6765de08964ed371eca577870593245afc9dfac16d037de7c10d18e6/filelock-3.32.3.tar.gz", hash = "sha256:0ffa185a3540854c95caa7fa76b76cb219d907415e2c5dc9af25fd970563487f", size = 218135, upload-time = "2026-08-13T16:00:05.577Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/e8/72f8cef9fdfeffe06213fe8508039396ee48daa0e3259457ed766173bfd6/filelock-3.32.2-py3-none-any.whl", hash = "sha256:87dd94cf281e586d135fa51132b8e3d9a598b316e90377a288663c9321036c82", size = 98830, upload-time = "2026-07-29T22:46:03.52Z" },
|
{ url = "https://files.pythonhosted.org/packages/a7/8e/50f46a9c0ce8d2861a394c1347caae037ea0431d2f67d7feb151cbc4649a/filelock-3.32.3-py3-none-any.whl", hash = "sha256:7f0ca4bcc0e181c60dbbd8aa9ab5b120ebb99e4e064e83636340056f833a1f09", size = 98901, upload-time = "2026-08-13T16:00:03.974Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -478,7 +478,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "huggingface-hub"
|
name = "huggingface-hub"
|
||||||
version = "1.27.0"
|
version = "1.28.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
@@ -491,18 +491,18 @@ dependencies = [
|
|||||||
{ name = "tqdm" },
|
{ name = "tqdm" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/3e/9b/ddf3d02a8681f1b9ce52fda03d755dad6b74c4f8172304c4c8d2975450f9/huggingface_hub-1.27.0.tar.gz", hash = "sha256:c1fed40ea82a6b41b477f5243546549b792ae0a93abcea608cff66089bf8f8df", size = 942668, upload-time = "2026-08-07T12:48:05.161Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/c6/ae/222a91937ebee7f62c0ca8f5ee0afd97577caf24c0abb927d1f5c7e9f6d2/huggingface_hub-1.28.0.tar.gz", hash = "sha256:46a2e950c09234de54093d587d1675382f0d08dbd600d9fb599b5932f5b2c6cb", size = 959609, upload-time = "2026-08-18T12:27:15.101Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/de/d8/95b735e183957c1f26d94c52977f09d466d55119cbbc1558ea4975e4c216/huggingface_hub-1.27.0-py3-none-any.whl", hash = "sha256:7df6827c2f956c60fbaa64646e979e566db76f619dd0a9729dfb8c5a3eb4f68d", size = 784926, upload-time = "2026-08-07T12:48:02.905Z" },
|
{ url = "https://files.pythonhosted.org/packages/51/0e/eafef18f1a75e125e68395db21131db0cf868a128ecd2fce69b4df6c584b/huggingface_hub-1.28.0-py3-none-any.whl", hash = "sha256:58a8bacb03072edfc38067065e9dc24bbb34805410fcd36a1632de0b329660bb", size = 793202, upload-time = "2026-08-18T12:27:12.719Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.18"
|
version = "3.19"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
|
{ url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -847,7 +847,7 @@ requires-dist = [
|
|||||||
{ name = "rednose", marker = "extra == 'submodules'", editable = "rednose_repo" },
|
{ name = "rednose", marker = "extra == 'submodules'", editable = "rednose_repo" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
{ name = "ruff", marker = "extra == 'testing'" },
|
{ name = "ruff", marker = "extra == 'testing'" },
|
||||||
{ name = "scons" },
|
{ name = "scons", specifier = "==4.10.1" },
|
||||||
{ name = "sentry-sdk" },
|
{ name = "sentry-sdk" },
|
||||||
{ name = "setproctitle" },
|
{ name = "setproctitle" },
|
||||||
{ name = "sounddevice" },
|
{ name = "sounddevice" },
|
||||||
@@ -971,11 +971,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.20.0"
|
version = "2.21.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1136,11 +1136,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scons"
|
name = "scons"
|
||||||
version = "4.11.0"
|
version = "4.10.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/dd/82/3c4e089ac8df2eaee8a7f14e489b2a76f94f4c1d8defa4e46c8ad15cae86/scons-4.11.0.tar.gz", hash = "sha256:5ba48f9e2eb6b9178cabdc9893792418e6970c84f43f4b027e4468e20616a89c", size = 3269126, upload-time = "2026-08-11T04:29:45.62Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/7d/c9/2f430bb39e4eccba32ce8008df4a3206df651276422204e177a09e12b30b/scons-4.10.1.tar.gz", hash = "sha256:99c0e94a42a2c1182fa6859b0be697953db07ba936ecc9817ae0d218ced20b15", size = 3258403, upload-time = "2025-11-16T22:43:39.258Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/ac/a4445bbbd58a5fa6a5c8b3b0458ffbee04e4acaff87677058eab9c6af682/scons-4.11.0-py3-none-any.whl", hash = "sha256:2edc077aaeafc43377ba46ce1fa3e7b40edea59c62db9ef7e39e07dc88b754fa", size = 4123742, upload-time = "2026-08-11T04:29:42.881Z" },
|
{ url = "https://files.pythonhosted.org/packages/ce/bf/931fb9fbb87234c32b8b1b1c15fba23472a10777c12043336675633809a7/scons-4.10.1-py3-none-any.whl", hash = "sha256:bd9d1c52f908d874eba92a8c0c0a8dcf2ed9f3b88ab956d0fce1da479c4e7126", size = 4136069, upload-time = "2025-11-16T22:43:35.933Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1194,18 +1194,18 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sounddevice"
|
name = "sounddevice"
|
||||||
version = "0.5.5"
|
version = "0.5.6"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cffi" },
|
{ name = "cffi" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/f9/2592608737553638fca98e21e54bfec40bf577bb98a61b2770c912aab25e/sounddevice-0.5.5.tar.gz", hash = "sha256:22487b65198cb5bf2208755105b524f78ad173e5ab6b445bdab1c989f6698df3", size = 143191, upload-time = "2026-01-23T18:36:43.529Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/ec/db/0c890e2d9aab9ba284021efc02e1d3aebfecab1b611762d7434602209bcf/sounddevice-0.5.6.tar.gz", hash = "sha256:8ec9fbfde2e32f020b167e348f3ab3bac6625a5f15af524d790108ac7147a410", size = 1120094, upload-time = "2026-08-17T07:55:05.048Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/0a/478e441fd049002cf308520c0d62dd8333e7c6cc8d997f0dda07b9fbcc46/sounddevice-0.5.5-py3-none-any.whl", hash = "sha256:30ff99f6c107f49d25ad16a45cacd8d91c25a1bcdd3e81a206b921a3a6405b1f", size = 32807, upload-time = "2026-01-23T18:36:35.649Z" },
|
{ url = "https://files.pythonhosted.org/packages/72/1f/62eef605172bddc1017508469a12f75bc7c4194ece35c734f822795f53b1/sounddevice-0.5.6-py3-none-any.whl", hash = "sha256:de099612311ad81e55d31ccbd83f43ea6bf4d87b48f9b6ea55a1fbcde0eee4e0", size = 32793, upload-time = "2026-08-17T07:54:57.507Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/f9/c037c35f6d0b6bc3bc7bfb314f1d6f1f9a341328ef47cd63fc4f850a7b27/sounddevice-0.5.5-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:05eb9fd6c54c38d67741441c19164c0dae8ce80453af2d8c4ad2e7823d15b722", size = 108557, upload-time = "2026-01-23T18:36:37.41Z" },
|
{ url = "https://files.pythonhosted.org/packages/b6/84/85e719d49cf98b2f406d9ac9c338892286c4448eb42ef0b2625ccf159616/sounddevice-0.5.6-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:e3aef00ad8b1d1740eb66d9a7671eab88a4d2b8fa4ab33498d742e63b65c309c", size = 1009647, upload-time = "2026-08-17T07:54:58.814Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/a1/d19dd9889cd4bce2e233c4fac007cd8daaf5b9fe6e6a5d432cf17be0b807/sounddevice-0.5.5-py3-none-win32.whl", hash = "sha256:1234cc9b4c9df97b6cbe748146ae0ec64dd7d6e44739e8e42eaa5b595313a103", size = 317765, upload-time = "2026-01-23T18:36:39.047Z" },
|
{ url = "https://files.pythonhosted.org/packages/c5/6f/6292145099f72a153a710245f46ae43e5fb6c77bec1b6086cb76c12dc280/sounddevice-0.5.6-py3-none-win32.whl", hash = "sha256:b36b807eb02abd257198bf84b2af05e4fea199a9d2f0019014169c7136d45e9c", size = 1009627, upload-time = "2026-08-17T07:55:00.401Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c3/0e/002ed7c4c1c2ab69031f78989d3b789fee3a7fba9e586eb2b81688bf4961/sounddevice-0.5.5-py3-none-win_amd64.whl", hash = "sha256:cfc6b2c49fb7f555591c78cb8ecf48d6a637fd5b6e1db5fec6ed9365d64b3519", size = 365324, upload-time = "2026-01-23T18:36:40.496Z" },
|
{ url = "https://files.pythonhosted.org/packages/8d/3e/cbc593c31a5f0d817b3fe97e64aa8461bd0f55cb07b67ce1b776296ae336/sounddevice-0.5.6-py3-none-win_amd64.whl", hash = "sha256:7f4162f514f007b0bf25a3ccfed3f1705bc2ec311888a90232729eec4f57a4f4", size = 1009630, upload-time = "2026-08-17T07:55:02.088Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4e/39/a61d4b83a7746b70d23d9173be688c0c6bfc7173772344b7442c2c155497/sounddevice-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3861901ddd8230d2e0e8ae62ac320cdd4c688d81df89da036dcb812f757bb3e6", size = 317115, upload-time = "2026-01-23T18:36:42.235Z" },
|
{ url = "https://files.pythonhosted.org/packages/60/a4/b0c21c9f215a6fd9606b8f8748c21212dc098e5d5a2d93068c50edcf19b4/sounddevice-0.5.6-py3-none-win_arm64.whl", hash = "sha256:c8ae19173e5f27f8c12d4b5eee2dbfe542cee125d591e663e0fb4dfb75246d45", size = 1009630, upload-time = "2026-08-17T07:55:03.689Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1285,7 +1285,6 @@ requires-dist = [
|
|||||||
{ name = "pylint", marker = "extra == 'linting'" },
|
{ name = "pylint", marker = "extra == 'linting'" },
|
||||||
{ name = "pytest", marker = "extra == 'testing-minimal'" },
|
{ name = "pytest", marker = "extra == 'testing-minimal'" },
|
||||||
{ name = "pytest-split", marker = "extra == 'testing-minimal'" },
|
{ name = "pytest-split", marker = "extra == 'testing-minimal'" },
|
||||||
{ name = "pytest-timeout", marker = "extra == 'testing-minimal'" },
|
|
||||||
{ name = "pytest-xdist", marker = "extra == 'testing-minimal'" },
|
{ name = "pytest-xdist", marker = "extra == 'testing-minimal'" },
|
||||||
{ name = "ruff", marker = "extra == 'linting'", specifier = "==0.14.10" },
|
{ name = "ruff", marker = "extra == 'linting'", specifier = "==0.14.10" },
|
||||||
{ name = "safetensors", marker = "extra == 'testing-unit'" },
|
{ name = "safetensors", marker = "extra == 'testing-unit'" },
|
||||||
@@ -1294,6 +1293,7 @@ requires-dist = [
|
|||||||
{ name = "tiktoken", marker = "extra == 'testing'" },
|
{ name = "tiktoken", marker = "extra == 'testing'" },
|
||||||
{ name = "tinygrad", extras = ["testing-minimal"], marker = "extra == 'testing-unit'" },
|
{ name = "tinygrad", extras = ["testing-minimal"], marker = "extra == 'testing-unit'" },
|
||||||
{ name = "tinygrad", extras = ["testing-unit"], marker = "extra == 'testing'" },
|
{ name = "tinygrad", extras = ["testing-unit"], marker = "extra == 'testing'" },
|
||||||
|
{ name = "tinymesa", marker = "extra == 'mesa'", specifier = "==25.2.7.2" },
|
||||||
{ name = "torch", marker = "extra == 'testing-minimal'", specifier = "==2.9.1" },
|
{ name = "torch", marker = "extra == 'testing-minimal'", specifier = "==2.9.1" },
|
||||||
{ name = "tqdm", marker = "extra == 'testing-unit'" },
|
{ name = "tqdm", marker = "extra == 'testing-unit'" },
|
||||||
{ name = "transformers", marker = "extra == 'testing'" },
|
{ name = "transformers", marker = "extra == 'testing'" },
|
||||||
@@ -1301,7 +1301,7 @@ requires-dist = [
|
|||||||
{ name = "typing-extensions", marker = "extra == 'linting'" },
|
{ name = "typing-extensions", marker = "extra == 'linting'" },
|
||||||
{ name = "z3-solver", marker = "extra == 'testing-minimal'", specifier = "<4.15.4" },
|
{ name = "z3-solver", marker = "extra == 'testing-minimal'", specifier = "<4.15.4" },
|
||||||
]
|
]
|
||||||
provides-extras = ["linting", "testing-minimal", "testing-unit", "testing", "docs"]
|
provides-extras = ["linting", "testing-minimal", "testing-unit", "testing", "docs", "mesa"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "tomli"
|
||||||
@@ -1335,27 +1335,27 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ty"
|
name = "ty"
|
||||||
version = "0.0.72"
|
version = "0.0.73"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/df/656e684bafb13c1d146e7d5b5f3e7978ca177232acc84998ff36427e9462/ty-0.0.72.tar.gz", hash = "sha256:ec2b8066b618df18cab4cb8e992f8da45d360332acb23fa34df7fa29cd1b9d3a", size = 6654939, upload-time = "2026-08-14T21:35:42.612Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/e5/90/c4e1bb4cead3b644c3e258a27f9b05c7dc5eb0ec96a4f5282194edae9e0d/ty-0.0.73.tar.gz", hash = "sha256:823d4ce0d237bfc7eb6bcee70842f2c0706113813a16951077840743712f4b74", size = 6712739, upload-time = "2026-08-19T03:12:43.381Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e2/3b/f51461239a4e66565d4b362f97a3b55fe7fdba2e944068341f87c62f6743/ty-0.0.72-py3-none-linux_armv6l.whl", hash = "sha256:fda86db153ffd85ee52000cf175d6a3f1c0223772cf7c5b6f726200bf92c7b44", size = 12621989, upload-time = "2026-08-14T21:35:01.676Z" },
|
{ url = "https://files.pythonhosted.org/packages/e4/0f/f5e1801e55cc631f2db193276675b30561b963a2403da832bffb5d100267/ty-0.0.73-py3-none-linux_armv6l.whl", hash = "sha256:90a946082bf9bc446b5e72973d9f4ff1222a240b2ca4c9e6eed61eb913e30810", size = 12715452, upload-time = "2026-08-19T03:12:06.673Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ca/fb/79ddf683affc679ca856f3510b5640ec3a88a842ba5f654f5d4bc78f1786/ty-0.0.72-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ceb944c612529b9023acfdc9cf4c0dcbb722549f9d17d46baecd1141baf01d7f", size = 12233910, upload-time = "2026-08-14T21:35:04.334Z" },
|
{ url = "https://files.pythonhosted.org/packages/54/32/515dd05074c213b433524ab97eb003b0132ae7e358e0d75633ba7a314ed8/ty-0.0.73-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b7d6b5c6a6db7ea95fbbc16af514ef44a27a29a2fe1dc798900790364d170209", size = 12301870, upload-time = "2026-08-19T03:12:08.924Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/45/10562a0d84802158db8fa4ec46de54aa9fdcecdeeaabbfe3639ae7042b66/ty-0.0.72-py3-none-macosx_11_0_arm64.whl", hash = "sha256:108d76218333d6c092e5f1cebf8e9b06f25738613a0236a28e2dd47c936ee52c", size = 12084108, upload-time = "2026-08-14T21:35:06.686Z" },
|
{ url = "https://files.pythonhosted.org/packages/50/4d/085b4889f0d4bbe4af8b96242d4a1cb209fff95967cfa239ea141983719b/ty-0.0.73-py3-none-macosx_11_0_arm64.whl", hash = "sha256:dd6f657f463e01372d8688f235be164750c8db722c97da27fa4903aa8d40b203", size = 12111741, upload-time = "2026-08-19T03:12:11.067Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a1/dc/1fe1aef8d697e3509face271a5331700c7aa1d1e44a4b622707bdfa41d4b/ty-0.0.72-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f3943f186f741a2499a31053872169250c9264a9a49684920e48d8fcf4ef4f5", size = 12132640, upload-time = "2026-08-14T21:35:09.305Z" },
|
{ url = "https://files.pythonhosted.org/packages/95/f6/d6ec277cadfecf03ad4c18551b67c4c6eb7807a0560d801db14be99d7a89/ty-0.0.73-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2de468e33fd44c9ff1c43473a7316f4289480f5cba8995a67b6d22aee39ca9", size = 12196124, upload-time = "2026-08-19T03:12:13.14Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/46/41ceb265e96969487311a2014bd0e53abb4fbc1395efb2ebe411fcb4db62/ty-0.0.72-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf283c07dc3cc52ca48a3ad8ab100fb5aec3aebbd03ef6a12d5f910b8e596fc5", size = 12402489, upload-time = "2026-08-14T21:35:11.555Z" },
|
{ url = "https://files.pythonhosted.org/packages/75/b7/ce78d8707563af9cae9bbd25328bfbc4931035085bd20089adf0c418f70e/ty-0.0.73-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2942fa0ef795a66034cdc8d75a72f453442f3b58ff2f69b4da05b7b954765b55", size = 12488557, upload-time = "2026-08-19T03:12:15.252Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/45/30bf43cb4fd505c5c2dd30fda27dde5f05208686cd21217adec77c954204/ty-0.0.72-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f3b6462c38f9f115d10cee21f47fedf715fcf2040daf36eef210359300bc7c", size = 13130835, upload-time = "2026-08-14T21:35:13.746Z" },
|
{ url = "https://files.pythonhosted.org/packages/d8/e8/329b9851b23502758c5c98e8cc875ea2a1b4c9674b4ca3a86da56a5063d3/ty-0.0.73-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e0f1ef14f642e18ac4e7a616a2796dcf7a5d82e28cd17f9796494acc7c4aabb", size = 13215606, upload-time = "2026-08-19T03:12:17.225Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/2f/03bba754d2613f640df168335c41f83f41db150bb515839c60d80e3a7880/ty-0.0.72-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30caf658feb8ffb250d9e9e47107657a78f5f3425c227df1664d8df2ebe38880", size = 13590392, upload-time = "2026-08-14T21:35:16.839Z" },
|
{ url = "https://files.pythonhosted.org/packages/36/38/67fedfd2cb77516ef0066b1642f487dba0eb3006493cf3475b15f5b8b228/ty-0.0.73-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16981e15fdceedb37d0aff76c5ac25914595dfee2675af95335550064251ad22", size = 13665497, upload-time = "2026-08-19T03:12:19.286Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/04/c7/03c67f00e63005ec41585653dc3096064570b1e6273742baae2798cd242f/ty-0.0.72-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27bdc012ddfbeec8948e4a6036c0dc39ac7cf2c8ec7c7d48dc7d2fd56d57b399", size = 13309629, upload-time = "2026-08-14T21:35:19.169Z" },
|
{ url = "https://files.pythonhosted.org/packages/8e/b3/154f4dd48ec5eebc186ab4b822c6e62f982fc5ddfd262d6e3903c2acba44/ty-0.0.73-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644b2bec8a2e2e4957a942ae81d6cff5571c489bb5a8675e4d3886de537a694d", size = 13351231, upload-time = "2026-08-19T03:12:21.353Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/df/102d3b264eb7f2a58dd11952f229bb5150bb5668d176a6154976a6675981/ty-0.0.72-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:802c5970a77d7739e6f499921fbb6984fb7ad8a31d95e1ff42fd46f3642e4f3b", size = 12734028, upload-time = "2026-08-14T21:35:22.099Z" },
|
{ url = "https://files.pythonhosted.org/packages/35/5f/d462496903fbe453fb76363f8478be929c8e6ff21e6928c57dcd7e5fa21f/ty-0.0.73-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:338d565be3186f50ff8e9d10483685549c2d23f0754485d5ede3b54f4319188a", size = 12782586, upload-time = "2026-08-19T03:12:23.667Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/85/d0737c8c54d0ba67366ddfb9f31d88edf0b02299e65923e6945ae60ebcb5/ty-0.0.72-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:47dce65114fdc615c68ca0edb393b433df0956447e4267df0e264137a789598d", size = 13174832, upload-time = "2026-08-14T21:35:24.71Z" },
|
{ url = "https://files.pythonhosted.org/packages/87/52/ec6d24b74abe3ec324204c1c71e6d0c6c76a17ffc15fd51d603b0a302abe/ty-0.0.73-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:11c7b6d839309d2c102cb3a4c03d817176bbfab5b2fccc95a75ec5c9597421c9", size = 13247134, upload-time = "2026-08-19T03:12:25.956Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/31/497f5a96c36d9b586ab6afe0574986835c6fd5b835a89773d2bec4711b49/ty-0.0.72-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:325144fa07e2675d0faa337fcc864213c272a499eb0cfe5bde2fdc62282d27bc", size = 12215005, upload-time = "2026-08-14T21:35:26.892Z" },
|
{ url = "https://files.pythonhosted.org/packages/26/20/cc74650fec56a54786c6d7c89e09576fcad3092be34cf21715d39a406a9b/ty-0.0.73-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:488572db7ff97fb50ea36a76250f2d617c9727d143da6c7bf0623276eb0fc507", size = 12309344, upload-time = "2026-08-19T03:12:28.122Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/7d/46e65b17b4966c7cd0140f134380d33d8e84fe6efccd761533ce793dc502/ty-0.0.72-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5c9f15d0f58e43707d8848274be1821a0ef408eccb8aa7dda28a4a9eddf7640", size = 12421298, upload-time = "2026-08-14T21:35:29.301Z" },
|
{ url = "https://files.pythonhosted.org/packages/89/bd/4b0a9087f4315d7fbadf77a3ce44c816cc9ffabed1ced06cc5be81fbc414/ty-0.0.73-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1b958ebceefbbf594e59eb8d3d55bbd033ce634026fcba3e4bc3179e78e45bb7", size = 12502319, upload-time = "2026-08-19T03:12:30.128Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/08/2a/12ada4ec17700b3cb1d4fd3bc3e5b1852df9e6885288429318cade87b3c1/ty-0.0.72-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8ee508d64b381871529cc22c412b41071bf5e908b7aa5d66a38f3f6b2573a806", size = 12669242, upload-time = "2026-08-14T21:35:31.444Z" },
|
{ url = "https://files.pythonhosted.org/packages/11/80/0a925074911fe111912ea29d9eed309bcc183f43d2fb3eef07db056a0beb/ty-0.0.73-py3-none-musllinux_1_2_i686.whl", hash = "sha256:91a32993b3c34e42c3f323ad6c0399cb596bd1c27e9b7f20db7cd64c1067b68e", size = 12753688, upload-time = "2026-08-19T03:12:32.433Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1c/1a/4692536880790fb550ed6d44a6096778dc71bb112f2c6d615cebb01a57e5/ty-0.0.72-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3699e2ec7921d44da79d6b089f7bf239b2cc53c4e45a5a38430adc34ee9e9a55", size = 12988199, upload-time = "2026-08-14T21:35:33.749Z" },
|
{ url = "https://files.pythonhosted.org/packages/24/6b/aeccaf89efbc2e112bd415340a22e2669ec998aa397242503e747b712ca4/ty-0.0.73-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bab8a19fbf51f479bddb2a12c5fabfe52f918a5590362321ed5d89b44eb62c15", size = 13069050, upload-time = "2026-08-19T03:12:35.398Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/0d/f5e5a50322e9c45865e7b7a428ba6cd6527387cf0f2472492ac3cf746243/ty-0.0.72-py3-none-win32.whl", hash = "sha256:f25f72a67bd36cd247707c4784e52fad0b6b4f42a1b7dd14804110fa95c486ed", size = 11939708, upload-time = "2026-08-14T21:35:36.006Z" },
|
{ url = "https://files.pythonhosted.org/packages/d7/3e/eae485fd86c1585943fd4e1746b0757b2da01e2c43136ebe8c686fe1c7f1/ty-0.0.73-py3-none-win32.whl", hash = "sha256:03347a612f0fa020b19bfd8dbd521db6ecc75d377a3e4d4f6e6c2e62871da4cc", size = 12053187, upload-time = "2026-08-19T03:12:37.565Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/4e/8af3534b2e4214e6184a5a59c34101e94a68d578f081f97b995866bab1bf/ty-0.0.72-py3-none-win_amd64.whl", hash = "sha256:cdeee869341717e1736cea2e2d7856738c6957c320f584ed2f68c8f90100d2f5", size = 12643876, upload-time = "2026-08-14T21:35:38.141Z" },
|
{ url = "https://files.pythonhosted.org/packages/a7/01/9b8b983786e3ce34924e372e8b76b92b508273ab65c589fc7e88cc03ee17/ty-0.0.73-py3-none-win_amd64.whl", hash = "sha256:cedd05122ded0b5dcc55431a370e974b747f99c41c290a3d2ab8c1867f197519", size = 12693838, upload-time = "2026-08-19T03:12:39.483Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/ea/a2606e654c7276bd08586391a2525b0af3f3bf60228a8c57b2d248f273f9/ty-0.0.72-py3-none-win_arm64.whl", hash = "sha256:1bd3ac3ed4424a6d6990a85dc388556aea012bd752de21349a84b685951de0d8", size = 12394857, upload-time = "2026-08-14T21:35:40.277Z" },
|
{ url = "https://files.pythonhosted.org/packages/ea/88/25333bbfea6a5dc064371d2002d3d4807db90b84d5448f9106b2712b0fbc/ty-0.0.73-py3-none-win_arm64.whl", hash = "sha256:e47068f8369dea5d641a26a2ad0a947a320b02ff87099b07e95de0323245a4dc", size = 12443573, upload-time = "2026-08-19T03:12:41.449Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1387,7 +1387,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zensical"
|
name = "zensical"
|
||||||
version = "0.0.54"
|
version = "0.0.56"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
@@ -1399,20 +1399,20 @@ dependencies = [
|
|||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
{ name = "tomli" },
|
{ name = "tomli" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/75/7e/343a78c0c9da1954d2f0a4d47ca778baac48bb18c6b9c0c6260e7974976e/zensical-0.0.54.tar.gz", hash = "sha256:4de205dbb323d0a443e2ebf3fef77e93e3c1493c34a58d205e7f3631dd7745af", size = 3992024, upload-time = "2026-08-13T16:04:49.297Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/f8/7d/18bb725a659352e9af0940a3d879c5edcff5d86fec3ac15ce500d484d9d3/zensical-0.0.56.tar.gz", hash = "sha256:c359163800d1c3a8c39af48f4e2869fcfc2b4fc00d28652bd2a5b0330c36530c", size = 3997416, upload-time = "2026-08-18T15:46:47.283Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/cf/13e887c303fd5c786c09f83362382a42d10292fca633a95067de6a6591a3/zensical-0.0.54-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f7177a3b6647e4ee47864ab02e5141f9e923dd57b9fa96e5dc5da4228daff505", size = 12893082, upload-time = "2026-08-13T16:04:17.337Z" },
|
{ url = "https://files.pythonhosted.org/packages/8b/4b/6810aec6e670451f39639039b570a43d90bc1d4a3b93cf13316ccf6bad11/zensical-0.0.56-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5135ea3aa5d1358503fc1903e866c191873b138028bc1ab170abac3a4b537ffe", size = 12874712, upload-time = "2026-08-18T15:46:18.378Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/82/ee/7fe1418fa31bc120cf9eb0fbce9e021c40752206ce993a7bc652c011f890/zensical-0.0.54-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:7b23c3b0c720885891b220c3e562074d93eb940314823d91875c6246976bd9b2", size = 12778626, upload-time = "2026-08-13T16:04:19.906Z" },
|
{ url = "https://files.pythonhosted.org/packages/97/98/23445d8ed708088dd6d9d51674f8836b77c53aab280360d7aa7a206bea8e/zensical-0.0.56-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a22ae2329ba755c6e58e1fe5967ca429d580d346a73cf467ad5185377e2cf809", size = 12764552, upload-time = "2026-08-18T15:46:20.746Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/b8/0420115270c1a22a2d4a1598f89dadc4e933eb7aa85539b72f3532acc5b6/zensical-0.0.54-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c012eb0ec20fda5794e90b4906b7401c77b712e2acc7f9f65026935368539da", size = 13225462, upload-time = "2026-08-13T16:04:22.663Z" },
|
{ url = "https://files.pythonhosted.org/packages/14/bd/ab89450728b0a55e6a52a86060b38a362a52a1b9f02eda7fef68b729eb2e/zensical-0.0.56-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c70ef328e31cce0e31739acdd6679e9272bc7a3016ca5eafaa16dcfda460c9", size = 13212920, upload-time = "2026-08-18T15:46:22.977Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/48/0dfdd3e00fb3b807de702383ef5f4e9cf0d2791fee0e8a56f39bd590f16b/zensical-0.0.54-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0e851d26ba4f7397db3b3532e534c0572a9826bbd451ee0a00e649c030dcb38", size = 13158184, upload-time = "2026-08-13T16:04:24.958Z" },
|
{ url = "https://files.pythonhosted.org/packages/bf/5a/969fd9a461204392a9266a544c185fbb223714b306534661dcbb7d290be7/zensical-0.0.56-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ffc50153a50292078357a5052e7a6b3e20a818523792ffcbeace70418b761eb", size = 13146652, upload-time = "2026-08-18T15:46:25.773Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b1/2c/f1fb1f5387108b206f985cdb394bbdc730557cc339e63067ded9b3565263/zensical-0.0.54-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a48583da6f485e2373845d4332882c9e9d504302dbd2731c589543584df5b087", size = 13536772, upload-time = "2026-08-13T16:04:27.373Z" },
|
{ url = "https://files.pythonhosted.org/packages/22/b3/28a7c8dea8fe2edbba75a664efbde797b5922651ea92ffc480947ab22197/zensical-0.0.56-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88bba0e339d36647ce638a42b4ba96f2521b59ea54be74c314340be7e401f94b", size = 13530946, upload-time = "2026-08-18T15:46:28.19Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/97/3224b3dd5d76cebddf9725ae871a5a6a8f2de177be59d802232d00073d8b/zensical-0.0.54-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da7781a906623fb7bc278cc874ec6f80691c8753b90fbdf90a451abb046ae204", size = 13191901, upload-time = "2026-08-13T16:04:30.295Z" },
|
{ url = "https://files.pythonhosted.org/packages/d6/a6/d55a18c6e041b788af1d19e4d8c61ee9b8a7de5b9cc79ffa7bc9565e3a28/zensical-0.0.56-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b7a37f6ac38ac218e3e0dd311b58c468603963e38ee23f2e25672dcd6e9c17b", size = 13178741, upload-time = "2026-08-18T15:46:30.378Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/00/b1f55530c8df4331e1f209ec605816a578142f124b5f8078a9d984514d63/zensical-0.0.54-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:75aff1c01f6104dd0e79d08c87bd595da13db3e1f6da55fc9933ebff3e94fdd5", size = 13402956, upload-time = "2026-08-13T16:04:32.899Z" },
|
{ url = "https://files.pythonhosted.org/packages/6c/4f/a07da2f761cfd6a27faf9e8d5a9475c396a9a0ca37424a0b67cab7ae4d2c/zensical-0.0.56-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f6d850bce3184422b37b9d98ef4d123047a47446844793251cabc04bd55f8f0", size = 13389836, upload-time = "2026-08-18T15:46:32.847Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/6e/58df496c2742600df3a2f273ba52b8ced1ce357340b825af9f1bb0f2042e/zensical-0.0.54-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1469c5d551a0ea2d0fcf922046a263d2afcff5f03ce3bb443e286970d04ff9f2", size = 13431462, upload-time = "2026-08-13T16:04:35.518Z" },
|
{ url = "https://files.pythonhosted.org/packages/34/aa/697ef9846b0e2071de4d03b0472e2658380ea9271bd01235f4ffaeef1978/zensical-0.0.56-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:d18944a4a111050b8f571e73d4e2475d7ce62ce78b64f09bcf33bb11cc346e1c", size = 13419551, upload-time = "2026-08-18T15:46:35.112Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5b/85/70ae775db7865be2434bc39e9b4ff1c7988978d796a7ff9d49294e7410c7/zensical-0.0.54-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:31c354f98b3374b9bcab65278a278dbec66125c997f7f1a5ee9501f413b87b9c", size = 13587289, upload-time = "2026-08-13T16:04:37.954Z" },
|
{ url = "https://files.pythonhosted.org/packages/97/6b/20e1b2443951d5182b3fc2ee54c200ea00c09bd8901a479be4147c94361b/zensical-0.0.56-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3937029ec5091d577c2a05ebccd38fde97fab28662d165ead66d566689b2a6df", size = 13579878, upload-time = "2026-08-18T15:46:37.381Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0f/ca/05e6b3e04323810bbf4da9240b8b710740fac82ec01659b14c86e44aa88e/zensical-0.0.54-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85ef75654e7845aa65f1392af771771566f33faac452a2aed004ba367454f812", size = 13534594, upload-time = "2026-08-13T16:04:41.006Z" },
|
{ url = "https://files.pythonhosted.org/packages/53/c7/9c3b400b8a7d78cc169b7a78d4f74a90f9114209034a604f04051f48037c/zensical-0.0.56-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:346a1cdbad157633d185f79039a97c8b4f8c2b40be7d7efd56d72622f40247b0", size = 13526142, upload-time = "2026-08-18T15:46:39.949Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/70/be34910c13632f85911f505bd9a4d1bb53d46c8498d20ebb18570bbe4b7b/zensical-0.0.54-cp310-abi3-win32.whl", hash = "sha256:b56c80a8cd234666afb917fb1ed8467104f6857b1acabd66f60ef1b8a0daa66f", size = 12448180, upload-time = "2026-08-13T16:04:43.486Z" },
|
{ url = "https://files.pythonhosted.org/packages/0e/f6/7a6f2a513054071e44a504d7157684f00ac5e5e5f741eadc78ab6c80642c/zensical-0.0.56-cp310-abi3-win32.whl", hash = "sha256:a06681046f74b5bdc506d22af8fcef505b2450e45538c7f01a5e028f4e50c6f7", size = 12433218, upload-time = "2026-08-18T15:46:42.329Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a1/c6/a965265946555023dc0e159a41038502190882669d177dfa59b1dd3d580b/zensical-0.0.54-cp310-abi3-win_amd64.whl", hash = "sha256:f5a602986c4123a349cfd075c8d494096a2a2db74422169d65f8092872e48dfa", size = 12712264, upload-time = "2026-08-13T16:04:46.155Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/ed/5b497c75fb1fd5845f3ec2f0b11b5e01f18f14c297041cd2f20d30d8b6a4/zensical-0.0.56-cp310-abi3-win_amd64.whl", hash = "sha256:c557985f12d042c15dcb7d577543d81ceee9281f96d591d265310b673437a325", size = 12702823, upload-time = "2026-08-18T15:46:44.836Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user