mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-23 01:21:40 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c04c8d76ed | ||
|
|
7b30c9306e | ||
|
|
ae22742f05 | ||
|
|
860d6807a8 | ||
|
|
e9006a2803 | ||
|
|
d3de87f72b | ||
|
|
be27bb9916 | ||
|
|
0525e762b4 | ||
|
|
4db3d7be57 | ||
|
|
02d0f2b047 | ||
|
|
7e9a67ec6a | ||
|
|
b78b0dad97 | ||
|
|
c146d559d0 | ||
|
|
645ff41ce9 | ||
|
|
6d579edcd5 | ||
|
|
6ef0f91600 | ||
|
|
a6288afcb4 | ||
|
|
996ca3649e | ||
|
|
0e834f0851 | ||
|
|
c221aed0c2 | ||
|
|
363b8274d0 | ||
|
|
2372e110f9 | ||
|
|
adfa39b6f4 | ||
|
|
94acd9d631 | ||
|
|
d587cd19bb | ||
|
|
3c017ab961 | ||
|
|
4c9b36aa5c | ||
|
|
de5fe76123 | ||
|
|
b8e9e4f5c7 | ||
|
|
dbf71029b7 | ||
|
|
c195d7ccc0 | ||
|
|
f640c28ac1 | ||
|
|
b2ee79164b | ||
|
|
d8dddf379f | ||
|
|
b99e85d0ba | ||
|
|
439f5fcd91 | ||
|
|
dbbe006fd3 | ||
|
|
803bdb222c | ||
|
|
d1a02a542c | ||
|
|
e970446c4c | ||
|
|
68fc1e7129 | ||
|
|
4412df1516 | ||
|
|
7764ccc533 | ||
|
|
872b587bae | ||
|
|
934eed21d2 | ||
|
|
c34c854583 | ||
|
|
83b4522bf3 | ||
|
|
d83d53650a | ||
|
|
0734d1b806 | ||
|
|
3ace6122b0 | ||
|
|
a5f9a0b673 | ||
|
|
ed1b31cb7f | ||
|
|
68bdb38a3d | ||
|
|
a5be3d062c | ||
|
|
3379a1a132 | ||
|
|
5f59e9ab22 |
338
.github/workflows/build.yml
vendored
338
.github/workflows/build.yml
vendored
@@ -10,7 +10,7 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
IMAGE_VERSION: v2026.1.3
|
IMAGE_VERSION: v2027.0.0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@@ -21,102 +21,116 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- uses: gradle/actions/wrapper-validation@v5
|
- uses: gradle/actions/wrapper-validation@v5
|
||||||
|
|
||||||
build-examples:
|
# build-examples:
|
||||||
|
|
||||||
strategy:
|
# strategy:
|
||||||
fail-fast: false
|
# fail-fast: false
|
||||||
matrix:
|
# matrix:
|
||||||
include:
|
# include:
|
||||||
- os: windows-2022
|
# - os: windows-2022
|
||||||
artifact-name: Win64
|
# artifact-name: Win64
|
||||||
- os: macos-14
|
# - os: macos-14
|
||||||
artifact-name: macOS
|
# artifact-name: macOS
|
||||||
- os: ubuntu-24.04
|
# - os: ubuntu-24.04
|
||||||
artifact-name: Linux
|
# artifact-name: Linux
|
||||||
|
|
||||||
name: "Photonlib - Build Examples - ${{ matrix.os }}"
|
# name: "Photonlib - Build Examples - ${{ matrix.os }}"
|
||||||
runs-on: ${{ matrix.os }}
|
# runs-on: ${{ matrix.os }}
|
||||||
needs: [build-photonlib-host, build-photonlib-docker]
|
# needs: [build-photonlib-host, build-photonlib-docker]
|
||||||
|
|
||||||
steps:
|
# steps:
|
||||||
- name: Checkout code
|
# - name: Checkout code
|
||||||
uses: actions/checkout@v6
|
# uses: actions/checkout@v6
|
||||||
with:
|
# with:
|
||||||
fetch-depth: 0
|
# fetch-depth: 0
|
||||||
- name: Fetch tags
|
# - name: Fetch tags
|
||||||
run: git fetch --tags --force
|
# run: git fetch --tags --force
|
||||||
- name: Install Java 17
|
# - uses: actions/setup-java@v5
|
||||||
uses: actions/setup-java@v5
|
# with:
|
||||||
with:
|
# java-version: 25
|
||||||
java-version: 17
|
# distribution: temurin
|
||||||
distribution: temurin
|
# - name: Install SystemCore Toolchain
|
||||||
- name: Install RoboRIO Toolchain
|
# run: ./gradlew installSystemCoreToolchain
|
||||||
run: ./gradlew installRoboRioToolchain
|
# - name: Delete duplicate toolchains
|
||||||
- name: Delete duplicate toolchains
|
# run: |
|
||||||
run: |
|
# find ~/.gradle/cache/ -name *bookworm* -exec rm -rf {} +
|
||||||
find ~/.gradle/cache/ -name *roborio-academic* -exec rm -rf {} +
|
# du -h . | sort -h
|
||||||
du -h . | sort -h
|
# if: matrix.os == 'ubuntu-24.04'
|
||||||
if: matrix.os == 'ubuntu-24.04'
|
# # Download prebuilt photonlib artifacts
|
||||||
# Download prebuilt photonlib artifacts
|
# - uses: actions/download-artifact@v7
|
||||||
- uses: actions/download-artifact@v7
|
# with:
|
||||||
with:
|
# name: maven-${{ matrix.artifact-name }}
|
||||||
name: maven-${{ matrix.artifact-name }}
|
# - uses: actions/download-artifact@v7
|
||||||
- uses: actions/download-artifact@v7
|
# with:
|
||||||
with:
|
# name: maven-SystemCore
|
||||||
name: maven-Athena
|
# - name: Move to maven local
|
||||||
- name: Move to maven local
|
# run: |
|
||||||
run: |
|
# mkdir -p ~/.m2/repository/
|
||||||
mkdir -p ~/.m2/repository/
|
# mv maven/org ~/.m2/repository/
|
||||||
mv maven/org ~/.m2/repository/
|
# - name: Copy vendordeps
|
||||||
- name: Copy vendordeps
|
# shell: bash
|
||||||
shell: bash
|
# run: |
|
||||||
run: |
|
# for vendordep_folder in photonlib-*-examples/*/; do
|
||||||
for vendordep_folder in photonlib-*-examples/*/; do
|
# # Remove trailing slash for cross-platform compatibility
|
||||||
# Remove trailing slash for cross-platform compatibility
|
# vendordep_folder="${vendordep_folder%/}"
|
||||||
vendordep_folder="${vendordep_folder%/}"
|
|
||||||
|
|
||||||
# Filter for projects only
|
# # Filter for projects only
|
||||||
if [ -e "$vendordep_folder/build.gradle" ]; then
|
# if [ -e "$vendordep_folder/build.gradle" ]; then
|
||||||
mkdir -p "$vendordep_folder/vendordeps/"
|
# mkdir -p "$vendordep_folder/vendordeps/"
|
||||||
cp vendordeps/photonlib-json-1.0.json "$vendordep_folder/vendordeps/"
|
# cp vendordeps/photonlib-json-1.0.json "$vendordep_folder/vendordeps/"
|
||||||
fi
|
# fi
|
||||||
done
|
# done
|
||||||
- name: Build Java examples
|
# - name: Build Java examples
|
||||||
working-directory: photonlib-java-examples
|
# working-directory: photonlib-java-examples
|
||||||
run: |
|
# run: |
|
||||||
./gradlew build
|
# ./gradlew build
|
||||||
./gradlew clean
|
# ./gradlew clean
|
||||||
- name: Build C++ examples
|
# - name: Build C++ examples
|
||||||
working-directory: photonlib-cpp-examples
|
# working-directory: photonlib-cpp-examples
|
||||||
run: |
|
# run: |
|
||||||
./gradlew build
|
# ./gradlew build
|
||||||
./gradlew clean
|
# ./gradlew clean
|
||||||
|
|
||||||
playwright-tests:
|
typecheck-client:
|
||||||
name: "Playwright E2E tests"
|
name: "Typecheck Client"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: [validation]
|
|
||||||
steps:
|
steps:
|
||||||
# Checkout code.
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Fetch tags
|
|
||||||
run: git fetch --tags --force
|
|
||||||
- name: Install Java 17
|
|
||||||
uses: actions/setup-java@v5
|
|
||||||
with:
|
|
||||||
java-version: 17
|
|
||||||
distribution: temurin
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v5
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
cache-dependency-path: photon-client/pnpm-lock.yaml
|
||||||
|
- name: Typecheck Client
|
||||||
|
working-directory: photon-client
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm type-check
|
||||||
|
playwright-tests:
|
||||||
|
name: "Playwright E2E tests"
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs: [validation]
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
java-version: 25
|
||||||
|
distribution: temurin
|
||||||
|
- uses: pnpm/action-setup@v5
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
cache: pnpm
|
||||||
|
cache-dependency-path: photon-client/pnpm-lock.yaml
|
||||||
|
node-version: 24
|
||||||
- name: Setup tests
|
- name: Setup tests
|
||||||
working-directory: photon-client
|
working-directory: photon-client
|
||||||
run: |
|
run: |
|
||||||
@@ -127,10 +141,10 @@ jobs:
|
|||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
working-directory: photon-client
|
working-directory: photon-client
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
with:
|
with:
|
||||||
name: playwright-report
|
name: "Playwright Report"
|
||||||
path: photon-client/playwright-report/
|
path: photon-client/playwright-report/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
build-gradle:
|
build-gradle:
|
||||||
@@ -138,26 +152,24 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: [validation]
|
needs: [validation]
|
||||||
steps:
|
steps:
|
||||||
# Checkout code.
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Fetch tags
|
- name: Fetch tags
|
||||||
run: git fetch --tags --force
|
run: git fetch --tags --force
|
||||||
- name: Install Java 17
|
- uses: actions/setup-java@v5
|
||||||
uses: actions/setup-java@v5
|
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 25
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
- name: Install pnpm
|
- uses: pnpm/action-setup@v5
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Setup Node.js
|
- uses: actions/setup-node@v6
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
cache: pnpm
|
||||||
|
cache-dependency-path: photon-client/pnpm-lock.yaml
|
||||||
|
node-version: 24
|
||||||
- name: Gradle Build
|
- name: Gradle Build
|
||||||
run: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check
|
run: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check
|
||||||
- name: Gradle Tests and Coverage
|
- name: Gradle Tests and Coverage
|
||||||
@@ -184,7 +196,7 @@ jobs:
|
|||||||
working-directory: docs
|
working-directory: docs
|
||||||
run: |
|
run: |
|
||||||
make html
|
make html
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: built-docs
|
name: built-docs
|
||||||
path: docs/build/html
|
path: docs/build/html
|
||||||
@@ -198,10 +210,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install Java 17
|
- uses: actions/setup-java@v5
|
||||||
uses: actions/setup-java@v5
|
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 25
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
|
|
||||||
# grab all tags
|
# grab all tags
|
||||||
@@ -214,9 +225,9 @@ jobs:
|
|||||||
mv photon-lib/build/generated/vendordeps/photonlib.json photon-lib/build/generated/vendordeps/photonlib-$(git describe --tags --match=v*).json
|
mv photon-lib/build/generated/vendordeps/photonlib.json photon-lib/build/generated/vendordeps/photonlib-$(git describe --tags --match=v*).json
|
||||||
|
|
||||||
# Upload it here so it shows up in releases
|
# Upload it here so it shows up in releases
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: photonlib-vendor-json
|
archive: false
|
||||||
path: photon-lib/build/generated/vendordeps/photonlib-*.json
|
path: photon-lib/build/generated/vendordeps/photonlib-*.json
|
||||||
|
|
||||||
build-photonlib-host:
|
build-photonlib-host:
|
||||||
@@ -226,12 +237,12 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
artifact-name: Win64
|
artifact-name: Win64
|
||||||
- os: macos-26
|
- os: macos-26
|
||||||
artifact-name: macOS
|
artifact-name: macOS
|
||||||
- os: ubuntu-24.04
|
- os: ubuntu-24.04
|
||||||
artifact-name: Linux
|
artifact-name: Linux
|
||||||
|
|
||||||
name: "Photonlib - Build Host - ${{ matrix.artifact-name }}"
|
name: "Photonlib - Build Host - ${{ matrix.artifact-name }}"
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@@ -240,10 +251,9 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Java 17
|
- uses: actions/setup-java@v5
|
||||||
uses: actions/setup-java@v5
|
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 25
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
- run: git fetch --tags --force
|
- run: git fetch --tags --force
|
||||||
- run: ./gradlew photon-targeting:build photon-lib:build
|
- run: ./gradlew photon-targeting:build photon-lib:build
|
||||||
@@ -252,10 +262,10 @@ jobs:
|
|||||||
name: Publish
|
name: Publish
|
||||||
env:
|
env:
|
||||||
ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }}
|
ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }}
|
||||||
if: github.event_name == 'push' && github.repository_owner == 'photonvision'
|
if: github.event_name == 'push' && github.repository_owner == 'photonvision' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||||
# Copy artifacts to build/outputs/maven
|
# Copy artifacts to build/outputs/maven
|
||||||
- run: ./gradlew photon-lib:publish photon-targeting:publish -PcopyOfflineArtifacts
|
- run: ./gradlew photon-lib:publish photon-targeting:publish -PcopyOfflineArtifacts
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: maven-${{ matrix.artifact-name }}
|
name: maven-${{ matrix.artifact-name }}
|
||||||
path: build/outputs
|
path: build/outputs
|
||||||
@@ -265,13 +275,13 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- container: wpilib/roborio-cross-ubuntu:2025-24.04
|
- container: wpilib/systemcore-cross-ubuntu:2027-24.04
|
||||||
artifact-name: Athena
|
artifact-name: SystemCore
|
||||||
build-options: "-Ponlylinuxathena"
|
build-options: "-Ponlylinuxsystemcore"
|
||||||
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
|
- container: wpilib/raspbian-cross-ubuntu:2027-bookworm-24.04
|
||||||
artifact-name: Raspbian
|
artifact-name: Raspbian
|
||||||
build-options: "-Ponlylinuxarm32"
|
build-options: "-Ponlylinuxarm32"
|
||||||
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
|
- container: wpilib/aarch64-cross-ubuntu:2027-bookworm-24.04
|
||||||
artifact-name: Aarch64
|
artifact-name: Aarch64
|
||||||
build-options: "-Ponlylinuxarm64"
|
build-options: "-Ponlylinuxarm64"
|
||||||
|
|
||||||
@@ -293,10 +303,10 @@ jobs:
|
|||||||
run: ./gradlew photon-lib:publish photon-targeting:publish ${{ matrix.build-options }}
|
run: ./gradlew photon-lib:publish photon-targeting:publish ${{ matrix.build-options }}
|
||||||
env:
|
env:
|
||||||
ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }}
|
ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }}
|
||||||
if: github.event_name == 'push' && github.repository_owner == 'photonvision'
|
if: github.event_name == 'push' && github.repository_owner == 'photonvision' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||||
# Copy artifacts to build/outputs/maven
|
# Copy artifacts to build/outputs/maven
|
||||||
- run: ./gradlew photon-lib:publish photon-targeting:publish -PcopyOfflineArtifacts ${{ matrix.build-options }}
|
- run: ./gradlew photon-lib:publish photon-targeting:publish -PcopyOfflineArtifacts ${{ matrix.build-options }}
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: maven-${{ matrix.artifact-name }}
|
name: maven-${{ matrix.artifact-name }}
|
||||||
path: build/outputs
|
path: build/outputs
|
||||||
@@ -311,7 +321,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: git fetch --tags --force
|
- run: git fetch --tags --force
|
||||||
# download all maven-* artifacts to outputs/
|
# download all maven-* artifacts to outputs/
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: output
|
path: output
|
||||||
@@ -321,7 +331,7 @@ jobs:
|
|||||||
name: ZIP stuff up
|
name: ZIP stuff up
|
||||||
working-directory: output
|
working-directory: output
|
||||||
- run: ls output
|
- run: ls output
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: photonlib-offline
|
name: photonlib-offline
|
||||||
path: output/*.zip
|
path: output/*.zip
|
||||||
@@ -335,7 +345,7 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: ubuntu-24.04
|
- os: ubuntu-24.04
|
||||||
artifact-name: Linux
|
artifact-name: Linux
|
||||||
arch-override: linuxx64
|
arch-override: linuxx86-64
|
||||||
- os: ubuntu-24.04
|
- os: ubuntu-24.04
|
||||||
artifact-name: LinuxArm64
|
artifact-name: LinuxArm64
|
||||||
arch-override: linuxarm64
|
arch-override: linuxarm64
|
||||||
@@ -347,25 +357,22 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Java 17
|
- uses: actions/setup-java@v5
|
||||||
uses: actions/setup-java@v5
|
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 25
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
- name: Install pnpm
|
- uses: pnpm/action-setup@v5
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Setup Node.js
|
- uses: actions/setup-node@v6
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: photon-client/pnpm-lock.yaml
|
cache-dependency-path: photon-client/pnpm-lock.yaml
|
||||||
- name: Install Arm64 Toolchain
|
- name: Install Arm64 Toolchain
|
||||||
run: ./gradlew installArm64Toolchain
|
run: ./gradlew installArm64Toolchain
|
||||||
if: ${{ (matrix.artifact-name) == 'LinuxArm64' }}
|
if: ${{ (matrix.artifact-name) == 'LinuxArm64' }}
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: built-docs
|
name: built-docs
|
||||||
path: photon-server/src/main/resources/web/docs
|
path: photon-server/src/main/resources/web/docs
|
||||||
@@ -373,11 +380,11 @@ jobs:
|
|||||||
if: ${{ (matrix.arch-override != 'none') }}
|
if: ${{ (matrix.arch-override != 'none') }}
|
||||||
- run: ./gradlew photon-server:shadowJar
|
- run: ./gradlew photon-server:shadowJar
|
||||||
if: ${{ (matrix.arch-override == 'none') }}
|
if: ${{ (matrix.arch-override == 'none') }}
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: jar-${{ matrix.artifact-name }}
|
archive: false
|
||||||
path: photon-server/build/libs
|
path: photon-server/build/libs
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: photon-targeting_jar-${{ matrix.artifact-name }}
|
name: photon-targeting_jar-${{ matrix.artifact-name }}
|
||||||
path: photon-targeting/build/libs
|
path: photon-targeting/build/libs
|
||||||
@@ -394,7 +401,7 @@ jobs:
|
|||||||
arch-override: macarm64
|
arch-override: macarm64
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
artifact-name: macOS
|
artifact-name: macOS
|
||||||
arch-override: macx64
|
arch-override: macx86-64
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
name: "Build fat JAR - ${{ matrix.artifact-name }}"
|
name: "Build fat JAR - ${{ matrix.artifact-name }}"
|
||||||
@@ -409,8 +416,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
artifact-name: Win64
|
artifact-name: Win
|
||||||
arch-override: winx64
|
arch-override: winx86-64
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
name: "Build fat JAR - ${{ matrix.artifact-name }}"
|
name: "Build fat JAR - ${{ matrix.artifact-name }}"
|
||||||
@@ -425,24 +432,26 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-24.04
|
- os: ubuntu-24.04
|
||||||
artifact-name: jar-Linux
|
artifact-name: photonvision-*-linuxx86-64.jar
|
||||||
extraOpts: -Djdk.lang.Process.launchMechanism=vfork
|
extraOpts: -Djdk.lang.Process.launchMechanism=vfork
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
artifact-name: jar-Win64
|
artifact-name: photonvision-*-winx86-64.jar
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
artifact-name: jar-macOS
|
artifact-name: photonvision-*-macarm64.jar
|
||||||
|
- os: ubuntu-24.04-arm
|
||||||
|
artifact-name: photonvision-*-linuxarm64.jar
|
||||||
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install Java 17
|
- uses: actions/setup-java@v5
|
||||||
uses: actions/setup-java@v5
|
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 25
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.artifact-name }}
|
pattern: ${{ matrix.artifact-name }}
|
||||||
# The jar is run twice to exercise different code paths.
|
# The jar is run twice to exercise different code paths.
|
||||||
- run: |
|
- run: |
|
||||||
echo "=== First run ==="
|
echo "=== First run ==="
|
||||||
@@ -479,85 +488,66 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: RaspberryPi
|
image_suffix: RaspberryPi
|
||||||
plat_override: LINUX_RASPBIAN64
|
plat_override: LINUX_RASPBIAN64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_raspi.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_raspi.img.xz
|
||||||
minimum_free_mb: 100
|
minimum_free_mb: 100
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: limelight2
|
image_suffix: limelight2
|
||||||
plat_override: LINUX_RASPBIAN64
|
plat_override: LINUX_RASPBIAN64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight.img.xz
|
||||||
minimum_free_mb: 100
|
minimum_free_mb: 100
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: limelight3
|
image_suffix: limelight3
|
||||||
plat_override: LINUX_RASPBIAN64
|
plat_override: LINUX_RASPBIAN64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight3.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight3.img.xz
|
||||||
minimum_free_mb: 100
|
minimum_free_mb: 100
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: limelight3G
|
image_suffix: limelight3G
|
||||||
plat_override: LINUX_RASPBIAN64
|
plat_override: LINUX_RASPBIAN64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight3g.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight3g.img.xz
|
||||||
minimum_free_mb: 100
|
minimum_free_mb: 100
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: limelight4
|
image_suffix: limelight4
|
||||||
plat_override: LINUX_RASPBIAN64
|
plat_override: LINUX_RASPBIAN64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight4.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight4.img.xz
|
||||||
minimum_free_mb: 100
|
minimum_free_mb: 100
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: luma_p1
|
image_suffix: luma_p1
|
||||||
plat_override: LINUX_RASPBIAN64
|
plat_override: LINUX_RASPBIAN64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_luma_p1.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_luma_p1.img.xz
|
||||||
minimum_free_mb: 100
|
minimum_free_mb: 100
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: orangepi5
|
image_suffix: orangepi5
|
||||||
plat_override: LINUX_RK3588_64
|
plat_override: LINUX_RK3588_64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5.img.xz
|
||||||
minimum_free_mb: 1024
|
minimum_free_mb: 1024
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: orangepi5b
|
image_suffix: orangepi5b
|
||||||
plat_override: LINUX_RK3588_64
|
plat_override: LINUX_RK3588_64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5b.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5b.img.xz
|
||||||
minimum_free_mb: 1024
|
minimum_free_mb: 1024
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: orangepi5plus
|
image_suffix: orangepi5plus
|
||||||
plat_override: LINUX_RK3588_64
|
plat_override: LINUX_RK3588_64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5plus.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5plus.img.xz
|
||||||
minimum_free_mb: 1024
|
minimum_free_mb: 1024
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: orangepi5pro
|
image_suffix: orangepi5pro
|
||||||
plat_override: LINUX_RK3588_64
|
plat_override: LINUX_RK3588_64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5pro.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5pro.img.xz
|
||||||
minimum_free_mb: 1024
|
minimum_free_mb: 1024
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: orangepi5max
|
image_suffix: orangepi5max
|
||||||
plat_override: LINUX_RK3588_64
|
plat_override: LINUX_RK3588_64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5max.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5max.img.xz
|
||||||
minimum_free_mb: 1024
|
minimum_free_mb: 1024
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: rock5c
|
image_suffix: rock5c
|
||||||
plat_override: LINUX_RK3588_64
|
plat_override: LINUX_RK3588_64
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_rock5c.img.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_rock5c.img.xz
|
||||||
minimum_free_mb: 1024
|
minimum_free_mb: 1024
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: orangepi6plus
|
|
||||||
plat_override: LINUX_AARCH64
|
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi6plus.img.xz
|
|
||||||
minimum_free_mb: 1024
|
|
||||||
- os: ubuntu-24.04-arm
|
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: rubikpi3
|
image_suffix: rubikpi3
|
||||||
plat_override: LINUX_QCS6490
|
plat_override: LINUX_QCS6490
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_rubikpi3.tar.xz
|
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_rubikpi3.tar.xz
|
||||||
@@ -573,9 +563,9 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar-${{ matrix.artifact-name }}
|
pattern: photonvision-*-linuxarm64.jar
|
||||||
- uses: photonvision/photon-image-runner@HEAD
|
- uses: photonvision/photon-image-runner@HEAD
|
||||||
name: Generate image
|
name: Generate image
|
||||||
id: generate_image
|
id: generate_image
|
||||||
@@ -611,10 +601,9 @@ jobs:
|
|||||||
# Point smoketest to the old image
|
# Point smoketest to the old image
|
||||||
echo "smoketest_image_loc=${{ steps.generate_image.outputs.image }}" >> $GITHUB_ENV
|
echo "smoketest_image_loc=${{ steps.generate_image.outputs.image }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
name: Upload image
|
|
||||||
with:
|
with:
|
||||||
name: image-${{ matrix.image_suffix }}
|
archive: false
|
||||||
path: photonvision*.xz
|
path: photonvision*.xz
|
||||||
|
|
||||||
# This is done after uploading the image to avoid contaminating the image with logs, caches, etc.
|
# This is done after uploading the image to avoid contaminating the image with logs, caches, etc.
|
||||||
@@ -643,25 +632,24 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
# Download all fat JARs
|
# Download all fat JARs
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
pattern: jar-*
|
pattern: photonvision-*.jar
|
||||||
# Download offline photonlib
|
# Download offline photonlib
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
pattern: photonlib-offline
|
pattern: photonlib-offline
|
||||||
# Download vendor json
|
# Download vendor json
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
merge-multiple: true
|
pattern: photonlib-*.json
|
||||||
pattern: photonlib-vendor-json
|
|
||||||
# Download all images
|
# Download all images
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
pattern: image-*
|
pattern: photonvision-*.xz
|
||||||
|
|
||||||
- run: find
|
- run: find
|
||||||
# Push to dev release
|
# Push to dev release
|
||||||
|
|||||||
5
.github/workflows/dependency-submission.yml
vendored
5
.github/workflows/dependency-submission.yml
vendored
@@ -13,10 +13,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: Setup Java
|
- uses: actions/setup-java@v5
|
||||||
uses: actions/setup-java@v5
|
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: 17
|
java-version: 25
|
||||||
- name: Generate and submit dependency graph
|
- name: Generate and submit dependency graph
|
||||||
uses: gradle/actions/dependency-submission@v5
|
uses: gradle/actions/dependency-submission@v5
|
||||||
|
|||||||
13
.github/workflows/lint-format.yml
vendored
13
.github/workflows/lint-format.yml
vendored
@@ -46,9 +46,9 @@ jobs:
|
|||||||
- name: Generate diff
|
- name: Generate diff
|
||||||
run: git diff HEAD > wpiformat-fixes.patch
|
run: git diff HEAD > wpiformat-fixes.patch
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: wpiformat fixes
|
archive: false
|
||||||
path: wpiformat-fixes.patch
|
path: wpiformat-fixes.patch
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
javaformat:
|
javaformat:
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-java@v5
|
- uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 25
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
- run: |
|
- run: |
|
||||||
set +e
|
set +e
|
||||||
@@ -81,13 +81,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v5
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Setup Node.js
|
- uses: actions/setup-node@v6
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: photon-client/pnpm-lock.yaml
|
cache-dependency-path: photon-client/pnpm-lock.yaml
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
|
|||||||
21
.github/workflows/photon-api-docs.yml
vendored
21
.github/workflows/photon-api-docs.yml
vendored
@@ -30,21 +30,19 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- name: Install pnpm
|
- uses: pnpm/action-setup@v5
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Setup Node.js
|
- uses: actions/setup-node@v6
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: photon-client/pnpm-lock.yaml
|
cache-dependency-path: photon-client/pnpm-lock.yaml
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: pnpm i --frozen-lockfile
|
run: pnpm i --frozen-lockfile
|
||||||
- name: Build Production Client
|
- name: Build Production Client
|
||||||
run: pnpm run build-demo
|
run: pnpm run build-demo
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: built-demo
|
name: built-demo
|
||||||
path: photon-client/dist/
|
path: photon-client/dist/
|
||||||
@@ -60,16 +58,15 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Fetch tags
|
- name: Fetch tags
|
||||||
run: git fetch --tags --force
|
run: git fetch --tags --force
|
||||||
- name: Install Java 17
|
- uses: actions/setup-java@v5
|
||||||
uses: actions/setup-java@v5
|
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 25
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
- name: Build javadocs/doxygen
|
- name: Build javadocs/doxygen
|
||||||
run: |
|
run: |
|
||||||
chmod +x gradlew
|
chmod +x gradlew
|
||||||
./gradlew photon-docs:generateJavaDocs photon-docs:doxygen
|
./gradlew photon-docs:generateJavaDocs photon-docs:doxygen
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: docs-java-cpp
|
name: docs-java-cpp
|
||||||
path: photon-docs/build/docs
|
path: photon-docs/build/docs
|
||||||
@@ -80,7 +77,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
# Download docs artifact
|
# Download docs artifact
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: docs-*
|
pattern: docs-*
|
||||||
- run: find .
|
- run: find .
|
||||||
@@ -106,7 +103,7 @@ jobs:
|
|||||||
needs: [build_demo]
|
needs: [build_demo]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: built-demo
|
name: built-demo
|
||||||
- run: find .
|
- run: find .
|
||||||
|
|||||||
11
.github/workflows/python.yml
vendored
11
.github/workflows/python.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
run: python setup.py sdist bdist_wheel
|
run: python setup.py sdist bdist_wheel
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: ./photon-lib/py/dist/
|
path: ./photon-lib/py/dist/
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
pip install pytest mypy
|
pip install pytest mypy
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: dist/
|
path: dist/
|
||||||
@@ -100,9 +100,10 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install mypy
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: ./photon-lib/py/dist/
|
path: ./photon-lib/py/dist/
|
||||||
@@ -120,7 +121,7 @@ jobs:
|
|||||||
for folder in */;
|
for folder in */;
|
||||||
do
|
do
|
||||||
echo $folder
|
echo $folder
|
||||||
./run.sh $folder
|
./test.sh $folder
|
||||||
done
|
done
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
@@ -131,7 +132,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: dist/
|
path: dist/
|
||||||
|
|||||||
30
.github/workflows/website.yml
vendored
30
.github/workflows/website.yml
vendored
@@ -11,13 +11,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v5
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Setup Node
|
- uses: actions/setup-node@v6
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: website/pnpm-lock.yaml
|
cache-dependency-path: website/pnpm-lock.yaml
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
@@ -37,22 +36,27 @@ jobs:
|
|||||||
|
|
||||||
format-check:
|
format-check:
|
||||||
name: Check Formatting
|
name: Check Formatting
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: website
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- name: Install pnpm
|
- uses: pnpm/action-setup@v5
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Setup Node
|
- uses: actions/setup-node@v6
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: website/pnpm-lock.yaml
|
cache-dependency-path: website/pnpm-lock.yaml
|
||||||
- name: Install Packages
|
- name: Install Packages
|
||||||
run: pnpm i --frozen-lockfile
|
run: pnpm i --frozen-lockfile
|
||||||
working-directory: website
|
- run: |
|
||||||
- name: Run Formatting Check
|
set +e
|
||||||
run: pnpm prettier -c .
|
pnpm run format-ci
|
||||||
working-directory: website
|
exit_code=$?
|
||||||
|
if test "$exit_code" -ne "0"; then
|
||||||
|
echo "::error ::Linting failed. See https://docs.photonvision.org/en/latest/docs/contributing/linting.html"
|
||||||
|
fi
|
||||||
|
exit $exit_code
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -161,3 +161,5 @@ photon-client/playwright/.auth/
|
|||||||
shell.nix
|
shell.nix
|
||||||
flake.nix
|
flake.nix
|
||||||
flake.lock
|
flake.lock
|
||||||
|
|
||||||
|
/workspace
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ cppHeaderFileInclude {
|
|||||||
\.h$
|
\.h$
|
||||||
}
|
}
|
||||||
|
|
||||||
modifiableFileExclude {
|
generatedFileExclude {
|
||||||
photon-lib/py/photonlibpy/generated/
|
photon-lib/py/photonlibpy/generated/
|
||||||
photon-targeting/src/generated/
|
photon-targeting/src/generated/
|
||||||
photon-targeting/src/main/native/cpp/photon/constrained_solvepnp/generate/
|
photon-targeting/src/main/native/cpp/photon/constrained_solvepnp/generate/
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -32,11 +32,11 @@ You can run one of the many built in examples straight from the command line, to
|
|||||||
Note that these are case sensitive!
|
Note that these are case sensitive!
|
||||||
|
|
||||||
* `-PArchOverride=foobar`: builds for a target system other than your current architecture. [Valid overrides](https://github.com/wpilibsuite/wpilib-tool-plugin/blob/main/src/main/java/edu/wpi/first/tools/NativePlatforms.java) are:
|
* `-PArchOverride=foobar`: builds for a target system other than your current architecture. [Valid overrides](https://github.com/wpilibsuite/wpilib-tool-plugin/blob/main/src/main/java/edu/wpi/first/tools/NativePlatforms.java) are:
|
||||||
* winx64
|
* winx86-64
|
||||||
* winarm64
|
* winarm64
|
||||||
* macx64
|
* macx86-64
|
||||||
* macarm64
|
* macarm64
|
||||||
* linuxx64
|
* linuxx86-64
|
||||||
* linuxarm64
|
* linuxarm64
|
||||||
* linuxathena
|
* linuxathena
|
||||||
- `-PtgtIP`: Specifies where `./gradlew deploy` should try to copy the fat JAR to
|
- `-PtgtIP`: Specifies where `./gradlew deploy` should try to copy the fat JAR to
|
||||||
@@ -45,7 +45,7 @@ Note that these are case sensitive!
|
|||||||
- `-Pprofile`: enables JVM profiling
|
- `-Pprofile`: enables JVM profiling
|
||||||
- `-PwithSanitizers`: On Linux, enables `-fsanitize=address,undefined,leak`
|
- `-PwithSanitizers`: On Linux, enables `-fsanitize=address,undefined,leak`
|
||||||
|
|
||||||
If you're cross-compiling, you'll need the WPILib toolchain installed. This must be done via Gradle: for example `./gradlew installArm64Toolchain` or `./gradlew installRoboRioToolchain`
|
If you're cross-compiling, you'll need the WPILib toolchain installed. This must be done via Gradle: for example `./gradlew installArm64Toolchain` or `./gradlew installSystemCoreToolchain`
|
||||||
|
|
||||||
## Out-of-Source Dependencies
|
## Out-of-Source Dependencies
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ PhotonVision was forked from [Chameleon Vision](https://github.com/Chameleon-Vis
|
|||||||
* [EJML](https://github.com/lessthanoptimal/ejml)
|
* [EJML](https://github.com/lessthanoptimal/ejml)
|
||||||
* [Javalin](https://javalin.io/)
|
* [Javalin](https://javalin.io/)
|
||||||
* [JSON](https://json.org)
|
* [JSON](https://json.org)
|
||||||
* [FasterXML](https://github.com/FasterXML) - Specifically [jackson](https://github.com/FasterXML/jackson)
|
* [Avaje](https://avaje.io) - Specifically [jsonb](https://avaje.io/jsonb/)
|
||||||
* [MessagePack for Java](https://github.com/msgpack/msgpack-java)
|
* [MessagePack for Java](https://github.com/msgpack/msgpack-java)
|
||||||
* [OSHI](https://github.com/oshi/oshi)
|
* [OSHI](https://github.com/oshi/oshi)
|
||||||
* [QuickBuffers](https://github.com/HebiRobotics/QuickBuffers)
|
* [QuickBuffers](https://github.com/HebiRobotics/QuickBuffers)
|
||||||
|
|||||||
47
build.gradle
47
build.gradle
@@ -1,16 +1,17 @@
|
|||||||
import edu.wpi.first.toolchain.*
|
import org.wpilib.toolchain.*
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "cpp"
|
id "cpp"
|
||||||
id "com.diffplug.spotless" version "8.1.0"
|
id "com.diffplug.spotless" version "8.1.0"
|
||||||
id "edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin" version "2020.2"
|
id "org.wpilib.WPILibRepositoriesPlugin" version "2027.0.0"
|
||||||
id "edu.wpi.first.GradleRIO" version "2026.2.1"
|
id 'org.wpilib.NativeUtils' version '2027.7.1' apply false
|
||||||
id 'org.photonvision.tools.WpilibTools' version '2.4.1-photon'
|
id 'org.wpilib.DeployUtils' version '2027.1.0' apply false
|
||||||
id 'com.google.protobuf' version '0.9.3' apply false
|
id 'org.photonvision.tools.WpilibTools' version 'v5.0.1'
|
||||||
id 'edu.wpi.first.GradleJni' version '1.1.0'
|
id 'com.google.protobuf' version '0.9.5' apply false
|
||||||
|
id 'org.wpilib.GradleJni' version '2027.0.0'
|
||||||
id "org.ysb33r.doxygen" version "2.0.0" apply false
|
id "org.ysb33r.doxygen" version "2.0.0" apply false
|
||||||
id 'com.gradleup.shadow' version '8.3.4' apply false
|
id 'com.gradleup.shadow' version '9.0.0' apply false
|
||||||
id "com.github.node-gradle.node" version "7.0.1" apply false
|
id "com.github.node-gradle.node" version "7.1.0" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
@@ -21,6 +22,7 @@ allprojects {
|
|||||||
maven { url = "https://maven.photonvision.org/releases" }
|
maven { url = "https://maven.photonvision.org/releases" }
|
||||||
maven { url = "https://maven.photonvision.org/snapshots" }
|
maven { url = "https://maven.photonvision.org/snapshots" }
|
||||||
}
|
}
|
||||||
|
wpilibRepositories.use2027Repos()
|
||||||
wpilibRepositories.addAllReleaseRepositories(it)
|
wpilibRepositories.addAllReleaseRepositories(it)
|
||||||
wpilibRepositories.addAllDevelopmentRepositories(it)
|
wpilibRepositories.addAllDevelopmentRepositories(it)
|
||||||
}
|
}
|
||||||
@@ -28,20 +30,21 @@ allprojects {
|
|||||||
ext.localMavenURL = file("$project.buildDir/outputs/maven")
|
ext.localMavenURL = file("$project.buildDir/outputs/maven")
|
||||||
ext.allOutputsFolder = file("$project.buildDir/outputs")
|
ext.allOutputsFolder = file("$project.buildDir/outputs")
|
||||||
|
|
||||||
// Configure the version number.
|
|
||||||
apply from: "versioningHelper.gradle"
|
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
wpilibVersion = "2026.2.1"
|
wpilibVersion = "2027.0.0-alpha-6"
|
||||||
wpimathVersion = wpilibVersion
|
openCVversion = "2027-4.13.0-3"
|
||||||
openCVYear = "2025"
|
ejmlVersion = "0.43.1";
|
||||||
openCVversion = "4.10.0-3"
|
avajeJsonbVersion = "3.14";
|
||||||
|
msgpackVersion = "0.9.0";
|
||||||
|
quickbufVersion = "1.3.3";
|
||||||
|
jacocoVersion = "0.8.14";
|
||||||
|
|
||||||
javalinVersion = "6.7.0"
|
javalinVersion = "6.7.0"
|
||||||
libcameraDriverVersion = "v2026.0.0"
|
libcameraDriverVersion = "v2027.0.0-alpha-1"
|
||||||
rknnVersion = "dev-v2026.0.1-1-g89b2888"
|
rknnVersion = "dev-v2026.0.1-3-g14c3ecb"
|
||||||
rubikVersion = "dev-v2026.0.1-4-g13d6279"
|
tfliteVersion = "v2027.0.2-alpha-1"
|
||||||
frcYear = "2026"
|
wpilibYear = "2027_alpha5"
|
||||||
mrcalVersion = "dev-v2026.0.0-1-g239d80e";
|
mrcalVersion = "v2027.0.2";
|
||||||
|
|
||||||
pubVersion = versionString
|
pubVersion = versionString
|
||||||
isDev = pubVersion.startsWith("dev")
|
isDev = pubVersion.startsWith("dev")
|
||||||
@@ -62,7 +65,7 @@ spotless {
|
|||||||
java {
|
java {
|
||||||
target fileTree('.') {
|
target fileTree('.') {
|
||||||
include '**/*.java'
|
include '**/*.java'
|
||||||
exclude '**/build/**', '**/build-*/**', '**/src/generated/**'
|
exclude '**/build/**', '**/build-*/**', '**/src/generated/**', "**/bin/generated-sources/**"
|
||||||
}
|
}
|
||||||
toggleOffOn()
|
toggleOffOn()
|
||||||
googleJavaFormat()
|
googleJavaFormat()
|
||||||
@@ -94,7 +97,7 @@ spotless {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wrapper {
|
wrapper {
|
||||||
gradleVersion = '8.14.3'
|
gradleVersion = '9.4.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.getCurrentArch = {
|
ext.getCurrentArch = {
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ sphinx-autobuild==2024.10.3
|
|||||||
sphinx-basic-ng==1.0.0b2
|
sphinx-basic-ng==1.0.0b2
|
||||||
sphinx-notfound-page==1.1.0
|
sphinx-notfound-page==1.1.0
|
||||||
sphinx-rtd-theme==3.0.2
|
sphinx-rtd-theme==3.0.2
|
||||||
sphinx-tabs==3.4.7
|
|
||||||
sphinx_design==0.6.1
|
sphinx_design==0.6.1
|
||||||
sphinxcontrib-applehelp==2.0.0
|
sphinxcontrib-applehelp==2.0.0
|
||||||
sphinxcontrib-devhelp==2.0.0
|
sphinxcontrib-devhelp==2.0.0
|
||||||
|
|||||||
@@ -181,4 +181,4 @@ if token:
|
|||||||
linkcheck_auth = [(R"https://github.com/.+", token)]
|
linkcheck_auth = [(R"https://github.com/.+", token)]
|
||||||
|
|
||||||
# MyST configuration (https://myst-parser.readthedocs.io/en/latest/configuration.html)
|
# MyST configuration (https://myst-parser.readthedocs.io/en/latest/configuration.html)
|
||||||
myst_enable_extensions = ["colon_fence", "substitution"]
|
myst_enable_extensions = ["colon_fence", "substitution", "attrs_inline"]
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
# NetworkTables API
|
# NetworkTables API
|
||||||
|
|
||||||
## About
|
## Usage
|
||||||
|
|
||||||
:::{warning}
|
:::{warning}
|
||||||
PhotonVision interfaces with PhotonLib, our vendor dependency, using NetworkTables. If you are running PhotonVision on a robot (ie. with a RoboRIO), you should **turn the NetworkTables server switch (in the settings tab) off** in order to get PhotonLib to work. Also ensure that you set your team number. **The NetworkTables server should only be enabled if you know what you're doing!**
|
PhotonVision's NetworkTables API is not designed for robot consumption, only internal at-a-glance debugging.**
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
**Use PhotonLib instead, as the NetworkTables API is not supported for robot consumption.**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
:::{warning}
|
|
||||||
NetworkTables is not a supported setup/viable option when using PhotonVision as we only send one target at a time (this is problematic when using AprilTags, which will return data from multiple tags at once).
|
|
||||||
|
|
||||||
**We strongly recommend using PhotonLib instead, as the NetworkTables API will most likely be removed in 2027.**
|
|
||||||
:::
|
|
||||||
|
|
||||||
The tables below contain the the name of the key for each entry that PhotonVision sends over the network and a short description of the key. The entries should be extracted from a subtable with your camera's nickname (visible in the PhotonVision UI) under the main `photonvision` table.
|
The tables below contain the the name of the key for each entry that PhotonVision sends over the network and a short description of the key. The entries should be extracted from a subtable with your camera's nickname (visible in the PhotonVision UI) under the main `photonvision` table.
|
||||||
|
|
||||||
### Getting Target Information
|
### Getting Target Information
|
||||||
@@ -64,9 +62,3 @@ These entries are global, meaning that they should be called on the main `photon
|
|||||||
| Key | Type | Description |
|
| Key | Type | Description |
|
||||||
| --------- | ----- | -------------------------------------------------------- |
|
| --------- | ----- | -------------------------------------------------------- |
|
||||||
| `ledMode` | `int` | Sets the LED Mode (-1: default, 0: off, 1: on, 2: blink) |
|
| `ledMode` | `int` | Sets the LED Mode (-1: default, 0: off, 1: on, 2: blink) |
|
||||||
|
|
||||||
:::{warning}
|
|
||||||
Setting the LED mode to -1 (default) when `multiple` cameras are connected may result in unexpected behavior. {ref}`This is a known limitation of PhotonVision. <docs/troubleshooting/common-errors:LED Control>`
|
|
||||||
|
|
||||||
Single camera operation should work without issue.
|
|
||||||
:::
|
|
||||||
|
|||||||
@@ -3,5 +3,8 @@
|
|||||||
"ledPins" : [ 13, 18 ],
|
"ledPins" : [ 13, 18 ],
|
||||||
"ledsCanDim" : true,
|
"ledsCanDim" : true,
|
||||||
"ledPWMFrequency" : 1000,
|
"ledPWMFrequency" : 1000,
|
||||||
|
"statusLEDType": "GreenYellow",
|
||||||
|
"statusLEDPins": [ 5, 4 ],
|
||||||
|
"statusLEDActiveHigh": false,
|
||||||
"vendorFOV" : 75.76079874010732
|
"vendorFOV" : 75.76079874010732
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,8 @@
|
|||||||
"deviceName" : "Limelight 2",
|
"deviceName" : "Limelight 2",
|
||||||
"ledPins" : [ 17, 18 ],
|
"ledPins" : [ 17, 18 ],
|
||||||
"ledsCanDim" : false,
|
"ledsCanDim" : false,
|
||||||
|
"statusLEDType": "GreenYellow",
|
||||||
|
"statusLEDPins": [ 5, 4 ],
|
||||||
|
"statusLEDActiveHigh": false,
|
||||||
"vendorFOV" : 75.76079874010732
|
"vendorFOV" : 75.76079874010732
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Go to the [GitHub releases page](https://github.com/PhotonVision/photonvision/re
|
|||||||
:::{note}
|
:::{note}
|
||||||
If your coprocessor has a 64 bit ARM based CPU architecture (OrangePi, Raspberry Pi, etc.), download the LinuxArm64.jar file.
|
If your coprocessor has a 64 bit ARM based CPU architecture (OrangePi, Raspberry Pi, etc.), download the LinuxArm64.jar file.
|
||||||
|
|
||||||
If your coprocessor has an 64 bit x86 based CPU architecture (Mini PC, laptop, etc.), download the Linuxx64.jar file.
|
If your coprocessor has an 64 bit x86 based CPU architecture (Mini PC, laptop, etc.), download the Linuxx86-64.jar file.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::{warning}
|
:::{warning}
|
||||||
|
|||||||
@@ -1,53 +1,9 @@
|
|||||||
# Mac OS Installation
|
# MacOS Installation
|
||||||
|
|
||||||
:::{warning}
|
## Builds
|
||||||
Due to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) restrictions, the PhotonVision server backend may have issues running macOS.
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::{note}
|
Builds for macOS are currently generated in CI for testing compatibility. This allows us to ensure that macOS remains a viable development platform.
|
||||||
You do not need to install PhotonVision on a Mac in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi).
|
|
||||||
:::
|
|
||||||
|
|
||||||
VERY Limited macOS support is available.
|
## Use as a coprocessor
|
||||||
|
|
||||||
## Installing Java
|
This is not a recommended path. MacOS is not officially supported, and your mileage may vary.
|
||||||
|
|
||||||
PhotonVision requires a JDK installed and on the system path. JDK 17 is needed (different versions will not work). You may already have this if you have installed WPILib 2026+. If not, [download and install it from here](https://adoptium.net/temurin/releases?version=17).
|
|
||||||
|
|
||||||
:::{warning}
|
|
||||||
Using a JDK other than JDK17 will cause issues when running PhotonVision and is not supported.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Downloading the Latest Stable Release of PhotonVision
|
|
||||||
|
|
||||||
Go to the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) and download the relevant .jar file for your coprocessor.
|
|
||||||
|
|
||||||
:::{note}
|
|
||||||
If you have an M Series Mac, download the macarm64.jar file.
|
|
||||||
|
|
||||||
If you have an Intel based Mac, download the macx64.jar file.
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::{warning}
|
|
||||||
Be careful to pick the latest stable release. "Draft" or "Pre-Release" versions are not stable and often have bugs.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Running PhotonVision
|
|
||||||
|
|
||||||
To run PhotonVision, open a terminal window of your choice and run the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ java -jar /path/to/photonvision/photonvision-xxx.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
:::{warning}
|
|
||||||
Due to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) restrictions, the PhotonVision using test mode is all that is known to work currently.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Accessing the PhotonVision Interface
|
|
||||||
|
|
||||||
Once the Java backend is up and running, you can access the main vision interface by navigating to `localhost:5800` inside your browser.
|
|
||||||
|
|
||||||
:::{warning}
|
|
||||||
Due to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) restrictions, it is unlikely any streams will open from real webcams.
|
|
||||||
:::
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ PhotonVision requires a JDK installed and on the system path. **JDK 17 is needed
|
|||||||
|
|
||||||
## Downloading the Latest Stable Release of PhotonVision
|
## Downloading the Latest Stable Release of PhotonVision
|
||||||
|
|
||||||
Go to the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) and download the winx64.jar file.
|
Go to the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) and download the winx86-64.jar file.
|
||||||
|
|
||||||
## Running PhotonVision
|
## Running PhotonVision
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Ensure that your camera is calibrated and 3D mode is enabled. Navigate to the Ou
|
|||||||
By default, enabling multi-target will disable calculating camera-to-target transforms for each observed AprilTag target to increase performance; the X/Y/angle numbers shown in the target table of the UI are instead calculated using the tag's expected location (per the field layout JSON) and the field-to-camera transform calculated using MultiTag. If you additionally want the individual camera-to-target transform calculated using SolvePNP for each target, enable "Always Do Single-Target Estimation".
|
By default, enabling multi-target will disable calculating camera-to-target transforms for each observed AprilTag target to increase performance; the X/Y/angle numbers shown in the target table of the UI are instead calculated using the tag's expected location (per the field layout JSON) and the field-to-camera transform calculated using MultiTag. If you additionally want the individual camera-to-target transform calculated using SolvePNP for each target, enable "Always Do Single-Target Estimation".
|
||||||
:::
|
:::
|
||||||
|
|
||||||
This multi-target pose estimate can be accessed using PhotonLib. We suggest using {ref}`the PhotonPoseEstimator class <docs/programming/photonlib/robot-pose-estimator:AprilTags and PhotonPoseEstimator>` with the `MULTI_TAG_PNP_ON_COPROCESSOR` strategy to simplify code, but the transform can be directly accessed using `getMultiTagResult`/`MultiTagResult()`/`multitagResult` (Java/C++/Python).
|
This multi-target pose estimate can be accessed using PhotonLib. We suggest using {ref}`the PhotonPoseEstimator class <docs/programming/photonlib/robot-pose-estimator:AprilTags and PhotonPoseEstimator>` and calling `estimateCoprocMultiTagPose` (with an optional fallback like `estimateLowestAmbiguityPose`) to simplify code, but the transform can be directly accessed using `getMultiTagResult`/`MultiTagResult()`/`multitagResult` (Java/C++/Python).
|
||||||
|
|
||||||
```{eval-rst}
|
```{eval-rst}
|
||||||
.. tab-set-code::
|
.. tab-set-code::
|
||||||
@@ -46,7 +46,7 @@ This multi-target pose estimate can be accessed using PhotonLib. We suggest usin
|
|||||||
{
|
{
|
||||||
auto multiTagResult = result.MultiTagResult();
|
auto multiTagResult = result.MultiTagResult();
|
||||||
if (multiTagResult.has_value()) {
|
if (multiTagResult.has_value()) {
|
||||||
frc::Transform3d fieldToCamera = multiTagResult->estimatedPose.best;
|
wpi::math::Transform3d fieldToCamera = multiTagResult->estimatedPose.best;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ In order to detect AprilTags and use 3D mode, your camera must be calibrated at
|
|||||||
To calibrate a camera, images of a ChArUco board (or chessboard) are taken. By comparing where the grid corners should be in object space (for example, a corner once every inch in an 8x6 grid) with where they appear in the camera image, we can find a least-squares estimate for intrinsic camera properties like focal lengths, center point, and distortion coefficients. For more on camera calibration, please review the [OpenCV documentation](https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html).
|
To calibrate a camera, images of a ChArUco board (or chessboard) are taken. By comparing where the grid corners should be in object space (for example, a corner once every inch in an 8x6 grid) with where they appear in the camera image, we can find a least-squares estimate for intrinsic camera properties like focal lengths, center point, and distortion coefficients. For more on camera calibration, please review the [OpenCV documentation](https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html).
|
||||||
|
|
||||||
:::{warning}
|
:::{warning}
|
||||||
While any resolution can be calibrated, higher resolutions may be too performance-intensive for some coprocessors to handle. Therefore, we recommend experimenting to see what works best for your coprocessor.
|
PhotonVision supports many resolution configuration options, with only a minimum of 640x480 required. However, higher resolutions may be too performance-intensive for some coprocessors to handle. Therefore, we recommend experimenting to see what works best for your coprocessor.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::{note}
|
:::{note}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ This section contains the build instructions from the source code available at [
|
|||||||
|
|
||||||
**Java Development Kit:**
|
**Java Development Kit:**
|
||||||
|
|
||||||
This project requires Java Development Kit (JDK) 17 to be compiled. This is the same Java version that comes with WPILib for 2026+. **Windows Users must use the JDK that ships with WPILib.** For other platforms, you can follow the instructions to install JDK 17 for your platform [here](https://bell-sw.com/pages/downloads/#jdk-17-lts).
|
This project requires Java Development Kit (JDK) 25 to be compiled. This is the same Java version that comes with WPILib for 2027. **Windows Users must use the JDK that ships with WPILib.** For other platforms, you can follow the instructions to install JDK 25 for your platform [here](https://bell-sw.com/pages/downloads/#jdk-25-lts).
|
||||||
|
|
||||||
**Node JS:**
|
**Node JS:**
|
||||||
|
|
||||||
The UI is written in Node JS. To compile the UI, Node 22 or later is required. To install Node JS, follow the instructions for your platform [on the official Node JS website](https://nodejs.org/en/download/).
|
The UI is written in Node JS. To compile the UI, Node 24 or later is required. To install Node JS, follow the instructions for your platform [on the official Node JS website](https://nodejs.org/en/download/).
|
||||||
|
|
||||||
**pnpm:**
|
**pnpm:**
|
||||||
|
|
||||||
@@ -47,6 +47,30 @@ In the photon-client directory:
|
|||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Building the UI
|
||||||
|
|
||||||
|
In order to properly build UI changes before running the project, run the following `gradlew` command in the project's root directory:
|
||||||
|
|
||||||
|
```{eval-rst}
|
||||||
|
.. tab-set::
|
||||||
|
|
||||||
|
.. tab-item:: Linux
|
||||||
|
:sync: linux
|
||||||
|
|
||||||
|
``./gradlew buildAndCopyUI``
|
||||||
|
|
||||||
|
.. tab-item:: macOS
|
||||||
|
:sync: macos
|
||||||
|
|
||||||
|
``./gradlew buildAndCopyUI``
|
||||||
|
|
||||||
|
.. tab-item:: Windows (cmd)
|
||||||
|
:sync: windows
|
||||||
|
|
||||||
|
``gradlew buildAndCopyUI``
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Using hot reload on the UI
|
### Using hot reload on the UI
|
||||||
|
|
||||||
In the photon-client directory:
|
In the photon-client directory:
|
||||||
|
|||||||
@@ -79,9 +79,9 @@ public class Robot extends TimedRobot {
|
|||||||
camera.getAllUnreadResults();
|
camera.getAllUnreadResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
var t1 = Timer.getFPGATimestamp();
|
var t1 = Timer.getMonotonicTimestamp();
|
||||||
light.set(true);
|
light.set(true);
|
||||||
var t2 = Timer.getFPGATimestamp();
|
var t2 = Timer.getMonotonicTimestamp();
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
|
|||||||
@@ -34,10 +34,16 @@ To install *doc8*, the python tool we use to lint our documentation, run `pipx i
|
|||||||
|
|
||||||
To lint the documentation, run `doc8 docs` from the root level of the docs.
|
To lint the documentation, run `doc8 docs` from the root level of the docs.
|
||||||
|
|
||||||
|
## Website
|
||||||
|
|
||||||
|
### Formatting the website
|
||||||
|
|
||||||
|
To format the website, run `pnpm -C website run format`.
|
||||||
|
|
||||||
## Alias
|
## Alias
|
||||||
|
|
||||||
The following [alias](https://www.computerworld.com/article/1373210/how-to-use-aliases-in-linux-shell-commands.html) can be added to your shell config, which will allow you to lint the entirety of the PhotonVision project by running `pvLint`. The alias will work on Linux, macOS, Git Bash on Windows, and WSL.
|
The following [alias](https://www.computerworld.com/article/1373210/how-to-use-aliases-in-linux-shell-commands.html) can be added to your shell config, which will allow you to lint the entirety of the PhotonVision project by running `pvLint`. The alias will work on Linux, macOS, Git Bash on Windows, and WSL.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
alias pvLint='wpiformat -v && ./gradlew spotlessApply && pnpm -C photon-client lint && pnpm -C photon-client format && doc8 docs'
|
alias pvLint='wpiformat -v && ./gradlew spotlessApply && pnpm -C photon-client lint && pnpm -C photon-client format && doc8 docs && pnpm -C website format'
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -19,14 +19,18 @@ When running on Linux, PhotonVision can use [diozero](https://www.diozero.com) t
|
|||||||
"ledsCanDim" : true,
|
"ledsCanDim" : true,
|
||||||
"ledBrightnessRange" : [ 0, 100 ],
|
"ledBrightnessRange" : [ 0, 100 ],
|
||||||
"ledPWMFrequency" : 0,
|
"ledPWMFrequency" : 0,
|
||||||
"statusRGBPins" : [ ],
|
"statusLEDType": "RGB",
|
||||||
"statusRGBActiveHigh" : false,
|
"statusLEDPins" : [ ],
|
||||||
|
"statusLEDActiveHigh" : false,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
:::{note}
|
There are currently two types of status LEDs supported:
|
||||||
No hardware boards with status RGB LED pins or non-dimming LED's have been tested yet. Please reach out to the development team if these features are desired, they can assist with configuration and testing.
|
|
||||||
:::
|
* `RGB` (default): A singular LED mixing separate red, green, and blue inputs
|
||||||
|
* `GreenYellow`: A pair of independent green and yellow LEDs
|
||||||
|
|
||||||
|
For an explanation of the colors used for status LEDs, see {ref}`Status LEDs<docs/troubleshooting/status-leds:Status LEDs>`
|
||||||
|
|
||||||
### GPIO Pinout
|
### GPIO Pinout
|
||||||
|
|
||||||
@@ -134,8 +138,9 @@ Here is a complete example `hardwareConfig.json`:
|
|||||||
"ledsCanDim" : true,
|
"ledsCanDim" : true,
|
||||||
"ledBrightnessRange" : [ 0, 100 ],
|
"ledBrightnessRange" : [ 0, 100 ],
|
||||||
"ledPWMFrequency" : 0,
|
"ledPWMFrequency" : 0,
|
||||||
"statusRGBPins" : [ ],
|
"statusLEDType": "RGB",
|
||||||
"statusRGBActiveHigh" : false,
|
"statusLEDPins" : [ ],
|
||||||
|
"statusLEDActiveHigh" : false,
|
||||||
"getGPIOCommand" : "getGPIO {p}",
|
"getGPIOCommand" : "getGPIO {p}",
|
||||||
"setGPIOCommand" : "setGPIO {p} {s}",
|
"setGPIOCommand" : "setGPIO {p} {s}",
|
||||||
"setPWMCommand" : "setPWM {p} {v}",
|
"setPWMCommand" : "setPWM {p} {v}",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ In order to use PhotonVision, you need a coprocessor and a camera. This page dis
|
|||||||
|
|
||||||
### Minimum System Requirements
|
### Minimum System Requirements
|
||||||
|
|
||||||
- Ubuntu 22.04 LTS or Windows 10/11
|
- Ubuntu 24.04 LTS or Windows 10/11
|
||||||
- We don't recommend using Windows for anything except testing out the system on a local machine.
|
- We don't recommend using Windows for anything except testing out the system on a local machine.
|
||||||
- CPU: ARM Cortex-A53 (the CPU on Raspberry Pi 3) or better
|
- CPU: ARM Cortex-A53 (the CPU on Raspberry Pi 3) or better
|
||||||
- At least 8GB of storage
|
- At least 8GB of storage
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ PhotonVision runs object detection on the Orange Pi 5 by use of the RKNN model a
|
|||||||
|
|
||||||
PhotonVision currently ONLY supports 640x640 Ultralytics YOLOv5, YOLOv8, and YOLOv11 models trained and converted to `.rknn` format for RK3588 SOCs! Other models require different post-processing code and will NOT work.
|
PhotonVision currently ONLY supports 640x640 Ultralytics YOLOv5, YOLOv8, and YOLOv11 models trained and converted to `.rknn` format for RK3588 SOCs! Other models require different post-processing code and will NOT work.
|
||||||
|
|
||||||
|
If you have a YOLO-Pro model from Edge Impulse, it can be converted using [this notebook](https://github.com/ramalamadingdong/yolo-pro-to-yolo11/tree/main).
|
||||||
|
|
||||||
## Converting Custom Models
|
## Converting Custom Models
|
||||||
|
|
||||||
:::{warning}
|
:::{warning}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ PhotonVision runs object detection on the Rubik Pi 3 by use of [TensorflowLite](
|
|||||||
|
|
||||||
PhotonVision currently ONLY supports 640x640 Ultralytics YOLOv8 and YOLOv11 models trained and converted to `.tflite` format for QCS6490 SOCs! Other models require different post-processing code and will NOT work.
|
PhotonVision currently ONLY supports 640x640 Ultralytics YOLOv8 and YOLOv11 models trained and converted to `.tflite` format for QCS6490 SOCs! Other models require different post-processing code and will NOT work.
|
||||||
|
|
||||||
|
If you have a YOLO-Pro model from Edge Impulse, it can be converted using [this notebook](https://github.com/ramalamadingdong/yolo-pro-to-yolo11/tree/main).
|
||||||
|
|
||||||
## Converting Custom Models
|
## Converting Custom Models
|
||||||
|
|
||||||
:::{warning}
|
:::{warning}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ You can also get the pipeline latency from a pipeline result using the `getLaten
|
|||||||
.. code-block:: c++
|
.. code-block:: c++
|
||||||
|
|
||||||
// Get the pipeline latency.
|
// Get the pipeline latency.
|
||||||
units::second_t latency = result.GetLatency();
|
wpi::units::second_t latency = result.GetLatency();
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ You can get a list of tracked targets using the `getTargets()`/`GetTargets()` (J
|
|||||||
.. code-block:: c++
|
.. code-block:: c++
|
||||||
|
|
||||||
// Get a list of currently tracked targets.
|
// Get a list of currently tracked targets.
|
||||||
wpi::ArrayRef<photonlib::PhotonTrackedTarget> targets = result.GetTargets();
|
std::span<photonlib::PhotonTrackedTarget> targets = result.GetTargets();
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -166,8 +166,8 @@ You can get the {ref}`best target <docs/reflectiveAndShape/contour-filtering:Con
|
|||||||
double pitch = target.GetPitch();
|
double pitch = target.GetPitch();
|
||||||
double area = target.GetArea();
|
double area = target.GetArea();
|
||||||
double skew = target.GetSkew();
|
double skew = target.GetSkew();
|
||||||
frc::Transform2d pose = target.GetCameraToTarget();
|
wpi::math::Transform2d pose = target.GetCameraToTarget();
|
||||||
wpi::SmallVector<std::pair<double, double>, 4> corners = target.GetCorners();
|
wpi::util::SmallVector<std::pair<double, double>, 4> corners = target.GetCorners();
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -206,8 +206,8 @@ All of the data above (**except skew**) is available when using AprilTags.
|
|||||||
// Get information from target.
|
// Get information from target.
|
||||||
int targetID = target.GetFiducialId();
|
int targetID = target.GetFiducialId();
|
||||||
double poseAmbiguity = target.GetPoseAmbiguity();
|
double poseAmbiguity = target.GetPoseAmbiguity();
|
||||||
frc::Transform3d bestCameraToTarget = target.getBestCameraToTarget();
|
wpi::math::Transform3d bestCameraToTarget = target.getBestCameraToTarget();
|
||||||
frc::Transform3d alternateCameraToTarget = target.getAlternateCameraToTarget();
|
wpi::math::Transform3d alternateCameraToTarget = target.getAlternateCameraToTarget();
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ You can get your robot's `Pose2D` on the field using various camera data, target
|
|||||||
.. code-block:: c++
|
.. code-block:: c++
|
||||||
|
|
||||||
// Calculate robot's field relative pose
|
// Calculate robot's field relative pose
|
||||||
frc::Pose2D robotPose = photonlib::EstimateFieldToRobot(
|
wpi::math::Pose2d robotPose = photonlib::EstimateFieldToRobot(
|
||||||
kCameraHeight, kTargetHeight, kCameraPitch, kTargetPitch, frc::Rotation2d(units::degree_t(-target.GetYaw())), frc::Rotation2d(units::degree_t(gyro.GetRotation2d)), targetPose, cameraToRobot);
|
kCameraHeight, kTargetHeight, kCameraPitch, kTargetPitch, wpi::math::Rotation2d(wpi::units::degree_t(-target.GetYaw())), wpi::math::Rotation2d(wpi::units::degree_t(gyro.GetRotation2d)), targetPose, cameraToRobot);
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -106,8 +106,8 @@ You can get a [translation](https://docs.wpilib.org/en/latest/docs/software/adva
|
|||||||
.. code-block:: c++
|
.. code-block:: c++
|
||||||
|
|
||||||
// Calculate a translation from the camera to the target.
|
// Calculate a translation from the camera to the target.
|
||||||
frc::Translation2d translation = photonlib::PhotonUtils::EstimateCameraToTargetTranslation(
|
wpi::math::Translation2d translation = photonlib::PhotonUtils::EstimateCameraToTargetTranslation(
|
||||||
distance, frc::Rotation2d(units::degree_t(-target.GetYaw())));
|
distance, wpi::math::Rotation2d(wpi::units::degree_t(-target.GetYaw())));
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ If you would like to access your Ethernet-connected vision device from a compute
|
|||||||
|
|
||||||
.. code-block:: c++
|
.. code-block:: c++
|
||||||
|
|
||||||
wpi::PortForwarder::GetInstance().Add(5800, "photonvision.local", 5800);
|
wpi::net::PortForwarder::GetInstance().Add(5800, "photonvision.local", 5800);
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|||||||
10
docs/source/docs/troubleshooting/images/led.svg
Normal file
10
docs/source/docs/troubleshooting/images/led.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="led" width="30" height="30" viewBox="0 0 100 100" style="color-scheme: dark light">
|
||||||
|
<circle cx="50" cy="50" r="20" fill="dimgrey"/>
|
||||||
|
<circle cx="50" cy="50" r="20" stroke="black" stroke-width="5" fill="currentColor"/>
|
||||||
|
<line x1="5" y1="50" x2="20" y2="50" stroke="currentColor" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<line x1="27.5" y1="11.03" x2="35" y2="24.02" stroke="currentColor" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<line x1="72.5" y1="11.03" x2="65" y2="24.02" stroke="currentColor" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<line x1="80" y1="50" x2="95" y2="50" stroke="currentColor" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<line x1="72.5" y1="88.97" x2="65" y2="75.98" stroke="currentColor" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<line x1="27.5" y1="88.97" x2="35" y2="75.98" stroke="currentColor" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 927 B |
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
common-errors
|
common-errors
|
||||||
logging
|
logging
|
||||||
|
status-leds
|
||||||
camera-troubleshooting
|
camera-troubleshooting
|
||||||
networking-troubleshooting
|
networking-troubleshooting
|
||||||
unix-commands
|
unix-commands
|
||||||
|
|||||||
101
docs/source/docs/troubleshooting/status-leds.md
Normal file
101
docs/source/docs/troubleshooting/status-leds.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
myst:
|
||||||
|
substitutions:
|
||||||
|
led_loader: |
|
||||||
|
```{image} images/led.svg
|
||||||
|
:height: 0
|
||||||
|
```
|
||||||
|
led: |
|
||||||
|
```{raw} html
|
||||||
|
<svg class="led" height="30" width="30">
|
||||||
|
<use href="../../_images/led.svg#led"/>
|
||||||
|
</svg>
|
||||||
|
```
|
||||||
|
---
|
||||||
|
<!-- markdownlint-disable-next-line MD033 MD041 -->
|
||||||
|
<style>
|
||||||
|
svg.led {
|
||||||
|
--off-color: transparent;
|
||||||
|
color: var(--on-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes led-blink {
|
||||||
|
66% {
|
||||||
|
color: var(--off-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(.solid) > svg.led {
|
||||||
|
animation: led-blink 0.45s steps(1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes led-even-blink {
|
||||||
|
50% {
|
||||||
|
color: var(--off-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(.solid).fast > svg.led {
|
||||||
|
animation-name: led-even-blink;
|
||||||
|
animation-duration: 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(.solid).error > svg.led {
|
||||||
|
animation-name: led-even-blink;
|
||||||
|
animation-duration: 0.90s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green > svg.led {
|
||||||
|
--on-color: limegreen;
|
||||||
|
}
|
||||||
|
.blue > svg.led {
|
||||||
|
--on-color: blue;
|
||||||
|
}
|
||||||
|
.yellow > svg.led {
|
||||||
|
--on-color: yellow;
|
||||||
|
}
|
||||||
|
.red > svg.led {
|
||||||
|
--on-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anti-yellow > svg.led {
|
||||||
|
--on-color: transparent;
|
||||||
|
--off-color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.off > svg.led {
|
||||||
|
color: var(--off-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
# Status LEDs
|
||||||
|
|
||||||
|
PhotonVision has support for multiple kinds of status LEDs. Make sure you reference the correct table for the type present on your hardware.
|
||||||
|
|
||||||
|
## RGB LED
|
||||||
|
|
||||||
|
Color | Flashing | Preview | Status
|
||||||
|
--------|----------|:-------------------------:|-----------------------------------------------
|
||||||
|
Green | Yes | [{{ led }}]{.green} | Running normally, no targets visible
|
||||||
|
Blue | No | [{{ led }}]{.solid .blue} | Running normally, targets visible
|
||||||
|
Yellow | Yes | [{{ led }}]{.yellow} | NT Disconnected, no targets visible
|
||||||
|
Blue | Yes | [{{ led }}]{.blue} | NT Disconnected, targets visible
|
||||||
|
Red | Yes | [{{ led }}]{.red} | Initializing or faulted, not running
|
||||||
|
Off | No | [{{ led }}]{.off} | No power or initialization fault, not running
|
||||||
|
|
||||||
|
## Green and Yellow LEDs
|
||||||
|
|
||||||
|
Used on Limelight 1, 2, 2+, 3, 3G, and 3A
|
||||||
|
|
||||||
|
Green and Yellow LED patterns may be active at the same time
|
||||||
|
|
||||||
|
Color | Pattern | Preview | Status
|
||||||
|
--------|----------------|:-----------------------------------------------------------:|-------------------------------------------------
|
||||||
|
Green | Slow Flashing | [{{ led }}]{.green} [{{ led }}]{.off} | No targets visible
|
||||||
|
Green | Quick Flashing | [{{ led }}]{.fast .green} [{{ led }}]{.off} | Targets visible
|
||||||
|
Yellow | Flashing | [{{ led }}]{.off} [{{ led }}]{.yellow} | NT Disconnected
|
||||||
|
Yellow | Solid | [{{ led }}]{.off} [{{ led }}]{.solid .yellow} | NT Connected
|
||||||
|
Both | Alternating | [{{ led }}]{.green .error} [{{ led }}]{.anti-yellow .error} | Initializing or faulted, not running
|
||||||
|
Both | Off | [{{ led }}]{.off} [{{ led }}]{.off} | No power or initialization fault, not running
|
||||||
|
|
||||||
|
{{ led_loader }}
|
||||||
@@ -13,10 +13,10 @@ You may see a warning similar to `The authenticity of host 'xxx' can't be establ
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
ssh pi@hostname
|
ssh photon@hostname
|
||||||
```
|
```
|
||||||
|
|
||||||
For PhotonVision, the username will be `pi` and the password will be `raspberry`.
|
For PhotonVision images, the username will be `photon` and the password will be `vision`.
|
||||||
|
|
||||||
### ip
|
### ip
|
||||||
|
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=permwrapper/dists
|
distributionPath=permwrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
2
photon-client/env.d.ts
vendored
2
photon-client/env.d.ts
vendored
@@ -1 +1,3 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module "vue3-virtual-scroll-list";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import skipFormattingConfig from "@vue/eslint-config-prettier/skip-formatting";
|
|||||||
|
|
||||||
export default defineConfigWithVueTs(
|
export default defineConfigWithVueTs(
|
||||||
pluginVue.configs["flat/recommended-error"],
|
pluginVue.configs["flat/recommended-error"],
|
||||||
vueTsConfigs.recommended,
|
vueTsConfigs.recommendedTypeChecked,
|
||||||
skipFormattingConfig,
|
skipFormattingConfig,
|
||||||
{
|
{
|
||||||
ignores: ["**/dist/**", "playwright-report"]
|
ignores: ["**/dist/**", "playwright-report"]
|
||||||
@@ -42,10 +42,13 @@ export default defineConfigWithVueTs(
|
|||||||
"vue/no-use-v-else-with-v-for": "error",
|
"vue/no-use-v-else-with-v-for": "error",
|
||||||
"vue/no-useless-mustaches": "error",
|
"vue/no-useless-mustaches": "error",
|
||||||
"vue/no-useless-v-bind": "error",
|
"vue/no-useless-v-bind": "error",
|
||||||
|
"vue/prefer-use-template-ref": "error",
|
||||||
"vue/require-default-prop": "off",
|
"vue/require-default-prop": "off",
|
||||||
|
"vue/require-typed-ref": "error",
|
||||||
"vue/v-for-delimiter-style": "error",
|
"vue/v-for-delimiter-style": "error",
|
||||||
"vue/v-on-event-hyphenation": "off",
|
"vue/v-on-event-hyphenation": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-empty-object-type": "error",
|
||||||
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"vue/valid-v-slot": ["error", { allowModifiers: true }]
|
"vue/valid-v-slot": ["error", { allowModifiers: true }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "24.x"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"preview": "vite preview --port 4173",
|
"preview": "vite preview --port 4173",
|
||||||
@@ -14,8 +17,8 @@
|
|||||||
"format-ci": "prettier --check src/",
|
"format-ci": "prettier --check src/",
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
"test-ui": "playwright test --ui",
|
"test-ui": "playwright test --ui",
|
||||||
"test-setup": "playwright install --with-deps"
|
"test-setup": "playwright install --with-deps",
|
||||||
|
"type-check": "vue-tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/prompt": "^5.2.6",
|
"@fontsource/prompt": "^5.2.6",
|
||||||
@@ -34,9 +37,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.31.0",
|
"@eslint/js": "^9.31.0",
|
||||||
"@playwright/test": "^1.56.1",
|
"@playwright/test": "^1.56.1",
|
||||||
"@types/node": "^22.15.14",
|
"@types/node": "^24.0.0",
|
||||||
"@types/three": "^0.178.0",
|
"@types/three": "^0.178.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.0",
|
"@vitejs/plugin-vue": "^6.0.6",
|
||||||
|
"vue-tsc": "^3.2.5",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
"@vue/eslint-config-typescript": "^14.5.0",
|
"@vue/eslint-config-typescript": "^14.5.0",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
@@ -45,7 +49,7 @@
|
|||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"sass": "^1.89.2",
|
"sass": "^1.89.2",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"vite": "^8.0.2",
|
"vite": "^8.0.10",
|
||||||
"vite-plugin-vuetify": "^2.1.1"
|
"vite-plugin-vuetify": "^2.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
352
photon-client/pnpm-lock.yaml
generated
352
photon-client/pnpm-lock.yaml
generated
@@ -52,14 +52,14 @@ importers:
|
|||||||
specifier: ^1.56.1
|
specifier: ^1.56.1
|
||||||
version: 1.56.1
|
version: 1.56.1
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.15.14
|
specifier: ^24.0.0
|
||||||
version: 22.15.14
|
version: 24.12.2
|
||||||
'@types/three':
|
'@types/three':
|
||||||
specifier: ^0.178.0
|
specifier: ^0.178.0
|
||||||
version: 0.178.1
|
version: 0.178.1
|
||||||
'@vitejs/plugin-vue':
|
'@vitejs/plugin-vue':
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.6
|
||||||
version: 6.0.0(vite@8.0.2(@types/node@22.15.14)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))
|
version: 6.0.6(vite@8.0.10(@types/node@24.12.2)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))
|
||||||
'@vue/eslint-config-prettier':
|
'@vue/eslint-config-prettier':
|
||||||
specifier: ^10.2.0
|
specifier: ^10.2.0
|
||||||
version: 10.2.0(eslint@9.31.0)(prettier@3.6.2)
|
version: 10.2.0(eslint@9.31.0)(prettier@3.6.2)
|
||||||
@@ -85,11 +85,14 @@ importers:
|
|||||||
specifier: ^5.8.3
|
specifier: ^5.8.3
|
||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
vite:
|
vite:
|
||||||
specifier: ^8.0.2
|
specifier: ^8.0.10
|
||||||
version: 8.0.2(@types/node@22.15.14)(sass@1.89.2)
|
version: 8.0.10(@types/node@24.12.2)(sass@1.89.2)
|
||||||
vite-plugin-vuetify:
|
vite-plugin-vuetify:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1(vite@8.0.2(@types/node@22.15.14)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3)
|
version: 2.1.1(vite@8.0.10(@types/node@24.12.2)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3)
|
||||||
|
vue-tsc:
|
||||||
|
specifier: ^3.2.5
|
||||||
|
version: 3.2.5(typescript@5.8.3)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -117,14 +120,14 @@ packages:
|
|||||||
'@dimforge/rapier3d-compat@0.12.0':
|
'@dimforge/rapier3d-compat@0.12.0':
|
||||||
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
||||||
|
|
||||||
'@emnapi/core@1.9.1':
|
'@emnapi/core@1.10.0':
|
||||||
resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==}
|
resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
|
||||||
|
|
||||||
'@emnapi/runtime@1.9.1':
|
'@emnapi/runtime@1.10.0':
|
||||||
resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==}
|
resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
|
||||||
|
|
||||||
'@emnapi/wasi-threads@1.2.0':
|
'@emnapi/wasi-threads@1.2.1':
|
||||||
resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==}
|
resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.7.0':
|
'@eslint-community/eslint-utils@4.7.0':
|
||||||
resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
|
resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
|
||||||
@@ -197,8 +200,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ==}
|
resolution: {integrity: sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ==}
|
||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.1.1':
|
'@napi-rs/wasm-runtime@1.1.4':
|
||||||
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
|
resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==}
|
||||||
|
peerDependencies:
|
||||||
|
'@emnapi/core': ^1.7.1
|
||||||
|
'@emnapi/runtime': ^1.7.1
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
@@ -212,8 +218,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
'@oxc-project/types@0.122.0':
|
'@oxc-project/types@0.127.0':
|
||||||
resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==}
|
resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==}
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.1':
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
||||||
@@ -244,36 +250,42 @@ packages:
|
|||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-win32-arm64@2.5.1':
|
'@parcel/watcher-win32-arm64@2.5.1':
|
||||||
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||||
@@ -306,100 +318,106 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@rolldown/binding-android-arm64@1.0.0-rc.11':
|
'@rolldown/binding-android-arm64@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==}
|
resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
'@rolldown/binding-darwin-arm64@1.0.0-rc.11':
|
'@rolldown/binding-darwin-arm64@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==}
|
resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@rolldown/binding-darwin-x64@1.0.0-rc.11':
|
'@rolldown/binding-darwin-x64@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==}
|
resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@rolldown/binding-freebsd-x64@1.0.0-rc.11':
|
'@rolldown/binding-freebsd-x64@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==}
|
resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
|
||||||
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.11':
|
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==}
|
resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11':
|
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==}
|
resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.11':
|
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==}
|
resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.11':
|
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==}
|
resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.11':
|
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==}
|
resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.11':
|
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==}
|
resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rolldown/binding-linux-x64-musl@1.0.0-rc.11':
|
'@rolldown/binding-linux-x64-musl@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==}
|
resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rolldown/binding-openharmony-arm64@1.0.0-rc.11':
|
'@rolldown/binding-openharmony-arm64@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==}
|
resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [openharmony]
|
os: [openharmony]
|
||||||
|
|
||||||
'@rolldown/binding-wasm32-wasi@1.0.0-rc.11':
|
'@rolldown/binding-wasm32-wasi@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==}
|
resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [wasm32]
|
cpu: [wasm32]
|
||||||
|
|
||||||
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11':
|
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==}
|
resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.11':
|
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==}
|
resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.19':
|
'@rolldown/pluginutils@1.0.0-rc.13':
|
||||||
resolution: {integrity: sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==}
|
resolution: {integrity: sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==}
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-rc.11':
|
'@rolldown/pluginutils@1.0.0-rc.17':
|
||||||
resolution: {integrity: sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==}
|
resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==}
|
||||||
|
|
||||||
'@tweenjs/tween.js@23.1.3':
|
'@tweenjs/tween.js@23.1.3':
|
||||||
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
||||||
@@ -413,8 +431,8 @@ packages:
|
|||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
'@types/node@22.15.14':
|
'@types/node@24.12.2':
|
||||||
resolution: {integrity: sha512-BL1eyu/XWsFGTtDWOYULQEs4KR0qdtYfCxYAUYRoB7JP7h9ETYLgQTww6kH8Sj2C0pFGgrpM0XKv6/kbIzYJ1g==}
|
resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==}
|
||||||
|
|
||||||
'@types/raf@3.4.3':
|
'@types/raf@3.4.3':
|
||||||
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
|
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
|
||||||
@@ -478,13 +496,22 @@ packages:
|
|||||||
resolution: {integrity: sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==}
|
resolution: {integrity: sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@vitejs/plugin-vue@6.0.0':
|
'@vitejs/plugin-vue@6.0.6':
|
||||||
resolution: {integrity: sha512-iAliE72WsdhjzTOp2DtvKThq1VBC4REhwRcaA+zPAAph6I+OQhUXv+Xu2KS7ElxYtb7Zc/3R30Hwv1DxEo7NXQ==}
|
resolution: {integrity: sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
|
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
vue: ^3.2.25
|
vue: ^3.2.25
|
||||||
|
|
||||||
|
'@volar/language-core@2.4.28':
|
||||||
|
resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==}
|
||||||
|
|
||||||
|
'@volar/source-map@2.4.28':
|
||||||
|
resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==}
|
||||||
|
|
||||||
|
'@volar/typescript@2.4.28':
|
||||||
|
resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==}
|
||||||
|
|
||||||
'@vue/compiler-core@3.5.13':
|
'@vue/compiler-core@3.5.13':
|
||||||
resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
|
resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
|
||||||
|
|
||||||
@@ -526,6 +553,9 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@vue/language-core@3.2.5':
|
||||||
|
resolution: {integrity: sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g==}
|
||||||
|
|
||||||
'@vue/reactivity@3.5.13':
|
'@vue/reactivity@3.5.13':
|
||||||
resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
|
resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
|
||||||
|
|
||||||
@@ -581,6 +611,9 @@ packages:
|
|||||||
ajv@6.12.6:
|
ajv@6.12.6:
|
||||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||||
|
|
||||||
|
alien-signals@3.1.2:
|
||||||
|
resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==}
|
||||||
|
|
||||||
ansi-styles@4.3.0:
|
ansi-styles@4.3.0:
|
||||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1051,24 +1084,28 @@ packages:
|
|||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.32.0:
|
lightningcss-linux-arm64-musl@1.32.0:
|
||||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.32.0:
|
lightningcss-linux-x64-gnu@1.32.0:
|
||||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.32.0:
|
lightningcss-linux-x64-musl@1.32.0:
|
||||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-win32-arm64-msvc@1.32.0:
|
lightningcss-win32-arm64-msvc@1.32.0:
|
||||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||||
@@ -1135,6 +1172,9 @@ packages:
|
|||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
|
muggle-string@0.4.1:
|
||||||
|
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
|
||||||
|
|
||||||
nanoid@3.3.11:
|
nanoid@3.3.11:
|
||||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
@@ -1165,6 +1205,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
path-browserify@1.0.1:
|
||||||
|
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||||
|
|
||||||
path-exists@4.0.0:
|
path-exists@4.0.0:
|
||||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1213,12 +1256,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
postcss@8.5.3:
|
postcss@8.5.13:
|
||||||
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
|
resolution: {integrity: sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
postcss@8.5.8:
|
postcss@8.5.3:
|
||||||
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
prelude-ls@1.2.1:
|
prelude-ls@1.2.1:
|
||||||
@@ -1269,8 +1312,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
|
resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
|
||||||
engines: {node: '>= 0.8.15'}
|
engines: {node: '>= 0.8.15'}
|
||||||
|
|
||||||
rolldown@1.0.0-rc.11:
|
rolldown@1.0.0-rc.17:
|
||||||
resolution: {integrity: sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==}
|
resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -1333,8 +1376,8 @@ packages:
|
|||||||
three@0.178.0:
|
three@0.178.0:
|
||||||
resolution: {integrity: sha512-ybFIB0+x8mz0wnZgSGy2MO/WCO6xZhQSZnmfytSPyNpM0sBafGRVhdaj+erYh5U+RhQOAg/eXqw5uVDiM2BjhQ==}
|
resolution: {integrity: sha512-ybFIB0+x8mz0wnZgSGy2MO/WCO6xZhQSZnmfytSPyNpM0sBafGRVhdaj+erYh5U+RhQOAg/eXqw5uVDiM2BjhQ==}
|
||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.16:
|
||||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
to-regex-range@5.0.1:
|
||||||
@@ -1369,8 +1412,8 @@ packages:
|
|||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
undici-types@6.21.0:
|
undici-types@7.16.0:
|
||||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||||
|
|
||||||
upath@2.0.1:
|
upath@2.0.1:
|
||||||
resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==}
|
resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==}
|
||||||
@@ -1393,14 +1436,14 @@ packages:
|
|||||||
vue: ^3.0.0
|
vue: ^3.0.0
|
||||||
vuetify: ^3.0.0
|
vuetify: ^3.0.0
|
||||||
|
|
||||||
vite@8.0.2:
|
vite@8.0.10:
|
||||||
resolution: {integrity: sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==}
|
resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/node': ^20.19.0 || >=22.12.0
|
'@types/node': ^20.19.0 || >=22.12.0
|
||||||
'@vitejs/devtools': ^0.1.0
|
'@vitejs/devtools': ^0.1.0
|
||||||
esbuild: ^0.27.0
|
esbuild: ^0.27.0 || ^0.28.0
|
||||||
jiti: '>=1.21.0'
|
jiti: '>=1.21.0'
|
||||||
less: ^4.0.0
|
less: ^4.0.0
|
||||||
sass: ^1.70.0
|
sass: ^1.70.0
|
||||||
@@ -1436,6 +1479,9 @@ packages:
|
|||||||
yaml:
|
yaml:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
vscode-uri@3.1.0:
|
||||||
|
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
||||||
|
|
||||||
vue-eslint-parser@10.1.3:
|
vue-eslint-parser@10.1.3:
|
||||||
resolution: {integrity: sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==}
|
resolution: {integrity: sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -1447,6 +1493,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.2.0
|
vue: ^3.2.0
|
||||||
|
|
||||||
|
vue-tsc@3.2.5:
|
||||||
|
resolution: {integrity: sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=5.0.0'
|
||||||
|
|
||||||
vue3-virtual-scroll-list@0.2.1:
|
vue3-virtual-scroll-list@0.2.1:
|
||||||
resolution: {integrity: sha512-G4KxITUOy9D4ro15zOp40D6ogmMefzjIyMsBKqN3xGbV1P6dlKYMx+BBXCKm3Nr/6iipcUKM272Sh2AJRyWMyQ==}
|
resolution: {integrity: sha512-G4KxITUOy9D4ro15zOp40D6ogmMefzjIyMsBKqN3xGbV1P6dlKYMx+BBXCKm3Nr/6iipcUKM272Sh2AJRyWMyQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1515,18 +1567,18 @@ snapshots:
|
|||||||
|
|
||||||
'@dimforge/rapier3d-compat@0.12.0': {}
|
'@dimforge/rapier3d-compat@0.12.0': {}
|
||||||
|
|
||||||
'@emnapi/core@1.9.1':
|
'@emnapi/core@1.10.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/wasi-threads': 1.2.0
|
'@emnapi/wasi-threads': 1.2.1
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@emnapi/runtime@1.9.1':
|
'@emnapi/runtime@1.10.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@emnapi/wasi-threads@1.2.0':
|
'@emnapi/wasi-threads@1.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
@@ -1596,10 +1648,10 @@ snapshots:
|
|||||||
|
|
||||||
'@msgpack/msgpack@3.1.2': {}
|
'@msgpack/msgpack@3.1.2': {}
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.1.1':
|
'@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.9.1
|
'@emnapi/core': 1.10.0
|
||||||
'@emnapi/runtime': 1.9.1
|
'@emnapi/runtime': 1.10.0
|
||||||
'@tybys/wasm-util': 0.10.1
|
'@tybys/wasm-util': 0.10.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -1615,7 +1667,7 @@ snapshots:
|
|||||||
'@nodelib/fs.scandir': 2.1.5
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
fastq: 1.19.1
|
fastq: 1.19.1
|
||||||
|
|
||||||
'@oxc-project/types@0.122.0': {}
|
'@oxc-project/types@0.127.0': {}
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.1':
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -1684,56 +1736,58 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
playwright: 1.56.1
|
playwright: 1.56.1
|
||||||
|
|
||||||
'@rolldown/binding-android-arm64@1.0.0-rc.11':
|
'@rolldown/binding-android-arm64@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-darwin-arm64@1.0.0-rc.11':
|
'@rolldown/binding-darwin-arm64@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-darwin-x64@1.0.0-rc.11':
|
'@rolldown/binding-darwin-x64@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-freebsd-x64@1.0.0-rc.11':
|
'@rolldown/binding-freebsd-x64@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.11':
|
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11':
|
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.11':
|
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.11':
|
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.11':
|
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.11':
|
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-linux-x64-musl@1.0.0-rc.11':
|
'@rolldown/binding-linux-x64-musl@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-openharmony-arm64@1.0.0-rc.11':
|
'@rolldown/binding-openharmony-arm64@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-wasm32-wasi@1.0.0-rc.11':
|
'@rolldown/binding-wasm32-wasi@1.0.0-rc.17':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@napi-rs/wasm-runtime': 1.1.1
|
'@emnapi/core': 1.10.0
|
||||||
|
'@emnapi/runtime': 1.10.0
|
||||||
|
'@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11':
|
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.11':
|
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.17':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.19': {}
|
'@rolldown/pluginutils@1.0.0-rc.13': {}
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-rc.11': {}
|
'@rolldown/pluginutils@1.0.0-rc.17': {}
|
||||||
|
|
||||||
'@tweenjs/tween.js@23.1.3': {}
|
'@tweenjs/tween.js@23.1.3': {}
|
||||||
|
|
||||||
@@ -1746,9 +1800,9 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/node@22.15.14':
|
'@types/node@24.12.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 7.16.0
|
||||||
|
|
||||||
'@types/raf@3.4.3':
|
'@types/raf@3.4.3':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -1847,12 +1901,24 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.32.0
|
'@typescript-eslint/types': 8.32.0
|
||||||
eslint-visitor-keys: 4.2.0
|
eslint-visitor-keys: 4.2.0
|
||||||
|
|
||||||
'@vitejs/plugin-vue@6.0.0(vite@8.0.2(@types/node@22.15.14)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))':
|
'@vitejs/plugin-vue@6.0.6(vite@8.0.10(@types/node@24.12.2)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rolldown/pluginutils': 1.0.0-beta.19
|
'@rolldown/pluginutils': 1.0.0-rc.13
|
||||||
vite: 8.0.2(@types/node@22.15.14)(sass@1.89.2)
|
vite: 8.0.10(@types/node@24.12.2)(sass@1.89.2)
|
||||||
vue: 3.5.13(typescript@5.8.3)
|
vue: 3.5.13(typescript@5.8.3)
|
||||||
|
|
||||||
|
'@volar/language-core@2.4.28':
|
||||||
|
dependencies:
|
||||||
|
'@volar/source-map': 2.4.28
|
||||||
|
|
||||||
|
'@volar/source-map@2.4.28': {}
|
||||||
|
|
||||||
|
'@volar/typescript@2.4.28':
|
||||||
|
dependencies:
|
||||||
|
'@volar/language-core': 2.4.28
|
||||||
|
path-browserify: 1.0.1
|
||||||
|
vscode-uri: 3.1.0
|
||||||
|
|
||||||
'@vue/compiler-core@3.5.13':
|
'@vue/compiler-core@3.5.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.27.2
|
'@babel/parser': 7.27.2
|
||||||
@@ -1925,6 +1991,16 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@vue/language-core@3.2.5':
|
||||||
|
dependencies:
|
||||||
|
'@volar/language-core': 2.4.28
|
||||||
|
'@vue/compiler-dom': 3.5.13
|
||||||
|
'@vue/shared': 3.5.13
|
||||||
|
alien-signals: 3.1.2
|
||||||
|
muggle-string: 0.4.1
|
||||||
|
path-browserify: 1.0.1
|
||||||
|
picomatch: 4.0.4
|
||||||
|
|
||||||
'@vue/reactivity@3.5.13':
|
'@vue/reactivity@3.5.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/shared': 3.5.13
|
'@vue/shared': 3.5.13
|
||||||
@@ -1981,6 +2057,8 @@ snapshots:
|
|||||||
json-schema-traverse: 0.4.1
|
json-schema-traverse: 0.4.1
|
||||||
uri-js: 4.4.1
|
uri-js: 4.4.1
|
||||||
|
|
||||||
|
alien-signals@3.1.2: {}
|
||||||
|
|
||||||
ansi-styles@4.3.0:
|
ansi-styles@4.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-convert: 2.0.1
|
color-convert: 2.0.1
|
||||||
@@ -2501,6 +2579,8 @@ snapshots:
|
|||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
|
muggle-string@0.4.1: {}
|
||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
@@ -2533,6 +2613,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
callsites: 3.1.0
|
callsites: 3.1.0
|
||||||
|
|
||||||
|
path-browserify@1.0.1: {}
|
||||||
|
|
||||||
path-exists@4.0.0: {}
|
path-exists@4.0.0: {}
|
||||||
|
|
||||||
path-key@3.1.1: {}
|
path-key@3.1.1: {}
|
||||||
@@ -2568,13 +2650,13 @@ snapshots:
|
|||||||
cssesc: 3.0.0
|
cssesc: 3.0.0
|
||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
postcss@8.5.3:
|
postcss@8.5.13:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.11
|
nanoid: 3.3.11
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
postcss@8.5.8:
|
postcss@8.5.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.11
|
nanoid: 3.3.11
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
@@ -2613,26 +2695,26 @@ snapshots:
|
|||||||
rgbcolor@1.0.1:
|
rgbcolor@1.0.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
rolldown@1.0.0-rc.11:
|
rolldown@1.0.0-rc.17:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@oxc-project/types': 0.122.0
|
'@oxc-project/types': 0.127.0
|
||||||
'@rolldown/pluginutils': 1.0.0-rc.11
|
'@rolldown/pluginutils': 1.0.0-rc.17
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@rolldown/binding-android-arm64': 1.0.0-rc.11
|
'@rolldown/binding-android-arm64': 1.0.0-rc.17
|
||||||
'@rolldown/binding-darwin-arm64': 1.0.0-rc.11
|
'@rolldown/binding-darwin-arm64': 1.0.0-rc.17
|
||||||
'@rolldown/binding-darwin-x64': 1.0.0-rc.11
|
'@rolldown/binding-darwin-x64': 1.0.0-rc.17
|
||||||
'@rolldown/binding-freebsd-x64': 1.0.0-rc.11
|
'@rolldown/binding-freebsd-x64': 1.0.0-rc.17
|
||||||
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.11
|
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17
|
||||||
'@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.11
|
'@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17
|
||||||
'@rolldown/binding-linux-arm64-musl': 1.0.0-rc.11
|
'@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17
|
||||||
'@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.11
|
'@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17
|
||||||
'@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.11
|
'@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17
|
||||||
'@rolldown/binding-linux-x64-gnu': 1.0.0-rc.11
|
'@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17
|
||||||
'@rolldown/binding-linux-x64-musl': 1.0.0-rc.11
|
'@rolldown/binding-linux-x64-musl': 1.0.0-rc.17
|
||||||
'@rolldown/binding-openharmony-arm64': 1.0.0-rc.11
|
'@rolldown/binding-openharmony-arm64': 1.0.0-rc.17
|
||||||
'@rolldown/binding-wasm32-wasi': 1.0.0-rc.11
|
'@rolldown/binding-wasm32-wasi': 1.0.0-rc.17
|
||||||
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.11
|
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17
|
||||||
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.11
|
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17
|
||||||
|
|
||||||
run-parallel@1.2.0:
|
run-parallel@1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2686,7 +2768,7 @@ snapshots:
|
|||||||
|
|
||||||
three@0.178.0: {}
|
three@0.178.0: {}
|
||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.16:
|
||||||
dependencies:
|
dependencies:
|
||||||
fdir: 6.5.0(picomatch@4.0.4)
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
@@ -2719,7 +2801,7 @@ snapshots:
|
|||||||
|
|
||||||
typescript@5.8.3: {}
|
typescript@5.8.3: {}
|
||||||
|
|
||||||
undici-types@6.21.0: {}
|
undici-types@7.16.0: {}
|
||||||
|
|
||||||
upath@2.0.1: {}
|
upath@2.0.1: {}
|
||||||
|
|
||||||
@@ -2734,29 +2816,31 @@ snapshots:
|
|||||||
base64-arraybuffer: 1.0.2
|
base64-arraybuffer: 1.0.2
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
vite-plugin-vuetify@2.1.1(vite@8.0.2(@types/node@22.15.14)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3):
|
vite-plugin-vuetify@2.1.1(vite@8.0.10(@types/node@24.12.2)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vuetify/loader-shared': 2.1.0(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3)
|
'@vuetify/loader-shared': 2.1.0(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3)
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
upath: 2.0.1
|
upath: 2.0.1
|
||||||
vite: 8.0.2(@types/node@22.15.14)(sass@1.89.2)
|
vite: 8.0.10(@types/node@24.12.2)(sass@1.89.2)
|
||||||
vue: 3.5.13(typescript@5.8.3)
|
vue: 3.5.13(typescript@5.8.3)
|
||||||
vuetify: 3.8.3(typescript@5.8.3)(vite-plugin-vuetify@2.1.1)(vue@3.5.13(typescript@5.8.3))
|
vuetify: 3.8.3(typescript@5.8.3)(vite-plugin-vuetify@2.1.1)(vue@3.5.13(typescript@5.8.3))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
vite@8.0.2(@types/node@22.15.14)(sass@1.89.2):
|
vite@8.0.10(@types/node@24.12.2)(sass@1.89.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
lightningcss: 1.32.0
|
lightningcss: 1.32.0
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
postcss: 8.5.8
|
postcss: 8.5.13
|
||||||
rolldown: 1.0.0-rc.11
|
rolldown: 1.0.0-rc.17
|
||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.16
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 22.15.14
|
'@types/node': 24.12.2
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
sass: 1.89.2
|
sass: 1.89.2
|
||||||
|
|
||||||
|
vscode-uri@3.1.0: {}
|
||||||
|
|
||||||
vue-eslint-parser@10.1.3(eslint@9.31.0):
|
vue-eslint-parser@10.1.3(eslint@9.31.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
@@ -2775,6 +2859,12 @@ snapshots:
|
|||||||
'@vue/devtools-api': 6.6.4
|
'@vue/devtools-api': 6.6.4
|
||||||
vue: 3.5.13(typescript@5.8.3)
|
vue: 3.5.13(typescript@5.8.3)
|
||||||
|
|
||||||
|
vue-tsc@3.2.5(typescript@5.8.3):
|
||||||
|
dependencies:
|
||||||
|
'@volar/typescript': 2.4.28
|
||||||
|
'@vue/language-core': 3.2.5
|
||||||
|
typescript: 5.8.3
|
||||||
|
|
||||||
vue3-virtual-scroll-list@0.2.1(vue@3.5.13(typescript@5.8.3)):
|
vue3-virtual-scroll-list@0.2.1(vue@3.5.13(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.13(typescript@5.8.3)
|
vue: 3.5.13(typescript@5.8.3)
|
||||||
@@ -2794,7 +2884,7 @@ snapshots:
|
|||||||
vue: 3.5.13(typescript@5.8.3)
|
vue: 3.5.13(typescript@5.8.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
vite-plugin-vuetify: 2.1.1(vite@8.0.2(@types/node@22.15.14)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3)
|
vite-plugin-vuetify: 2.1.1(vite@8.0.10(@types/node@24.12.2)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3)
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
3
photon-client/pnpm-workspace.yaml
Normal file
3
photon-client/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
allowBuilds:
|
||||||
|
'@parcel/watcher': true
|
||||||
|
core-js: true
|
||||||
@@ -11,9 +11,10 @@ import { useTheme } from "vuetify";
|
|||||||
import { restoreThemeConfig } from "@/lib/ThemeManager";
|
import { restoreThemeConfig } from "@/lib/ThemeManager";
|
||||||
|
|
||||||
const is_demo = import.meta.env.MODE === "demo";
|
const is_demo = import.meta.env.MODE === "demo";
|
||||||
|
const backendHost = inject<string>("backendHost");
|
||||||
if (!is_demo) {
|
if (!is_demo) {
|
||||||
const websocket = new AutoReconnectingWebsocket(
|
const websocket = new AutoReconnectingWebsocket(
|
||||||
`ws://${inject("backendHost")}/websocket_data`,
|
`ws://${backendHost}/websocket_data`,
|
||||||
() => {
|
() => {
|
||||||
useStateStore().$patch({ backendConnected: true });
|
useStateStore().$patch({ backendConnected: true });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { PhotonTarget } from "@/types/PhotonTrackingTypes";
|
|||||||
// @ts-expect-error Intellisense says these conflict with the dynamic imports below
|
// @ts-expect-error Intellisense says these conflict with the dynamic imports below
|
||||||
import type { Mesh, Object3D, PerspectiveCamera, Scene, WebGLRenderer } from "three";
|
import type { Mesh, Object3D, PerspectiveCamera, Scene, WebGLRenderer } from "three";
|
||||||
// @ts-expect-error Intellisense says these conflict with the dynamic imports below
|
// @ts-expect-error Intellisense says these conflict with the dynamic imports below
|
||||||
import type { TrackballControls } from "three/examples/jsm/controls/TrackballControls";
|
import type { TrackballControls } from "three/examples/jsm/controls/TrackballControls.js";
|
||||||
import { onBeforeUnmount, onMounted, watchEffect } from "vue";
|
import { onBeforeUnmount, onMounted, watchEffect } from "vue";
|
||||||
const {
|
const {
|
||||||
ArrowHelper,
|
ArrowHelper,
|
||||||
@@ -20,7 +20,7 @@ const {
|
|||||||
Scene,
|
Scene,
|
||||||
WebGLRenderer
|
WebGLRenderer
|
||||||
} = await import("three");
|
} = await import("three");
|
||||||
const { TrackballControls } = await import("three/examples/jsm/controls/TrackballControls");
|
const { TrackballControls } = await import("three/examples/jsm/controls/TrackballControls.js");
|
||||||
|
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { createPerspectiveCamera } from "@/lib/ThreeUtils";
|
import { createPerspectiveCamera } from "@/lib/ThreeUtils";
|
||||||
@@ -213,14 +213,14 @@ onMounted(async () => {
|
|||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
};
|
};
|
||||||
|
|
||||||
drawTargets(props.targets);
|
await drawTargets(props.targets);
|
||||||
animate();
|
animate();
|
||||||
});
|
});
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener("resize", onWindowResize);
|
window.removeEventListener("resize", onWindowResize);
|
||||||
});
|
});
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
drawTargets(props.targets);
|
void drawTargets(props.targets);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeUnmount, onMounted, ref, watch, watchEffect, type Ref } from "vue";
|
import { onBeforeUnmount, onMounted, ref, watch, watchEffect, type Ref } from "vue";
|
||||||
|
import type {
|
||||||
|
Scene as SceneType,
|
||||||
|
PerspectiveCamera as PerspectiveCameraType,
|
||||||
|
WebGLRenderer as WebGLRendererType,
|
||||||
|
Group as GroupType,
|
||||||
|
Object3D
|
||||||
|
} from "three";
|
||||||
|
import type { TrackballControls as TrackballControlsType } from "three/examples/jsm/controls/TrackballControls.js";
|
||||||
const {
|
const {
|
||||||
AmbientLight,
|
AmbientLight,
|
||||||
AxesHelper,
|
AxesHelper,
|
||||||
@@ -16,7 +24,7 @@ const {
|
|||||||
SphereGeometry,
|
SphereGeometry,
|
||||||
WebGLRenderer
|
WebGLRenderer
|
||||||
} = await import("three");
|
} = await import("three");
|
||||||
const { TrackballControls } = await import("three/examples/jsm/controls/TrackballControls");
|
const { TrackballControls } = await import("three/examples/jsm/controls/TrackballControls.js");
|
||||||
import type { BoardObservation, CameraCalibrationResult } from "@/types/SettingTypes";
|
import type { BoardObservation, CameraCalibrationResult } from "@/types/SettingTypes";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
@@ -31,12 +39,12 @@ const props = defineProps<{
|
|||||||
title: string;
|
title: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let scene: Scene | undefined;
|
let scene: SceneType | undefined;
|
||||||
let camera: PerspectiveCamera | undefined;
|
let camera: PerspectiveCameraType | undefined;
|
||||||
let renderer: WebGLRenderer | undefined;
|
let renderer: WebGLRendererType | undefined;
|
||||||
let controls: TrackballControls | undefined;
|
let controls: TrackballControlsType | undefined;
|
||||||
|
|
||||||
const createChessboard = (obs: BoardObservation, cal: CameraCalibrationResult): Group => {
|
const createChessboard = (obs: BoardObservation, cal: CameraCalibrationResult): GroupType => {
|
||||||
const group = new Group();
|
const group = new Group();
|
||||||
|
|
||||||
if (obs.locationInImageSpace.length === 0) return group;
|
if (obs.locationInImageSpace.length === 0) return group;
|
||||||
@@ -194,9 +202,6 @@ const resetCamThirdPerson = () => {
|
|||||||
let animationFrameId: number | null = null;
|
let animationFrameId: number | null = null;
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// Grab data first off
|
|
||||||
fetchCalibrationData();
|
|
||||||
|
|
||||||
scene = new Scene();
|
scene = new Scene();
|
||||||
camera = new PerspectiveCamera(75, 800 / 800, 0.1, 1000);
|
camera = new PerspectiveCamera(75, 800 / 800, 0.1, 1000);
|
||||||
|
|
||||||
@@ -256,6 +261,10 @@ onMounted(async () => {
|
|||||||
|
|
||||||
controls.update();
|
controls.update();
|
||||||
|
|
||||||
|
// Fetch calibration only after the scene is ready so the initial draw
|
||||||
|
// can happen immediately when the data arrives.
|
||||||
|
await fetchCalibrationData();
|
||||||
|
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
if (!scene || !camera || !renderer || !controls) {
|
if (!scene || !camera || !renderer || !controls) {
|
||||||
return;
|
return;
|
||||||
@@ -318,7 +327,7 @@ if (import.meta.hot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
drawCalibration(calibrationData.value);
|
void drawCalibration(calibrationData.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -328,9 +337,9 @@ watch(
|
|||||||
props.resolution.height,
|
props.resolution.height,
|
||||||
useCameraSettingsStore().getCalibrationCoeffs(props.resolution)
|
useCameraSettingsStore().getCalibrationCoeffs(props.resolution)
|
||||||
],
|
],
|
||||||
() => {
|
async () => {
|
||||||
console.log("Camera or resolution changed, refetching calibration");
|
console.log("Camera or resolution changed, refetching calibration");
|
||||||
fetchCalibrationData();
|
await fetchCalibrationData();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, ref, onBeforeUnmount } from "vue";
|
import { computed, inject, onBeforeUnmount, useTemplateRef } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import type { StyleValue } from "vue";
|
import type { StyleValue } from "vue";
|
||||||
@@ -13,6 +13,7 @@ const props = defineProps<{
|
|||||||
cameraSettings: UiCameraConfiguration;
|
cameraSettings: UiCameraConfiguration;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const backendHostname = inject<string>("backendHostname");
|
||||||
const emptyStreamSrc = "//:0";
|
const emptyStreamSrc = "//:0";
|
||||||
const streamSrc = computed<string>(() => {
|
const streamSrc = computed<string>(() => {
|
||||||
const port = props.cameraSettings.stream[props.streamType === "Raw" ? "inputPort" : "outputPort"];
|
const port = props.cameraSettings.stream[props.streamType === "Raw" ? "inputPort" : "outputPort"];
|
||||||
@@ -21,7 +22,7 @@ const streamSrc = computed<string>(() => {
|
|||||||
return emptyStreamSrc;
|
return emptyStreamSrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `http://${inject("backendHostname")}:${port}/stream.mjpg`;
|
return `http://${backendHostname}:${port}/stream.mjpg`;
|
||||||
});
|
});
|
||||||
const streamDesc = computed<string>(() => `${props.streamType} Stream View`);
|
const streamDesc = computed<string>(() => `${props.streamType} Stream View`);
|
||||||
const streamStyle = computed<StyleValue>(() => {
|
const streamStyle = computed<StyleValue>(() => {
|
||||||
@@ -67,26 +68,26 @@ const handleCaptureClick = () => {
|
|||||||
const handlePopoutClick = () => {
|
const handlePopoutClick = () => {
|
||||||
window.open(streamSrc.value);
|
window.open(streamSrc.value);
|
||||||
};
|
};
|
||||||
const handleFullscreenRequest = () => {
|
const handleFullscreenRequest = async () => {
|
||||||
const stream = document.getElementById(props.id);
|
const stream = document.getElementById(props.id);
|
||||||
if (!stream) return;
|
if (!stream) return;
|
||||||
stream.requestFullscreen();
|
await stream.requestFullscreen();
|
||||||
};
|
};
|
||||||
|
|
||||||
const mjpgStream: any = ref(null);
|
const mjpgStream = useTemplateRef("mjpgStream");
|
||||||
|
|
||||||
const handleStreamError = () => {
|
const handleStreamError = () => {
|
||||||
if (streamSrc.value && streamSrc.value !== emptyStreamSrc) {
|
if (streamSrc.value && streamSrc.value !== emptyStreamSrc) {
|
||||||
console.error("Error loading stream:", streamSrc.value, " Trying again.");
|
console.error("Error loading stream:", streamSrc.value, " Trying again.");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
mjpgStream.value.src = streamSrc.value;
|
mjpgStream.value!.src = streamSrc.value;
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (!mjpgStream.value) return;
|
if (!mjpgStream.value) return;
|
||||||
mjpgStream.value["src"] = emptyStreamSrc;
|
mjpgStream.value.src = emptyStreamSrc;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, ref, watch } from "vue";
|
import { computed, inject, ref, useTemplateRef, watch } from "vue";
|
||||||
import { LogLevel, type LogMessage } from "@/types/SettingTypes";
|
import { LogLevel, type LogMessage } from "@/types/SettingTypes";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import LogEntry from "@/components/app/photon-log-entry.vue";
|
import LogEntry from "@/components/app/photon-log-entry.vue";
|
||||||
@@ -10,10 +10,10 @@ const backendHost = inject<string>("backendHost");
|
|||||||
const searchQuery = ref("");
|
const searchQuery = ref("");
|
||||||
const timeInput = ref<string>();
|
const timeInput = ref<string>();
|
||||||
const autoScroll = ref(true);
|
const autoScroll = ref(true);
|
||||||
const logList = ref();
|
const logList = useTemplateRef<InstanceType<typeof VirtualList>>("logList"); // this needs to be typed in the definition since vue has trouble inferring it
|
||||||
const logKeeps = ref(40);
|
const logKeeps = ref(40);
|
||||||
const exportLogFile = ref();
|
const exportLogFile = useTemplateRef("exportLogFile");
|
||||||
const selectedLogLevels = ref({
|
const selectedLogLevels = ref<Record<number, boolean>>({
|
||||||
[LogLevel.ERROR]: true,
|
[LogLevel.ERROR]: true,
|
||||||
[LogLevel.WARN]: true,
|
[LogLevel.WARN]: true,
|
||||||
[LogLevel.INFO]: true,
|
[LogLevel.INFO]: true,
|
||||||
@@ -48,7 +48,7 @@ watch(logs, () => {
|
|||||||
);
|
);
|
||||||
autoScroll.value = bottomOffset < 50;
|
autoScroll.value = bottomOffset < 50;
|
||||||
|
|
||||||
if (autoScroll.value) logList.value.scrollToBottom();
|
if (autoScroll.value) logList.value?.scrollToBottom();
|
||||||
});
|
});
|
||||||
|
|
||||||
const getLogLevelFromIndex = (index: number): string => {
|
const getLogLevelFromIndex = (index: number): string => {
|
||||||
@@ -56,7 +56,7 @@ const getLogLevelFromIndex = (index: number): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLogExport = () => {
|
const handleLogExport = () => {
|
||||||
exportLogFile.value.click();
|
exportLogFile.value?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogClear = () => {
|
const handleLogClear = () => {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const PromptRegular = import("@/assets/fonts/PromptRegular");
|
|||||||
const jspdf = import("jspdf");
|
const jspdf = import("jspdf");
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const MM_PER_INCH = 25.4;
|
||||||
|
|
||||||
const settingsValid = ref(true);
|
const settingsValid = ref(true);
|
||||||
|
|
||||||
@@ -38,6 +39,11 @@ const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
|
|||||||
|
|
||||||
if (!skip) {
|
if (!skip) {
|
||||||
const calib = useCameraSettingsStore().getCalibrationCoeffs(format.resolution);
|
const calib = useCameraSettingsStore().getCalibrationCoeffs(format.resolution);
|
||||||
|
|
||||||
|
// minPixelCount is the multiplied area of a 640x480 (the minimum for proper calibration) resolution
|
||||||
|
const minPixelCount = 640 * 480;
|
||||||
|
const resArea = format.resolution.width * format.resolution.height;
|
||||||
|
|
||||||
if (calib !== undefined) {
|
if (calib !== undefined) {
|
||||||
// Mean overall reprojection error
|
// Mean overall reprojection error
|
||||||
// Calculated as average of each observation's mean error
|
// Calculated as average of each observation's mean error
|
||||||
@@ -60,7 +66,10 @@ const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
|
|||||||
) *
|
) *
|
||||||
(180 / Math.PI);
|
(180 / Math.PI);
|
||||||
}
|
}
|
||||||
uniqueResolutions.push(format);
|
|
||||||
|
if (resArea >= minPixelCount) {
|
||||||
|
uniqueResolutions.push(format);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
uniqueResolutions.sort(
|
uniqueResolutions.sort(
|
||||||
@@ -81,18 +90,26 @@ const calibrationDivisors = computed(() =>
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const uniqueVideoResolutionString = ref("");
|
const uniqueVideoResolutionIndex = ref(getUniqueVideoResolutionStrings()?.[0]?.value);
|
||||||
|
|
||||||
// Use a watchEffect so the value is populated/reacts when the stores become available or update.
|
// Use a watchEffect so the value is populated/reacts when the stores become available or update.
|
||||||
// This avoids trying to index into an array that may be empty during page reload.
|
// This avoids trying to index into an array that may be empty during page reload.
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
const currentIndex = useCameraSettingsStore().currentVideoFormat.index ?? 0;
|
|
||||||
useStateStore().calibrationData.videoFormatIndex = currentIndex;
|
|
||||||
const names = useCameraSettingsStore().currentCameraSettings.validVideoFormats.map((f) =>
|
const names = useCameraSettingsStore().currentCameraSettings.validVideoFormats.map((f) =>
|
||||||
getResolutionString(f.resolution)
|
getResolutionString(f.resolution)
|
||||||
);
|
);
|
||||||
uniqueVideoResolutionString.value = names[currentIndex] ?? names[0] ?? "";
|
const currentFormatIndex = useCameraSettingsStore().currentVideoFormat.index ?? 0;
|
||||||
|
// Checks if the current resolution is present in the list of valid formats, if not defaults to the last index (which is usually the highest resolution)
|
||||||
|
const currentIndex =
|
||||||
|
getUniqueVideoResolutionStrings()
|
||||||
|
.map((x) => x.name)
|
||||||
|
.find((n) => n === names[currentFormatIndex]) !== undefined
|
||||||
|
? currentFormatIndex
|
||||||
|
: names.length - 1;
|
||||||
|
useStateStore().calibrationData.videoFormatIndex = currentIndex;
|
||||||
|
uniqueVideoResolutionIndex.value = currentIndex;
|
||||||
});
|
});
|
||||||
|
const dimensionUnit = ref<"in" | "mm">("in");
|
||||||
const squareSizeIn = ref(1);
|
const squareSizeIn = ref(1);
|
||||||
const markerSizeIn = ref(0.75);
|
const markerSizeIn = ref(0.75);
|
||||||
const patternWidth = ref(8);
|
const patternWidth = ref(8);
|
||||||
@@ -102,6 +119,28 @@ const useOldPattern = ref(false);
|
|||||||
const tagFamily = ref<CalibrationTagFamilies>(CalibrationTagFamilies.Dict_4X4_1000);
|
const tagFamily = ref<CalibrationTagFamilies>(CalibrationTagFamilies.Dict_4X4_1000);
|
||||||
const requestedVideoFormatIndex = ref(0);
|
const requestedVideoFormatIndex = ref(0);
|
||||||
|
|
||||||
|
const convertInchesToDisplay = (valueInInches: number) =>
|
||||||
|
dimensionUnit.value === "mm" ? valueInInches * MM_PER_INCH : valueInInches;
|
||||||
|
|
||||||
|
const convertDisplayToInches = (displayValue: number) =>
|
||||||
|
dimensionUnit.value === "mm" ? displayValue / MM_PER_INCH : displayValue;
|
||||||
|
|
||||||
|
const squareSize = computed({
|
||||||
|
get: () => convertInchesToDisplay(squareSizeIn.value),
|
||||||
|
set(value) {
|
||||||
|
squareSizeIn.value = convertDisplayToInches(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const markerSize = computed({
|
||||||
|
get: () => convertInchesToDisplay(markerSizeIn.value),
|
||||||
|
set(value) {
|
||||||
|
markerSizeIn.value = convertDisplayToInches(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const dimensionStep = computed(() => (dimensionUnit.value === "mm" ? 0.1 : 0.01));
|
||||||
|
|
||||||
// Emperical testing - with stack size limit of 1MB, we can handle at -least- 700k points
|
// Emperical testing - with stack size limit of 1MB, we can handle at -least- 700k points
|
||||||
const tooManyPoints = computed(
|
const tooManyPoints = computed(
|
||||||
() => useStateStore().calibrationData.imageCount * patternWidth.value * patternHeight.value > 700000
|
() => useStateStore().calibrationData.imageCount * patternWidth.value * patternHeight.value > 700000
|
||||||
@@ -176,7 +215,7 @@ const downloadCalibBoard = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isCalibrating = computed(
|
const isCalibrating = computed(
|
||||||
() => useCameraSettingsStore().currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.Calib3d
|
() => useCameraSettingsStore().currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.Calib3d.valueOf()
|
||||||
);
|
);
|
||||||
|
|
||||||
const startCalibration = () => {
|
const startCalibration = () => {
|
||||||
@@ -203,7 +242,7 @@ const endCalibration = () => {
|
|||||||
calibSuccess.value = undefined;
|
calibSuccess.value = undefined;
|
||||||
calibEndpointFail.value = false;
|
calibEndpointFail.value = false;
|
||||||
|
|
||||||
if (!useStateStore().calibrationData.hasEnoughImages) {
|
if (!hasEnoughImages.value) {
|
||||||
calibCanceled.value = true;
|
calibCanceled.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +270,10 @@ const endCalibration = () => {
|
|||||||
|
|
||||||
const drawAllSnapshots = ref(true);
|
const drawAllSnapshots = ref(true);
|
||||||
|
|
||||||
|
const bypassVal = ref(false);
|
||||||
|
const minCount = computed(() => (bypassVal.value ? 10 : 100));
|
||||||
|
const hasEnoughImages = computed(() => useStateStore().calibrationData.imageCount >= minCount.value);
|
||||||
|
|
||||||
const showCalDialog = ref(false);
|
const showCalDialog = ref(false);
|
||||||
const selectedVideoFormat = ref<VideoFormat | undefined>(undefined);
|
const selectedVideoFormat = ref<VideoFormat | undefined>(undefined);
|
||||||
const setSelectedVideoFormat = (format: VideoFormat) => {
|
const setSelectedVideoFormat = (format: VideoFormat) => {
|
||||||
@@ -295,23 +338,23 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
|||||||
>
|
>
|
||||||
<v-form v-model="settingsValid">
|
<v-form v-model="settingsValid">
|
||||||
<pv-select
|
<pv-select
|
||||||
v-model="uniqueVideoResolutionString"
|
v-model="uniqueVideoResolutionIndex"
|
||||||
label="Resolution"
|
label="Resolution"
|
||||||
:select-cols="8"
|
:select-cols="8"
|
||||||
:disabled="isCalibrating"
|
:disabled="isCalibrating"
|
||||||
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
|
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
|
||||||
:items="getUniqueVideoResolutionStrings()"
|
:items="getUniqueVideoResolutionStrings()"
|
||||||
@update:model-value="
|
@update:model-value="(value) => (useStateStore().calibrationData.videoFormatIndex = value)"
|
||||||
useStateStore().calibrationData.videoFormatIndex =
|
|
||||||
getUniqueVideoResolutionStrings().find((v) => v.value === $event)?.value || 0
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
<pv-select
|
<pv-select
|
||||||
v-model="boardType"
|
v-model="boardType"
|
||||||
label="Board Type"
|
label="Board Type"
|
||||||
tooltip="Calibration board pattern to use"
|
tooltip="Calibration board pattern to use"
|
||||||
:select-cols="8"
|
:select-cols="8"
|
||||||
:items="['Chessboard', 'ChArUco']"
|
:items="[
|
||||||
|
{ value: CalibrationBoardTypes.Charuco, name: 'ChArUco' },
|
||||||
|
{ value: CalibrationBoardTypes.Chessboard, name: 'Chessboard' }
|
||||||
|
]"
|
||||||
:disabled="isCalibrating"
|
:disabled="isCalibrating"
|
||||||
/>
|
/>
|
||||||
<v-alert
|
<v-alert
|
||||||
@@ -341,25 +384,43 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
|||||||
label="Tag Family"
|
label="Tag Family"
|
||||||
tooltip="Dictionary of ArUco markers on the ChArUco board"
|
tooltip="Dictionary of ArUco markers on the ChArUco board"
|
||||||
:select-cols="8"
|
:select-cols="8"
|
||||||
:items="['Dict_4X4_1000', 'Dict_5X5_1000', 'Dict_6X6_1000', 'Dict_7X7_1000']"
|
:items="[
|
||||||
|
{ value: CalibrationTagFamilies.Dict_4X4_1000, name: 'Dict_4X4_1000' },
|
||||||
|
{ value: CalibrationTagFamilies.Dict_5X5_1000, name: 'Dict_5X5_1000' },
|
||||||
|
{ value: CalibrationTagFamilies.Dict_6X6_1000, name: 'Dict_6X6_1000' },
|
||||||
|
{ value: CalibrationTagFamilies.Dict_7X7_1000, name: 'Dict_7X7_1000' }
|
||||||
|
]"
|
||||||
|
:disabled="isCalibrating"
|
||||||
|
/>
|
||||||
|
<pv-select
|
||||||
|
v-model="dimensionUnit"
|
||||||
|
label="Dimension Unit"
|
||||||
|
tooltip="Units used for pattern spacing and marker size inputs"
|
||||||
|
:select-cols="8"
|
||||||
|
:items="[
|
||||||
|
{ value: 'in', name: 'Inches' },
|
||||||
|
{ value: 'mm', name: 'Millimeters' }
|
||||||
|
]"
|
||||||
:disabled="isCalibrating"
|
:disabled="isCalibrating"
|
||||||
/>
|
/>
|
||||||
<pv-number-input
|
<pv-number-input
|
||||||
v-model="squareSizeIn"
|
v-model="squareSize"
|
||||||
label="Pattern Spacing (in)"
|
:label="`Pattern Spacing (${dimensionUnit})`"
|
||||||
tooltip="Spacing between pattern features in inches"
|
:tooltip="`Spacing between pattern features in ${dimensionUnit === 'mm' ? 'millimeters' : 'inches'}`"
|
||||||
:disabled="isCalibrating"
|
:disabled="isCalibrating"
|
||||||
:rules="[(v) => v > 0 || 'Size must be positive']"
|
:rules="[(v) => v > 0 || 'Size must be positive']"
|
||||||
:label-cols="4"
|
:label-cols="4"
|
||||||
|
:step="dimensionStep"
|
||||||
/>
|
/>
|
||||||
<pv-number-input
|
<pv-number-input
|
||||||
v-if="boardType === CalibrationBoardTypes.Charuco"
|
v-if="boardType === CalibrationBoardTypes.Charuco"
|
||||||
v-model="markerSizeIn"
|
v-model="markerSize"
|
||||||
label="Marker Size (in)"
|
:label="`Marker Size (${dimensionUnit})`"
|
||||||
tooltip="Size of the tag markers in inches must be smaller than pattern spacing"
|
:tooltip="`Size of the tag markers in ${dimensionUnit === 'mm' ? 'millimeters' : 'inches'}; must be smaller than pattern spacing`"
|
||||||
:disabled="isCalibrating"
|
:disabled="isCalibrating"
|
||||||
:rules="[(v) => v > 0 || 'Size must be positive']"
|
:rules="[(v) => v > 0 || 'Size must be positive']"
|
||||||
:label-cols="4"
|
:label-cols="4"
|
||||||
|
:step="dimensionStep"
|
||||||
/>
|
/>
|
||||||
<pv-number-input
|
<pv-number-input
|
||||||
v-model="patternWidth"
|
v-model="patternWidth"
|
||||||
@@ -483,11 +544,22 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
|||||||
<v-chip
|
<v-chip
|
||||||
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
|
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
|
||||||
label
|
label
|
||||||
:color="useStateStore().calibrationData.hasEnoughImages ? 'buttonPassive' : 'light-grey'"
|
:color="hasEnoughImages ? 'buttonPassive' : 'light-grey'"
|
||||||
>
|
>
|
||||||
Snapshots: {{ useStateStore().calibrationData.imageCount }} of at least
|
Snapshots: {{ useStateStore().calibrationData.imageCount }} of at least
|
||||||
{{ useStateStore().calibrationData.minimumImageCount }}
|
{{ minCount }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
|
<v-spacer />
|
||||||
|
<pv-switch
|
||||||
|
v-model="bypassVal"
|
||||||
|
color="error"
|
||||||
|
hide-details
|
||||||
|
class="ml-4"
|
||||||
|
label="Bypass minimum"
|
||||||
|
:label-cols="6"
|
||||||
|
:switch-cols="6"
|
||||||
|
tooltip="Bypass the minimum recommended amount of snapshots for a calibration. Should only be used for dev work or temporary tests not competitions. Still requires 10 images to calibrate."
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<v-btn
|
<v-btn
|
||||||
@@ -532,16 +604,14 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
|||||||
size="small"
|
size="small"
|
||||||
block
|
block
|
||||||
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
||||||
:color="useStateStore().calibrationData.hasEnoughImages ? 'buttonActive' : 'error'"
|
:color="hasEnoughImages ? 'buttonActive' : 'error'"
|
||||||
:disabled="!isCalibrating || !settingsValid"
|
:disabled="!isCalibrating || !settingsValid"
|
||||||
@click="endCalibration"
|
@click="endCalibration"
|
||||||
>
|
>
|
||||||
<v-icon start class="calib-btn-icon" size="large">
|
<v-icon start class="calib-btn-icon" size="large">
|
||||||
{{ useStateStore().calibrationData.hasEnoughImages ? "mdi-flag-checkered" : "mdi-flag-off-outline" }}
|
{{ hasEnoughImages ? "mdi-flag-checkered" : "mdi-flag-off-outline" }}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
<span class="calib-btn-label">{{
|
<span class="calib-btn-label">{{ hasEnoughImages ? "Finish Calibration" : "Cancel Calibration" }}</span>
|
||||||
useStateStore().calibrationData.hasEnoughImages ? "Finish Calibration" : "Cancel Calibration"
|
|
||||||
}}</span>
|
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PhotonCalibrationVisualizer from "@/components/app/photon-calibration-vis
|
|||||||
import type { CameraCalibrationResult, VideoFormat } from "@/types/SettingTypes";
|
import type { CameraCalibrationResult, VideoFormat } from "@/types/SettingTypes";
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref, useTemplateRef } from "vue";
|
||||||
import { axiosPost, getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
|
import { axiosPost, getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
@@ -13,28 +13,28 @@ const props = defineProps<{
|
|||||||
videoFormat: VideoFormat;
|
videoFormat: VideoFormat;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const confirmRemoveDialog = ref({ show: false, vf: props.videoFormat as VideoFormat });
|
const confirmRemoveDialog = ref({ show: false, vf: props.videoFormat });
|
||||||
|
|
||||||
const removeCalibration = (vf: VideoFormat) => {
|
const removeCalibration = async (vf: VideoFormat) => {
|
||||||
axiosPost("/calibration/remove", "delete a camera calibration", {
|
await axiosPost("/calibration/remove", "delete a camera calibration", {
|
||||||
cameraUniqueName: useCameraSettingsStore().currentCameraSettings.uniqueName,
|
cameraUniqueName: useCameraSettingsStore().currentCameraSettings.uniqueName,
|
||||||
width: vf.resolution.width,
|
width: vf.resolution.width,
|
||||||
height: vf.resolution.height
|
height: vf.resolution.height
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportCalibration = ref();
|
const exportCalibration = useTemplateRef("exportCalibration");
|
||||||
const openExportCalibrationPrompt = () => {
|
const openExportCalibrationPrompt = () => {
|
||||||
exportCalibration.value.click();
|
exportCalibration.value?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const importCalibrationFromPhotonJson = ref();
|
const importCalibrationFromPhotonJson = useTemplateRef("importCalibrationFromPhotonJson");
|
||||||
const openUploadPhotonCalibJsonPrompt = () => {
|
const openUploadPhotonCalibJsonPrompt = () => {
|
||||||
importCalibrationFromPhotonJson.value.click();
|
importCalibrationFromPhotonJson.value?.click();
|
||||||
};
|
};
|
||||||
const importCalibration = async () => {
|
const importCalibration = async () => {
|
||||||
const files = importCalibrationFromPhotonJson.value.files;
|
const files = importCalibrationFromPhotonJson.value?.files;
|
||||||
if (files.length === 0) return;
|
if (!files?.length) return;
|
||||||
const uploadedJson = files[0];
|
const uploadedJson = files[0];
|
||||||
|
|
||||||
const data = await parseJsonFile<CameraCalibrationResult>(uploadedJson);
|
const data = await parseJsonFile<CameraCalibrationResult>(uploadedJson);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const fetchSnapshots = () => {
|
|||||||
.get("/utils/getImageSnapshots")
|
.get("/utils/getImageSnapshots")
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
imgData.value = response.data.map(
|
imgData.value = response.data.map(
|
||||||
(snapshotData: { snapshotName: string; cameraUniqueName: string; snapshotData: string }, index) => {
|
(snapshotData: { snapshotName: string; cameraUniqueName: string; snapshotData: string }, index: number) => {
|
||||||
const metadata = getSnapshotMetadataFromName(snapshotData.snapshotName);
|
const metadata = getSnapshotMetadataFromName(snapshotData.snapshotName);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -99,7 +99,7 @@ const expanded = ref([]);
|
|||||||
<v-card-text class="pt-0">
|
<v-card-text class="pt-0">
|
||||||
<v-btn
|
<v-btn
|
||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
:variant="theme.global.current.value.dark ? 'outlined' : 'tonal'"
|
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
||||||
@click="fetchSnapshots"
|
@click="fetchSnapshots"
|
||||||
>
|
>
|
||||||
<v-icon start class="open-icon" size="large"> mdi-folder </v-icon>
|
<v-icon start class="open-icon" size="large"> mdi-folder </v-icon>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { computed, ref, watchEffect } from "vue";
|
|||||||
import { type CameraSettingsChangeRequest, ValidQuirks } from "@/types/SettingTypes";
|
import { type CameraSettingsChangeRequest, ValidQuirks } from "@/types/SettingTypes";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
import { axiosPost } from "@/lib/PhotonUtils";
|
import { axiosPost } from "@/lib/PhotonUtils";
|
||||||
|
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ const focusMode = computed<boolean>({
|
|||||||
get: () => useCameraSettingsStore().isFocusMode,
|
get: () => useCameraSettingsStore().isFocusMode,
|
||||||
set: (v) =>
|
set: (v) =>
|
||||||
useCameraSettingsStore().changeCurrentPipelineIndex(
|
useCameraSettingsStore().changeCurrentPipelineIndex(
|
||||||
v ? -3 : useCameraSettingsStore().currentCameraSettings.lastPipelineIndex || 0,
|
v ? WebsocketPipelineType.FocusCamera : useCameraSettingsStore().currentCameraSettings.lastPipelineIndex || 0,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -65,8 +66,8 @@ const settingsHaveChanged = (): boolean => {
|
|||||||
const a = tempSettingsStruct.value;
|
const a = tempSettingsStruct.value;
|
||||||
const b = useCameraSettingsStore().currentCameraSettings;
|
const b = useCameraSettingsStore().currentCameraSettings;
|
||||||
|
|
||||||
for (const q in ValidQuirks) {
|
for (const quirk of Object.values(ValidQuirks)) {
|
||||||
if (a.quirksToChange[q] !== b.cameraQuirks.quirks[q]) return true;
|
if (a.quirksToChange[quirk] !== b.cameraQuirks.quirks[quirk]) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.fov !== b.fov.value;
|
return a.fov !== b.fov.value;
|
||||||
@@ -120,12 +121,12 @@ watchEffect(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const showDeleteCamera = ref(false);
|
const showDeleteCamera = ref(false);
|
||||||
const deleteThisCamera = () => {
|
const deleteThisCamera = async () => {
|
||||||
axiosPost("/utils/nukeOneCamera", "delete this camera", {
|
await axiosPost("/utils/nukeOneCamera", "delete this camera", {
|
||||||
cameraUniqueName: useStateStore().currentCameraUniqueName
|
cameraUniqueName: useStateStore().currentCameraUniqueName
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const wrappedCameras = computed<SelectItem[]>(() =>
|
const wrappedCameras = computed<SelectItem<string>[]>(() =>
|
||||||
Object.keys(useCameraSettingsStore().cameras).map((cameraUniqueName) => ({
|
Object.keys(useCameraSettingsStore().cameras).map((cameraUniqueName) => ({
|
||||||
name: useCameraSettingsStore().cameras[cameraUniqueName].nickname,
|
name: useCameraSettingsStore().cameras[cameraUniqueName].nickname,
|
||||||
value: cameraUniqueName
|
value: cameraUniqueName
|
||||||
@@ -159,7 +160,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
|||||||
v-model="arducamSelectWrapper"
|
v-model="arducamSelectWrapper"
|
||||||
label="Arducam Model"
|
label="Arducam Model"
|
||||||
:items="[
|
:items="[
|
||||||
{ name: 'None', value: 0, disabled: true },
|
{ name: 'None', value: 0 },
|
||||||
{ name: 'OV9281', value: 1 },
|
{ name: 'OV9281', value: 1 },
|
||||||
{ name: 'OV2311', value: 2 },
|
{ name: 'OV2311', value: 2 },
|
||||||
{ name: 'OV9782', value: 3 }
|
{ name: 'OV9782', value: 3 }
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { PipelineType } from "@/types/PipelineTypes";
|
|||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ const driverMode = computed<boolean>({
|
|||||||
get: () => useCameraSettingsStore().isDriverMode,
|
get: () => useCameraSettingsStore().isDriverMode,
|
||||||
set: (v) =>
|
set: (v) =>
|
||||||
useCameraSettingsStore().changeCurrentPipelineIndex(
|
useCameraSettingsStore().changeCurrentPipelineIndex(
|
||||||
v ? -1 : useCameraSettingsStore().currentCameraSettings.lastPipelineIndex || 0,
|
v ? WebsocketPipelineType.DriverMode : useCameraSettingsStore().currentCameraSettings.lastPipelineIndex || 0,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,69 +1,51 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PVCameraInfo } from "@/types/SettingTypes";
|
import type { PVCameraInfo } from "@/types/SettingTypes";
|
||||||
|
|
||||||
const { camera } = defineProps({
|
const { camera } = defineProps<{ camera: PVCameraInfo }>();
|
||||||
camera: {
|
|
||||||
type: PVCameraInfo,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const cameraInfoFor: any = (camera: PVCameraInfo) => {
|
|
||||||
if (camera.PVUsbCameraInfo) {
|
|
||||||
return camera.PVUsbCameraInfo;
|
|
||||||
}
|
|
||||||
if (camera.PVCSICameraInfo) {
|
|
||||||
return camera.PVCSICameraInfo;
|
|
||||||
}
|
|
||||||
if (camera.PVFileCameraInfo) {
|
|
||||||
return camera.PVFileCameraInfo;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<v-table density="compact" :style="{ backgroundColor: 'var(--v-primary-base)' }">
|
<v-table density="compact" :style="{ backgroundColor: 'var(--v-primary-base)' }">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-if="cameraInfoFor(camera).dev !== undefined && cameraInfoFor(camera).dev !== null">
|
<tr v-if="'dev' in camera && camera.dev !== null">
|
||||||
<td>Device Number:</td>
|
<td>Device Number:</td>
|
||||||
<td>{{ cameraInfoFor(camera).dev }}</td>
|
<td>{{ camera.dev }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).name !== undefined && cameraInfoFor(camera).name !== null">
|
<tr v-if="'name' in camera && camera.name !== null">
|
||||||
<td>Name:</td>
|
<td>Name:</td>
|
||||||
<td>{{ cameraInfoFor(camera).name }}</td>
|
<td>{{ camera.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type:</td>
|
<td>Type:</td>
|
||||||
<td v-if="camera.PVUsbCameraInfo" class="mb-3">USB Camera</td>
|
<td v-if="camera.type === 'PVUsbCameraInfo'" class="mb-3">USB Camera</td>
|
||||||
<td v-else-if="camera.PVCSICameraInfo" class="mb-3">CSI Camera</td>
|
<td v-else-if="camera.type === 'PVCSICameraInfo'" class="mb-3">CSI Camera</td>
|
||||||
<td v-else-if="camera.PVFileCameraInfo" class="mb-3">File Camera</td>
|
<td v-else-if="camera.type === 'PVFileCameraInfo'" class="mb-3">File Camera</td>
|
||||||
<td v-else>Unidentified Camera Type</td>
|
<td v-else>Unidentified Camera Type</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).baseName !== undefined && cameraInfoFor(camera).baseName !== null">
|
<tr v-if="'baseName' in camera && camera.baseName !== null">
|
||||||
<td>Base Name:</td>
|
<td>Base Name:</td>
|
||||||
<td>{{ cameraInfoFor(camera).baseName }}</td>
|
<td>{{ camera.baseName }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).vendorId !== undefined && cameraInfoFor(camera).vendorId !== null">
|
<tr v-if="'vendorId' in camera && camera.vendorId !== null">
|
||||||
<td>Vendor ID:</td>
|
<td>Vendor ID:</td>
|
||||||
<td>{{ cameraInfoFor(camera).vendorId }}</td>
|
<td>{{ camera.vendorId }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).productId !== undefined && cameraInfoFor(camera).productId !== null">
|
<tr v-if="'productId' in camera && camera.productId !== null">
|
||||||
<td>Product ID:</td>
|
<td>Product ID:</td>
|
||||||
<td>{{ cameraInfoFor(camera).productId }}</td>
|
<td>{{ camera.productId }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).path !== undefined && cameraInfoFor(camera).path !== null">
|
<tr v-if="'path' in camera && camera.path !== null">
|
||||||
<td>Path:</td>
|
<td>Path:</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(camera).path }}</td>
|
<td style="word-break: break-all">{{ camera.path }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).uniquePath !== undefined && cameraInfoFor(camera).uniquePath !== null">
|
<tr v-if="'uniquePath' in camera && camera.uniquePath !== null">
|
||||||
<td>Unique Path:</td>
|
<td>Unique Path:</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(camera).uniquePath }}</td>
|
<td style="word-break: break-all">{{ camera.uniquePath }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="cameraInfoFor(camera).otherPaths !== undefined && cameraInfoFor(camera).otherPaths !== null">
|
<tr v-if="'otherPaths' in camera && camera.otherPaths !== null">
|
||||||
<td>Other Paths:</td>
|
<td>Other Paths:</td>
|
||||||
<td>{{ cameraInfoFor(camera).otherPaths }}</td>
|
<td>{{ camera.otherPaths }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PVCameraInfo } from "@/types/SettingTypes";
|
import type { PVCameraInfo } from "@/types/SettingTypes";
|
||||||
|
|
||||||
function isEqual<T>(a: T, b: T): boolean {
|
function isEqual<T>(a: T, b: T): boolean {
|
||||||
if (a === b) {
|
if (a === b) {
|
||||||
@@ -15,29 +15,7 @@ function isEqual<T>(a: T, b: T): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { saved, current } = defineProps({
|
const { saved, current } = defineProps<{ saved: PVCameraInfo; current: PVCameraInfo }>();
|
||||||
saved: {
|
|
||||||
type: PVCameraInfo,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
current: {
|
|
||||||
type: PVCameraInfo,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const cameraInfoFor = (camera: PVCameraInfo): any => {
|
|
||||||
if (camera.PVUsbCameraInfo) {
|
|
||||||
return camera.PVUsbCameraInfo;
|
|
||||||
}
|
|
||||||
if (camera.PVCSICameraInfo) {
|
|
||||||
return camera.PVCSICameraInfo;
|
|
||||||
}
|
|
||||||
if (camera.PVFileCameraInfo) {
|
|
||||||
return camera.PVFileCameraInfo;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -50,79 +28,70 @@ const cameraInfoFor = (camera: PVCameraInfo): any => {
|
|||||||
<th>Current</th>
|
<th>Current</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="cameraInfoFor(saved).dev !== undefined && cameraInfoFor(saved).dev !== null"
|
v-if="'dev' in saved && 'dev' in current && saved.dev !== null"
|
||||||
:class="cameraInfoFor(saved).dev !== cameraInfoFor(current).dev ? 'mismatch' : ''"
|
:class="saved.dev !== current.dev ? 'mismatch' : ''"
|
||||||
>
|
>
|
||||||
<td>Device Number:</td>
|
<td>Device Number:</td>
|
||||||
<td>{{ cameraInfoFor(saved).dev }}</td>
|
<td>{{ saved.dev }}</td>
|
||||||
<td>{{ cameraInfoFor(current).dev }}</td>
|
<td>{{ current.dev }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr v-if="saved.name !== null" :class="saved.name !== current.name ? 'mismatch' : ''">
|
||||||
v-if="cameraInfoFor(saved).name !== undefined && cameraInfoFor(saved).name !== null"
|
|
||||||
:class="cameraInfoFor(saved).name !== cameraInfoFor(current).name ? 'mismatch' : ''"
|
|
||||||
>
|
|
||||||
<td>Name:</td>
|
<td>Name:</td>
|
||||||
<td>{{ cameraInfoFor(saved).name }}</td>
|
<td>{{ saved.name }}</td>
|
||||||
<td>{{ cameraInfoFor(current).name }}</td>
|
<td>{{ current.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="cameraInfoFor(saved).baseName !== undefined && cameraInfoFor(saved).baseName !== null"
|
v-if="'baseName' in saved && 'baseName' in current && saved.baseName !== null"
|
||||||
:class="cameraInfoFor(saved).baseName !== cameraInfoFor(current).baseName ? 'mismatch' : ''"
|
:class="saved.baseName !== current.baseName ? 'mismatch' : ''"
|
||||||
>
|
>
|
||||||
<td>Base Name:</td>
|
<td>Base Name:</td>
|
||||||
<td>{{ cameraInfoFor(saved).baseName }}</td>
|
<td>{{ saved.baseName }}</td>
|
||||||
<td>{{ cameraInfoFor(current).baseName }}</td>
|
<td>{{ current.baseName }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type:</td>
|
<td>Type:</td>
|
||||||
<td v-if="saved.PVUsbCameraInfo" class="mb-3">USB Camera</td>
|
<td v-if="saved.type === 'PVUsbCameraInfo'" class="mb-3">USB Camera</td>
|
||||||
<td v-else-if="saved.PVCSICameraInfo" class="mb-3">CSI Camera</td>
|
<td v-else-if="saved.type === 'PVCSICameraInfo'" class="mb-3">CSI Camera</td>
|
||||||
<td v-else-if="saved.PVFileCameraInfo" class="mb-3">File Camera</td>
|
<td v-else-if="saved.type === 'PVFileCameraInfo'" class="mb-3">File Camera</td>
|
||||||
<td v-else>Unidentified Camera Type</td>
|
<td v-else>Unidentified Camera Type</td>
|
||||||
<td v-if="current.PVUsbCameraInfo" class="mb-3">USB Camera</td>
|
<td v-if="current.type === 'PVUsbCameraInfo'" class="mb-3">USB Camera</td>
|
||||||
<td v-else-if="current.PVCSICameraInfo" class="mb-3">CSI Camera</td>
|
<td v-else-if="current.type === 'PVCSICameraInfo'" class="mb-3">CSI Camera</td>
|
||||||
<td v-else-if="current.PVFileCameraInfo" class="mb-3">File Camera</td>
|
<td v-else-if="current.type === 'PVFileCameraInfo'" class="mb-3">File Camera</td>
|
||||||
<td v-else>Unidentified Camera Type</td>
|
<td v-else>Unidentified Camera Type</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="cameraInfoFor(saved).vendorId !== undefined && cameraInfoFor(saved).vendorId !== null"
|
v-if="'vendorId' in saved && 'vendorId' in current && saved.vendorId !== null"
|
||||||
:class="cameraInfoFor(saved).vendorId !== cameraInfoFor(current).vendorId ? 'mismatch' : ''"
|
:class="saved.vendorId !== current.vendorId ? 'mismatch' : ''"
|
||||||
>
|
>
|
||||||
<td>Vendor ID:</td>
|
<td>Vendor ID:</td>
|
||||||
<td>{{ cameraInfoFor(saved).vendorId }}</td>
|
<td>{{ saved.vendorId }}</td>
|
||||||
<td>{{ cameraInfoFor(current).vendorId }}</td>
|
<td>{{ current.vendorId }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="cameraInfoFor(saved).productId !== undefined && cameraInfoFor(saved).productId !== null"
|
v-if="'productId' in saved && 'productId' in current && saved.productId !== null"
|
||||||
:class="cameraInfoFor(saved).productId !== cameraInfoFor(current).productId ? 'mismatch' : ''"
|
:class="saved.productId !== current.productId ? 'mismatch' : ''"
|
||||||
>
|
>
|
||||||
<td>Product ID:</td>
|
<td>Product ID:</td>
|
||||||
<td>{{ cameraInfoFor(saved).productId }}</td>
|
<td>{{ saved.productId }}</td>
|
||||||
<td>{{ cameraInfoFor(current).productId }}</td>
|
<td>{{ current.productId }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr v-if="saved.path !== null" :class="saved.path !== current.path ? 'mismatch' : ''">
|
||||||
v-if="cameraInfoFor(saved).path !== undefined && cameraInfoFor(saved).path !== null"
|
|
||||||
:class="cameraInfoFor(saved).path !== cameraInfoFor(current).path ? 'mismatch' : ''"
|
|
||||||
>
|
|
||||||
<td>Path:</td>
|
<td>Path:</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(saved).path }}</td>
|
<td style="word-break: break-all">{{ saved.path }}</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(current).path }}</td>
|
<td style="word-break: break-all">{{ current.path }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr v-if="saved.uniquePath !== null" :class="saved.uniquePath !== current.uniquePath ? 'mismatch' : ''">
|
||||||
v-if="cameraInfoFor(saved).uniquePath !== undefined && cameraInfoFor(saved).uniquePath !== null"
|
|
||||||
:class="cameraInfoFor(saved).uniquePath !== cameraInfoFor(current).uniquePath ? 'mismatch' : ''"
|
|
||||||
>
|
|
||||||
<td>Unique Path:</td>
|
<td>Unique Path:</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(saved).uniquePath }}</td>
|
<td style="word-break: break-all">{{ saved.uniquePath }}</td>
|
||||||
<td style="word-break: break-all">{{ cameraInfoFor(current).uniquePath }}</td>
|
<td style="word-break: break-all">{{ current.uniquePath }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-if="cameraInfoFor(saved).otherPaths !== undefined && cameraInfoFor(saved).otherPaths !== null"
|
v-if="'otherPaths' in saved && 'otherPaths' in current && saved.otherPaths !== null"
|
||||||
:class="isEqual(cameraInfoFor(saved).otherPaths, cameraInfoFor(current).otherPaths) ? '' : 'mismatch'"
|
:class="isEqual(saved.otherPaths, current.otherPaths) ? '' : 'mismatch'"
|
||||||
>
|
>
|
||||||
<td>Other Paths:</td>
|
<td>Other Paths:</td>
|
||||||
<td>{{ cameraInfoFor(saved).otherPaths }}</td>
|
<td>{{ saved.otherPaths }}</td>
|
||||||
<td>{{ cameraInfoFor(current).otherPaths }}</td>
|
<td>{{ current.otherPaths }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const emit = defineEmits<{
|
|||||||
(e: "onEscape"): void;
|
(e: "onEscape"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const handleKeydown = ({ key }) => {
|
const handleKeydown = ({ key }: KeyboardEvent) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "Enter":
|
case "Enter":
|
||||||
// Explicitly check that all rule props return true
|
// Explicitly check that all rule props return true
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts" generic="T extends string | number">
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import TooltippedLabel from "@/components/common/pv-tooltipped-label.vue";
|
import TooltippedLabel from "@/components/common/pv-tooltipped-label.vue";
|
||||||
|
|
||||||
export interface SelectItem {
|
export interface SelectItem<TValue extends string | number> {
|
||||||
name: string | number;
|
name: string | number;
|
||||||
value: string | number;
|
value: TValue;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
const value = defineModel<string | number | undefined>({ required: true });
|
|
||||||
|
type SelectItems = SelectItem<T>[] | ReadonlyArray<T>;
|
||||||
|
const value = defineModel<T>({ required: true });
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -15,7 +17,7 @@ const props = withDefaults(
|
|||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
selectCols?: number;
|
selectCols?: number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
items: string[] | number[] | SelectItem[];
|
items: SelectItems;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
selectCols: 9,
|
selectCols: 9,
|
||||||
@@ -23,18 +25,20 @@ const props = withDefaults(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const areSelectItems = (items: SelectItems): items is SelectItem<T>[] => typeof items[0] === "object";
|
||||||
|
|
||||||
// Computed in case items changes
|
// Computed in case items changes
|
||||||
const items = computed<SelectItem[]>(() => {
|
const items = computed<SelectItem<T>[]>(() => {
|
||||||
// Trivial case for empty list; we have no data
|
// Trivial case for empty list; we have no data
|
||||||
if (!props.items.length) {
|
if (!props.items.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the prop exists on the object to infer object type
|
if (areSelectItems(props.items)) {
|
||||||
if ((props.items[0] as SelectItem).name) {
|
return props.items;
|
||||||
return props.items as SelectItem[];
|
|
||||||
}
|
}
|
||||||
return props.items.map((v, i) => ({ name: v, value: i }));
|
|
||||||
|
return props.items.map((item) => ({ name: item, value: item }));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -49,7 +53,7 @@ const items = computed<SelectItem[]>(() => {
|
|||||||
:items="items"
|
:items="items"
|
||||||
item-title="name"
|
item-title="name"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
item-props.disabled="disabled"
|
item-props
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details="auto"
|
hide-details="auto"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ const props = withDefaults(
|
|||||||
const emit = defineEmits<{ (e: "update:modelValue", value: number): void }>();
|
const emit = defineEmits<{ (e: "update:modelValue", value: number): void }>();
|
||||||
|
|
||||||
// Debounce function
|
// Debounce function
|
||||||
function debounce(func: (...args: any[]) => void, wait: number) {
|
function debounce(func: (...args: number[]) => void, wait: number) {
|
||||||
let timeout: ReturnType<typeof setTimeout>;
|
let timeout: ReturnType<typeof setTimeout>;
|
||||||
return function (...args: any[]) {
|
return function (...args: number[]) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
timeout = setTimeout(() => func(...args), wait);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,28 +13,6 @@ import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
|||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const changeCurrentCameraUniqueName = (cameraUniqueName: string) => {
|
|
||||||
useCameraSettingsStore().setCurrentCameraUniqueName(cameraUniqueName, true);
|
|
||||||
|
|
||||||
switch (useCameraSettingsStore().cameras[cameraUniqueName].pipelineSettings.pipelineType) {
|
|
||||||
case PipelineType.Reflective:
|
|
||||||
pipelineType.value = WebsocketPipelineType.Reflective;
|
|
||||||
break;
|
|
||||||
case PipelineType.ColoredShape:
|
|
||||||
pipelineType.value = WebsocketPipelineType.ColoredShape;
|
|
||||||
break;
|
|
||||||
case PipelineType.AprilTag:
|
|
||||||
pipelineType.value = WebsocketPipelineType.AprilTag;
|
|
||||||
break;
|
|
||||||
case PipelineType.Aruco:
|
|
||||||
pipelineType.value = WebsocketPipelineType.Aruco;
|
|
||||||
break;
|
|
||||||
case PipelineType.ObjectDetection:
|
|
||||||
pipelineType.value = WebsocketPipelineType.ObjectDetection;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Common RegEx used for naming both pipelines and cameras
|
// Common RegEx used for naming both pipelines and cameras
|
||||||
const nameChangeRegex = /^[A-Za-z0-9_ \-)(]*[A-Za-z0-9][A-Za-z0-9_ \-)(.]*$/;
|
const nameChangeRegex = /^[A-Za-z0-9_ \-)(]*[A-Za-z0-9][A-Za-z0-9_ \-)(.]*$/;
|
||||||
|
|
||||||
@@ -87,17 +65,17 @@ const cancelCameraNameEdit = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Pipeline Name Edit
|
// Pipeline Name Edit
|
||||||
const pipelineNamesWrapper = computed<SelectItem[]>(() => {
|
const pipelineNamesWrapper = computed(() => {
|
||||||
const pipelineNames = useCameraSettingsStore().pipelineNames.map((name, index) => ({ name: name, value: index }));
|
const pipelineNames = useCameraSettingsStore().pipelineNames.map((name, index) => ({ name: name, value: index }));
|
||||||
|
|
||||||
if (useCameraSettingsStore().isDriverMode) {
|
if (useCameraSettingsStore().isDriverMode) {
|
||||||
pipelineNames.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
|
pipelineNames.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode.valueOf() });
|
||||||
}
|
}
|
||||||
if (useCameraSettingsStore().isFocusMode) {
|
if (useCameraSettingsStore().isFocusMode) {
|
||||||
pipelineNames.push({ name: "Focus Mode", value: WebsocketPipelineType.FocusCamera });
|
pipelineNames.push({ name: "Focus Mode", value: WebsocketPipelineType.FocusCamera.valueOf() });
|
||||||
}
|
}
|
||||||
if (useCameraSettingsStore().isCalibrationMode) {
|
if (useCameraSettingsStore().isCalibrationMode) {
|
||||||
pipelineNames.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
|
pipelineNames.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d.valueOf() });
|
||||||
}
|
}
|
||||||
|
|
||||||
return pipelineNames;
|
return pipelineNames;
|
||||||
@@ -240,7 +218,7 @@ useCameraSettingsStore().$subscribe((mutation, state) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const wrappedCameras = computed<SelectItem[]>(() =>
|
const wrappedCameras = computed<SelectItem<string>[]>(() =>
|
||||||
Object.keys(useCameraSettingsStore().cameras).map((cameraUniqueName) => ({
|
Object.keys(useCameraSettingsStore().cameras).map((cameraUniqueName) => ({
|
||||||
name: useCameraSettingsStore().cameras[cameraUniqueName].nickname,
|
name: useCameraSettingsStore().cameras[cameraUniqueName].nickname,
|
||||||
value: cameraUniqueName
|
value: cameraUniqueName
|
||||||
@@ -257,7 +235,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
|||||||
v-model="useStateStore().currentCameraUniqueName"
|
v-model="useStateStore().currentCameraUniqueName"
|
||||||
label="Camera"
|
label="Camera"
|
||||||
:items="wrappedCameras"
|
:items="wrappedCameras"
|
||||||
@update:modelValue="changeCurrentCameraUniqueName"
|
@update:modelValue="pipelineType = useCameraSettingsStore().currentWebsocketPipelineType"
|
||||||
/>
|
/>
|
||||||
<pv-input
|
<pv-input
|
||||||
v-else
|
v-else
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import OutputTab from "@/components/dashboard/tabs/OutputTab.vue";
|
|||||||
import TargetsTab from "@/components/dashboard/tabs/TargetsTab.vue";
|
import TargetsTab from "@/components/dashboard/tabs/TargetsTab.vue";
|
||||||
import PnPTab from "@/components/dashboard/tabs/PnPTab.vue";
|
import PnPTab from "@/components/dashboard/tabs/PnPTab.vue";
|
||||||
import Map3DTab from "@/components/dashboard/tabs/Map3DTab.vue";
|
import Map3DTab from "@/components/dashboard/tabs/Map3DTab.vue";
|
||||||
|
import { PipelineType } from "@/types/PipelineTypes";
|
||||||
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
|
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
|
||||||
import { useDisplay } from "vuetify/lib/composables/display";
|
import { useDisplay, useTheme } from "vuetify";
|
||||||
import { useTheme } from "vuetify";
|
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -106,6 +106,17 @@ const tabGroups = computed<ConfigOption[][]>(() => {
|
|||||||
.filter((it) => it.length); // Remove empty tab groups
|
.filter((it) => it.length); // Remove empty tab groups
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This boolean is used to satisfy type-checking requirements.
|
||||||
|
const shouldUseWideSecondTabGroup = computed(() => {
|
||||||
|
const currentPipelineSettings = useCameraSettingsStore().currentPipelineSettings;
|
||||||
|
|
||||||
|
return (
|
||||||
|
(currentPipelineSettings.pipelineType === PipelineType.AprilTag ||
|
||||||
|
currentPipelineSettings.pipelineType === PipelineType.Aruco) &&
|
||||||
|
currentPipelineSettings.doMultiTarget
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const onBeforeTabUpdate = () => {
|
const onBeforeTabUpdate = () => {
|
||||||
// Force the current tab to the input tab on driver mode change
|
// Force the current tab to the input tab on driver mode change
|
||||||
if (useCameraSettingsStore().isDriverMode) {
|
if (useCameraSettingsStore().isDriverMode) {
|
||||||
@@ -129,7 +140,7 @@ const onBeforeTabUpdate = () => {
|
|||||||
<v-col
|
<v-col
|
||||||
v-for="(tabGroupData, tabGroupIndex) in tabGroups"
|
v-for="(tabGroupData, tabGroupIndex) in tabGroups"
|
||||||
:key="tabGroupIndex"
|
:key="tabGroupIndex"
|
||||||
:cols="tabGroupIndex === 1 && useCameraSettingsStore().currentPipelineSettings.doMultiTarget ? 7 : ''"
|
:cols="tabGroupIndex === 1 && shouldUseWideSecondTabGroup ? 7 : ''"
|
||||||
:class="tabGroupIndex !== tabGroups.length - 1 && 'pr-3'"
|
:class="tabGroupIndex !== tabGroups.length - 1 && 'pr-3'"
|
||||||
@vue:before-update="onBeforeTabUpdate"
|
@vue:before-update="onBeforeTabUpdate"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PipelineType } from "@/types/PipelineTypes";
|
import { PipelineType, type AprilTagPipelineSettings, AprilTagFamily } from "@/types/PipelineTypes";
|
||||||
import PvSelect from "@/components/common/pv-select.vue";
|
import PvSelect from "@/components/common/pv-select.vue";
|
||||||
import PvSlider from "@/components/common/pv-slider.vue";
|
import PvSlider from "@/components/common/pv-slider.vue";
|
||||||
import PvSwitch from "@/components/common/pv-switch.vue";
|
import PvSwitch from "@/components/common/pv-switch.vue";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import type { ActivePipelineSettings } from "@/types/PipelineTypes";
|
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { useDisplay } from "vuetify";
|
import { useDisplay } from "vuetify";
|
||||||
|
|
||||||
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
|
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
|
||||||
// Defer reference to store access method
|
// Defer reference to store access method
|
||||||
const currentPipelineSettings = computed<ActivePipelineSettings>(
|
const currentPipelineSettings = computed<AprilTagPipelineSettings>(
|
||||||
() => useCameraSettingsStore().currentPipelineSettings
|
() => useCameraSettingsStore().currentPipelineSettings as AprilTagPipelineSettings
|
||||||
);
|
);
|
||||||
const { mdAndDown } = useDisplay();
|
const { mdAndDown } = useDisplay();
|
||||||
const interactiveCols = computed(() =>
|
const interactiveCols = computed(() =>
|
||||||
@@ -25,7 +24,10 @@ const interactiveCols = computed(() =>
|
|||||||
<pv-select
|
<pv-select
|
||||||
v-model="currentPipelineSettings.tagFamily"
|
v-model="currentPipelineSettings.tagFamily"
|
||||||
label="Target family"
|
label="Target family"
|
||||||
:items="['AprilTag 36h11 (6.5in)', 'AprilTag 16h5 (6in)']"
|
:items="[
|
||||||
|
{ value: AprilTagFamily.Family36h11, name: 'AprilTag 36h11 (6.5in)' },
|
||||||
|
{ value: AprilTagFamily.Family16h5, name: 'AprilTag 16h5 (6in)' }
|
||||||
|
]"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ tagFamily: value }, false)"
|
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ tagFamily: value }, false)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { PipelineType, type ActivePipelineSettings } from "@/types/PipelineTypes";
|
import { PipelineType, type ArucoPipelineSettings, AprilTagFamily } from "@/types/PipelineTypes";
|
||||||
import PvSlider from "@/components/common/pv-slider.vue";
|
import PvSlider from "@/components/common/pv-slider.vue";
|
||||||
import PvSwitch from "@/components/common/pv-switch.vue";
|
import PvSwitch from "@/components/common/pv-switch.vue";
|
||||||
import PvRangeSlider from "@/components/common/pv-range-slider.vue";
|
import PvRangeSlider from "@/components/common/pv-range-slider.vue";
|
||||||
@@ -11,8 +11,8 @@ import { useDisplay } from "vuetify";
|
|||||||
|
|
||||||
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
|
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
|
||||||
// Defer reference to store access method
|
// Defer reference to store access method
|
||||||
const currentPipelineSettings = computed<ActivePipelineSettings>(
|
const currentPipelineSettings = computed<ArucoPipelineSettings>(
|
||||||
() => useCameraSettingsStore().currentPipelineSettings
|
() => useCameraSettingsStore().currentPipelineSettings as ArucoPipelineSettings
|
||||||
);
|
);
|
||||||
const { mdAndDown } = useDisplay();
|
const { mdAndDown } = useDisplay();
|
||||||
const interactiveCols = computed(() =>
|
const interactiveCols = computed(() =>
|
||||||
@@ -25,7 +25,10 @@ const interactiveCols = computed(() =>
|
|||||||
<pv-select
|
<pv-select
|
||||||
v-model="currentPipelineSettings.tagFamily"
|
v-model="currentPipelineSettings.tagFamily"
|
||||||
label="Target family"
|
label="Target family"
|
||||||
:items="['AprilTag Family 36h11', 'AprilTag Family 16h5']"
|
:items="[
|
||||||
|
{ value: AprilTagFamily.Family36h11, name: 'AprilTag 36h11 (6.5in)' },
|
||||||
|
{ value: AprilTagFamily.Family16h5, name: 'AprilTag 16h5 (6in)' }
|
||||||
|
]"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ tagFamily: value }, false)"
|
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ tagFamily: value }, false)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { type ActivePipelineSettings, PipelineType } from "@/types/PipelineTypes";
|
import {
|
||||||
|
type ActivePipelineSettings,
|
||||||
|
PipelineType,
|
||||||
|
ContourSortMode,
|
||||||
|
ContourTargetOrientation,
|
||||||
|
ContourGroupingMode,
|
||||||
|
ContourIntersection,
|
||||||
|
ContourShape
|
||||||
|
} from "@/types/PipelineTypes";
|
||||||
import PvRangeSlider from "@/components/common/pv-range-slider.vue";
|
import PvRangeSlider from "@/components/common/pv-range-slider.vue";
|
||||||
import PvSelect from "@/components/common/pv-select.vue";
|
import PvSelect from "@/components/common/pv-select.vue";
|
||||||
import PvSlider from "@/components/common/pv-slider.vue";
|
import PvSlider from "@/components/common/pv-slider.vue";
|
||||||
@@ -61,7 +69,10 @@ const interactiveCols = computed(() =>
|
|||||||
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOrientation"
|
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOrientation"
|
||||||
label="Target Orientation"
|
label="Target Orientation"
|
||||||
tooltip="Used to determine how to calculate target landmarks, as well as aspect ratio"
|
tooltip="Used to determine how to calculate target landmarks, as well as aspect ratio"
|
||||||
:items="['Portrait', 'Landscape']"
|
:items="[
|
||||||
|
{ value: ContourTargetOrientation.Portrait, name: 'Portrait' },
|
||||||
|
{ value: ContourTargetOrientation.Landscape, name: 'Landscape' }
|
||||||
|
]"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
|
||||||
@@ -72,7 +83,15 @@ const interactiveCols = computed(() =>
|
|||||||
label="Target Sort"
|
label="Target Sort"
|
||||||
tooltip="Chooses the sorting mode used to determine the 'best' targets to provide to user code"
|
tooltip="Chooses the sorting mode used to determine the 'best' targets to provide to user code"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
:items="['Largest', 'Smallest', 'Highest', 'Lowest', 'Rightmost', 'Leftmost', 'Centermost']"
|
:items="[
|
||||||
|
{ value: ContourSortMode.Largest, name: 'Largest' },
|
||||||
|
{ value: ContourSortMode.Smallest, name: 'Smallest' },
|
||||||
|
{ value: ContourSortMode.Highest, name: 'Highest' },
|
||||||
|
{ value: ContourSortMode.Lowest, name: 'Lowest' },
|
||||||
|
{ value: ContourSortMode.Rightmost, name: 'Rightmost' },
|
||||||
|
{ value: ContourSortMode.Leftmost, name: 'Leftmost' },
|
||||||
|
{ value: ContourSortMode.Centermost, name: 'Centermost' }
|
||||||
|
]"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourSortMode: value }, false)
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourSortMode: value }, false)
|
||||||
"
|
"
|
||||||
@@ -166,7 +185,11 @@ const interactiveCols = computed(() =>
|
|||||||
label="Target Grouping"
|
label="Target Grouping"
|
||||||
tooltip="Whether or not every two targets are paired with each other (good for e.g. 2019 targets)"
|
tooltip="Whether or not every two targets are paired with each other (good for e.g. 2019 targets)"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
:items="['Single', 'Dual', 'Two or More']"
|
:items="[
|
||||||
|
{ value: ContourGroupingMode.Single, name: 'Single' },
|
||||||
|
{ value: ContourGroupingMode.Dual, name: 'Dual' },
|
||||||
|
{ value: ContourGroupingMode.TwoOrMore, name: 'Two or More' }
|
||||||
|
]"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourGroupingMode: value }, false)
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourGroupingMode: value }, false)
|
||||||
"
|
"
|
||||||
@@ -176,7 +199,13 @@ const interactiveCols = computed(() =>
|
|||||||
label="Target Intersection"
|
label="Target Intersection"
|
||||||
tooltip="If target grouping is in dual mode it will use this dropdown to decide how targets are grouped with adjacent targets"
|
tooltip="If target grouping is in dual mode it will use this dropdown to decide how targets are grouped with adjacent targets"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
:items="['None', 'Up', 'Down', 'Left', 'Right']"
|
:items="[
|
||||||
|
{ value: ContourIntersection.None, name: 'None' },
|
||||||
|
{ value: ContourIntersection.Up, name: 'Up' },
|
||||||
|
{ value: ContourIntersection.Down, name: 'Down' },
|
||||||
|
{ value: ContourIntersection.Left, name: 'Left' },
|
||||||
|
{ value: ContourIntersection.Right, name: 'Right' }
|
||||||
|
]"
|
||||||
:disabled="useCameraSettingsStore().currentPipelineSettings.contourGroupingMode === 0"
|
:disabled="useCameraSettingsStore().currentPipelineSettings.contourGroupingMode === 0"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourIntersection: value }, false)
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourIntersection: value }, false)
|
||||||
@@ -189,7 +218,12 @@ const interactiveCols = computed(() =>
|
|||||||
label="Target Shape"
|
label="Target Shape"
|
||||||
tooltip="The shape of targets to look for"
|
tooltip="The shape of targets to look for"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
:items="['Circle', 'Polygon', 'Triangle', 'Quadrilateral']"
|
:items="[
|
||||||
|
{ value: ContourShape.Circle, name: 'Circle' },
|
||||||
|
{ value: ContourShape.Polygon, name: 'Polygon' },
|
||||||
|
{ value: ContourShape.Triangle, name: 'Triangle' },
|
||||||
|
{ value: ContourShape.Quadrilateral, name: 'Quadrilateral' }
|
||||||
|
]"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourShape: value }, false)
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourShape: value }, false)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ const getFilteredStreamDivisors = (): number[] => {
|
|||||||
};
|
};
|
||||||
const getNumberOfSkippedDivisors = () => streamDivisors.length - getFilteredStreamDivisors().length;
|
const getNumberOfSkippedDivisors = () => streamDivisors.length - getFilteredStreamDivisors().length;
|
||||||
|
|
||||||
const cameraResolutions = computed(() =>
|
const cameraResolutions = (): { name: string; value: number }[] =>
|
||||||
useCameraSettingsStore().currentCameraSettings.validVideoFormats.map(
|
useCameraSettingsStore().currentCameraSettings.validVideoFormats.map<{ name: string; value: number }>((f) => ({
|
||||||
(f) => `${getResolutionString(f.resolution)} at ${f.fps} FPS, ${f.pixelFormat}`
|
name: `${getResolutionString(f.resolution)} at ${f.fps} FPS, ${f.pixelFormat}`,
|
||||||
)
|
value: f.index || 0 // Index won't ever be undefined
|
||||||
);
|
}));
|
||||||
const handleResolutionChange = (value: number) => {
|
const handleResolutionChange = (value: number) => {
|
||||||
useCameraSettingsStore().changeCurrentPipelineSetting({ cameraVideoModeIndex: value }, false);
|
useCameraSettingsStore().changeCurrentPipelineSetting({ cameraVideoModeIndex: value }, false);
|
||||||
|
|
||||||
@@ -49,20 +49,23 @@ const handleResolutionChange = (value: number) => {
|
|||||||
const streamResolutions = computed(() => {
|
const streamResolutions = computed(() => {
|
||||||
const streamDivisors = getFilteredStreamDivisors();
|
const streamDivisors = getFilteredStreamDivisors();
|
||||||
const currentResolution = useCameraSettingsStore().currentVideoFormat.resolution;
|
const currentResolution = useCameraSettingsStore().currentVideoFormat.resolution;
|
||||||
return streamDivisors.map(
|
return streamDivisors.map((x, i) => ({
|
||||||
(x) =>
|
name: `${Math.floor(currentResolution.width / x)}x${Math.floor(currentResolution.height / x)}`,
|
||||||
`${getResolutionString({
|
value: i
|
||||||
width: Math.floor(currentResolution.width / x),
|
}));
|
||||||
height: Math.floor(currentResolution.height / x)
|
});
|
||||||
})}`
|
const currentStreamResolutionIndex = computed<number>({
|
||||||
);
|
get: () => {
|
||||||
|
const stored = useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor;
|
||||||
|
const skipped = getNumberOfSkippedDivisors();
|
||||||
|
return stored - skipped;
|
||||||
|
},
|
||||||
|
set: (index) => {
|
||||||
|
useCameraSettingsStore().changeCurrentPipelineSetting({
|
||||||
|
streamingFrameDivisor: index + getNumberOfSkippedDivisors()
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const handleStreamResolutionChange = (value: number) => {
|
|
||||||
useCameraSettingsStore().changeCurrentPipelineSetting(
|
|
||||||
{ streamingFrameDivisor: value + getNumberOfSkippedDivisors() },
|
|
||||||
false
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const { mdAndDown } = useDisplay();
|
const { mdAndDown } = useDisplay();
|
||||||
|
|
||||||
const interactiveCols = computed(() =>
|
const interactiveCols = computed(() =>
|
||||||
@@ -160,7 +163,7 @@ const interactiveCols = computed(() =>
|
|||||||
/>
|
/>
|
||||||
<pv-switch
|
<pv-switch
|
||||||
v-model="useCameraSettingsStore().currentPipelineSettings.blockForFrames"
|
v-model="useCameraSettingsStore().currentPipelineSettings.blockForFrames"
|
||||||
:disabled="!useCameraSettingsStore().currentCameraSettings.matchedCameraInfo.PVUsbCameraInfo"
|
:disabled="useCameraSettingsStore().currentCameraSettings.matchedCameraInfo.type !== 'PVUsbCameraInfo'"
|
||||||
label="Low Latency Mode"
|
label="Low Latency Mode"
|
||||||
:switch-cols="interactiveCols"
|
:switch-cols="interactiveCols"
|
||||||
tooltip="When enabled, USB cameras wait for the next camera frame for lowest latency. When disabled, uses the most recent available frame for higher FPS."
|
tooltip="When enabled, USB cameras wait for the next camera frame for lowest latency. When disabled, uses the most recent available frame for higher FPS."
|
||||||
@@ -182,17 +185,16 @@ const interactiveCols = computed(() =>
|
|||||||
v-model="useCameraSettingsStore().currentPipelineSettings.cameraVideoModeIndex"
|
v-model="useCameraSettingsStore().currentPipelineSettings.cameraVideoModeIndex"
|
||||||
label="Resolution"
|
label="Resolution"
|
||||||
tooltip="Resolution and FPS the camera should directly capture at"
|
tooltip="Resolution and FPS the camera should directly capture at"
|
||||||
:items="cameraResolutions"
|
:items="cameraResolutions()"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
@update:modelValue="(args) => handleResolutionChange(args)"
|
@update:modelValue="(args) => handleResolutionChange(args)"
|
||||||
/>
|
/>
|
||||||
<pv-select
|
<pv-select
|
||||||
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
|
v-model="currentStreamResolutionIndex"
|
||||||
label="Stream Resolution"
|
label="Stream Resolution"
|
||||||
tooltip="Resolution to which camera frames are downscaled for streaming to the dashboard"
|
tooltip="Resolution to which camera frames are downscaled for streaming to the dashboard"
|
||||||
:items="streamResolutions"
|
:items="streamResolutions"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
@update:modelValue="(args) => handleStreamResolutionChange(args)"
|
|
||||||
/>
|
/>
|
||||||
<pv-switch
|
<pv-switch
|
||||||
v-if="useCameraSettingsStore().isDriverMode"
|
v-if="useCameraSettingsStore().isDriverMode"
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { type ObjectDetectionPipelineSettings, PipelineType } from "@/types/PipelineTypes";
|
import {
|
||||||
|
type ObjectDetectionPipelineSettings,
|
||||||
|
PipelineType,
|
||||||
|
ContourSortMode,
|
||||||
|
ContourTargetOrientation
|
||||||
|
} from "@/types/PipelineTypes";
|
||||||
import PvSlider from "@/components/common/pv-slider.vue";
|
import PvSlider from "@/components/common/pv-slider.vue";
|
||||||
import PvSelect from "@/components/common/pv-select.vue";
|
import PvSelect, { type SelectItem } from "@/components/common/pv-select.vue";
|
||||||
import PvRangeSlider from "@/components/common/pv-range-slider.vue";
|
import PvRangeSlider from "@/components/common/pv-range-slider.vue";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
@@ -44,19 +49,19 @@ const supportedModels = computed<ObjectDetectionModelProperties[]>(() => {
|
|||||||
return availableModels.filter(isSupported);
|
return availableModels.filter(isSupported);
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedModel = computed({
|
const modelWrapper = computed<SelectItem<string>[]>(() =>
|
||||||
get: () => {
|
supportedModels.value.map((model) => ({
|
||||||
const currentModel = currentPipelineSettings.value.model;
|
name: model.nickname,
|
||||||
if (!currentModel) return undefined;
|
value: model.modelPath
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
const index = supportedModels.value.findIndex((model) => model.modelPath === currentModel.modelPath);
|
const selectedModel = computed<string>({
|
||||||
return index === -1 ? undefined : index;
|
get: () => currentPipelineSettings.value.model?.modelPath ?? "",
|
||||||
},
|
set: (value) => {
|
||||||
|
const model = supportedModels.value.find((supportedModel) => supportedModel.modelPath === value);
|
||||||
set: (v) => {
|
if (model) {
|
||||||
if (v !== undefined && v >= 0 && v < supportedModels.value.length) {
|
useCameraSettingsStore().changeCurrentPipelineSetting({ model }, true);
|
||||||
const newModel = supportedModels.value[v];
|
|
||||||
useCameraSettingsStore().changeCurrentPipelineSetting({ model: newModel }, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -69,7 +74,7 @@ const selectedModel = computed({
|
|||||||
label="Model"
|
label="Model"
|
||||||
tooltip="The model used to detect objects in the camera feed"
|
tooltip="The model used to detect objects in the camera feed"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
:items="supportedModels.map((model) => model.nickname)"
|
:items="modelWrapper"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<pv-slider
|
<pv-slider
|
||||||
@@ -123,14 +128,13 @@ const selectedModel = computed({
|
|||||||
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOrientation"
|
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOrientation"
|
||||||
label="Target Orientation"
|
label="Target Orientation"
|
||||||
tooltip="Used to determine how to calculate target landmarks, as well as aspect ratio"
|
tooltip="Used to determine how to calculate target landmarks, as well as aspect ratio"
|
||||||
:items="['Portrait', 'Landscape']"
|
:items="[
|
||||||
|
{ value: ContourTargetOrientation.Portrait, name: 'Portrait' },
|
||||||
|
{ value: ContourTargetOrientation.Landscape, name: 'Landscape' }
|
||||||
|
]"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
(value) =>
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
|
||||||
useCameraSettingsStore().changeCurrentPipelineSetting(
|
|
||||||
{ contourTargetOrientation: typeof value === 'string' ? Number(value) : value },
|
|
||||||
false
|
|
||||||
)
|
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<pv-select
|
<pv-select
|
||||||
@@ -138,13 +142,17 @@ const selectedModel = computed({
|
|||||||
label="Target Sort"
|
label="Target Sort"
|
||||||
tooltip="Chooses the sorting mode used to determine the 'best' targets to provide to user code"
|
tooltip="Chooses the sorting mode used to determine the 'best' targets to provide to user code"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
:items="['Largest', 'Smallest', 'Highest', 'Lowest', 'Rightmost', 'Leftmost', 'Centermost']"
|
:items="[
|
||||||
|
{ value: ContourSortMode.Largest, name: 'Largest' },
|
||||||
|
{ value: ContourSortMode.Smallest, name: 'Smallest' },
|
||||||
|
{ value: ContourSortMode.Highest, name: 'Highest' },
|
||||||
|
{ value: ContourSortMode.Lowest, name: 'Lowest' },
|
||||||
|
{ value: ContourSortMode.Rightmost, name: 'Rightmost' },
|
||||||
|
{ value: ContourSortMode.Leftmost, name: 'Leftmost' },
|
||||||
|
{ value: ContourSortMode.Centermost, name: 'Centermost' }
|
||||||
|
]"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
(value) =>
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourSortMode: value }, false)
|
||||||
useCameraSettingsStore().changeCurrentPipelineSetting(
|
|
||||||
{ contourSortMode: typeof value === 'string' ? Number(value) : value },
|
|
||||||
false
|
|
||||||
)
|
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PvSelect from "@/components/common/pv-select.vue";
|
import PvSelect from "@/components/common/pv-select.vue";
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { type ActivePipelineSettings, PipelineType, RobotOffsetPointMode } from "@/types/PipelineTypes";
|
import {
|
||||||
|
type ActivePipelineSettings,
|
||||||
|
PipelineType,
|
||||||
|
RobotOffsetPointMode,
|
||||||
|
ContourTargetOrientation,
|
||||||
|
ContourTargetOffsetPointEdge
|
||||||
|
} from "@/types/PipelineTypes";
|
||||||
import PvSwitch from "@/components/common/pv-switch.vue";
|
import PvSwitch from "@/components/common/pv-switch.vue";
|
||||||
import PvSlider from "@/components/common/pv-slider.vue";
|
import PvSlider from "@/components/common/pv-slider.vue";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
@@ -108,7 +114,13 @@ const interactiveCols = computed(() =>
|
|||||||
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOffsetPointEdge"
|
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOffsetPointEdge"
|
||||||
label="Target Offset Point"
|
label="Target Offset Point"
|
||||||
tooltip="Changes where the 'center' of the target is (used for calculating e.g. pitch and yaw)"
|
tooltip="Changes where the 'center' of the target is (used for calculating e.g. pitch and yaw)"
|
||||||
:items="['Center', 'Top', 'Bottom', 'Left', 'Right']"
|
:items="[
|
||||||
|
{ value: ContourTargetOffsetPointEdge.Center, name: 'Center' },
|
||||||
|
{ value: ContourTargetOffsetPointEdge.Top, name: 'Top' },
|
||||||
|
{ value: ContourTargetOffsetPointEdge.Bottom, name: 'Bottom' },
|
||||||
|
{ value: ContourTargetOffsetPointEdge.Left, name: 'Left' },
|
||||||
|
{ value: ContourTargetOffsetPointEdge.Right, name: 'Right' }
|
||||||
|
]"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOffsetPointEdge: value }, false)
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOffsetPointEdge: value }, false)
|
||||||
@@ -119,7 +131,10 @@ const interactiveCols = computed(() =>
|
|||||||
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOrientation"
|
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOrientation"
|
||||||
label="Target Orientation"
|
label="Target Orientation"
|
||||||
tooltip="Used to determine how to calculate target landmarks (e.g. the top, left, or bottom of the target)"
|
tooltip="Used to determine how to calculate target landmarks (e.g. the top, left, or bottom of the target)"
|
||||||
:items="['Portrait', 'Landscape']"
|
:items="[
|
||||||
|
{ value: ContourTargetOrientation.Portrait, name: 'Portrait' },
|
||||||
|
{ value: ContourTargetOrientation.Landscape, name: 'Landscape' }
|
||||||
|
]"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
|
||||||
@@ -129,7 +144,11 @@ const interactiveCols = computed(() =>
|
|||||||
v-model="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode"
|
v-model="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode"
|
||||||
label="Robot Offset Mode"
|
label="Robot Offset Mode"
|
||||||
tooltip="Used to add an arbitrary offset to the location of the targeting crosshair"
|
tooltip="Used to add an arbitrary offset to the location of the targeting crosshair"
|
||||||
:items="['None', 'Single Point', 'Dual Point']"
|
:items="[
|
||||||
|
{ value: RobotOffsetPointMode.None, name: 'None' },
|
||||||
|
{ value: RobotOffsetPointMode.Single, name: 'Single Point' },
|
||||||
|
{ value: RobotOffsetPointMode.Dual, name: 'Dual Point' }
|
||||||
|
]"
|
||||||
:select-cols="interactiveCols"
|
:select-cols="interactiveCols"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ offsetRobotOffsetMode: value }, false)
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ offsetRobotOffsetMode: value }, false)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
@ -0,0 +1,565 @@
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inject, computed, ref, watch } from "vue";
|
import { inject, computed, ref, watch, useTemplateRef } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||||
import PvSelect from "@/components/common/pv-select.vue";
|
import PvSelect from "@/components/common/pv-select.vue";
|
||||||
@@ -15,20 +14,20 @@ const theme = useTheme();
|
|||||||
|
|
||||||
const restartProgram = async () => {
|
const restartProgram = async () => {
|
||||||
if (await axiosPost("/utils/restartProgram", "restart PhotonVision")) {
|
if (await axiosPost("/utils/restartProgram", "restart PhotonVision")) {
|
||||||
forceReloadPage();
|
await forceReloadPage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const restartDevice = async () => {
|
const restartDevice = async () => {
|
||||||
if (await axiosPost("/utils/restartDevice", "restart the device")) {
|
if (await axiosPost("/utils/restartDevice", "restart the device")) {
|
||||||
forceReloadPage();
|
await forceReloadPage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const address = inject<string>("backendHost");
|
const address = inject<string>("backendHost");
|
||||||
|
|
||||||
const offlineUpdate = ref();
|
const offlineUpdate = useTemplateRef("offlineUpdate");
|
||||||
const openOfflineUpdatePrompt = () => {
|
const openOfflineUpdatePrompt = () => {
|
||||||
offlineUpdate.value.click();
|
offlineUpdate.value?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const offlineUpdateRegex = new RegExp("photonvision-((?:dev-)?v[\\w.-]+)-((?:linux|win|mac)\\w+)\\.jar");
|
const offlineUpdateRegex = new RegExp("photonvision-((?:dev-)?v[\\w.-]+)-((?:linux|win|mac)\\w+)\\.jar");
|
||||||
@@ -37,8 +36,8 @@ const majorVersionRegex = new RegExp("(?:dev-)?(\\d+)\\.\\d+\\.\\d+");
|
|||||||
const offlineUpdateDialog = ref({ show: false, confirmString: "" });
|
const offlineUpdateDialog = ref({ show: false, confirmString: "" });
|
||||||
|
|
||||||
const handleOfflineUpdateRequest = async () => {
|
const handleOfflineUpdateRequest = async () => {
|
||||||
const files = offlineUpdate.value.files;
|
const files = offlineUpdate.value?.files;
|
||||||
if (files.length === 0) return;
|
if (!files?.length) return;
|
||||||
|
|
||||||
const match = files[0].name.match(offlineUpdateRegex);
|
const match = files[0].name.match(offlineUpdateRegex);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
@@ -68,7 +67,7 @@ const handleOfflineUpdateRequest = async () => {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else if (versionMatch && !dev) {
|
} else if (versionMatch && !dev) {
|
||||||
handleOfflineUpdate(files[0]);
|
await handleOfflineUpdate(files[0]);
|
||||||
} else if (!versionMatch && !dev) {
|
} else if (!versionMatch && !dev) {
|
||||||
offlineUpdateDialog.value = {
|
offlineUpdateDialog.value = {
|
||||||
show: true,
|
show: true,
|
||||||
@@ -99,7 +98,7 @@ const handleOfflineUpdate = async (file: File) => {
|
|||||||
if (
|
if (
|
||||||
await axiosPost("/utils/offlineUpdate", "upload new software", formData, {
|
await axiosPost("/utils/offlineUpdate", "upload new software", formData, {
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
onUploadProgress: ({ progress }) => {
|
onUploadProgress: ({ progress }: { progress?: number }) => {
|
||||||
const uploadPercentage = (progress || 0) * 100.0;
|
const uploadPercentage = (progress || 0) * 100.0;
|
||||||
if (uploadPercentage < 99.5) {
|
if (uploadPercentage < 99.5) {
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
@@ -118,18 +117,18 @@ const handleOfflineUpdate = async (file: File) => {
|
|||||||
color: "secondary",
|
color: "secondary",
|
||||||
timeout: -1
|
timeout: -1
|
||||||
});
|
});
|
||||||
forceReloadPage();
|
await forceReloadPage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportLogFile = ref();
|
const exportLogFile = useTemplateRef("exportLogFile");
|
||||||
const openExportLogsPrompt = () => {
|
const openExportLogsPrompt = () => {
|
||||||
exportLogFile.value.click();
|
exportLogFile.value?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportSettings = ref();
|
const exportSettings = useTemplateRef("exportSettings");
|
||||||
const openExportSettingsPrompt = () => {
|
const openExportSettingsPrompt = () => {
|
||||||
exportSettings.value.click();
|
exportSettings.value?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ImportType {
|
enum ImportType {
|
||||||
@@ -141,10 +140,10 @@ enum ImportType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const showImportDialog = ref(false);
|
const showImportDialog = ref(false);
|
||||||
const importType = ref<ImportType | undefined>(undefined);
|
const importType = ref<ImportType>(ImportType.AllSettings);
|
||||||
const importFile = ref<File | null>(null);
|
const importFile = ref<File | null>(null);
|
||||||
|
|
||||||
const handleSettingsImport = () => {
|
const handleSettingsImport = async () => {
|
||||||
if (importType.value === undefined || importFile.value === null) return;
|
if (importType.value === undefined || importFile.value === null) return;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", importFile.value);
|
formData.append("data", importFile.value);
|
||||||
@@ -167,18 +166,18 @@ const handleSettingsImport = () => {
|
|||||||
settingsEndpoint = "";
|
settingsEndpoint = "";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
axiosPost(`/settings${settingsEndpoint}`, "import settings", formData, {
|
await axiosPost(`/settings${settingsEndpoint}`, "import settings", formData, {
|
||||||
headers: { "Content-Type": "multipart/form-data" }
|
headers: { "Content-Type": "multipart/form-data" }
|
||||||
});
|
});
|
||||||
showImportDialog.value = false;
|
showImportDialog.value = false;
|
||||||
importType.value = undefined;
|
importType.value = ImportType.AllSettings;
|
||||||
importFile.value = null;
|
importFile.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showFactoryReset = ref(false);
|
const showFactoryReset = ref(false);
|
||||||
const nukePhotonConfigDirectory = async () => {
|
const nukePhotonConfigDirectory = async () => {
|
||||||
if (await axiosPost("/utils/nukeConfigDirectory", "delete the config directory")) {
|
if (await axiosPost("/utils/nukeConfigDirectory", "delete the config directory")) {
|
||||||
forceReloadPage();
|
await forceReloadPage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -503,7 +502,7 @@ watch(metricsHistorySnapshot, () => {
|
|||||||
width="600"
|
width="600"
|
||||||
@update:modelValue="
|
@update:modelValue="
|
||||||
() => {
|
() => {
|
||||||
importType = undefined;
|
importType = ImportType.AllSettings;
|
||||||
importFile = null;
|
importFile = null;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -517,7 +516,13 @@ watch(metricsHistorySnapshot, () => {
|
|||||||
v-model="importType"
|
v-model="importType"
|
||||||
label="Type"
|
label="Type"
|
||||||
tooltip="Select the type of settings file you are trying to upload"
|
tooltip="Select the type of settings file you are trying to upload"
|
||||||
:items="['All Settings', 'Hardware Config', 'Hardware Settings', 'Network Config', 'Apriltag Layout']"
|
:items="[
|
||||||
|
{ value: ImportType.AllSettings, name: 'All Settings' },
|
||||||
|
{ value: ImportType.HardwareConfig, name: 'Hardware Config' },
|
||||||
|
{ value: ImportType.HardwareSettings, name: 'Hardware Settings' },
|
||||||
|
{ value: ImportType.NetworkConfig, name: 'Network Config' },
|
||||||
|
{ value: ImportType.ApriltagFieldLayout, name: 'AprilTag Field Layout' }
|
||||||
|
]"
|
||||||
:select-cols="10"
|
:select-cols="10"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
@@ -558,7 +563,9 @@ watch(metricsHistorySnapshot, () => {
|
|||||||
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
||||||
@click="
|
@click="
|
||||||
offlineUpdateDialog.show = false;
|
offlineUpdateDialog.show = false;
|
||||||
handleOfflineUpdate(offlineUpdate.files[0]);
|
if (offlineUpdate?.files?.length) {
|
||||||
|
handleOfflineUpdate(offlineUpdate.files[0]);
|
||||||
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<v-icon start class="open-icon" size="large"> mdi-upload </v-icon>
|
<v-icon start class="open-icon" size="large"> mdi-upload </v-icon>
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ const saveGeneralSettings = async () => {
|
|||||||
|
|
||||||
// Update the local settings cause the backend checked their validity. Assign is to deref value
|
// Update the local settings cause the backend checked their validity. Assign is to deref value
|
||||||
useSettingsStore().network = { ...useSettingsStore().network, ...Object.assign({}, tempSettingsStruct.value) };
|
useSettingsStore().network = { ...useSettingsStore().network, ...Object.assign({}, tempSettingsStruct.value) };
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
resetTempSettingsStruct();
|
resetTempSettingsStruct();
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
@@ -150,14 +151,11 @@ const saveGeneralSettings = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentNetworkInterfaceIndex = computed<number | undefined>({
|
const currentNetworkInterface = computed<string>({
|
||||||
get: () => {
|
get: () => useSettingsStore().network.networkManagerIface || "",
|
||||||
const index = useSettingsStore().networkInterfaceNames.indexOf(
|
set: (v) => {
|
||||||
useSettingsStore().network.networkManagerIface || ""
|
tempSettingsStruct.value.networkManagerIface = v;
|
||||||
);
|
}
|
||||||
return index === -1 ? undefined : index;
|
|
||||||
},
|
|
||||||
set: (v) => v && (tempSettingsStruct.value.networkManagerIface = useSettingsStore().networkInterfaceNames[v])
|
|
||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
@@ -256,7 +254,7 @@ watchEffect(() => {
|
|||||||
/>
|
/>
|
||||||
<pv-select
|
<pv-select
|
||||||
v-show="!useSettingsStore().network.networkingDisabled"
|
v-show="!useSettingsStore().network.networkingDisabled"
|
||||||
v-model="currentNetworkInterfaceIndex"
|
v-model="currentNetworkInterface"
|
||||||
label="NetworkManager interface"
|
label="NetworkManager interface"
|
||||||
:disabled="
|
:disabled="
|
||||||
!tempSettingsStruct.shouldManage ||
|
!tempSettingsStruct.shouldManage ||
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref, onBeforeUnmount, watch } from "vue";
|
import { onMounted, onBeforeUnmount, watch, useTemplateRef } from "vue";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
|
||||||
// Color - original (adjusted)
|
// Color - original (adjusted)
|
||||||
@@ -8,14 +8,14 @@ import { useTheme } from "vuetify";
|
|||||||
// green - 65, 181, 127 (r: 75, g: 209, b: 147)
|
// green - 65, 181, 127 (r: 75, g: 209, b: 147)
|
||||||
// red - 238, 102, 102 (r: 238, g: 102, b: 102)
|
// red - 238, 102, 102 (r: 238, g: 102, b: 102)
|
||||||
const colors = {
|
const colors = {
|
||||||
"blue-LightTheme": { r: 255, g: 216, b: 67 },
|
"blue-light": { r: 255, g: 216, b: 67 },
|
||||||
"blue-DarkTheme": { r: 92, g: 154, b: 255 },
|
"blue-dark": { r: 92, g: 154, b: 255 },
|
||||||
"purple-LightTheme": { r: 255, g: 216, b: 67 },
|
"purple-light": { r: 255, g: 216, b: 67 },
|
||||||
"purple-DarkTheme": { r: 167, g: 104, b: 196 },
|
"purple-dark": { r: 167, g: 104, b: 196 },
|
||||||
"red-LightTheme": { r: 255, g: 216, b: 67 },
|
"red-light": { r: 255, g: 216, b: 67 },
|
||||||
"red-DarkTheme": { r: 238, g: 102, b: 102 },
|
"red-dark": { r: 238, g: 102, b: 102 },
|
||||||
"green-LightTheme": { r: 255, g: 216, b: 67 },
|
"green-light": { r: 255, g: 216, b: 67 },
|
||||||
"green-DarkTheme": { r: 75, g: 209, b: 147 }
|
"green-dark": { r: 75, g: 209, b: 147 }
|
||||||
};
|
};
|
||||||
const DEFAULT_COLOR = "blue";
|
const DEFAULT_COLOR = "blue";
|
||||||
|
|
||||||
@@ -26,9 +26,13 @@ const typeLabels = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const chartRef = ref(null);
|
const chartRef = useTemplateRef("chartRef");
|
||||||
let chart: echarts.ECharts | null = null;
|
let chart: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
interface TooltipSeriesParam {
|
||||||
|
value: [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
const getOptions = (data: ChartData[] = []) => {
|
const getOptions = (data: ChartData[] = []) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
return {
|
return {
|
||||||
@@ -37,7 +41,7 @@ const getOptions = (data: ChartData[] = []) => {
|
|||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: "axis",
|
||||||
formatter: (params: any) => {
|
formatter: (params: TooltipSeriesParam[]) => {
|
||||||
const p = params[0];
|
const p = params[0];
|
||||||
const append = typeLabels[props.type];
|
const append = typeLabels[props.type];
|
||||||
const fmsLimitLabel = "FMS Limit - 7.000 Mb/s";
|
const fmsLimitLabel = "FMS Limit - 7.000 Mb/s";
|
||||||
@@ -80,12 +84,12 @@ const getOptions = (data: ChartData[] = []) => {
|
|||||||
min: now - 55 * 1000,
|
min: now - 55 * 1000,
|
||||||
axisLine: {
|
axisLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: theme.global.name.value === "LightTheme" ? "#aaa" : "#777"
|
color: theme.global.current.value.dark ? "#777" : "#aaa"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
align: "left",
|
align: "left",
|
||||||
color: theme.global.name.value === "LightTheme" ? "#fff" : "#ddd",
|
color: theme.global.current.value.dark ? "#ddd" : "#fff",
|
||||||
formatter: (value: number) => {
|
formatter: (value: number) => {
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
return date.toLocaleTimeString([], {
|
return date.toLocaleTimeString([], {
|
||||||
@@ -102,12 +106,12 @@ const getOptions = (data: ChartData[] = []) => {
|
|||||||
position: "right",
|
position: "right",
|
||||||
min:
|
min:
|
||||||
props.min ??
|
props.min ??
|
||||||
function (value) {
|
function (value: { min: number; max: number }) {
|
||||||
return Math.max(0, (value.min - 10) | 0);
|
return Math.max(0, (value.min - 10) | 0);
|
||||||
},
|
},
|
||||||
max:
|
max:
|
||||||
props.max ??
|
props.max ??
|
||||||
function (value) {
|
function (value: { min: number; max: number }) {
|
||||||
return (value.max + 10) | 0;
|
return (value.max + 10) | 0;
|
||||||
},
|
},
|
||||||
splitNumber: 2,
|
splitNumber: 2,
|
||||||
@@ -118,7 +122,7 @@ const getOptions = (data: ChartData[] = []) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: theme.global.name.value === "LightTheme" ? "#fff" : "#ddd"
|
color: theme.global.current.value.dark ? "#ddd" : "#fff"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
series: getSeries(data),
|
series: getSeries(data),
|
||||||
@@ -127,7 +131,7 @@ const getOptions = (data: ChartData[] = []) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getSeries = (data: ChartData[] = []) => {
|
const getSeries = (data: ChartData[] = []) => {
|
||||||
const color = colors[`${props.color ?? DEFAULT_COLOR}-${theme.global.name.value}`];
|
const color = colors[`${props.color ?? DEFAULT_COLOR}-${theme.global.current.value.dark ? "dark" : "light"}`];
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: "line",
|
type: "line",
|
||||||
@@ -188,10 +192,10 @@ interface ChartData {
|
|||||||
// Type options: "percentage", "temperature", "mb"
|
// Type options: "percentage", "temperature", "mb"
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: ChartData[];
|
data: ChartData[];
|
||||||
type: string;
|
type: keyof typeof typeLabels;
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
color?: string;
|
color?: "red" | "green" | "blue" | "purple";
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, inject } from "vue";
|
import { ref, computed, inject, useTemplateRef } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||||
import { type ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
import { type ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
||||||
@@ -46,7 +46,7 @@ const handleImport = async () => {
|
|||||||
if (
|
if (
|
||||||
await axiosPost("/objectdetection/import", "import an object detection model", formData, {
|
await axiosPost("/objectdetection/import", "import an object detection model", formData, {
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
onUploadProgress: ({ progress }) => {
|
onUploadProgress: ({ progress }: { progress?: number }) => {
|
||||||
const uploadPercentage = (progress || 0) * 100.0;
|
const uploadPercentage = (progress || 0) * 100.0;
|
||||||
if (uploadPercentage < 99.5) {
|
if (uploadPercentage < 99.5) {
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
@@ -74,20 +74,20 @@ const handleImport = async () => {
|
|||||||
importVersion.value = null;
|
importVersion.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteModel = (model: ObjectDetectionModelProperties) => {
|
const deleteModel = async (model: ObjectDetectionModelProperties) => {
|
||||||
axiosPost("/objectdetection/delete", "delete an object detection model", {
|
await axiosPost("/objectdetection/delete", "delete an object detection model", {
|
||||||
modelPath: model.modelPath
|
modelPath: model.modelPath
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renameModel = (model: ObjectDetectionModelProperties, newName: string) => {
|
const renameModel = async (model: ObjectDetectionModelProperties, newName: string) => {
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
message: "Renaming Object Detection Model...",
|
message: "Renaming Object Detection Model...",
|
||||||
color: "secondary",
|
color: "secondary",
|
||||||
timeout: -1
|
timeout: -1
|
||||||
});
|
});
|
||||||
|
|
||||||
axiosPost("/objectdetection/rename", "rename an object detection model", {
|
await axiosPost("/objectdetection/rename", "rename an object detection model", {
|
||||||
modelPath: model.modelPath,
|
modelPath: model.modelPath,
|
||||||
newName: newName
|
newName: newName
|
||||||
});
|
});
|
||||||
@@ -97,7 +97,7 @@ const renameModel = (model: ObjectDetectionModelProperties, newName: string) =>
|
|||||||
// Filters out models that are not supported by the current backend, and returns a flattened list.
|
// Filters out models that are not supported by the current backend, and returns a flattened list.
|
||||||
const supportedModels = computed(() => {
|
const supportedModels = computed(() => {
|
||||||
const { availableModels, supportedBackends } = useSettingsStore().general;
|
const { availableModels, supportedBackends } = useSettingsStore().general;
|
||||||
const isSupported = (model: any) => {
|
const isSupported = (model: ObjectDetectionModelProperties) => {
|
||||||
// Check if model's family is in the list of supported backends
|
// Check if model's family is in the list of supported backends
|
||||||
return supportedBackends.some((backend: string) => backend.toLowerCase() === model.family.toLowerCase());
|
return supportedBackends.some((backend: string) => backend.toLowerCase() === model.family.toLowerCase());
|
||||||
};
|
};
|
||||||
@@ -106,19 +106,19 @@ const supportedModels = computed(() => {
|
|||||||
return availableModels.filter(isSupported);
|
return availableModels.filter(isSupported);
|
||||||
});
|
});
|
||||||
|
|
||||||
const exportModels = ref();
|
const exportModels = useTemplateRef("exportModels");
|
||||||
const openExportPrompt = () => {
|
const openExportPrompt = () => {
|
||||||
exportModels.value.click();
|
exportModels.value?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportIndividualModel = ref();
|
const exportIndividualModel = useTemplateRef("exportIndividualModel");
|
||||||
const openExportIndividualModelPrompt = () => {
|
const openExportIndividualModelPrompt = () => {
|
||||||
exportIndividualModel.value.click();
|
exportIndividualModel.value?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const showNukeDialog = ref(false);
|
const showNukeDialog = ref(false);
|
||||||
const nukeModels = () => {
|
const nukeModels = async () => {
|
||||||
axiosPost("/objectdetection/nuke", "clear and reset object detection models");
|
await axiosPost("/objectdetection/nuke", "clear and reset object detection models");
|
||||||
};
|
};
|
||||||
|
|
||||||
const showBulkImportDialog = ref(false);
|
const showBulkImportDialog = ref(false);
|
||||||
@@ -132,7 +132,7 @@ const handleBulkImport = async () => {
|
|||||||
if (
|
if (
|
||||||
await axiosPost("/objectdetection/bulkimport", "import object detection models", formData, {
|
await axiosPost("/objectdetection/bulkimport", "import object detection models", formData, {
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
onUploadProgress: ({ progress }) => {
|
onUploadProgress: ({ progress }: { progress?: number }) => {
|
||||||
const uploadPercentage = (progress || 0) * 100.0;
|
const uploadPercentage = (progress || 0) * 100.0;
|
||||||
if (uploadPercentage < 99.5) {
|
if (uploadPercentage < 99.5) {
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
|
|||||||
@@ -40,18 +40,13 @@ export class AutoReconnectingWebsocket {
|
|||||||
* Send data over the websocket. This is a no-op if the websocket is not in the OPEN state.
|
* Send data over the websocket. This is a no-op if the websocket is not in the OPEN state.
|
||||||
*
|
*
|
||||||
* @param data data to send
|
* @param data data to send
|
||||||
* @param encodeData whether or not to encode the data using msgpack (defaults to true)
|
|
||||||
* @see isConnected
|
* @see isConnected
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
send(data, encodeData = true) {
|
send(data: unknown) {
|
||||||
// Only send data if the websocket is open
|
// Only send data if the websocket is open
|
||||||
if (this.isConnected()) {
|
if (this.isConnected()) {
|
||||||
if (encodeData) {
|
this.websocket?.send(encode(data));
|
||||||
this.websocket?.send(encode(data));
|
|
||||||
} else {
|
|
||||||
this.websocket?.send(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import type { Resolution } from "@/types/SettingTypes";
|
import type { Resolution } from "@/types/SettingTypes";
|
||||||
import axios from "axios";
|
import axios, { type AxiosRequestConfig } from "axios";
|
||||||
|
|
||||||
export const resolutionsAreEqual = (a: Resolution, b: Resolution) => {
|
export const resolutionsAreEqual = (a: Resolution, b: Resolution) => {
|
||||||
return a.height === b.height && a.width === b.width;
|
return a.height === b.height && a.width === b.width;
|
||||||
@@ -51,15 +51,16 @@ export const forceReloadPage = async () => {
|
|||||||
|
|
||||||
export const getResolutionString = (resolution: Resolution): string => `${resolution.width}x${resolution.height}`;
|
export const getResolutionString = (resolution: Resolution): string => `${resolution.width}x${resolution.height}`;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const parseJsonFile = async <T extends Record<string, any>>(file: File): Promise<T> => {
|
export const parseJsonFile = async <T extends Record<string, any>>(file: File): Promise<T> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const fileReader = new FileReader();
|
const fileReader = new FileReader();
|
||||||
fileReader.onload = (event) => {
|
fileReader.onload = (event) => {
|
||||||
const target: FileReader | null = event.target;
|
const target: FileReader | null = event.target;
|
||||||
if (target === null) reject();
|
if (target === null) reject(new Error("FileReader event target is null"));
|
||||||
else resolve(JSON.parse(target.result as string) as T);
|
else resolve(JSON.parse(target.result as string) as T);
|
||||||
};
|
};
|
||||||
fileReader.onerror = (error) => reject(error);
|
fileReader.onerror = () => reject(new Error("Error reading file"));
|
||||||
fileReader.readAsText(file);
|
fileReader.readAsText(file);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -73,7 +74,13 @@ export const parseJsonFile = async <T extends Record<string, any>>(file: File):
|
|||||||
* @param config Optional axios request configuration
|
* @param config Optional axios request configuration
|
||||||
* @returns A promise that resolves to true if the POST request is successful, or false if an error occurs.
|
* @returns A promise that resolves to true if the POST request is successful, or false if an error occurs.
|
||||||
*/
|
*/
|
||||||
export const axiosPost = async (url: string, description: string, data?: any, config?: any): Promise<boolean> => {
|
export const axiosPost = async (
|
||||||
|
url: string,
|
||||||
|
description: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
data?: any,
|
||||||
|
config?: AxiosRequestConfig
|
||||||
|
): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
await axios.post(url, data, config);
|
await axios.post(url, data, config);
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
@@ -81,6 +88,7 @@ export const axiosPost = async (url: string, description: string, data?: any, co
|
|||||||
color: "success"
|
color: "success"
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const runtimeMode: PhotonClientRuntimeMode = process.env.NODE_ENV as PhotonClien
|
|||||||
|
|
||||||
let backendHost: string;
|
let backendHost: string;
|
||||||
let backendHostname: string;
|
let backendHostname: string;
|
||||||
switch (runtimeMode as PhotonClientRuntimeMode) {
|
switch (runtimeMode) {
|
||||||
case "development":
|
case "development":
|
||||||
backendHost = `${location.hostname}:5800`;
|
backendHost = `${location.hostname}:5800`;
|
||||||
backendHostname = location.hostname;
|
backendHostname = location.hostname;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "vuetify/styles";
|
import "vuetify/styles";
|
||||||
import("@mdi/font/css/materialdesignicons.css");
|
void import("@mdi/font/css/materialdesignicons.css");
|
||||||
import type { ThemeDefinition } from "vuetify/lib/composables/theme";
|
import type { ThemeDefinition } from "vuetify";
|
||||||
import { createVuetify } from "vuetify";
|
import { createVuetify } from "vuetify";
|
||||||
|
|
||||||
const CommonColors = {
|
const CommonColors = {
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ interface StateStore {
|
|||||||
currentCameraUniqueName: string;
|
currentCameraUniqueName: string;
|
||||||
networkUsageHistory: NetworkUsageEntry[];
|
networkUsageHistory: NetworkUsageEntry[];
|
||||||
|
|
||||||
backendResults: Record<number, PipelineResult>;
|
// Key is a string, although often used as an index, because we need to reference using the camera unique name at times.
|
||||||
|
backendResults: Record<string, PipelineResult>;
|
||||||
multitagResultBuffer: Record<string, MultitagResult[]>;
|
multitagResultBuffer: Record<string, MultitagResult[]>;
|
||||||
|
|
||||||
colorPickingMode: boolean;
|
colorPickingMode: boolean;
|
||||||
@@ -39,8 +40,6 @@ interface StateStore {
|
|||||||
calibrationData: {
|
calibrationData: {
|
||||||
imageCount: number;
|
imageCount: number;
|
||||||
videoFormatIndex: number;
|
videoFormatIndex: number;
|
||||||
minimumImageCount: number;
|
|
||||||
hasEnoughImages: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
snackbarData: {
|
snackbarData: {
|
||||||
@@ -88,9 +87,7 @@ export const useStateStore = defineStore("state", {
|
|||||||
|
|
||||||
calibrationData: {
|
calibrationData: {
|
||||||
imageCount: 0,
|
imageCount: 0,
|
||||||
videoFormatIndex: 0,
|
videoFormatIndex: 0
|
||||||
minimumImageCount: 12,
|
|
||||||
hasEnoughImages: false
|
|
||||||
},
|
},
|
||||||
|
|
||||||
snackbarData: {
|
snackbarData: {
|
||||||
@@ -161,9 +158,7 @@ export const useStateStore = defineStore("state", {
|
|||||||
updateCalibrationStateValuesFromWebsocket(data: WebsocketCalibrationData) {
|
updateCalibrationStateValuesFromWebsocket(data: WebsocketCalibrationData) {
|
||||||
this.calibrationData = {
|
this.calibrationData = {
|
||||||
imageCount: data.count,
|
imageCount: data.count,
|
||||||
videoFormatIndex: data.videoModeIndex,
|
videoFormatIndex: data.videoModeIndex
|
||||||
minimumImageCount: data.minCount,
|
|
||||||
hasEnoughImages: data.hasEnough
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
updateDiscoveredCameras(data: VsmState) {
|
updateDiscoveredCameras(data: VsmState) {
|
||||||
|
|||||||
@@ -64,17 +64,14 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
pipelineNames(): string[] {
|
pipelineNames(): string[] {
|
||||||
return this.currentCameraSettings.pipelineNicknames;
|
return this.currentCameraSettings.pipelineNicknames;
|
||||||
},
|
},
|
||||||
currentPipelineName(): string {
|
|
||||||
return this.pipelineNames[useStateStore().currentCameraUniqueName];
|
|
||||||
},
|
|
||||||
isDriverMode(): boolean {
|
isDriverMode(): boolean {
|
||||||
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.DriverMode;
|
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.DriverMode.valueOf();
|
||||||
},
|
},
|
||||||
isCalibrationMode(): boolean {
|
isCalibrationMode(): boolean {
|
||||||
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.Calib3d;
|
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.Calib3d.valueOf();
|
||||||
},
|
},
|
||||||
isFocusMode(): boolean {
|
isFocusMode(): boolean {
|
||||||
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.FocusCamera;
|
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.FocusCamera.valueOf();
|
||||||
},
|
},
|
||||||
isCSICamera(): boolean {
|
isCSICamera(): boolean {
|
||||||
return this.currentCameraSettings.isCSICamera;
|
return this.currentCameraSettings.isCSICamera;
|
||||||
@@ -94,6 +91,9 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
fpsLimit(): number {
|
fpsLimit(): number {
|
||||||
return this.currentCameraSettings.fpsLimit;
|
return this.currentCameraSettings.fpsLimit;
|
||||||
},
|
},
|
||||||
|
isEnabled(): boolean {
|
||||||
|
return this.currentCameraSettings.isEnabled;
|
||||||
|
},
|
||||||
isConnected(): boolean {
|
isConnected(): boolean {
|
||||||
return this.currentCameraSettings.isConnected;
|
return this.currentCameraSettings.isConnected;
|
||||||
},
|
},
|
||||||
@@ -116,23 +116,20 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
inputPort: d.inputStreamPort,
|
inputPort: d.inputStreamPort,
|
||||||
outputPort: d.outputStreamPort
|
outputPort: d.outputStreamPort
|
||||||
},
|
},
|
||||||
validVideoFormats: Object.entries(d.videoFormatList)
|
validVideoFormats: d.videoFormatList.map((v, i) => ({
|
||||||
.sort(([firstKey], [secondKey]) => parseInt(firstKey) - parseInt(secondKey))
|
resolution: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
width: v.width,
|
||||||
.map<VideoFormat>(([k, v], i) => ({
|
height: v.height
|
||||||
resolution: {
|
},
|
||||||
width: v.width,
|
fps: v.fps,
|
||||||
height: v.height
|
pixelFormat: v.pixelFormat,
|
||||||
},
|
index: v.index || i,
|
||||||
fps: v.fps,
|
diagonalFOV: v.diagonalFOV,
|
||||||
pixelFormat: v.pixelFormat,
|
horizontalFOV: v.horizontalFOV,
|
||||||
index: v.index || i,
|
verticalFOV: v.verticalFOV,
|
||||||
diagonalFOV: v.diagonalFOV,
|
standardDeviation: v.standardDeviation,
|
||||||
horizontalFOV: v.horizontalFOV,
|
mean: v.mean
|
||||||
verticalFOV: v.verticalFOV,
|
})),
|
||||||
standardDeviation: v.standardDeviation,
|
|
||||||
mean: v.mean
|
|
||||||
})),
|
|
||||||
completeCalibrations: d.calibrations,
|
completeCalibrations: d.calibrations,
|
||||||
isCSICamera: d.isCSICamera,
|
isCSICamera: d.isCSICamera,
|
||||||
minExposureRaw: d.minExposureRaw,
|
minExposureRaw: d.minExposureRaw,
|
||||||
@@ -145,6 +142,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
maxWhiteBalanceTemp: d.maxWhiteBalanceTemp,
|
maxWhiteBalanceTemp: d.maxWhiteBalanceTemp,
|
||||||
matchedCameraInfo: d.matchedCameraInfo,
|
matchedCameraInfo: d.matchedCameraInfo,
|
||||||
fpsLimit: d.fpsLimit,
|
fpsLimit: d.fpsLimit,
|
||||||
|
isEnabled: d.isEnabled,
|
||||||
isConnected: d.isConnected,
|
isConnected: d.isConnected,
|
||||||
hasConnected: d.hasConnected,
|
hasConnected: d.hasConnected,
|
||||||
mismatch: d.mismatch
|
mismatch: d.mismatch
|
||||||
@@ -196,7 +194,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
addNewPipeline: [newPipelineName, pipelineType],
|
addNewPipeline: [newPipelineName, pipelineType],
|
||||||
cameraUniqueName: cameraUniqueName
|
cameraUniqueName: cameraUniqueName
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Modify the settings of the currently selected pipeline of the provided camera.
|
* Modify the settings of the currently selected pipeline of the provided camera.
|
||||||
@@ -220,15 +218,13 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
if (updateStore) {
|
if (updateStore) {
|
||||||
this.changePipelineSettingsInStore(settings, cameraUniqueName);
|
this.changePipelineSettingsInStore(settings, cameraUniqueName);
|
||||||
}
|
}
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
changePipelineSettingsInStore(
|
changePipelineSettingsInStore(
|
||||||
settings: Partial<ActivePipelineSettings>,
|
settings: Partial<ActivePipelineSettings>,
|
||||||
cameraUniqueName: string = useStateStore().currentCameraUniqueName
|
cameraUniqueName: string = useStateStore().currentCameraUniqueName
|
||||||
) {
|
) {
|
||||||
Object.entries(settings).forEach(([k, v]) => {
|
Object.assign(this.cameras[cameraUniqueName].pipelineSettings, settings);
|
||||||
this.cameras[cameraUniqueName].pipelineSettings[k] = v;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Change the nickname of the currently selected pipeline of the provided camera.
|
* Change the nickname of the currently selected pipeline of the provided camera.
|
||||||
@@ -249,7 +245,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
if (updateStore) {
|
if (updateStore) {
|
||||||
this.cameras[cameraUniqueName].pipelineSettings.pipelineNickname = newName;
|
this.cameras[cameraUniqueName].pipelineSettings.pipelineNickname = newName;
|
||||||
}
|
}
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Modify the Pipeline type of the currently selected pipeline of the provided camera. This overwrites the current pipeline's settings when the backend resets the current pipeline settings.
|
* Modify the Pipeline type of the currently selected pipeline of the provided camera. This overwrites the current pipeline's settings when the backend resets the current pipeline settings.
|
||||||
@@ -265,7 +261,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
pipelineType: type,
|
pipelineType: type,
|
||||||
cameraUniqueName: cameraUniqueName
|
cameraUniqueName: cameraUniqueName
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Change the index of the pipeline of the currently selected camera.
|
* Change the index of the pipeline of the currently selected camera.
|
||||||
@@ -285,21 +281,22 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
};
|
};
|
||||||
if (updateStore) {
|
if (updateStore) {
|
||||||
if (
|
if (
|
||||||
this.cameras[cameraUniqueName].currentPipelineIndex !== -1 &&
|
this.cameras[cameraUniqueName].currentPipelineIndex !== WebsocketPipelineType.DriverMode.valueOf() &&
|
||||||
this.cameras[cameraUniqueName].currentPipelineIndex !== -2
|
this.cameras[cameraUniqueName].currentPipelineIndex !== WebsocketPipelineType.Calib3d.valueOf() &&
|
||||||
|
this.cameras[cameraUniqueName].currentPipelineIndex !== WebsocketPipelineType.FocusCamera.valueOf()
|
||||||
) {
|
) {
|
||||||
this.cameras[cameraUniqueName].lastPipelineIndex = this.cameras[cameraUniqueName].currentPipelineIndex;
|
this.cameras[cameraUniqueName].lastPipelineIndex = this.cameras[cameraUniqueName].currentPipelineIndex;
|
||||||
}
|
}
|
||||||
this.cameras[cameraUniqueName].currentPipelineIndex = index;
|
this.cameras[cameraUniqueName].currentPipelineIndex = index;
|
||||||
}
|
}
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
setDriverMode(isDriverMode: boolean, cameraUniqueName: string = useStateStore().currentCameraUniqueName) {
|
setDriverMode(isDriverMode: boolean, cameraUniqueName: string = useStateStore().currentCameraUniqueName) {
|
||||||
const payload = {
|
const payload = {
|
||||||
driverMode: isDriverMode,
|
driverMode: isDriverMode,
|
||||||
cameraUniqueName: cameraUniqueName
|
cameraUniqueName: cameraUniqueName
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Change the currently selected pipeline of the provided camera.
|
* Change the currently selected pipeline of the provided camera.
|
||||||
@@ -311,7 +308,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
deleteCurrentPipeline: {},
|
deleteCurrentPipeline: {},
|
||||||
cameraUniqueName: cameraUniqueName
|
cameraUniqueName: cameraUniqueName
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Duplicate the pipeline at the provided index.
|
* Duplicate the pipeline at the provided index.
|
||||||
@@ -324,7 +321,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
duplicatePipeline: pipelineIndex,
|
duplicatePipeline: pipelineIndex,
|
||||||
cameraUniqueName: cameraUniqueName
|
cameraUniqueName: cameraUniqueName
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Change the currently set camera
|
* Change the currently set camera
|
||||||
@@ -339,7 +336,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
if (updateStore) {
|
if (updateStore) {
|
||||||
useStateStore().currentCameraUniqueName = cameraUniqueName;
|
useStateStore().currentCameraUniqueName = cameraUniqueName;
|
||||||
}
|
}
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Change the nickname of the provided camera.
|
* Change the nickname of the provided camera.
|
||||||
@@ -385,14 +382,12 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
const payload = {
|
const payload = {
|
||||||
startPnpCalibration: {
|
startPnpCalibration: {
|
||||||
count: stateCalibData.imageCount,
|
count: stateCalibData.imageCount,
|
||||||
minCount: stateCalibData.minimumImageCount,
|
|
||||||
hasEnough: stateCalibData.hasEnoughImages,
|
|
||||||
videoModeIndex: stateCalibData.videoFormatIndex,
|
videoModeIndex: stateCalibData.videoFormatIndex,
|
||||||
...calibrationInitData
|
...calibrationInitData
|
||||||
},
|
},
|
||||||
cameraUniqueName: cameraUniqueName
|
cameraUniqueName: cameraUniqueName
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* End the 3D calibration process for the provided camera.
|
* End the 3D calibration process for the provided camera.
|
||||||
@@ -424,7 +419,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
takeCalibrationSnapshot: true,
|
takeCalibrationSnapshot: true,
|
||||||
cameraUniqueName: cameraUniqueName
|
cameraUniqueName: cameraUniqueName
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Save a snapshot of the input frame of the camera.
|
* Save a snapshot of the input frame of the camera.
|
||||||
@@ -436,7 +431,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
saveInputSnapshot: true,
|
saveInputSnapshot: true,
|
||||||
cameraUniqueName: cameraUniqueName
|
cameraUniqueName: cameraUniqueName
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Save a snapshot of the output frame of the camera.
|
* Save a snapshot of the output frame of the camera.
|
||||||
@@ -448,7 +443,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
saveOutputSnapshot: true,
|
saveOutputSnapshot: true,
|
||||||
cameraUniqueName: cameraUniqueName
|
cameraUniqueName: cameraUniqueName
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Set the robot offset mode type.
|
* Set the robot offset mode type.
|
||||||
@@ -461,7 +456,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
robotOffsetPoint: type,
|
robotOffsetPoint: type,
|
||||||
cameraUniqueName: cameraUniqueName
|
cameraUniqueName: cameraUniqueName
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
},
|
},
|
||||||
getCalibrationCoeffs(
|
getCalibrationCoeffs(
|
||||||
resolution: Resolution,
|
resolution: Resolution,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { NetworkConnectionType } from "@/types/SettingTypes";
|
|||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import type { WebsocketSettingsUpdate } from "@/types/WebsocketDataTypes";
|
import type { WebsocketSettingsUpdate } from "@/types/WebsocketDataTypes";
|
||||||
|
import type { AprilTagFieldLayout } from "@/types/PhotonTrackingTypes";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
interface GeneralSettingsStore {
|
interface GeneralSettingsStore {
|
||||||
@@ -17,7 +18,7 @@ interface GeneralSettingsStore {
|
|||||||
network: NetworkSettings;
|
network: NetworkSettings;
|
||||||
lighting: LightingSettings;
|
lighting: LightingSettings;
|
||||||
metrics: MetricData;
|
metrics: MetricData;
|
||||||
currentFieldLayout;
|
currentFieldLayout: AprilTagFieldLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MetricsEntry {
|
interface MetricsEntry {
|
||||||
@@ -184,7 +185,7 @@ export const useSettingsStore = defineStore("settings", {
|
|||||||
const payload = {
|
const payload = {
|
||||||
enabledLEDPercentage: brightness
|
enabledLEDPercentage: brightness
|
||||||
};
|
};
|
||||||
useStateStore().websocket?.send(payload, true);
|
useStateStore().websocket?.send(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
|||||||
* The on-wire form of PipelineType.java (the enum is serialized with `ordinal()`)
|
* The on-wire form of PipelineType.java (the enum is serialized with `ordinal()`)
|
||||||
*/
|
*/
|
||||||
export enum PipelineType {
|
export enum PipelineType {
|
||||||
|
Calibration3d = 1,
|
||||||
DriverMode = 2,
|
DriverMode = 2,
|
||||||
Reflective = 3,
|
Reflective = 3,
|
||||||
ColoredShape = 4,
|
ColoredShape = 4,
|
||||||
@@ -35,18 +36,62 @@ export enum TargetModel {
|
|||||||
ReefscapeAlgae = 7
|
ReefscapeAlgae = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ContourSortMode {
|
||||||
|
Largest = 0,
|
||||||
|
Smallest = 1,
|
||||||
|
Highest = 2,
|
||||||
|
Lowest = 3,
|
||||||
|
Leftmost = 4,
|
||||||
|
Rightmost = 5,
|
||||||
|
Centermost = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ContourTargetOrientation {
|
||||||
|
Portrait = 0,
|
||||||
|
Landscape = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ContourGroupingMode {
|
||||||
|
Single = 0,
|
||||||
|
Dual = 1,
|
||||||
|
TwoOrMore = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ContourIntersection {
|
||||||
|
None = 0,
|
||||||
|
Up = 1,
|
||||||
|
Down = 2,
|
||||||
|
Left = 3,
|
||||||
|
Right = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ContourShape {
|
||||||
|
Circle = 0,
|
||||||
|
Polygon = 1,
|
||||||
|
Triangle = 2,
|
||||||
|
Quadrilateral = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ContourTargetOffsetPointEdge {
|
||||||
|
Center = 0,
|
||||||
|
Top = 1,
|
||||||
|
Bottom = 2,
|
||||||
|
Left = 3,
|
||||||
|
Right = 4
|
||||||
|
}
|
||||||
|
|
||||||
export interface PipelineSettings {
|
export interface PipelineSettings {
|
||||||
offsetRobotOffsetMode: RobotOffsetPointMode;
|
offsetRobotOffsetMode: RobotOffsetPointMode;
|
||||||
streamingFrameDivisor: number;
|
streamingFrameDivisor: number;
|
||||||
offsetDualPointBArea: number;
|
offsetDualPointBArea: number;
|
||||||
contourGroupingMode: number;
|
contourGroupingMode: ContourGroupingMode;
|
||||||
hsvValue: WebsocketNumberPair | [number, number];
|
hsvValue: WebsocketNumberPair | [number, number];
|
||||||
cameraGain: number;
|
cameraGain: number;
|
||||||
cameraBlueGain: number;
|
cameraBlueGain: number;
|
||||||
cameraRedGain: number;
|
cameraRedGain: number;
|
||||||
cornerDetectionSideCount: number;
|
cornerDetectionSideCount: number;
|
||||||
contourRatio: WebsocketNumberPair | [number, number];
|
contourRatio: WebsocketNumberPair | [number, number];
|
||||||
contourTargetOffsetPointEdge: number;
|
contourTargetOffsetPointEdge: ContourTargetOffsetPointEdge;
|
||||||
pipelineNickname: string;
|
pipelineNickname: string;
|
||||||
inputImageRotationMode: number;
|
inputImageRotationMode: number;
|
||||||
contourArea: WebsocketNumberPair | [number, number];
|
contourArea: WebsocketNumberPair | [number, number];
|
||||||
@@ -56,7 +101,7 @@ export interface PipelineSettings {
|
|||||||
inputShouldShow: boolean;
|
inputShouldShow: boolean;
|
||||||
cameraAutoExposure: boolean;
|
cameraAutoExposure: boolean;
|
||||||
contourSpecklePercentage: number;
|
contourSpecklePercentage: number;
|
||||||
contourTargetOrientation: number;
|
contourTargetOrientation: ContourTargetOrientation;
|
||||||
targetModel: TargetModel;
|
targetModel: TargetModel;
|
||||||
cornerDetectionUseConvexHulls: boolean;
|
cornerDetectionUseConvexHulls: boolean;
|
||||||
outputShouldShow: boolean;
|
outputShouldShow: boolean;
|
||||||
@@ -67,7 +112,7 @@ export interface PipelineSettings {
|
|||||||
ledMode: boolean;
|
ledMode: boolean;
|
||||||
hueInverted: boolean;
|
hueInverted: boolean;
|
||||||
outputMaximumTargets: number;
|
outputMaximumTargets: number;
|
||||||
contourSortMode: number;
|
contourSortMode: ContourSortMode;
|
||||||
cameraExposureRaw: number;
|
cameraExposureRaw: number;
|
||||||
cameraMinExposureRaw: number;
|
cameraMinExposureRaw: number;
|
||||||
cameraMaxExposureRaw: number;
|
cameraMaxExposureRaw: number;
|
||||||
@@ -80,11 +125,13 @@ export interface PipelineSettings {
|
|||||||
cornerDetectionAccuracyPercentage: number;
|
cornerDetectionAccuracyPercentage: number;
|
||||||
hsvSaturation: WebsocketNumberPair | [number, number];
|
hsvSaturation: WebsocketNumberPair | [number, number];
|
||||||
pipelineType: PipelineType;
|
pipelineType: PipelineType;
|
||||||
contourIntersection: number;
|
contourIntersection: ContourIntersection;
|
||||||
|
|
||||||
cameraAutoWhiteBalance: boolean;
|
cameraAutoWhiteBalance: boolean;
|
||||||
cameraWhiteBalanceTemp: number;
|
cameraWhiteBalanceTemp: number;
|
||||||
|
|
||||||
|
crosshair: boolean;
|
||||||
|
|
||||||
blockForFrames: boolean;
|
blockForFrames: boolean;
|
||||||
}
|
}
|
||||||
export type ConfigurablePipelineSettings = Partial<
|
export type ConfigurablePipelineSettings = Partial<
|
||||||
@@ -113,13 +160,13 @@ export const DefaultPipelineSettings: Omit<
|
|||||||
offsetRobotOffsetMode: RobotOffsetPointMode.None,
|
offsetRobotOffsetMode: RobotOffsetPointMode.None,
|
||||||
streamingFrameDivisor: 0,
|
streamingFrameDivisor: 0,
|
||||||
offsetDualPointBArea: 0,
|
offsetDualPointBArea: 0,
|
||||||
contourGroupingMode: 0,
|
contourGroupingMode: ContourGroupingMode.Single,
|
||||||
hsvValue: { first: 50, second: 255 },
|
hsvValue: { first: 50, second: 255 },
|
||||||
cameraBlueGain: 20,
|
cameraBlueGain: 20,
|
||||||
cameraRedGain: 11,
|
cameraRedGain: 11,
|
||||||
cornerDetectionSideCount: 4,
|
cornerDetectionSideCount: 4,
|
||||||
contourRatio: { first: 0, second: 20 },
|
contourRatio: { first: 0, second: 20 },
|
||||||
contourTargetOffsetPointEdge: 0,
|
contourTargetOffsetPointEdge: ContourTargetOffsetPointEdge.Center,
|
||||||
pipelineNickname: "Placeholder Pipeline",
|
pipelineNickname: "Placeholder Pipeline",
|
||||||
inputImageRotationMode: 0,
|
inputImageRotationMode: 0,
|
||||||
contourArea: { first: 0, second: 100 },
|
contourArea: { first: 0, second: 100 },
|
||||||
@@ -129,7 +176,7 @@ export const DefaultPipelineSettings: Omit<
|
|||||||
inputShouldShow: false,
|
inputShouldShow: false,
|
||||||
cameraAutoExposure: false,
|
cameraAutoExposure: false,
|
||||||
contourSpecklePercentage: 5,
|
contourSpecklePercentage: 5,
|
||||||
contourTargetOrientation: 1,
|
contourTargetOrientation: ContourTargetOrientation.Landscape,
|
||||||
cornerDetectionUseConvexHulls: true,
|
cornerDetectionUseConvexHulls: true,
|
||||||
outputShouldShow: true,
|
outputShouldShow: true,
|
||||||
outputShouldDraw: true,
|
outputShouldDraw: true,
|
||||||
@@ -138,7 +185,7 @@ export const DefaultPipelineSettings: Omit<
|
|||||||
hsvHue: { first: 50, second: 180 },
|
hsvHue: { first: 50, second: 180 },
|
||||||
hueInverted: false,
|
hueInverted: false,
|
||||||
outputMaximumTargets: 20,
|
outputMaximumTargets: 20,
|
||||||
contourSortMode: 0,
|
contourSortMode: ContourSortMode.Largest,
|
||||||
offsetSinglePoint: { x: 0, y: 0 },
|
offsetSinglePoint: { x: 0, y: 0 },
|
||||||
cameraBrightness: 50,
|
cameraBrightness: 50,
|
||||||
offsetDualPointAArea: 0,
|
offsetDualPointAArea: 0,
|
||||||
@@ -147,11 +194,12 @@ export const DefaultPipelineSettings: Omit<
|
|||||||
cornerDetectionStrategy: 0,
|
cornerDetectionStrategy: 0,
|
||||||
cornerDetectionAccuracyPercentage: 10,
|
cornerDetectionAccuracyPercentage: 10,
|
||||||
hsvSaturation: { first: 50, second: 255 },
|
hsvSaturation: { first: 50, second: 255 },
|
||||||
contourIntersection: 1,
|
contourIntersection: ContourIntersection.Up,
|
||||||
cameraAutoWhiteBalance: false,
|
cameraAutoWhiteBalance: false,
|
||||||
cameraWhiteBalanceTemp: 4000,
|
cameraWhiteBalanceTemp: 4000,
|
||||||
cameraMinExposureRaw: 1,
|
cameraMinExposureRaw: 1,
|
||||||
cameraMaxExposureRaw: 2,
|
cameraMaxExposureRaw: 2,
|
||||||
|
crosshair: true,
|
||||||
blockForFrames: true
|
blockForFrames: true
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -184,7 +232,7 @@ export interface ColoredShapePipelineSettings extends PipelineSettings {
|
|||||||
contourRadius: WebsocketNumberPair | [number, number];
|
contourRadius: WebsocketNumberPair | [number, number];
|
||||||
circleDetectThreshold: number;
|
circleDetectThreshold: number;
|
||||||
accuracyPercentage: number;
|
accuracyPercentage: number;
|
||||||
contourShape: number;
|
contourShape: ContourShape;
|
||||||
contourPerimeter: WebsocketNumberPair | [number, number];
|
contourPerimeter: WebsocketNumberPair | [number, number];
|
||||||
minDist: number;
|
minDist: number;
|
||||||
maxCannyThresh: number;
|
maxCannyThresh: number;
|
||||||
@@ -209,7 +257,7 @@ export const DefaultColoredShapePipelineSettings: ColoredShapePipelineSettings =
|
|||||||
contourRadius: { first: 0, second: 100 },
|
contourRadius: { first: 0, second: 100 },
|
||||||
circleDetectThreshold: 5,
|
circleDetectThreshold: 5,
|
||||||
accuracyPercentage: 10,
|
accuracyPercentage: 10,
|
||||||
contourShape: 2,
|
contourShape: ContourShape.Triangle,
|
||||||
contourPerimeter: { first: 0, second: 1.7976931348623157e308 },
|
contourPerimeter: { first: 0, second: 1.7976931348623157e308 },
|
||||||
minDist: 20,
|
minDist: 20,
|
||||||
maxCannyThresh: 90
|
maxCannyThresh: 90
|
||||||
@@ -324,13 +372,14 @@ export const DefaultObjectDetectionPipelineSettings: ObjectDetectionPipelineSett
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface Calibration3dPipelineSettings extends PipelineSettings {
|
export interface Calibration3dPipelineSettings extends PipelineSettings {
|
||||||
|
pipelineType: PipelineType.Calibration3d;
|
||||||
drawAllSnapshots: boolean;
|
drawAllSnapshots: boolean;
|
||||||
}
|
}
|
||||||
export type ConfigurableCalibration3dPipelineSettings = Partial<Omit<Calibration3dPipelineSettings, "pipelineType">> &
|
export type ConfigurableCalibration3dPipelineSettings = Partial<Omit<Calibration3dPipelineSettings, "pipelineType">> &
|
||||||
ConfigurablePipelineSettings;
|
ConfigurablePipelineSettings;
|
||||||
export const DefaultCalibration3dPipelineSettings: Calibration3dPipelineSettings = {
|
export const DefaultCalibration3dPipelineSettings: Calibration3dPipelineSettings = {
|
||||||
...DefaultPipelineSettings,
|
...DefaultPipelineSettings,
|
||||||
pipelineType: PipelineType.ObjectDetection,
|
pipelineType: PipelineType.Calibration3d,
|
||||||
cameraGain: 20,
|
cameraGain: 20,
|
||||||
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
|
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
|
||||||
ledMode: true,
|
ledMode: true,
|
||||||
|
|||||||
@@ -75,46 +75,29 @@ export type ConfigurableNetworkSettings = Omit<
|
|||||||
"canManage" | "networkInterfaceNames" | "networkingDisabled"
|
"canManage" | "networkInterfaceNames" | "networkingDisabled"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface PVCameraInfoBase {
|
interface PVCameraInfoBase {
|
||||||
/*
|
type: "PVUsbCameraInfo" | "PVCSICameraInfo" | "PVFileCameraInfo";
|
||||||
Huge hack. In Jackson, this is set based on the underlying type -- this
|
path: string;
|
||||||
then maps to one of the 3 subclasses here below. Not sure how to best deal with this.
|
name: string;
|
||||||
*/
|
uniquePath: string;
|
||||||
cameraTypename: "PVUsbCameraInfo" | "PVCSICameraInfo" | "PVFileCameraInfo";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PVUsbCameraInfo {
|
export interface PVUsbCameraInfo extends PVCameraInfoBase {
|
||||||
|
type: "PVUsbCameraInfo";
|
||||||
dev: number;
|
dev: number;
|
||||||
name: string;
|
|
||||||
otherPaths: string[];
|
otherPaths: string[];
|
||||||
path: string;
|
|
||||||
vendorId: number;
|
vendorId: number;
|
||||||
productId: number;
|
productId: number;
|
||||||
|
|
||||||
// In Java, PVCameraInfo provides a uniquePath property so we can have one Source of Truth here
|
|
||||||
uniquePath: string;
|
|
||||||
}
|
}
|
||||||
export interface PVCSICameraInfo {
|
export interface PVCSICameraInfo extends PVCameraInfoBase {
|
||||||
|
type: "PVCSICameraInfo";
|
||||||
baseName: string;
|
baseName: string;
|
||||||
path: string;
|
|
||||||
|
|
||||||
// In Java, PVCameraInfo provides a uniquePath property so we can have one Source of Truth here
|
|
||||||
uniquePath: string;
|
|
||||||
}
|
}
|
||||||
export interface PVFileCameraInfo {
|
export interface PVFileCameraInfo extends PVCameraInfoBase {
|
||||||
path: string;
|
type: "PVFileCameraInfo";
|
||||||
name: string;
|
|
||||||
|
|
||||||
// In Java, PVCameraInfo provides a uniquePath property so we can have one Source of Truth here
|
|
||||||
uniquePath: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This camera info will only ever hold one of its members - the others should be undefined.
|
export type PVCameraInfo = PVUsbCameraInfo | PVCSICameraInfo | PVFileCameraInfo;
|
||||||
export class PVCameraInfo {
|
|
||||||
PVUsbCameraInfo: PVUsbCameraInfo | undefined;
|
|
||||||
PVCSICameraInfo: PVCSICameraInfo | undefined;
|
|
||||||
PVFileCameraInfo: PVFileCameraInfo | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VsmState {
|
export interface VsmState {
|
||||||
disabledConfigs: WebsocketCameraSettingsUpdate[];
|
disabledConfigs: WebsocketCameraSettingsUpdate[];
|
||||||
@@ -276,6 +259,7 @@ export interface UiCameraConfiguration {
|
|||||||
maxWhiteBalanceTemp: number;
|
maxWhiteBalanceTemp: number;
|
||||||
|
|
||||||
fpsLimit: number;
|
fpsLimit: number;
|
||||||
|
isEnabled: boolean;
|
||||||
|
|
||||||
matchedCameraInfo: PVCameraInfo;
|
matchedCameraInfo: PVCameraInfo;
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
@@ -438,15 +422,13 @@ export const PlaceholderCameraSettings: UiCameraConfiguration = reactive({
|
|||||||
minWhiteBalanceTemp: 2000,
|
minWhiteBalanceTemp: 2000,
|
||||||
maxWhiteBalanceTemp: 10000,
|
maxWhiteBalanceTemp: 10000,
|
||||||
matchedCameraInfo: {
|
matchedCameraInfo: {
|
||||||
PVFileCameraInfo: {
|
type: "PVFileCameraInfo",
|
||||||
name: "Foobar",
|
name: "Foobar",
|
||||||
path: "/dev/foobar",
|
path: "/dev/foobar",
|
||||||
uniquePath: "/dev/foobar2"
|
uniquePath: "/dev/foobar2"
|
||||||
},
|
|
||||||
PVCSICameraInfo: undefined,
|
|
||||||
PVUsbCameraInfo: undefined
|
|
||||||
},
|
},
|
||||||
fpsLimit: -1,
|
fpsLimit: -1,
|
||||||
|
isEnabled: true,
|
||||||
isConnected: true,
|
isConnected: true,
|
||||||
hasConnected: true,
|
hasConnected: true,
|
||||||
mismatch: false
|
mismatch: false
|
||||||
|
|||||||
@@ -30,21 +30,18 @@ export interface WebsocketNumberPair {
|
|||||||
second: number;
|
second: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WebsocketVideoFormat = Record<
|
export type WebsocketVideoFormat = {
|
||||||
number,
|
fps: number;
|
||||||
{
|
height: number;
|
||||||
fps: number;
|
width: number;
|
||||||
height: number;
|
pixelFormat: string;
|
||||||
width: number;
|
index?: number;
|
||||||
pixelFormat: string;
|
diagonalFOV?: number;
|
||||||
index?: number;
|
horizontalFOV?: number;
|
||||||
diagonalFOV?: number;
|
verticalFOV?: number;
|
||||||
horizontalFOV?: number;
|
standardDeviation?: number;
|
||||||
verticalFOV?: number;
|
mean?: number;
|
||||||
standardDeviation?: number;
|
}[];
|
||||||
mean?: number;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
// Companion to UICameraConfiguration in Java
|
// Companion to UICameraConfiguration in Java
|
||||||
export interface WebsocketCameraSettingsUpdate {
|
export interface WebsocketCameraSettingsUpdate {
|
||||||
@@ -68,6 +65,7 @@ export interface WebsocketCameraSettingsUpdate {
|
|||||||
maxWhiteBalanceTemp: number;
|
maxWhiteBalanceTemp: number;
|
||||||
matchedCameraInfo: PVCameraInfo;
|
matchedCameraInfo: PVCameraInfo;
|
||||||
fpsLimit: number;
|
fpsLimit: number;
|
||||||
|
isEnabled: boolean;
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
hasConnected: boolean;
|
hasConnected: boolean;
|
||||||
mismatch: boolean;
|
mismatch: boolean;
|
||||||
|
|||||||
@@ -2,13 +2,7 @@
|
|||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import {
|
import { PlaceholderCameraSettings, type PVCameraInfo } from "@/types/SettingTypes";
|
||||||
PlaceholderCameraSettings,
|
|
||||||
PVCameraInfo,
|
|
||||||
type PVCSICameraInfo,
|
|
||||||
type PVFileCameraInfo,
|
|
||||||
type PVUsbCameraInfo
|
|
||||||
} from "@/types/SettingTypes";
|
|
||||||
import { axiosPost, getResolutionString } from "@/lib/PhotonUtils";
|
import { axiosPost, getResolutionString } from "@/lib/PhotonUtils";
|
||||||
import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
|
import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
|
||||||
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
@@ -18,20 +12,22 @@ import { useTheme } from "vuetify";
|
|||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const formatUrl = (port) => `http://${inject("backendHostname")}:${port}/stream.mjpg`;
|
const backendHostname = inject<string>("backendHostname");
|
||||||
|
const formatUrl = (port: number) => `http://${backendHostname}:${port}/stream.mjpg`;
|
||||||
|
|
||||||
const activatingModule = ref(false);
|
const activatingModule = ref(false);
|
||||||
const activateModule = (moduleUniqueName: string) => {
|
const activateModule = async (moduleUniqueName: string) => {
|
||||||
if (activatingModule.value) return;
|
if (activatingModule.value) return;
|
||||||
activatingModule.value = true;
|
activatingModule.value = true;
|
||||||
|
|
||||||
axiosPost("/utils/activateMatchedCamera", "activate a matched camera", {
|
await axiosPost("/utils/activateMatchedCamera", "activate a matched camera", {
|
||||||
cameraUniqueName: moduleUniqueName
|
cameraUniqueName: moduleUniqueName
|
||||||
}).finally(() => (activatingModule.value = false));
|
});
|
||||||
|
activatingModule.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const assigningCamera = ref(false);
|
const assigningCamera = ref(false);
|
||||||
const assignCamera = (cameraInfo: PVCameraInfo) => {
|
const assignCamera = async (cameraInfo: PVCameraInfo) => {
|
||||||
if (assigningCamera.value) return;
|
if (assigningCamera.value) return;
|
||||||
assigningCamera.value = true;
|
assigningCamera.value = true;
|
||||||
|
|
||||||
@@ -39,48 +35,39 @@ const assignCamera = (cameraInfo: PVCameraInfo) => {
|
|||||||
cameraInfo: cameraInfo
|
cameraInfo: cameraInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
axiosPost("/utils/assignUnmatchedCamera", "assign an unmatched camera", payload).finally(
|
await axiosPost("/utils/assignUnmatchedCamera", "assign an unmatched camera", payload);
|
||||||
() => (assigningCamera.value = false)
|
assigningCamera.value = false;
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deactivatingModule = ref(false);
|
const deactivatingModule = ref(false);
|
||||||
const deactivateModule = (cameraUniqueName: string) => {
|
const deactivateModule = async (cameraUniqueName: string) => {
|
||||||
if (deactivatingModule.value) return;
|
if (deactivatingModule.value) return;
|
||||||
deactivatingModule.value = true;
|
deactivatingModule.value = true;
|
||||||
axiosPost("/utils/unassignCamera", "unassign a camera", { cameraUniqueName: cameraUniqueName }).finally(
|
await axiosPost("/utils/unassignCamera", "unassign a camera", { cameraUniqueName: cameraUniqueName });
|
||||||
() => (deactivatingModule.value = false)
|
deactivatingModule.value = false;
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmDeleteDialog = ref({ show: false, nickname: "", cameraUniqueName: "" });
|
const confirmDeleteDialog = ref({ show: false, nickname: "", cameraUniqueName: "" });
|
||||||
const deletingCamera = ref<string | null>(null);
|
const deletingCamera = ref<string | null>(null);
|
||||||
|
|
||||||
const deleteThisCamera = (cameraUniqueName: string) => {
|
const deleteThisCamera = async (cameraUniqueName: string) => {
|
||||||
if (deletingCamera.value) return;
|
if (deletingCamera.value) return;
|
||||||
deletingCamera.value = cameraUniqueName;
|
deletingCamera.value = cameraUniqueName;
|
||||||
axiosPost("/utils/nukeOneCamera", "delete a camera", { cameraUniqueName: cameraUniqueName }).finally(() => {
|
await axiosPost("/utils/nukeOneCamera", "delete a camera", { cameraUniqueName: cameraUniqueName });
|
||||||
deletingCamera.value = null;
|
deletingCamera.value = null;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cameraConnected = (uniquePath: string): boolean => {
|
const cameraConnected = (uniquePath: string | undefined): boolean => {
|
||||||
return (
|
if (!uniquePath) return false;
|
||||||
useStateStore().vsmState.allConnectedCameras.find((it) => cameraInfoFor(it).uniquePath === uniquePath) !== undefined
|
return useStateStore().vsmState.allConnectedCameras.find((it) => it.uniquePath === uniquePath) !== undefined;
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const unmatchedCameras = computed(() => {
|
const unmatchedCameras = computed(() => {
|
||||||
const activeVmPaths = Object.values(useCameraSettingsStore().cameras).map(
|
const activeVmPaths = Object.values(useCameraSettingsStore().cameras).map((it) => it.matchedCameraInfo.uniquePath);
|
||||||
(it) => cameraInfoFor(it.matchedCameraInfo).uniquePath
|
const disabledVmPaths = useStateStore().vsmState.disabledConfigs.map((it) => it.matchedCameraInfo.uniquePath);
|
||||||
);
|
|
||||||
const disabledVmPaths = useStateStore().vsmState.disabledConfigs.map(
|
|
||||||
(it) => cameraInfoFor(it.matchedCameraInfo).uniquePath
|
|
||||||
);
|
|
||||||
|
|
||||||
return useStateStore().vsmState.allConnectedCameras.filter(
|
return useStateStore().vsmState.allConnectedCameras.filter(
|
||||||
(it) =>
|
(it) => !activeVmPaths.includes(it.uniquePath) && !disabledVmPaths.includes(it.uniquePath)
|
||||||
!activeVmPaths.includes(cameraInfoFor(it).uniquePath) && !disabledVmPaths.includes(cameraInfoFor(it).uniquePath)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,8 +78,8 @@ const activeVisionModules = computed(() =>
|
|||||||
// Display connected cameras first
|
// Display connected cameras first
|
||||||
.sort(
|
.sort(
|
||||||
(first, second) =>
|
(first, second) =>
|
||||||
(cameraConnected(cameraInfoFor(second.matchedCameraInfo).uniquePath) ? 1 : 0) -
|
(cameraConnected(second.matchedCameraInfo.uniquePath) ? 1 : 0) -
|
||||||
(cameraConnected(cameraInfoFor(first.matchedCameraInfo).uniquePath) ? 1 : 0)
|
(cameraConnected(first.matchedCameraInfo.uniquePath) ? 1 : 0)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -105,41 +92,24 @@ const setCameraView = (camera: PVCameraInfo | null, isConnected: boolean | null)
|
|||||||
viewingCamera.value = [camera, isConnected];
|
viewingCamera.value = [camera, isConnected];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the connection-type-specific camera info from the given PVCameraInfo object.
|
|
||||||
*/
|
|
||||||
const cameraInfoFor = (camera: PVCameraInfo | null): PVUsbCameraInfo | PVCSICameraInfo | PVFileCameraInfo | any => {
|
|
||||||
if (!camera) return null;
|
|
||||||
if (camera.PVUsbCameraInfo) {
|
|
||||||
return camera.PVUsbCameraInfo;
|
|
||||||
}
|
|
||||||
if (camera.PVCSICameraInfo) {
|
|
||||||
return camera.PVCSICameraInfo;
|
|
||||||
}
|
|
||||||
if (camera.PVFileCameraInfo) {
|
|
||||||
return camera.PVFileCameraInfo;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the PVCameraInfo currently occupying the same uniquePath as the the given module
|
* Find the PVCameraInfo currently occupying the same uniquePath as the the given module
|
||||||
*/
|
*/
|
||||||
const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||||
if (!info) {
|
if (!info) {
|
||||||
return {
|
return {
|
||||||
PVFileCameraInfo: undefined,
|
type: "PVFileCameraInfo",
|
||||||
PVCSICameraInfo: undefined,
|
path: "",
|
||||||
PVUsbCameraInfo: undefined
|
name: "",
|
||||||
|
uniquePath: ""
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
useStateStore().vsmState.allConnectedCameras.find(
|
useStateStore().vsmState.allConnectedCameras.find((it) => it.uniquePath === info.uniquePath) || {
|
||||||
(it) => cameraInfoFor(it).uniquePath === cameraInfoFor(info).uniquePath
|
type: "PVFileCameraInfo",
|
||||||
) || {
|
path: "",
|
||||||
PVFileCameraInfo: undefined,
|
name: "",
|
||||||
PVCSICameraInfo: undefined,
|
uniquePath: ""
|
||||||
PVUsbCameraInfo: undefined
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -158,12 +128,11 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
class="pr-0"
|
class="pr-0"
|
||||||
>
|
>
|
||||||
<v-card color="surface" class="rounded-12">
|
<v-card color="surface" class="rounded-12">
|
||||||
<v-card-title>{{ cameraInfoFor(module.matchedCameraInfo).name }}</v-card-title>
|
<v-card-title>{{ module.matchedCameraInfo.name }}</v-card-title>
|
||||||
<v-card-subtitle v-if="!cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)"
|
<v-card-subtitle v-if="!cameraConnected(module.matchedCameraInfo.uniquePath)"
|
||||||
>Status: <span class="inactive-status">Disconnected</span></v-card-subtitle
|
>Status: <span class="inactive-status">Disconnected</span></v-card-subtitle
|
||||||
>
|
>
|
||||||
<v-card-subtitle
|
<v-card-subtitle v-else-if="cameraConnected(module.matchedCameraInfo.uniquePath) && !module.mismatch"
|
||||||
v-else-if="cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath) && !module.mismatch"
|
|
||||||
>Status: <span class="active-status">Active</span></v-card-subtitle
|
>Status: <span class="active-status">Active</span></v-card-subtitle
|
||||||
>
|
>
|
||||||
<v-card-subtitle v-else>Status: <span class="mismatch-status">Mismatch</span></v-card-subtitle>
|
<v-card-subtitle v-else>Status: <span class="mismatch-status">Mismatch</span></v-card-subtitle>
|
||||||
@@ -172,7 +141,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-if="
|
v-if="
|
||||||
cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath) &&
|
cameraConnected(module.matchedCameraInfo.uniquePath) &&
|
||||||
useStateStore().backendResults[module.uniqueName]
|
useStateStore().backendResults[module.uniqueName]
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
@@ -214,7 +183,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
<div
|
<div
|
||||||
v-if="cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)"
|
v-if="cameraConnected(module.matchedCameraInfo.uniquePath)"
|
||||||
:id="`stream-container-${index}`"
|
:id="`stream-container-${index}`"
|
||||||
class="d-flex flex-column justify-center align-center mt-3"
|
class="d-flex flex-column justify-center align-center mt-3"
|
||||||
style="height: 250px"
|
style="height: 250px"
|
||||||
@@ -233,12 +202,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
||||||
@click="
|
@click="setCameraView(module.matchedCameraInfo, cameraConnected(module.matchedCameraInfo.uniquePath))"
|
||||||
setCameraView(
|
|
||||||
module.matchedCameraInfo,
|
|
||||||
cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<span>Details</span>
|
<span>Details</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -315,7 +279,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Connected</td>
|
<td>Connected</td>
|
||||||
<td>{{ cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath) }}</td>
|
<td>{{ cameraConnected(module.matchedCameraInfo.uniquePath) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
@@ -327,12 +291,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
:variant="theme.global.current.value.dark ? 'outlined' : 'elevated'"
|
||||||
@click="
|
@click="setCameraView(module.matchedCameraInfo, cameraConnected(module.matchedCameraInfo.uniquePath))"
|
||||||
setCameraView(
|
|
||||||
module.matchedCameraInfo,
|
|
||||||
cameraConnected(cameraInfoFor(module.matchedCameraInfo).uniquePath)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<span>Details</span>
|
<span>Details</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -377,15 +336,15 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
<v-col v-for="(camera, index) in unmatchedCameras" :key="index" cols="12" sm="6" lg="4" class="pr-0">
|
<v-col v-for="(camera, index) in unmatchedCameras" :key="index" cols="12" sm="6" lg="4" class="pr-0">
|
||||||
<v-card class="pr-0 rounded-12" color="surface">
|
<v-card class="pr-0 rounded-12" color="surface">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
<span v-if="camera.PVUsbCameraInfo">USB Camera:</span>
|
<span v-if="camera.type === 'PVUsbCameraInfo'">USB Camera:</span>
|
||||||
<span v-else-if="camera.PVCSICameraInfo">CSI Camera:</span>
|
<span v-else-if="camera.type === 'PVCSICameraInfo'">CSI Camera:</span>
|
||||||
<span v-else-if="camera.PVFileCameraInfo">File Camera:</span>
|
<span v-else-if="camera.type === 'PVFileCameraInfo'">File Camera:</span>
|
||||||
<span v-else>Unknown Camera:</span>
|
<span v-else>Unknown Camera:</span>
|
||||||
<span>{{ cameraInfoFor(camera)?.name ?? cameraInfoFor(camera)?.baseName }}</span>
|
<span>{{ camera.name }}</span>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-subtitle>Status: Unassigned</v-card-subtitle>
|
<v-card-subtitle>Status: Unassigned</v-card-subtitle>
|
||||||
<v-card-text class="pt-3">
|
<v-card-text class="pt-3">
|
||||||
<span style="word-break: break-all">{{ cameraInfoFor(camera)?.path }}</span>
|
<span style="word-break: break-all">{{ camera?.path }}</span>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text class="pt-0">
|
<v-card-text class="pt-0">
|
||||||
<v-row>
|
<v-row>
|
||||||
@@ -436,7 +395,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
<v-dialog v-model="viewingDetails" max-width="800">
|
<v-dialog v-model="viewingDetails" max-width="800">
|
||||||
<v-card v-if="viewingCamera[0] !== null" flat color="surface">
|
<v-card v-if="viewingCamera[0] !== null" flat color="surface">
|
||||||
<v-card-title class="d-flex justify-space-between">
|
<v-card-title class="d-flex justify-space-between">
|
||||||
<span>{{ cameraInfoFor(viewingCamera[0])?.name ?? cameraInfoFor(viewingCamera[0])?.baseName }}</span>
|
<span>{{ viewingCamera[0].name }}</span>
|
||||||
<v-btn variant="text" @click="setCameraView(null, null)">
|
<v-btn variant="text" @click="setCameraView(null, null)">
|
||||||
<v-icon size="x-large">mdi-close</v-icon>
|
<v-icon size="x-large">mdi-close</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -446,9 +405,7 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text
|
<v-card-text
|
||||||
v-else-if="
|
v-else-if="
|
||||||
activeVisionModules.find(
|
activeVisionModules.find((it) => it.matchedCameraInfo.uniquePath === viewingCamera[0]?.uniquePath)?.mismatch
|
||||||
(it) => cameraInfoFor(it.matchedCameraInfo).uniquePath === cameraInfoFor(viewingCamera[0]).uniquePath
|
|
||||||
)?.mismatch
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<v-alert
|
<v-alert
|
||||||
|
|||||||
@@ -77,8 +77,18 @@ const conflictingCameraShown = computed<boolean>(() => {
|
|||||||
return useSettingsStore().general.conflictingCameras.length > 0;
|
return useSettingsStore().general.conflictingCameras.length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const fpsLimitWarningShown = computed<boolean>(() => {
|
const fpsLimitedCameras = computed<string>(() => {
|
||||||
return Object.values(useCameraSettingsStore().cameras).some((c) => c.fpsLimit > 0);
|
return Object.values(useCameraSettingsStore().cameras)
|
||||||
|
.filter((c) => c.fpsLimit > 0)
|
||||||
|
.map((c) => c.nickname)
|
||||||
|
.join(", ");
|
||||||
|
});
|
||||||
|
|
||||||
|
const disabledCameras = computed<string>(() => {
|
||||||
|
return Object.values(useCameraSettingsStore().cameras)
|
||||||
|
.filter((c) => !c.isEnabled)
|
||||||
|
.map((c) => c.nickname)
|
||||||
|
.join(", ");
|
||||||
});
|
});
|
||||||
|
|
||||||
const showCameraSetupDialog = ref(useCameraSettingsStore().needsCameraConfiguration);
|
const showCameraSetupDialog = ref(useCameraSettingsStore().needsCameraConfiguration);
|
||||||
@@ -111,7 +121,7 @@ const showCameraSetupDialog = ref(useCameraSettingsStore().needsCameraConfigurat
|
|||||||
</span>
|
</span>
|
||||||
</v-alert>
|
</v-alert>
|
||||||
<v-alert
|
<v-alert
|
||||||
v-if="fpsLimitWarningShown"
|
v-if="fpsLimitedCameras"
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
color="error"
|
color="error"
|
||||||
density="compact"
|
density="compact"
|
||||||
@@ -119,10 +129,22 @@ const showCameraSetupDialog = ref(useCameraSettingsStore().needsCameraConfigurat
|
|||||||
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
|
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
>One or more cameras have an FPS limit set! This may cause performance issues. Check your logs for more
|
>{{ fpsLimitedCameras }} have an FPS limit set! This may cause performance issues. Check your logs for more
|
||||||
information.
|
information.
|
||||||
</span>
|
</span>
|
||||||
</v-alert>
|
</v-alert>
|
||||||
|
<v-alert
|
||||||
|
v-if="disabledCameras"
|
||||||
|
class="mb-3"
|
||||||
|
color="error"
|
||||||
|
density="compact"
|
||||||
|
icon="mdi-alert-circle-outline"
|
||||||
|
:variant="theme.global.current.value.dark ? 'tonal' : 'elevated'"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
>{{ disabledCameras }} are disabled! This may cause performance issues. Check your logs for more information.
|
||||||
|
</span>
|
||||||
|
</v-alert>
|
||||||
<v-alert
|
<v-alert
|
||||||
v-if="conflictingCameraShown"
|
v-if="conflictingCameraShown"
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const devMode = process.env.NODE_ENV === "development";
|
const devMode = process.env.NODE_ENV === "development";
|
||||||
|
const docsSrc = import.meta.env.MODE === "demo" ? "https://docs.photonvision.org" : "docs/index.html";
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div style="overflow: hidden; height: 100vh; width: 100%">
|
<div style="overflow: hidden; height: 100vh; width: 100%">
|
||||||
@@ -21,7 +22,7 @@ const devMode = process.env.NODE_ENV === "development";
|
|||||||
</div>
|
</div>
|
||||||
<div v-else style="width: 100%; height: 100%">
|
<div v-else style="width: 100%; height: 100%">
|
||||||
<!--suppress HtmlUnknownTarget -->
|
<!--suppress HtmlUnknownTarget -->
|
||||||
<iframe src="docs/index.html" style="overflow: hidden; height: 100%; width: 100%; border: 0" />
|
<iframe :src="docsSrc" style="overflow: hidden; height: 100%; width: 100%; border: 0" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@vue/tsconfig/tsconfig.json",
|
|
||||||
"include": ["vite.config.*"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"types": ["node"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
"include": ["env.d.ts", "vite.config.ts", "playwright.config.ts", "src/**/*", "src/**/*.vue", "tests/**/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"moduleResolution": "node",
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
"noImplicitAny": false,
|
"moduleResolution": "bundler",
|
||||||
|
"noImplicitAny": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
@@ -14,9 +15,4 @@
|
|||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"path": "./tsconfig.config.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ ext.licenseFile = file("$rootDir/LICENSE")
|
|||||||
ext.externalLicensesFolder = file("$rootDir/ExternalLicenses")
|
ext.externalLicensesFolder = file("$rootDir/ExternalLicenses")
|
||||||
apply from: "${rootDir}/shared/common.gradle"
|
apply from: "${rootDir}/shared/common.gradle"
|
||||||
|
|
||||||
wpilibTools.deps.wpilibVersion = wpi.versions.wpilibVersion.get()
|
wpilibTools.deps.wpilibVersion = wpilibVersion
|
||||||
|
|
||||||
def nativeConfigName = 'wpilibNatives'
|
def nativeConfigName = 'wpilibNatives'
|
||||||
configurations {
|
configurations {
|
||||||
@@ -23,11 +23,12 @@ dependencies {
|
|||||||
wpilibNatives wpilibTools.deps.wpilib("wpimath")
|
wpilibNatives wpilibTools.deps.wpilib("wpimath")
|
||||||
wpilibNatives wpilibTools.deps.wpilib("wpinet")
|
wpilibNatives wpilibTools.deps.wpilib("wpinet")
|
||||||
wpilibNatives wpilibTools.deps.wpilib("wpiutil")
|
wpilibNatives wpilibTools.deps.wpilib("wpiutil")
|
||||||
|
wpilibNatives wpilibTools.deps.wpilib("datalog")
|
||||||
wpilibNatives wpilibTools.deps.wpilib("ntcore")
|
wpilibNatives wpilibTools.deps.wpilib("ntcore")
|
||||||
wpilibNatives wpilibTools.deps.wpilib("cscore")
|
wpilibNatives wpilibTools.deps.wpilib("cscore")
|
||||||
wpilibNatives wpilibTools.deps.wpilib("apriltag")
|
wpilibNatives wpilibTools.deps.wpilib("apriltag")
|
||||||
wpilibNatives wpilibTools.deps.wpilib("hal")
|
wpilibNatives wpilibTools.deps.wpilib("hal")
|
||||||
wpilibNatives wpilibTools.deps.wpilibOpenCv("frc" + openCVYear, wpi.versions.opencvVersion.get())
|
wpilibNatives wpilibTools.deps.wpilibOpenCv(openCVversion)
|
||||||
|
|
||||||
// These stay as implementation dependencies since they don't have native code that gets packaged
|
// These stay as implementation dependencies since they don't have native code that gets packaged
|
||||||
implementation 'org.zeroturnaround:zt-zip:1.14'
|
implementation 'org.zeroturnaround:zt-zip:1.14'
|
||||||
@@ -40,7 +41,7 @@ dependencies {
|
|||||||
wpilibNatives("org.photonvision:rknn_jni-jni:$rknnVersion:$jniPlatform") {
|
wpilibNatives("org.photonvision:rknn_jni-jni:$rknnVersion:$jniPlatform") {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
wpilibNatives("org.photonvision:rubik_jni-jni:$rubikVersion:$jniPlatform") {
|
wpilibNatives("org.photonvision:tflite_jni-jni:$tfliteVersion:$jniPlatform") {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
wpilibNatives("org.photonvision:photon-libcamera-gl-driver-jni:$libcameraDriverVersion:$jniPlatform") {
|
wpilibNatives("org.photonvision:photon-libcamera-gl-driver-jni:$libcameraDriverVersion:$jniPlatform") {
|
||||||
@@ -48,11 +49,17 @@ dependencies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (jniPlatform == "linuxx86-64") {
|
||||||
|
wpilibNatives("org.photonvision:tflite_jni-jni:$tfliteVersion:$jniPlatform") {
|
||||||
|
transitive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
implementation("org.photonvision:rknn_jni-java:$rknnVersion") {
|
implementation("org.photonvision:rknn_jni-java:$rknnVersion") {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation("org.photonvision:rubik_jni-java:$rubikVersion") {
|
implementation("org.photonvision:tflite_jni-java:$tfliteVersion") {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class LoadJNI {
|
|||||||
private static HashMap<JNITypes, Boolean> loadedMap = new HashMap<>();
|
private static HashMap<JNITypes, Boolean> loadedMap = new HashMap<>();
|
||||||
|
|
||||||
public enum JNITypes {
|
public enum JNITypes {
|
||||||
RUBIK_DETECTOR("tensorflowlite", "tensorflowlite_c", "external_delegate", "rubik_jni"),
|
RUBIK_DETECTOR("tflite_jni"),
|
||||||
RKNN_DETECTOR("rga", "rknnrt", "rknn_jni"),
|
RKNN_DETECTOR("rga", "rknnrt", "rknn_jni"),
|
||||||
MRCAL("mrcal_jni"),
|
MRCAL("mrcal_jni"),
|
||||||
LIBCAMERA("photonlibcamera");
|
LIBCAMERA("photonlibcamera");
|
||||||
|
|||||||
@@ -17,10 +17,7 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import edu.wpi.first.cscore.UsbCameraInfo;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -35,7 +32,9 @@ import org.photonvision.vision.camera.QuirkyCamera;
|
|||||||
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
||||||
import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
||||||
import org.photonvision.vision.processes.PipelineManager;
|
import org.photonvision.vision.processes.PipelineManager;
|
||||||
|
import org.wpilib.vision.camera.UsbCameraInfo;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class CameraConfiguration {
|
public class CameraConfiguration {
|
||||||
private static final Logger logger = new Logger(CameraConfiguration.class, LogGroup.Camera);
|
private static final Logger logger = new Logger(CameraConfiguration.class, LogGroup.Camera);
|
||||||
|
|
||||||
@@ -62,11 +61,8 @@ public class CameraConfiguration {
|
|||||||
|
|
||||||
public int streamIndex = 0; // 0 index means ports [1181, 1182], 1 means [1183, 1184], etc...
|
public int streamIndex = 0; // 0 index means ports [1181, 1182], 1 means [1183, 1184], etc...
|
||||||
|
|
||||||
// Ignore the pipes, as we serialize them to their own column to hack around
|
public List<CVPipelineSettings> pipelineSettings = new ArrayList<>();
|
||||||
// polymorphic lists
|
|
||||||
@JsonIgnore public List<CVPipelineSettings> pipelineSettings = new ArrayList<>();
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public DriverModePipelineSettings driveModeSettings = new DriverModePipelineSettings();
|
public DriverModePipelineSettings driveModeSettings = new DriverModePipelineSettings();
|
||||||
|
|
||||||
public CameraConfiguration(PVCameraInfo cameraInfo, String uniqueName, String nickname) {
|
public CameraConfiguration(PVCameraInfo cameraInfo, String uniqueName, String nickname) {
|
||||||
@@ -78,24 +74,22 @@ public class CameraConfiguration {
|
|||||||
logger.debug("Creating USB camera configuration for " + this.toShortString());
|
logger.debug("Creating USB camera configuration for " + this.toShortString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shiny new constructor
|
// JSON Constructor (can't be marked with @Json.Creator due to public fields that aren't part of
|
||||||
@JsonCreator
|
// the parameters)
|
||||||
public CameraConfiguration(
|
public CameraConfiguration(
|
||||||
@JsonProperty("uniqueName") String uniqueName,
|
String uniqueName,
|
||||||
@JsonProperty("matchedCameraInfo") PVCameraInfo matchedCameraInfo,
|
PVCameraInfo matchedCameraInfo,
|
||||||
@JsonProperty("nickname") String nickname,
|
String nickname,
|
||||||
@JsonProperty("deactivated") boolean deactivated,
|
boolean deactivated,
|
||||||
@JsonProperty("cameraQuirks") QuirkyCamera cameraQuirks,
|
QuirkyCamera cameraQuirks,
|
||||||
@JsonProperty("FOV") double FOV,
|
double FOV,
|
||||||
@JsonProperty("calibrations") List<CameraCalibrationCoefficients> calibrations,
|
int currentPipelineIndex) {
|
||||||
@JsonProperty("currentPipelineIndex") int currentPipelineIndex) {
|
|
||||||
this.uniqueName = uniqueName;
|
this.uniqueName = uniqueName;
|
||||||
this.matchedCameraInfo = matchedCameraInfo;
|
this.matchedCameraInfo = matchedCameraInfo;
|
||||||
this.nickname = nickname;
|
this.nickname = nickname;
|
||||||
this.deactivated = deactivated;
|
this.deactivated = deactivated;
|
||||||
this.cameraQuirks = cameraQuirks;
|
this.cameraQuirks = cameraQuirks;
|
||||||
this.FOV = FOV;
|
this.FOV = FOV;
|
||||||
this.calibrations = calibrations != null ? calibrations : new ArrayList<>();
|
|
||||||
this.currentPipelineIndex = currentPipelineIndex;
|
this.currentPipelineIndex = currentPipelineIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,14 +114,14 @@ public class CameraConfiguration {
|
|||||||
PVCameraInfo matchedCameraInfo;
|
PVCameraInfo matchedCameraInfo;
|
||||||
|
|
||||||
/** Legacy constructor for compat with 2024.3.1 */
|
/** Legacy constructor for compat with 2024.3.1 */
|
||||||
@JsonCreator
|
@Json.Creator
|
||||||
public LegacyCameraConfigStruct(
|
public LegacyCameraConfigStruct(
|
||||||
@JsonProperty("baseName") String baseName,
|
String baseName,
|
||||||
@JsonProperty("path") String path,
|
String path,
|
||||||
@JsonProperty("otherPaths") String[] otherPaths,
|
String[] otherPaths,
|
||||||
@JsonProperty("cameraType") CameraType cameraType,
|
CameraType cameraType,
|
||||||
@JsonProperty("usbVID") int usbVID,
|
int usbVID,
|
||||||
@JsonProperty("usbPID") int usbPID) {
|
int usbPID) {
|
||||||
if (cameraType == CameraType.UsbCamera) {
|
if (cameraType == CameraType.UsbCamera) {
|
||||||
this.matchedCameraInfo =
|
this.matchedCameraInfo =
|
||||||
PVCameraInfo.fromUsbCameraInfo(
|
PVCameraInfo.fromUsbCameraInfo(
|
||||||
@@ -171,16 +165,16 @@ public class CameraConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace a calibration in our list with the same unrotatedImageSize with a new one, or add it if
|
* Replace a calibration in our list with the same resolution with a new one, or add it if none
|
||||||
* none exists yet. If we are replacing an existing calibration, the old one will be "released"
|
* exists yet. If we are replacing an existing calibration, the old one will be "released" and the
|
||||||
* and the underlying data matrices will become invalid.
|
* underlying data matrices will become invalid.
|
||||||
*
|
*
|
||||||
* @param calibration The calibration to add.
|
* @param calibration The calibration to add.
|
||||||
*/
|
*/
|
||||||
public void addCalibration(CameraCalibrationCoefficients calibration) {
|
public void addCalibration(CameraCalibrationCoefficients calibration) {
|
||||||
logger.info("adding calibration " + calibration.unrotatedImageSize);
|
logger.info("adding calibration " + calibration.resolution);
|
||||||
calibrations.stream()
|
calibrations.stream()
|
||||||
.filter(it -> it.unrotatedImageSize.equals(calibration.unrotatedImageSize))
|
.filter(it -> it.resolution.equals(calibration.resolution))
|
||||||
.findAny()
|
.findAny()
|
||||||
.ifPresent(
|
.ifPresent(
|
||||||
(it) -> {
|
(it) -> {
|
||||||
@@ -194,12 +188,12 @@ public class CameraConfiguration {
|
|||||||
* Remove a calibration from our list. If found, the calibration will be "released". If not found,
|
* Remove a calibration from our list. If found, the calibration will be "released". If not found,
|
||||||
* no-op.
|
* no-op.
|
||||||
*
|
*
|
||||||
* @param unrotatedImageSize The resolution to remove.
|
* @param resolution The resolution to remove.
|
||||||
*/
|
*/
|
||||||
public void removeCalibration(Size unrotatedImageSize) {
|
public void removeCalibration(Size resolution) {
|
||||||
logger.info("deleting calibration " + unrotatedImageSize);
|
logger.info("deleting calibration " + resolution);
|
||||||
calibrations.stream()
|
calibrations.stream()
|
||||||
.filter(it -> it.unrotatedImageSize.equals(unrotatedImageSize))
|
.filter(it -> it.resolution.equals(resolution))
|
||||||
.findAny()
|
.findAny()
|
||||||
.ifPresent(
|
.ifPresent(
|
||||||
(it) -> {
|
(it) -> {
|
||||||
@@ -215,7 +209,6 @@ public class CameraConfiguration {
|
|||||||
*
|
*
|
||||||
* <p>This represents our best guess at an immutable path to detect a camera at.
|
* <p>This represents our best guess at an immutable path to detect a camera at.
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
|
||||||
public String getDevicePath() {
|
public String getDevicePath() {
|
||||||
return matchedCameraInfo.uniquePath();
|
return matchedCameraInfo.uniquePath();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,10 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
|
import io.avaje.json.JsonException;
|
||||||
|
import io.avaje.jsonb.Jsonb;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -34,7 +37,6 @@ import org.opencv.core.Size;
|
|||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.file.FileUtils;
|
import org.photonvision.common.util.file.FileUtils;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
import org.photonvision.vision.processes.VisionSource;
|
import org.photonvision.vision.processes.VisionSource;
|
||||||
import org.zeroturnaround.zip.ZipUtil;
|
import org.zeroturnaround.zip.ZipUtil;
|
||||||
|
|
||||||
@@ -233,14 +235,15 @@ public class ConfigManager {
|
|||||||
Path.of(getModelsDirectory().toString(), "photonvision-object-detection-models.json")
|
Path.of(getModelsDirectory().toString(), "photonvision-object-detection-models.json")
|
||||||
.toFile();
|
.toFile();
|
||||||
try {
|
try {
|
||||||
JacksonUtils.serialize(
|
Jsonb.instance()
|
||||||
tempProperties.toPath(), this.getConfig().neuralNetworkPropertyManager());
|
.type(NeuralNetworkModelsSettings.class)
|
||||||
|
.toJson(this.getConfig().getNeuralNetworkProperties(), new FileWriter(tempProperties));
|
||||||
ZipUtil.pack(getModelsDirectory(), out);
|
ZipUtil.pack(getModelsDirectory(), out);
|
||||||
// Now delete the tempProperties
|
// Now delete the tempProperties
|
||||||
if (tempProperties.exists()) {
|
if (tempProperties.exists()) {
|
||||||
Files.delete(tempProperties.toPath());
|
Files.delete(tempProperties.toPath());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -17,40 +17,50 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import io.avaje.jsonb.Json;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.photonvision.common.hardware.statusLED.StatusLEDType;
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@Json
|
||||||
public class HardwareConfig {
|
public class HardwareConfig {
|
||||||
public final String deviceName;
|
public String deviceName;
|
||||||
|
|
||||||
// LED control
|
// LED control
|
||||||
public final ArrayList<Integer> ledPins;
|
public List<Integer> ledPins;
|
||||||
public final boolean ledsCanDim;
|
public boolean ledsCanDim;
|
||||||
public final ArrayList<Integer> ledBrightnessRange;
|
public List<Integer> ledBrightnessRange;
|
||||||
public final int ledPWMFrequency;
|
public int ledPWMFrequency;
|
||||||
public final ArrayList<Integer> statusRGBPins;
|
public StatusLEDType statusLEDType;
|
||||||
public final boolean statusRGBActiveHigh;
|
|
||||||
|
// MIGRATION: 2026
|
||||||
|
@Json.Alias("statusRGBPins")
|
||||||
|
public List<Integer> statusLEDPins;
|
||||||
|
|
||||||
|
// MIGRATION: 2026
|
||||||
|
@Json.Alias("statusRGBActiveHigh")
|
||||||
|
public boolean statusLEDActiveHigh;
|
||||||
|
|
||||||
// Custom GPIO
|
// Custom GPIO
|
||||||
public final String getGPIOCommand;
|
public String getGPIOCommand;
|
||||||
public final String setGPIOCommand;
|
public String setGPIOCommand;
|
||||||
public final String setPWMCommand;
|
public String setPWMCommand;
|
||||||
public final String setPWMFrequencyCommand;
|
public String setPWMFrequencyCommand;
|
||||||
public final String releaseGPIOCommand;
|
public String releaseGPIOCommand;
|
||||||
|
|
||||||
// Device stuff
|
// Device stuff
|
||||||
public final String restartHardwareCommand;
|
public String restartHardwareCommand;
|
||||||
public final double vendorFOV; // -1 for unmanaged
|
public double vendorFOV; // -1 for unmanaged
|
||||||
|
|
||||||
public HardwareConfig(
|
public HardwareConfig(
|
||||||
String deviceName,
|
String deviceName,
|
||||||
ArrayList<Integer> ledPins,
|
List<Integer> ledPins,
|
||||||
boolean ledsCanDim,
|
boolean ledsCanDim,
|
||||||
ArrayList<Integer> ledBrightnessRange,
|
List<Integer> ledBrightnessRange,
|
||||||
int ledPwmFrequency,
|
int ledPwmFrequency,
|
||||||
ArrayList<Integer> statusRGBPins,
|
StatusLEDType statusLEDType,
|
||||||
boolean statusRGBActiveHigh,
|
List<Integer> statusLEDPins,
|
||||||
|
boolean statusLEDActiveHigh,
|
||||||
String getGPIOCommand,
|
String getGPIOCommand,
|
||||||
String setGPIOCommand,
|
String setGPIOCommand,
|
||||||
String setPWMCommand,
|
String setPWMCommand,
|
||||||
@@ -63,8 +73,9 @@ public class HardwareConfig {
|
|||||||
this.ledsCanDim = ledsCanDim;
|
this.ledsCanDim = ledsCanDim;
|
||||||
this.ledBrightnessRange = ledBrightnessRange;
|
this.ledBrightnessRange = ledBrightnessRange;
|
||||||
this.ledPWMFrequency = ledPwmFrequency;
|
this.ledPWMFrequency = ledPwmFrequency;
|
||||||
this.statusRGBPins = statusRGBPins;
|
this.statusLEDType = statusLEDType;
|
||||||
this.statusRGBActiveHigh = statusRGBActiveHigh;
|
this.statusLEDPins = statusLEDPins;
|
||||||
|
this.statusLEDActiveHigh = statusLEDActiveHigh;
|
||||||
this.getGPIOCommand = getGPIOCommand;
|
this.getGPIOCommand = getGPIOCommand;
|
||||||
this.setGPIOCommand = setGPIOCommand;
|
this.setGPIOCommand = setGPIOCommand;
|
||||||
this.setPWMCommand = setPWMCommand;
|
this.setPWMCommand = setPWMCommand;
|
||||||
@@ -80,8 +91,9 @@ public class HardwareConfig {
|
|||||||
ledsCanDim = false;
|
ledsCanDim = false;
|
||||||
ledBrightnessRange = new ArrayList<>();
|
ledBrightnessRange = new ArrayList<>();
|
||||||
ledPWMFrequency = 0;
|
ledPWMFrequency = 0;
|
||||||
statusRGBPins = new ArrayList<>();
|
statusLEDType = StatusLEDType.RGB;
|
||||||
statusRGBActiveHigh = false;
|
statusLEDPins = new ArrayList<>();
|
||||||
|
statusLEDActiveHigh = false;
|
||||||
getGPIOCommand = "";
|
getGPIOCommand = "";
|
||||||
setGPIOCommand = "";
|
setGPIOCommand = "";
|
||||||
setPWMCommand = "";
|
setPWMCommand = "";
|
||||||
@@ -121,10 +133,12 @@ public class HardwareConfig {
|
|||||||
+ ledBrightnessRange
|
+ ledBrightnessRange
|
||||||
+ ", ledPWMFrequency="
|
+ ", ledPWMFrequency="
|
||||||
+ ledPWMFrequency
|
+ ledPWMFrequency
|
||||||
+ ", statusRGBPins="
|
+ ", statusLEDType="
|
||||||
+ statusRGBPins
|
+ statusLEDType
|
||||||
+ ", statusRGBActiveHigh"
|
+ ", statusLEDPins="
|
||||||
+ statusRGBActiveHigh
|
+ statusLEDPins
|
||||||
|
+ ", statusLEDActiveHigh"
|
||||||
|
+ statusLEDActiveHigh
|
||||||
+ ", getGPIOCommand="
|
+ ", getGPIOCommand="
|
||||||
+ getGPIOCommand
|
+ getGPIOCommand
|
||||||
+ ", setGPIOCommand="
|
+ ", setGPIOCommand="
|
||||||
|
|||||||
@@ -17,10 +17,11 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import io.avaje.json.JsonException;
|
||||||
import edu.wpi.first.apriltag.AprilTagFieldLayout;
|
import io.avaje.jsonb.Jsonb;
|
||||||
import edu.wpi.first.apriltag.AprilTagFields;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -36,10 +37,9 @@ import java.util.stream.Stream;
|
|||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.file.FileUtils;
|
import org.photonvision.common.util.file.FileUtils;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
|
||||||
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
|
||||||
import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
|
||||||
import org.photonvision.vision.processes.VisionSource;
|
import org.photonvision.vision.processes.VisionSource;
|
||||||
|
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||||
|
import org.wpilib.vision.apriltag.AprilTagFields;
|
||||||
import org.zeroturnaround.zip.ZipUtil;
|
import org.zeroturnaround.zip.ZipUtil;
|
||||||
|
|
||||||
class LegacyConfigProvider extends ConfigProvider {
|
class LegacyConfigProvider extends ConfigProvider {
|
||||||
@@ -126,14 +126,13 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
AprilTagFieldLayout atfl = null;
|
AprilTagFieldLayout atfl = null;
|
||||||
|
|
||||||
if (hardwareConfigFile.exists()) {
|
if (hardwareConfigFile.exists()) {
|
||||||
try {
|
try (var stream = new FileInputStream(hardwareConfigFile)) {
|
||||||
hardwareConfig =
|
hardwareConfig = Jsonb.instance().type(HardwareConfig.class).fromJson(stream);
|
||||||
JacksonUtils.deserialize(hardwareConfigFile.toPath(), HardwareConfig.class);
|
|
||||||
if (hardwareConfig == null) {
|
if (hardwareConfig == null) {
|
||||||
logger.error("Could not deserialize hardware config! Loading defaults");
|
logger.error("Could not deserialize hardware config! Loading defaults");
|
||||||
hardwareConfig = new HardwareConfig();
|
hardwareConfig = new HardwareConfig();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not deserialize hardware config! Loading defaults");
|
logger.error("Could not deserialize hardware config! Loading defaults");
|
||||||
hardwareConfig = new HardwareConfig();
|
hardwareConfig = new HardwareConfig();
|
||||||
}
|
}
|
||||||
@@ -143,14 +142,13 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hardwareSettingsFile.exists()) {
|
if (hardwareSettingsFile.exists()) {
|
||||||
try {
|
try (var stream = new FileInputStream(hardwareSettingsFile)) {
|
||||||
hardwareSettings =
|
hardwareSettings = Jsonb.instance().type(HardwareSettings.class).fromJson(stream);
|
||||||
JacksonUtils.deserialize(hardwareSettingsFile.toPath(), HardwareSettings.class);
|
|
||||||
if (hardwareSettings == null) {
|
if (hardwareSettings == null) {
|
||||||
logger.error("Could not deserialize hardware settings! Loading defaults");
|
logger.error("Could not deserialize hardware settings! Loading defaults");
|
||||||
hardwareSettings = new HardwareSettings();
|
hardwareSettings = new HardwareSettings();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not deserialize hardware settings! Loading defaults");
|
logger.error("Could not deserialize hardware settings! Loading defaults");
|
||||||
hardwareSettings = new HardwareSettings();
|
hardwareSettings = new HardwareSettings();
|
||||||
}
|
}
|
||||||
@@ -160,13 +158,13 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (networkConfigFile.exists()) {
|
if (networkConfigFile.exists()) {
|
||||||
try {
|
try (var stream = new FileInputStream(networkConfigFile)) {
|
||||||
networkConfig = JacksonUtils.deserialize(networkConfigFile.toPath(), NetworkConfig.class);
|
networkConfig = Jsonb.instance().type(NetworkConfig.class).fromJson(stream);
|
||||||
if (networkConfig == null) {
|
if (networkConfig == null) {
|
||||||
logger.error("Could not deserialize network config! Loading defaults");
|
logger.error("Could not deserialize network config! Loading defaults");
|
||||||
networkConfig = new NetworkConfig();
|
networkConfig = new NetworkConfig();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not deserialize network config! Loading defaults");
|
logger.error("Could not deserialize network config! Loading defaults");
|
||||||
networkConfig = new NetworkConfig();
|
networkConfig = new NetworkConfig();
|
||||||
}
|
}
|
||||||
@@ -184,13 +182,12 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (apriltagFieldLayoutFile.exists()) {
|
if (apriltagFieldLayoutFile.exists()) {
|
||||||
try {
|
try (var stream = new FileInputStream(apriltagFieldLayoutFile)) {
|
||||||
atfl =
|
atfl = Jsonb.instance().type(AprilTagFieldLayout.class).fromJson(stream);
|
||||||
JacksonUtils.deserialize(apriltagFieldLayoutFile.toPath(), AprilTagFieldLayout.class);
|
|
||||||
if (atfl == null) {
|
if (atfl == null) {
|
||||||
logger.error("Could not deserialize apriltag field layout! (still null)");
|
logger.error("Could not deserialize apriltag field layout! (still null)");
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not deserialize apriltag field layout!", e);
|
logger.error("Could not deserialize apriltag field layout!", e);
|
||||||
atfl = null; // not required, nice to be explicit
|
atfl = null; // not required, nice to be explicit
|
||||||
}
|
}
|
||||||
@@ -227,14 +224,14 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
// Delete old configs
|
// Delete old configs
|
||||||
FileUtils.deleteDirectory(camerasFolder.toPath());
|
FileUtils.deleteDirectory(camerasFolder.toPath());
|
||||||
|
|
||||||
try {
|
try (var stream = new FileOutputStream(networkConfigFile)) {
|
||||||
JacksonUtils.serialize(networkConfigFile.toPath(), config.getNetworkConfig());
|
Jsonb.instance().type(NetworkConfig.class).toJson(config.getNetworkConfig(), stream);
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not save network config!", e);
|
logger.error("Could not save network config!", e);
|
||||||
}
|
}
|
||||||
try {
|
try (var stream = new FileOutputStream(hardwareSettingsFile)) {
|
||||||
JacksonUtils.serialize(hardwareSettingsFile.toPath(), config.getHardwareSettings());
|
Jsonb.instance().type(HardwareSettings.class).toJson(config.getHardwareSettings(), stream);
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not save hardware config!", e);
|
logger.error("Could not save hardware config!", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,33 +246,11 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
subdir.toFile().mkdirs();
|
subdir.toFile().mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try (var stream = new FileOutputStream(Path.of(subdir.toString(), "config.json").toFile())) {
|
||||||
JacksonUtils.serialize(Path.of(subdir.toString(), "config.json"), camConfig);
|
Jsonb.instance().type(CameraConfiguration.class).toJson(camConfig, stream);
|
||||||
} catch (IOException e) {
|
} catch (IOException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not save config.json for " + subdir, e);
|
logger.error("Could not save config.json for " + subdir, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
JacksonUtils.serialize(
|
|
||||||
Path.of(subdir.toString(), "drivermode.json"), camConfig.driveModeSettings);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Could not save drivermode.json for " + subdir, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var pipe : camConfig.pipelineSettings) {
|
|
||||||
var pipePath = Path.of(subdir.toString(), "pipelines", pipe.pipelineNickname + ".json");
|
|
||||||
|
|
||||||
if (!pipePath.getParent().toFile().exists()) {
|
|
||||||
// TODO: check for error
|
|
||||||
pipePath.getParent().toFile().mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
JacksonUtils.serialize(pipePath, pipe);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Could not save " + pipe.pipelineNickname + ".json!", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
logger.info("Settings saved!");
|
logger.info("Settings saved!");
|
||||||
return false; // TODO, deal with this. Do I need to?
|
return false; // TODO, deal with this. Do I need to?
|
||||||
@@ -289,11 +264,9 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
for (var subdir : subdirectories) {
|
for (var subdir : subdirectories) {
|
||||||
var cameraConfigPath = Path.of(subdir.toString(), "config.json");
|
var cameraConfigPath = Path.of(subdir.toString(), "config.json");
|
||||||
CameraConfiguration loadedConfig = null;
|
CameraConfiguration loadedConfig = null;
|
||||||
try {
|
try (var stream = new FileInputStream(cameraConfigPath.toFile())) {
|
||||||
loadedConfig =
|
loadedConfig = Jsonb.instance().type(CameraConfiguration.class).fromJson(stream);
|
||||||
JacksonUtils.deserialize(
|
} catch (IllegalStateException | JsonException e) {
|
||||||
cameraConfigPath.toAbsolutePath(), CameraConfiguration.class);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
logger.error("Camera config deserialization failed!", e);
|
logger.error("Camera config deserialization failed!", e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -302,63 +275,6 @@ class LegacyConfigProvider extends ConfigProvider {
|
|||||||
continue; // TODO how do we later try to load this camera if it gets reconnected?
|
continue; // TODO how do we later try to load this camera if it gets reconnected?
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we have only loaded the base stuff
|
|
||||||
// We still need to deserialize pipelines, as well as
|
|
||||||
// driver mode settings
|
|
||||||
var driverModeFile = Path.of(subdir.toString(), "drivermode.json");
|
|
||||||
DriverModePipelineSettings driverMode;
|
|
||||||
try {
|
|
||||||
driverMode =
|
|
||||||
JacksonUtils.deserialize(
|
|
||||||
driverModeFile.toAbsolutePath(), DriverModePipelineSettings.class);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
logger.error("Could not deserialize drivermode.json! Loading defaults");
|
|
||||||
logger.debug(Arrays.toString(e.getStackTrace()));
|
|
||||||
driverMode = new DriverModePipelineSettings();
|
|
||||||
}
|
|
||||||
if (driverMode == null) {
|
|
||||||
logger.warn(
|
|
||||||
"Could not load camera " + subdir + "'s drivermode.json! Loading" + " default");
|
|
||||||
driverMode = new DriverModePipelineSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load pipelines by mapping the files within the pipelines subdir
|
|
||||||
// to their deserialized equivalents
|
|
||||||
var pipelineSubdirectory = Path.of(subdir.toString(), "pipelines");
|
|
||||||
List<CVPipelineSettings> settings = Collections.emptyList();
|
|
||||||
if (pipelineSubdirectory.toFile().exists()) {
|
|
||||||
try (Stream<Path> subdirectoryFiles = Files.list(pipelineSubdirectory)) {
|
|
||||||
settings =
|
|
||||||
subdirectoryFiles
|
|
||||||
.filter(p -> p.toFile().isFile())
|
|
||||||
.map(
|
|
||||||
p -> {
|
|
||||||
var relativizedFilePath =
|
|
||||||
configDirectoryFile
|
|
||||||
.toPath()
|
|
||||||
.toAbsolutePath()
|
|
||||||
.relativize(p)
|
|
||||||
.toString();
|
|
||||||
try {
|
|
||||||
return JacksonUtils.deserialize(p, CVPipelineSettings.class);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
logger.error("Exception while deserializing " + relativizedFilePath, e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn(
|
|
||||||
"Could not load pipeline at "
|
|
||||||
+ relativizedFilePath
|
|
||||||
+ "! Skipping...");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedConfig.driveModeSettings = driverMode;
|
|
||||||
loadedConfig.addPipelineSettings(settings);
|
|
||||||
|
|
||||||
loadedConfigurations.put(subdir.toFile().getName(), loadedConfig);
|
loadedConfigurations.put(subdir.toFile().getName(), loadedConfig);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -17,16 +17,17 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import org.photonvision.common.hardware.Platform;
|
import org.photonvision.common.hardware.Platform;
|
||||||
import org.photonvision.common.networking.NetworkMode;
|
import org.photonvision.common.networking.NetworkMode;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class NetworkConfig {
|
public class NetworkConfig {
|
||||||
// Can be an integer team number, or an IP address
|
// Can be an integer team number, or an IP address
|
||||||
|
// MIGRATION: 2023
|
||||||
|
@Json.Alias("teamNumber")
|
||||||
public String ntServerAddress = "0";
|
public String ntServerAddress = "0";
|
||||||
|
|
||||||
public NetworkMode connectionType = NetworkMode.DHCP;
|
public NetworkMode connectionType = NetworkMode.DHCP;
|
||||||
public String staticIp = "";
|
public String staticIp = "";
|
||||||
public String hostname = "photonvision";
|
public String hostname = "photonvision";
|
||||||
@@ -34,8 +35,8 @@ public class NetworkConfig {
|
|||||||
public boolean shouldManage;
|
public boolean shouldManage;
|
||||||
public boolean shouldPublishProto = false;
|
public boolean shouldPublishProto = false;
|
||||||
|
|
||||||
@JsonIgnore public static final String NM_IFACE_STRING = "${interface}";
|
public static final String NM_IFACE_STRING = "${interface}";
|
||||||
@JsonIgnore public static final String NM_IP_STRING = "${ipaddr}";
|
public static final String NM_IP_STRING = "${ipaddr}";
|
||||||
|
|
||||||
public String networkManagerIface = "";
|
public String networkManagerIface = "";
|
||||||
// TODO: remove these strings if no longer needed
|
// TODO: remove these strings if no longer needed
|
||||||
@@ -50,19 +51,17 @@ public class NetworkConfig {
|
|||||||
setShouldManage(deviceCanManageNetwork());
|
setShouldManage(deviceCanManageNetwork());
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonCreator
|
|
||||||
public NetworkConfig(
|
public NetworkConfig(
|
||||||
@JsonProperty("ntServerAddress") @JsonAlias({"ntServerAddress", "teamNumber"})
|
String ntServerAddress,
|
||||||
String ntServerAddress,
|
NetworkMode connectionType,
|
||||||
@JsonProperty("connectionType") NetworkMode connectionType,
|
String staticIp,
|
||||||
@JsonProperty("staticIp") String staticIp,
|
String hostname,
|
||||||
@JsonProperty("hostname") String hostname,
|
boolean runNTServer,
|
||||||
@JsonProperty("runNTServer") boolean runNTServer,
|
boolean shouldManage,
|
||||||
@JsonProperty("shouldManage") boolean shouldManage,
|
boolean shouldPublishProto,
|
||||||
@JsonProperty("shouldPublishProto") boolean shouldPublishProto,
|
String networkManagerIface,
|
||||||
@JsonProperty("networkManagerIface") String networkManagerIface,
|
String setStaticCommand,
|
||||||
@JsonProperty("setStaticCommand") String setStaticCommand,
|
String setDHCPcommand) {
|
||||||
@JsonProperty("setDHCPcommand") String setDHCPcommand) {
|
|
||||||
this.ntServerAddress = ntServerAddress;
|
this.ntServerAddress = ntServerAddress;
|
||||||
this.connectionType = connectionType;
|
this.connectionType = connectionType;
|
||||||
this.staticIp = staticIp;
|
this.staticIp = staticIp;
|
||||||
@@ -89,12 +88,10 @@ public class NetworkConfig {
|
|||||||
config.setDHCPcommand);
|
config.setDHCPcommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public String getPhysicalInterfaceName() {
|
public String getPhysicalInterfaceName() {
|
||||||
return this.networkManagerIface;
|
return this.networkManagerIface;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public String getEscapedInterfaceName() {
|
public String getEscapedInterfaceName() {
|
||||||
return "\"" + networkManagerIface + "\"";
|
return "\"" + networkManagerIface + "\"";
|
||||||
}
|
}
|
||||||
@@ -103,7 +100,6 @@ public class NetworkConfig {
|
|||||||
this.shouldManage = shouldManage && this.deviceCanManageNetwork();
|
this.shouldManage = shouldManage && this.deviceCanManageNetwork();
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
protected boolean deviceCanManageNetwork() {
|
protected boolean deviceCanManageNetwork() {
|
||||||
return Platform.isLinux();
|
return Platform.isLinux();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ public class NeuralNetworkModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ModelProperties properties =
|
ModelProperties properties =
|
||||||
ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().getModel(path);
|
ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().getModel(path);
|
||||||
|
|
||||||
if (properties == null) {
|
if (properties == null) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
@@ -332,7 +332,7 @@ public class NeuralNetworkModelManager {
|
|||||||
// NeuralNetworkModelsSettings
|
// NeuralNetworkModelsSettings
|
||||||
ConfigManager.getInstance()
|
ConfigManager.getInstance()
|
||||||
.getConfig()
|
.getConfig()
|
||||||
.neuralNetworkPropertyManager()
|
.getNeuralNetworkProperties()
|
||||||
.addModelProperties(properties);
|
.addModelProperties(properties);
|
||||||
} catch (IllegalArgumentException | IOException e) {
|
} catch (IllegalArgumentException | IOException e) {
|
||||||
logger.error("Failed to translate legacy model filename to properties: " + path, e);
|
logger.error("Failed to translate legacy model filename to properties: " + path, e);
|
||||||
@@ -486,7 +486,7 @@ public class NeuralNetworkModelManager {
|
|||||||
.getConfig()
|
.getConfig()
|
||||||
.setNeuralNetworkProperties(
|
.setNeuralNetworkProperties(
|
||||||
supportedProperties.sum(
|
supportedProperties.sum(
|
||||||
ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager()));
|
ConfigManager.getInstance().getConfig().getNeuralNetworkProperties()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean clearModels() {
|
public boolean clearModels() {
|
||||||
@@ -511,7 +511,7 @@ public class NeuralNetworkModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete model info
|
// Delete model info
|
||||||
return ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().clear();
|
return ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public File exportSingleModel(String modelPath) {
|
public File exportSingleModel(String modelPath) {
|
||||||
@@ -525,7 +525,7 @@ public class NeuralNetworkModelManager {
|
|||||||
ModelProperties properties =
|
ModelProperties properties =
|
||||||
ConfigManager.getInstance()
|
ConfigManager.getInstance()
|
||||||
.getConfig()
|
.getConfig()
|
||||||
.neuralNetworkPropertyManager()
|
.getNeuralNetworkProperties()
|
||||||
.getModel(Path.of(modelPath));
|
.getModel(Path.of(modelPath));
|
||||||
|
|
||||||
String fileName = "";
|
String fileName = "";
|
||||||
|
|||||||
@@ -17,9 +17,10 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import io.avaje.jsonb.Json;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import io.avaje.jsonb.JsonType;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import io.avaje.jsonb.Types;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -27,27 +28,29 @@ import java.nio.file.Paths;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
|
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
|
||||||
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
|
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class NeuralNetworkModelsSettings {
|
public class NeuralNetworkModelsSettings {
|
||||||
/*
|
/*
|
||||||
* The properties of the model. This is used to determine which model to load.
|
* The properties of the model. This is used to determine which model to load.
|
||||||
* The only families currently supported are RKNN and Rubik (custom .tflite)
|
* The only families currently supported are RKNN and Rubik (custom .tflite)
|
||||||
*/
|
*/
|
||||||
|
@Json
|
||||||
public record ModelProperties(
|
public record ModelProperties(
|
||||||
@JsonProperty("modelPath") Path modelPath,
|
Path modelPath,
|
||||||
@JsonProperty("nickname") String nickname,
|
String nickname,
|
||||||
@JsonProperty("labels") List<String> labels,
|
List<String> labels,
|
||||||
@JsonProperty("resolutionWidth") int resolutionWidth,
|
int resolutionWidth,
|
||||||
@JsonProperty("resolutionHeight") int resolutionHeight,
|
int resolutionHeight,
|
||||||
@JsonProperty("family") Family family,
|
Family family,
|
||||||
@JsonProperty("version") Version version) {
|
Version version) {
|
||||||
@JsonCreator
|
|
||||||
public ModelProperties {}
|
|
||||||
|
|
||||||
ModelProperties(ModelProperties other) {
|
ModelProperties(ModelProperties other) {
|
||||||
this(
|
this(
|
||||||
other.modelPath,
|
other.modelPath,
|
||||||
@@ -59,13 +62,6 @@ public class NeuralNetworkModelsSettings {
|
|||||||
other.version);
|
other.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In v2025.3.1, this was single string for the model path. but the first argument
|
|
||||||
// is now nickname
|
|
||||||
public ModelProperties(@JsonProperty("nickname") String filename)
|
|
||||||
throws IllegalArgumentException, IOException {
|
|
||||||
this(createFromFilename(filename));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============= Migration code from v2025.3.1 ===========
|
// ============= Migration code from v2025.3.1 ===========
|
||||||
|
|
||||||
private static Pattern modelPattern =
|
private static Pattern modelPattern =
|
||||||
@@ -160,25 +156,58 @@ public class NeuralNetworkModelsSettings {
|
|||||||
|
|
||||||
// The path to the model is used as the key in the map because it is unique to
|
// The path to the model is used as the key in the map because it is unique to
|
||||||
// the model, and should not change
|
// the model, and should not change
|
||||||
@JsonProperty("modelPathToProperties")
|
@Json.Ignore
|
||||||
private HashMap<Path, ModelProperties> modelPathToProperties =
|
private HashMap<Path, ModelProperties> modelPathToProperties =
|
||||||
new HashMap<Path, ModelProperties>();
|
new HashMap<Path, ModelProperties>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the NeuralNetworkProperties class.
|
* Constructor for the NeuralNetworkProperties class.
|
||||||
*
|
*
|
||||||
* <p>This object holds a LinkedList of {@link ModelProperties} objects
|
* <p>This object holds a HashMap of {@link ModelProperties} objects
|
||||||
*/
|
*/
|
||||||
public NeuralNetworkModelsSettings() {}
|
public NeuralNetworkModelsSettings() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the NeuralNetworkProperties class.
|
* Constructor for the NeuralNetworkProperties class.
|
||||||
*
|
*
|
||||||
* <p>This object holds a LinkedList of {@link ModelProperties} objects.
|
* <p>This object holds a HashMap of {@link ModelProperties} objects.
|
||||||
|
*
|
||||||
|
* @param modelPropertiesMap When the class is constructed, it will hold the provided map
|
||||||
|
*/
|
||||||
|
public NeuralNetworkModelsSettings(HashMap<Path, ModelProperties> modelPropertiesMap) {
|
||||||
|
modelPathToProperties = modelPropertiesMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the NeuralNetworkProperties class.
|
||||||
|
*
|
||||||
|
* <p>This object holds a HashMap of {@link ModelProperties} objects.
|
||||||
*
|
*
|
||||||
* @param modelPropertiesList When the class is constructed, it will hold the provided list
|
* @param modelPropertiesList When the class is constructed, it will hold the provided list
|
||||||
*/
|
*/
|
||||||
public NeuralNetworkModelsSettings(HashMap<Path, ModelProperties> modelPropertiesList) {}
|
@Json.Creator
|
||||||
|
public NeuralNetworkModelsSettings(
|
||||||
|
ModelProperties[] models, @Json.Unmapped Map<String, Object> unmapped) {
|
||||||
|
JsonType<Map<String, ModelProperties>> modelPropsMapJsonb =
|
||||||
|
Jsonb.instance().type(Types.mapOf(ModelProperties.class));
|
||||||
|
Stream<ModelProperties> modelPropsStream;
|
||||||
|
if (models != null) {
|
||||||
|
modelPropsStream = Arrays.stream(models);
|
||||||
|
} else if (unmapped.containsKey("modelPathToProperties")) {
|
||||||
|
// MIGRATION: 2026
|
||||||
|
modelPropsStream =
|
||||||
|
modelPropsMapJsonb.fromObject(unmapped.get("modelPathToProperties")).values().stream();
|
||||||
|
} else {
|
||||||
|
modelPropsStream = Stream.empty();
|
||||||
|
}
|
||||||
|
this(
|
||||||
|
modelPropsStream.collect(
|
||||||
|
Collectors.toMap(
|
||||||
|
(model) -> model.modelPath(),
|
||||||
|
(model) -> model,
|
||||||
|
(prev, next) -> next,
|
||||||
|
HashMap::new)));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
@@ -239,7 +268,7 @@ public class NeuralNetworkModelsSettings {
|
|||||||
*
|
*
|
||||||
* @return A list of all models
|
* @return A list of all models
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
@Json.Property("models")
|
||||||
public ModelProperties[] getModels() {
|
public ModelProperties[] getModels() {
|
||||||
return modelPathToProperties.values().toArray(new ModelProperties[0]);
|
return modelPathToProperties.values().toArray(new ModelProperties[0]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,19 +17,25 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import edu.wpi.first.apriltag.AprilTagFieldLayout;
|
import io.avaje.jsonb.Json;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import org.photonvision.vision.processes.VisionSource;
|
import org.photonvision.vision.processes.VisionSource;
|
||||||
|
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||||
|
|
||||||
|
@Json
|
||||||
public class PhotonConfiguration {
|
public class PhotonConfiguration {
|
||||||
private final HardwareConfig hardwareConfig;
|
private final HardwareConfig hardwareConfig;
|
||||||
private final HardwareSettings hardwareSettings;
|
private final HardwareSettings hardwareSettings;
|
||||||
private NetworkConfig networkConfig;
|
private NetworkConfig networkConfig;
|
||||||
private AprilTagFieldLayout atfl;
|
|
||||||
|
@Json.Property("atfl")
|
||||||
|
private AprilTagFieldLayout aprilTagFieldLayout;
|
||||||
|
|
||||||
private NeuralNetworkModelsSettings neuralNetworkProperties;
|
private NeuralNetworkModelsSettings neuralNetworkProperties;
|
||||||
private HashMap<String, CameraConfiguration> cameraConfigurations;
|
private Map<String, CameraConfiguration> cameraConfigurations;
|
||||||
|
|
||||||
public PhotonConfiguration(
|
public PhotonConfiguration(
|
||||||
HardwareConfig hardwareConfig,
|
HardwareConfig hardwareConfig,
|
||||||
@@ -46,19 +52,20 @@ public class PhotonConfiguration {
|
|||||||
new HashMap<>());
|
new HashMap<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Json.Creator
|
||||||
public PhotonConfiguration(
|
public PhotonConfiguration(
|
||||||
HardwareConfig hardwareConfig,
|
HardwareConfig hardwareConfig,
|
||||||
HardwareSettings hardwareSettings,
|
HardwareSettings hardwareSettings,
|
||||||
NetworkConfig networkConfig,
|
NetworkConfig networkConfig,
|
||||||
AprilTagFieldLayout atfl,
|
AprilTagFieldLayout atfl,
|
||||||
NeuralNetworkModelsSettings neuralNetworkProperties,
|
NeuralNetworkModelsSettings neuralNetworkProperties,
|
||||||
HashMap<String, CameraConfiguration> cameraConfigurations) {
|
Map<String, CameraConfiguration> cameraConfigurations) {
|
||||||
this.hardwareConfig = hardwareConfig;
|
this.hardwareConfig = hardwareConfig;
|
||||||
this.hardwareSettings = hardwareSettings;
|
this.hardwareSettings = hardwareSettings;
|
||||||
this.networkConfig = networkConfig;
|
this.networkConfig = networkConfig;
|
||||||
this.neuralNetworkProperties = neuralNetworkProperties;
|
this.neuralNetworkProperties = neuralNetworkProperties;
|
||||||
this.cameraConfigurations = cameraConfigurations;
|
this.cameraConfigurations = cameraConfigurations;
|
||||||
this.atfl = atfl;
|
this.aprilTagFieldLayout = atfl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PhotonConfiguration() {
|
public PhotonConfiguration() {
|
||||||
@@ -83,15 +90,15 @@ public class PhotonConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AprilTagFieldLayout getApriltagFieldLayout() {
|
public AprilTagFieldLayout getApriltagFieldLayout() {
|
||||||
return atfl;
|
return aprilTagFieldLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NeuralNetworkModelsSettings neuralNetworkPropertyManager() {
|
public NeuralNetworkModelsSettings getNeuralNetworkProperties() {
|
||||||
return neuralNetworkProperties;
|
return neuralNetworkProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setApriltagFieldLayout(AprilTagFieldLayout atfl) {
|
public void setApriltagFieldLayout(AprilTagFieldLayout atfl) {
|
||||||
this.atfl = atfl;
|
this.aprilTagFieldLayout = atfl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNetworkConfig(NetworkConfig networkConfig) {
|
public void setNetworkConfig(NetworkConfig networkConfig) {
|
||||||
@@ -102,7 +109,7 @@ public class PhotonConfiguration {
|
|||||||
this.neuralNetworkProperties = neuralNetworkProperties;
|
this.neuralNetworkProperties = neuralNetworkProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HashMap<String, CameraConfiguration> getCameraConfigurations() {
|
public Map<String, CameraConfiguration> getCameraConfigurations() {
|
||||||
return cameraConfigurations;
|
return cameraConfigurations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +155,8 @@ public class PhotonConfiguration {
|
|||||||
+ hardwareSettings
|
+ hardwareSettings
|
||||||
+ "\n networkConfig="
|
+ "\n networkConfig="
|
||||||
+ networkConfig
|
+ networkConfig
|
||||||
+ "\n atfl="
|
+ "\n aprilTagFieldLayout="
|
||||||
+ atfl
|
+ aprilTagFieldLayout
|
||||||
+ "\n neuralNetworkProperties="
|
+ "\n neuralNetworkProperties="
|
||||||
+ neuralNetworkProperties
|
+ neuralNetworkProperties
|
||||||
+ "\n cameraConfigurations={"
|
+ "\n cameraConfigurations={"
|
||||||
|
|||||||
@@ -17,29 +17,32 @@
|
|||||||
|
|
||||||
package org.photonvision.common.configuration;
|
package org.photonvision.common.configuration;
|
||||||
|
|
||||||
import edu.wpi.first.apriltag.AprilTagFieldLayout;
|
import io.avaje.json.JsonException;
|
||||||
import edu.wpi.first.apriltag.AprilTagFields;
|
import io.avaje.jsonb.JsonType;
|
||||||
import edu.wpi.first.cscore.UsbCameraInfo;
|
import io.avaje.jsonb.Jsonb;
|
||||||
|
import io.avaje.jsonb.Types;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration.LegacyCameraConfigStruct;
|
import org.photonvision.common.configuration.CameraConfiguration.LegacyCameraConfigStruct;
|
||||||
import org.photonvision.common.configuration.DatabaseSchema.Columns;
|
import org.photonvision.common.configuration.DatabaseSchema.Columns;
|
||||||
import org.photonvision.common.configuration.DatabaseSchema.Tables;
|
import org.photonvision.common.configuration.DatabaseSchema.Tables;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
import org.photonvision.vision.camera.PVCameraInfo;
|
||||||
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
import org.photonvision.vision.pipeline.CVPipelineSettings;
|
||||||
import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
||||||
|
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||||
|
import org.wpilib.vision.apriltag.AprilTagFields;
|
||||||
|
import org.wpilib.vision.camera.UsbCameraInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves settings in a SQLite database file (called photon.sqlite).
|
* Saves settings in a SQLite database file (called photon.sqlite).
|
||||||
@@ -260,16 +263,16 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
T configObj;
|
T configObj;
|
||||||
if (!configString.isBlank()) {
|
if (!configString.isBlank()) {
|
||||||
try {
|
try {
|
||||||
configObj = JacksonUtils.deserialize(configString, ref);
|
configObj = Jsonb.instance().type(ref).fromJson(configString);
|
||||||
logger.info("Loaded " + ref.getSimpleName() + " from database");
|
logger.info("Loaded " + ref.getSimpleName() + " from database");
|
||||||
return configObj;
|
return configObj;
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
logger.error("Could not deserialize " + ref.getSimpleName() + " from database!", e);
|
logger.error("Could not deserialize " + ref.getSimpleName() + " from database!", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debug("No " + ref.getSimpleName() + " in database");
|
logger.debug("No " + ref.getSimpleName() + " in database");
|
||||||
}
|
}
|
||||||
// either the config entry is empty or Jackson threw an exception
|
// either the config entry is empty or Jsonb threw an exception
|
||||||
try {
|
try {
|
||||||
configObj = factory.get();
|
configObj = factory.get();
|
||||||
logger.info("Loaded default " + ref.getSimpleName());
|
logger.info("Loaded default " + ref.getSimpleName());
|
||||||
@@ -390,30 +393,16 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
|
|
||||||
var config = c.getValue();
|
var config = c.getValue();
|
||||||
statement.setString(1, c.getKey());
|
statement.setString(1, c.getKey());
|
||||||
statement.setString(2, JacksonUtils.serializeToString(config));
|
statement.setString(2, Jsonb.instance().type(CameraConfiguration.class).toJson(config));
|
||||||
statement.setString(3, JacksonUtils.serializeToString(config.driveModeSettings));
|
|
||||||
|
|
||||||
// Serializing a list of abstract classes sucks. Instead, make it into an array
|
// MIGRATION: 2026
|
||||||
// of strings, which we can later unpack back into individual settings
|
// We used to serialize pipelines separately, but don't anymore
|
||||||
List<String> settings =
|
statement.setString(3, "null");
|
||||||
config.pipelineSettings.stream()
|
statement.setString(4, "[]");
|
||||||
.map(
|
|
||||||
it -> {
|
|
||||||
try {
|
|
||||||
return JacksonUtils.serializeToString(it);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.toList();
|
|
||||||
statement.setString(4, JacksonUtils.serializeToString(settings));
|
|
||||||
|
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
|
} catch (SQLException | IllegalStateException | JsonException e) {
|
||||||
} catch (SQLException | IOException e) {
|
|
||||||
logger.error("Err saving cameras", e);
|
logger.error("Err saving cameras", e);
|
||||||
try {
|
try {
|
||||||
conn.rollback();
|
conn.rollback();
|
||||||
@@ -469,7 +458,7 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
addFile(
|
addFile(
|
||||||
statement1,
|
statement1,
|
||||||
GlobalKeys.HARDWARE_SETTINGS,
|
GlobalKeys.HARDWARE_SETTINGS,
|
||||||
JacksonUtils.serializeToString(config.getHardwareSettings()));
|
Jsonb.instance().type(HardwareSettings.class).toJson(config.getHardwareSettings()));
|
||||||
statement1.executeUpdate();
|
statement1.executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +467,7 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
addFile(
|
addFile(
|
||||||
statement2,
|
statement2,
|
||||||
GlobalKeys.NETWORK_CONFIG,
|
GlobalKeys.NETWORK_CONFIG,
|
||||||
JacksonUtils.serializeToString(config.getNetworkConfig()));
|
Jsonb.instance().type(NetworkConfig.class).toJson(config.getNetworkConfig()));
|
||||||
statement2.executeUpdate();
|
statement2.executeUpdate();
|
||||||
statement2.close();
|
statement2.close();
|
||||||
}
|
}
|
||||||
@@ -488,7 +477,7 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
addFile(
|
addFile(
|
||||||
statement3,
|
statement3,
|
||||||
GlobalKeys.HARDWARE_CONFIG,
|
GlobalKeys.HARDWARE_CONFIG,
|
||||||
JacksonUtils.serializeToString(config.getHardwareConfig()));
|
Jsonb.instance().type(HardwareConfig.class).toJson(config.getHardwareConfig()));
|
||||||
statement3.executeUpdate();
|
statement3.executeUpdate();
|
||||||
statement3.close();
|
statement3.close();
|
||||||
}
|
}
|
||||||
@@ -498,12 +487,14 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
addFile(
|
addFile(
|
||||||
statement3,
|
statement3,
|
||||||
GlobalKeys.NEURAL_NETWORK_PROPERTIES,
|
GlobalKeys.NEURAL_NETWORK_PROPERTIES,
|
||||||
JacksonUtils.serializeToString(config.neuralNetworkPropertyManager()));
|
Jsonb.instance()
|
||||||
|
.type(NeuralNetworkModelsSettings.class)
|
||||||
|
.toJson(config.getNeuralNetworkProperties()));
|
||||||
statement3.executeUpdate();
|
statement3.executeUpdate();
|
||||||
statement3.close();
|
statement3.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SQLException | IOException e) {
|
} catch (SQLException | IllegalStateException | JsonException e) {
|
||||||
logger.error("Err saving global", e);
|
logger.error("Err saving global", e);
|
||||||
try {
|
try {
|
||||||
conn.rollback();
|
conn.rollback();
|
||||||
@@ -594,6 +585,12 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
private HashMap<String, CameraConfiguration> loadCameraConfigs(Connection conn) {
|
private HashMap<String, CameraConfiguration> loadCameraConfigs(Connection conn) {
|
||||||
HashMap<String, CameraConfiguration> loadedConfigurations = new HashMap<>();
|
HashMap<String, CameraConfiguration> loadedConfigurations = new HashMap<>();
|
||||||
|
|
||||||
|
// MIGRATION: 2026
|
||||||
|
// This is designed to always match for efficiency reasons, so that the whole camera config
|
||||||
|
// isn't scanned. The second capture group determines if the camera info is in the old or new
|
||||||
|
// format
|
||||||
|
final var cameraInfoPattern = Pattern.compile("\"(PV[\\w.]*CameraInfo)\"\\s*(:?)");
|
||||||
|
|
||||||
// Query every single row of the cameras db
|
// Query every single row of the cameras db
|
||||||
PreparedStatement query = null;
|
PreparedStatement query = null;
|
||||||
try {
|
try {
|
||||||
@@ -614,57 +611,82 @@ public class SqlConfigProvider extends ConfigProvider {
|
|||||||
while (result.next()) {
|
while (result.next()) {
|
||||||
String uniqueName = "";
|
String uniqueName = "";
|
||||||
try {
|
try {
|
||||||
List<String> dummyList = new ArrayList<>();
|
JsonType<List<String>> strListJsonb = Jsonb.instance().type(Types.listOf(String.class));
|
||||||
|
|
||||||
uniqueName = result.getString(Columns.CAM_UNIQUE_NAME);
|
uniqueName = result.getString(Columns.CAM_UNIQUE_NAME);
|
||||||
|
|
||||||
// A horrifying hack to keep backward compat with otherpaths
|
// A horrifying hack to keep backward compat with otherpaths
|
||||||
// We -really- need to delete this -stupid- otherpaths column. I hate it.
|
// We -really- need to delete this -stupid- otherpaths column. I hate it.
|
||||||
var configStr = result.getString(Columns.CAM_CONFIG_JSON);
|
// MIGRATION: 2024
|
||||||
CameraConfiguration config =
|
var configJson = result.getString(Columns.CAM_CONFIG_JSON);
|
||||||
JacksonUtils.deserialize(configStr, CameraConfiguration.class);
|
|
||||||
|
|
||||||
|
// MIGRATION: 2026
|
||||||
|
var cameraInfoMatcher = cameraInfoPattern.matcher(configJson);
|
||||||
|
if (cameraInfoMatcher.find() && cameraInfoMatcher.group(2).equals(":")) {
|
||||||
|
logger.info("Legacy type-wrapper PVCameraInfo being migrated");
|
||||||
|
configJson = PVCameraInfo.remapConfigJson(configJson, cameraInfoMatcher.group(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraConfiguration config =
|
||||||
|
Jsonb.instance().type(CameraConfiguration.class).fromJson(configJson);
|
||||||
|
|
||||||
|
// MIGRATION: 2024
|
||||||
if (config.matchedCameraInfo == null) {
|
if (config.matchedCameraInfo == null) {
|
||||||
logger.info("Legacy CameraConfiguration detected - upgrading");
|
logger.info("Legacy CameraConfiguration detected - upgrading");
|
||||||
|
|
||||||
// manually create the matchedCameraInfo ourselves. Need to upgrade:
|
// manually create the matchedCameraInfo ourselves. Need to upgrade:
|
||||||
// baseName, path, otherPaths, cameraType, usbvid/pid -> matchedCameraInfo
|
// baseName, path, otherPaths, cameraType, usbvid/pid -> matchedCameraInfo
|
||||||
config.matchedCameraInfo =
|
config.matchedCameraInfo =
|
||||||
JacksonUtils.deserialize(configStr, LegacyCameraConfigStruct.class)
|
Jsonb.instance()
|
||||||
|
.type(LegacyCameraConfigStruct.class)
|
||||||
|
.fromJson(configJson)
|
||||||
.matchedCameraInfo;
|
.matchedCameraInfo;
|
||||||
|
|
||||||
// Except that otherPaths used to be its own column. so hack that in here as well
|
// Except that otherPaths used to be its own column. so hack that in here as well
|
||||||
var otherPaths =
|
var otherPaths =
|
||||||
JacksonUtils.deserialize(
|
Jsonb.instance()
|
||||||
result.getString(Columns.CAM_OTHERPATHS_JSON), String[].class);
|
.type(String[].class)
|
||||||
|
.fromJson(result.getString(Columns.CAM_OTHERPATHS_JSON));
|
||||||
if (config.matchedCameraInfo instanceof UsbCameraInfo usbInfo) {
|
if (config.matchedCameraInfo instanceof UsbCameraInfo usbInfo) {
|
||||||
usbInfo.otherPaths = otherPaths;
|
usbInfo.otherPaths = otherPaths;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var driverMode =
|
// MIGRATION: 2026
|
||||||
JacksonUtils.deserialize(
|
List<String> legacyPipelineSettings =
|
||||||
result.getString(Columns.CAM_DRIVERMODE_JSON), DriverModePipelineSettings.class);
|
strListJsonb.fromJson(result.getString(Columns.CAM_PIPELINE_JSONS));
|
||||||
List<?> pipelineSettings =
|
|
||||||
JacksonUtils.deserialize(
|
|
||||||
result.getString(Columns.CAM_PIPELINE_JSONS), dummyList.getClass());
|
|
||||||
|
|
||||||
List<CVPipelineSettings> loadedSettings = new ArrayList<>();
|
for (var pipelineJson : legacyPipelineSettings) {
|
||||||
for (var setting : pipelineSettings) {
|
logger.info("Importing pipeline JSON into camera settings");
|
||||||
if (setting instanceof String str) {
|
if (pipelineJson.startsWith("[")) {
|
||||||
try {
|
logger.info("Legacy type-wrapper CVPipelineSettings being migrated");
|
||||||
loadedSettings.add(JacksonUtils.deserialize(str, CVPipelineSettings.class));
|
pipelineJson = CVPipelineSettings.remapSettingsJson(pipelineJson);
|
||||||
} catch (IOException e) {
|
}
|
||||||
logger.error(
|
|
||||||
"Could not deserialize pipeline setting for camera " + config.nickname, e);
|
try {
|
||||||
}
|
config.pipelineSettings.add(
|
||||||
|
Jsonb.instance().type(CVPipelineSettings.class).fromJson(pipelineJson));
|
||||||
|
} catch (IllegalStateException | JsonException e) {
|
||||||
|
logger.error(
|
||||||
|
"Could not deserialize pipeline setting for camera " + config.nickname, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.pipelineSettings = loadedSettings;
|
// MIGRATION: 2026
|
||||||
config.driveModeSettings = driverMode;
|
if (config.driveModeSettings == null) {
|
||||||
|
logger.info("Importing driver mode JSON into camera settings");
|
||||||
|
var driverModeJson = result.getString(Columns.CAM_DRIVERMODE_JSON);
|
||||||
|
if (driverModeJson.startsWith("[")) {
|
||||||
|
logger.info("Legacy type-wrapper CVPipelineSettings being migrated");
|
||||||
|
driverModeJson = CVPipelineSettings.remapSettingsJson(driverModeJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.driveModeSettings =
|
||||||
|
Jsonb.instance().type(DriverModePipelineSettings.class).fromJson(driverModeJson);
|
||||||
|
}
|
||||||
|
|
||||||
loadedConfigurations.put(uniqueName, config);
|
loadedConfigurations.put(uniqueName, config);
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Could not deserialize camera configuration " + uniqueName + " from database!", e);
|
"Could not deserialize camera configuration " + uniqueName + " from database!", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public abstract class DataChangeSubscriber {
|
|||||||
this(DataChangeSource.AllSources, wantedDestinations);
|
this(DataChangeSource.AllSources, wantedDestinations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void onDataChangeEvent(DataChangeEvent<?> event);
|
public abstract <T> void onDataChangeEvent(DataChangeEvent<T> event);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.networktables;
|
package org.photonvision.common.dataflow.networktables;
|
||||||
|
|
||||||
import edu.wpi.first.networktables.NetworkTableEvent;
|
|
||||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
|
||||||
import edu.wpi.first.networktables.Subscriber;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import org.wpilib.networktables.NetworkTableEvent;
|
||||||
|
import org.wpilib.networktables.NetworkTableInstance;
|
||||||
|
import org.wpilib.networktables.Subscriber;
|
||||||
|
|
||||||
public class NTDataChangeListener {
|
public class NTDataChangeListener {
|
||||||
private final NetworkTableInstance instance;
|
private final NetworkTableInstance instance;
|
||||||
@@ -36,7 +36,7 @@ public class NTDataChangeListener {
|
|||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
listenerID =
|
listenerID =
|
||||||
this.instance.addListener(
|
this.instance.addListener(
|
||||||
watchedEntry, EnumSet.of(NetworkTableEvent.Kind.kValueAll), dataChangeConsumer);
|
watchedEntry, EnumSet.of(NetworkTableEvent.Kind.VALUE_ALL), dataChangeConsumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove() {
|
public void remove() {
|
||||||
|
|||||||
@@ -17,10 +17,6 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.networktables;
|
package org.photonvision.common.dataflow.networktables;
|
||||||
|
|
||||||
import edu.wpi.first.math.geometry.Transform3d;
|
|
||||||
import edu.wpi.first.networktables.NetworkTable;
|
|
||||||
import edu.wpi.first.networktables.NetworkTableEvent;
|
|
||||||
import edu.wpi.first.networktables.NetworkTablesJNI;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -35,6 +31,10 @@ import org.photonvision.targeting.PhotonPipelineResult;
|
|||||||
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
||||||
import org.photonvision.vision.pipeline.result.CalibrationPipelineResult;
|
import org.photonvision.vision.pipeline.result.CalibrationPipelineResult;
|
||||||
import org.photonvision.vision.target.TrackedTarget;
|
import org.photonvision.vision.target.TrackedTarget;
|
||||||
|
import org.wpilib.math.geometry.Transform3d;
|
||||||
|
import org.wpilib.networktables.NetworkTable;
|
||||||
|
import org.wpilib.networktables.NetworkTableEvent;
|
||||||
|
import org.wpilib.networktables.NetworkTablesJNI;
|
||||||
|
|
||||||
public class NTDataPublisher implements CVPipelineResultConsumer {
|
public class NTDataPublisher implements CVPipelineResultConsumer {
|
||||||
private final Logger logger = new Logger(NTDataPublisher.class, LogGroup.General);
|
private final Logger logger = new Logger(NTDataPublisher.class, LogGroup.General);
|
||||||
@@ -55,6 +55,10 @@ public class NTDataPublisher implements CVPipelineResultConsumer {
|
|||||||
private final Consumer<Integer> fpsLimitConsumer;
|
private final Consumer<Integer> fpsLimitConsumer;
|
||||||
private final Supplier<Integer> fpsLimitSupplier;
|
private final Supplier<Integer> fpsLimitSupplier;
|
||||||
|
|
||||||
|
NTDataChangeListener isEnabledListener;
|
||||||
|
private final Consumer<Boolean> isEnabledConsumer;
|
||||||
|
private final BooleanSupplier enabledSupplier;
|
||||||
|
|
||||||
public NTDataPublisher(
|
public NTDataPublisher(
|
||||||
String cameraNickname,
|
String cameraNickname,
|
||||||
Supplier<Integer> pipelineIndexSupplier,
|
Supplier<Integer> pipelineIndexSupplier,
|
||||||
@@ -62,13 +66,17 @@ public class NTDataPublisher implements CVPipelineResultConsumer {
|
|||||||
BooleanSupplier driverModeSupplier,
|
BooleanSupplier driverModeSupplier,
|
||||||
Consumer<Boolean> driverModeConsumer,
|
Consumer<Boolean> driverModeConsumer,
|
||||||
Supplier<Integer> fpsLimitSupplier,
|
Supplier<Integer> fpsLimitSupplier,
|
||||||
Consumer<Integer> fpsLimitConsumer) {
|
Consumer<Integer> fpsLimitConsumer,
|
||||||
|
BooleanSupplier enabledSupplier,
|
||||||
|
Consumer<Boolean> isEnabledConsumer) {
|
||||||
this.pipelineIndexSupplier = pipelineIndexSupplier;
|
this.pipelineIndexSupplier = pipelineIndexSupplier;
|
||||||
this.pipelineIndexConsumer = pipelineIndexConsumer;
|
this.pipelineIndexConsumer = pipelineIndexConsumer;
|
||||||
this.driverModeSupplier = driverModeSupplier;
|
this.driverModeSupplier = driverModeSupplier;
|
||||||
this.driverModeConsumer = driverModeConsumer;
|
this.driverModeConsumer = driverModeConsumer;
|
||||||
this.fpsLimitSupplier = fpsLimitSupplier;
|
this.fpsLimitSupplier = fpsLimitSupplier;
|
||||||
this.fpsLimitConsumer = fpsLimitConsumer;
|
this.fpsLimitConsumer = fpsLimitConsumer;
|
||||||
|
this.enabledSupplier = enabledSupplier;
|
||||||
|
this.isEnabledConsumer = isEnabledConsumer;
|
||||||
|
|
||||||
updateCameraNickname(cameraNickname);
|
updateCameraNickname(cameraNickname);
|
||||||
updateEntries();
|
updateEntries();
|
||||||
@@ -124,6 +132,19 @@ public class NTDataPublisher implements CVPipelineResultConsumer {
|
|||||||
logger.debug("Set FPS limit to " + newFPSLimit);
|
logger.debug("Set FPS limit to " + newFPSLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onEnabledChange(NetworkTableEvent entryNotification) {
|
||||||
|
var newEnabled = entryNotification.valueData.value.getBoolean();
|
||||||
|
var originalEnabled = enabledSupplier.getAsBoolean();
|
||||||
|
|
||||||
|
if (newEnabled == originalEnabled) {
|
||||||
|
logger.debug("Enabled value is already " + newEnabled);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEnabledConsumer.accept(newEnabled);
|
||||||
|
logger.debug("Set is enabled to " + newEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
private void removeEntries() {
|
private void removeEntries() {
|
||||||
if (pipelineIndexListener != null) pipelineIndexListener.remove();
|
if (pipelineIndexListener != null) pipelineIndexListener.remove();
|
||||||
if (driverModeListener != null) driverModeListener.remove();
|
if (driverModeListener != null) driverModeListener.remove();
|
||||||
@@ -148,6 +169,10 @@ public class NTDataPublisher implements CVPipelineResultConsumer {
|
|||||||
fpsLimitListener =
|
fpsLimitListener =
|
||||||
new NTDataChangeListener(
|
new NTDataChangeListener(
|
||||||
ts.subTable.getInstance(), ts.fpsLimitSubscriber, this::onFPSLimitChange);
|
ts.subTable.getInstance(), ts.fpsLimitSubscriber, this::onFPSLimitChange);
|
||||||
|
|
||||||
|
isEnabledListener =
|
||||||
|
new NTDataChangeListener(
|
||||||
|
ts.subTable.getInstance(), ts.enabledSubscriber, this::onEnabledChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateCameraNickname(String newCameraNickname) {
|
public void updateCameraNickname(String newCameraNickname) {
|
||||||
@@ -177,7 +202,8 @@ public class NTDataPublisher implements CVPipelineResultConsumer {
|
|||||||
|
|
||||||
var offset = NetworkTablesManager.getInstance().getOffset();
|
var offset = NetworkTablesManager.getInstance().getOffset();
|
||||||
|
|
||||||
// Transform the metadata timestamps from the local nt::Now timebase to the Time Sync Server's
|
// Transform the metadata timestamps from the local wpi::nt::Now timebase to the Time Sync
|
||||||
|
// Server's
|
||||||
// timebase
|
// timebase
|
||||||
var simplified =
|
var simplified =
|
||||||
new PhotonPipelineResult(
|
new PhotonPipelineResult(
|
||||||
@@ -197,6 +223,7 @@ public class NTDataPublisher implements CVPipelineResultConsumer {
|
|||||||
ts.pipelineIndexPublisher.set(pipelineIndexSupplier.get());
|
ts.pipelineIndexPublisher.set(pipelineIndexSupplier.get());
|
||||||
ts.driverModePublisher.set(driverModeSupplier.getAsBoolean());
|
ts.driverModePublisher.set(driverModeSupplier.getAsBoolean());
|
||||||
ts.fpsLimitPublisher.set(fpsLimitSupplier.get());
|
ts.fpsLimitPublisher.set(fpsLimitSupplier.get());
|
||||||
|
ts.enabledPublisher.set(enabledSupplier.getAsBoolean());
|
||||||
ts.latencyMillisEntry.set(acceptedResult.getLatencyMillis());
|
ts.latencyMillisEntry.set(acceptedResult.getLatencyMillis());
|
||||||
ts.fpsEntry.set(acceptedResult.fps);
|
ts.fpsEntry.set(acceptedResult.fps);
|
||||||
ts.hasTargetEntry.set(acceptedResult.hasTargets());
|
ts.hasTargetEntry.set(acceptedResult.hasTargets());
|
||||||
|
|||||||
@@ -17,15 +17,15 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.networktables;
|
package org.photonvision.common.dataflow.networktables;
|
||||||
|
|
||||||
import edu.wpi.first.networktables.BooleanSubscriber;
|
|
||||||
import edu.wpi.first.networktables.IntegerSubscriber;
|
|
||||||
import edu.wpi.first.networktables.NetworkTable;
|
|
||||||
import edu.wpi.first.networktables.NetworkTableEvent.Kind;
|
|
||||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
|
||||||
import edu.wpi.first.networktables.StringSubscriber;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
|
import org.wpilib.networktables.BooleanSubscriber;
|
||||||
|
import org.wpilib.networktables.IntegerSubscriber;
|
||||||
|
import org.wpilib.networktables.NetworkTable;
|
||||||
|
import org.wpilib.networktables.NetworkTableEvent.Kind;
|
||||||
|
import org.wpilib.networktables.NetworkTableInstance;
|
||||||
|
import org.wpilib.networktables.StringSubscriber;
|
||||||
|
|
||||||
// Helper to print when the robot transitions modes
|
// Helper to print when the robot transitions modes
|
||||||
public class NTDriverStation {
|
public class NTDriverStation {
|
||||||
@@ -68,9 +68,9 @@ public class NTDriverStation {
|
|||||||
|
|
||||||
fmsTable.addListener(
|
fmsTable.addListener(
|
||||||
"FMSControlData",
|
"FMSControlData",
|
||||||
EnumSet.of(Kind.kValueAll),
|
EnumSet.of(Kind.VALUE_ALL),
|
||||||
(table, key, event) -> {
|
(table, key, event) -> {
|
||||||
if (event.is(Kind.kValueAll) && event.valueData.value.isInteger()) {
|
if (event.is(Kind.VALUE_ALL) && event.valueData.value.isInteger()) {
|
||||||
// Logger totally isnt thread safe but whatevs
|
// Logger totally isnt thread safe but whatevs
|
||||||
var word = NTDriverStation.getControlWord(event.valueData.value.getInteger());
|
var word = NTDriverStation.getControlWord(event.valueData.value.getInteger());
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ public class NTDriverStation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copied from
|
// Copied from
|
||||||
// https://github.com/wpilibsuite/allwpilib/blob/07192285f65321a2f7363227a2216f09b715252d/hal/src/main/java/edu/wpi/first/hal/DriverStationJNI.java#L123C1-L140C4
|
// https://github.com/wpilibsuite/allwpilib/blob/07192285f65321a2f7363227a2216f09b715252d/hal/src/main/java/org/wpilib/hardware/hal/DriverStationJNI.java#L123C1-L140C4
|
||||||
// TODO: upstream!
|
// TODO: upstream!
|
||||||
/**
|
/**
|
||||||
* Gets the current control word of the driver station.
|
* Gets the current control word of the driver station.
|
||||||
|
|||||||
@@ -17,22 +17,12 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.networktables;
|
package org.photonvision.common.dataflow.networktables;
|
||||||
|
|
||||||
import edu.wpi.first.apriltag.AprilTagFieldLayout;
|
import io.avaje.json.JsonException;
|
||||||
import edu.wpi.first.cscore.CameraServerJNI;
|
import io.avaje.jsonb.Jsonb;
|
||||||
import edu.wpi.first.networktables.LogMessage;
|
|
||||||
import edu.wpi.first.networktables.MultiSubscriber;
|
|
||||||
import edu.wpi.first.networktables.NetworkTable;
|
|
||||||
import edu.wpi.first.networktables.NetworkTableEvent;
|
|
||||||
import edu.wpi.first.networktables.NetworkTableEvent.Kind;
|
|
||||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
|
||||||
import edu.wpi.first.networktables.StringSubscriber;
|
|
||||||
import edu.wpi.first.wpilibj.Alert;
|
|
||||||
import edu.wpi.first.wpilibj.Alert.AlertType;
|
|
||||||
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import org.photonvision.PhotonVersion;
|
import org.photonvision.PhotonVersion;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.common.configuration.ConfigManager;
|
import org.photonvision.common.configuration.ConfigManager;
|
||||||
@@ -46,7 +36,18 @@ import org.photonvision.common.logging.LogLevel;
|
|||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.networking.NetworkUtils;
|
import org.photonvision.common.networking.NetworkUtils;
|
||||||
import org.photonvision.common.util.TimedTaskManager;
|
import org.photonvision.common.util.TimedTaskManager;
|
||||||
import org.photonvision.common.util.file.JacksonUtils;
|
import org.wpilib.driverstation.Alert;
|
||||||
|
import org.wpilib.driverstation.Alert.Level;
|
||||||
|
import org.wpilib.networktables.LogMessage;
|
||||||
|
import org.wpilib.networktables.MultiSubscriber;
|
||||||
|
import org.wpilib.networktables.NetworkTable;
|
||||||
|
import org.wpilib.networktables.NetworkTableEvent;
|
||||||
|
import org.wpilib.networktables.NetworkTableEvent.Kind;
|
||||||
|
import org.wpilib.networktables.NetworkTableInstance;
|
||||||
|
import org.wpilib.networktables.StringSubscriber;
|
||||||
|
import org.wpilib.smartdashboard.SmartDashboard;
|
||||||
|
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
|
||||||
|
import org.wpilib.vision.camera.CameraServerJNI;
|
||||||
|
|
||||||
public class NetworkTablesManager {
|
public class NetworkTablesManager {
|
||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
@@ -66,9 +67,9 @@ public class NetworkTablesManager {
|
|||||||
new MultiSubscriber(ntInstance, new String[] {kRootTableName + "/" + kCoprocTableName + "/"});
|
new MultiSubscriber(ntInstance, new String[] {kRootTableName + "/" + kCoprocTableName + "/"});
|
||||||
|
|
||||||
// Creating the alert up here since it should be persistent
|
// Creating the alert up here since it should be persistent
|
||||||
private final Alert conflictAlert = new Alert("PhotonAlerts", "", AlertType.kWarning);
|
private final Alert conflictAlert = new Alert("PhotonAlerts", "", Level.MEDIUM);
|
||||||
|
|
||||||
private final Alert mismatchAlert = new Alert("PhotonAlerts", "", AlertType.kWarning);
|
private final Alert mismatchAlert = new Alert("PhotonAlerts", "", Level.MEDIUM);
|
||||||
|
|
||||||
public boolean conflictingHostname = false;
|
public boolean conflictingHostname = false;
|
||||||
public String conflictingCameras = "";
|
public String conflictingCameras = "";
|
||||||
@@ -85,11 +86,11 @@ public class NetworkTablesManager {
|
|||||||
|
|
||||||
private NetworkTablesManager() {
|
private NetworkTablesManager() {
|
||||||
ntInstance.addLogger(
|
ntInstance.addLogger(
|
||||||
LogMessage.kInfo, LogMessage.kCritical, this::logNtMessage); // to hide error messages
|
LogMessage.INFO, LogMessage.CRITICAL, this::logNtMessage); // to hide error messages
|
||||||
ntInstance.addConnectionListener(true, this::checkNtConnectState); // to hide error messages
|
ntInstance.addConnectionListener(true, this::checkNtConnectState); // to hide error messages
|
||||||
|
|
||||||
ntInstance.addListener(
|
ntInstance.addListener(
|
||||||
m_fieldLayoutSubscriber, EnumSet.of(Kind.kValueAll), this::onFieldLayoutChanged);
|
m_fieldLayoutSubscriber, EnumSet.of(Kind.VALUE_ALL), this::onFieldLayoutChanged);
|
||||||
|
|
||||||
ntDriverStation = new NTDriverStation(this.getNTInst());
|
ntDriverStation = new NTDriverStation(this.getNTInst());
|
||||||
|
|
||||||
@@ -126,16 +127,16 @@ public class NetworkTablesManager {
|
|||||||
private void logNtMessage(NetworkTableEvent event) {
|
private void logNtMessage(NetworkTableEvent event) {
|
||||||
String levelmsg = "DEBUG";
|
String levelmsg = "DEBUG";
|
||||||
LogLevel pvlevel = LogLevel.DEBUG;
|
LogLevel pvlevel = LogLevel.DEBUG;
|
||||||
if (event.logMessage.level >= LogMessage.kCritical) {
|
if (event.logMessage.level >= LogMessage.CRITICAL) {
|
||||||
pvlevel = LogLevel.ERROR;
|
pvlevel = LogLevel.ERROR;
|
||||||
levelmsg = "CRITICAL";
|
levelmsg = "CRITICAL";
|
||||||
} else if (event.logMessage.level >= LogMessage.kError) {
|
} else if (event.logMessage.level >= LogMessage.ERROR) {
|
||||||
pvlevel = LogLevel.ERROR;
|
pvlevel = LogLevel.ERROR;
|
||||||
levelmsg = "ERROR";
|
levelmsg = "ERROR";
|
||||||
} else if (event.logMessage.level >= LogMessage.kWarning) {
|
} else if (event.logMessage.level >= LogMessage.WARNING) {
|
||||||
pvlevel = LogLevel.WARN;
|
pvlevel = LogLevel.WARN;
|
||||||
levelmsg = "WARNING";
|
levelmsg = "WARNING";
|
||||||
} else if (event.logMessage.level >= LogMessage.kInfo) {
|
} else if (event.logMessage.level >= LogMessage.INFO) {
|
||||||
pvlevel = LogLevel.INFO;
|
pvlevel = LogLevel.INFO;
|
||||||
levelmsg = "INFO";
|
levelmsg = "INFO";
|
||||||
}
|
}
|
||||||
@@ -156,16 +157,14 @@ public class NetworkTablesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void checkNtConnectState(NetworkTableEvent event) {
|
public void checkNtConnectState(NetworkTableEvent event) {
|
||||||
var isConnEvent = event.is(Kind.kConnected);
|
var isConnEvent = event.is(Kind.CONNECTED);
|
||||||
var isDisconnEvent = event.is(Kind.kDisconnected);
|
var isDisconnEvent = event.is(Kind.DISCONNECTED);
|
||||||
|
|
||||||
if (isDisconnEvent) {
|
if (isDisconnEvent) {
|
||||||
var msg =
|
var msg =
|
||||||
String.format(
|
String.format(
|
||||||
"NT lost connection to %s:%d! (NT version %d). Will retry in background.",
|
"NT lost connection to %s:%d! (NT version %d). Will retry in background.",
|
||||||
event.connInfo.remote_ip,
|
event.connInfo.remoteIp, event.connInfo.remotePort, event.connInfo.protocolVersion);
|
||||||
event.connInfo.remote_port,
|
|
||||||
event.connInfo.protocol_version);
|
|
||||||
logger.error(msg);
|
logger.error(msg);
|
||||||
HardwareManager.getInstance().setNTConnected(false);
|
HardwareManager.getInstance().setNTConnected(false);
|
||||||
|
|
||||||
@@ -174,9 +173,7 @@ public class NetworkTablesManager {
|
|||||||
var msg =
|
var msg =
|
||||||
String.format(
|
String.format(
|
||||||
"NT connected to %s:%d! (NT version %d)",
|
"NT connected to %s:%d! (NT version %d)",
|
||||||
event.connInfo.remote_ip,
|
event.connInfo.remoteIp, event.connInfo.remotePort, event.connInfo.protocolVersion);
|
||||||
event.connInfo.remote_port,
|
|
||||||
event.connInfo.protocol_version);
|
|
||||||
logger.info(msg);
|
logger.info(msg);
|
||||||
HardwareManager.getInstance().setNTConnected(true);
|
HardwareManager.getInstance().setNTConnected(true);
|
||||||
|
|
||||||
@@ -199,7 +196,7 @@ public class NetworkTablesManager {
|
|||||||
var atfl_json = event.valueData.value.getString();
|
var atfl_json = event.valueData.value.getString();
|
||||||
try {
|
try {
|
||||||
System.out.println("Got new field layout!");
|
System.out.println("Got new field layout!");
|
||||||
var atfl = JacksonUtils.deserialize(atfl_json, AprilTagFieldLayout.class);
|
var atfl = Jsonb.instance().type(AprilTagFieldLayout.class).fromJson(atfl_json);
|
||||||
ConfigManager.getInstance().getConfig().setApriltagFieldLayout(atfl);
|
ConfigManager.getInstance().getConfig().setApriltagFieldLayout(atfl);
|
||||||
ConfigManager.getInstance().requestSave();
|
ConfigManager.getInstance().requestSave();
|
||||||
DataChangeService.getInstance()
|
DataChangeService.getInstance()
|
||||||
@@ -207,7 +204,7 @@ public class NetworkTablesManager {
|
|||||||
new OutgoingUIEvent<>(
|
new OutgoingUIEvent<>(
|
||||||
"fullsettings",
|
"fullsettings",
|
||||||
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
|
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
|
||||||
} catch (IOException e) {
|
} catch (IllegalStateException | JsonException e) {
|
||||||
logger.error("Error deserializing atfl!");
|
logger.error("Error deserializing atfl!");
|
||||||
logger.error(atfl_json);
|
logger.error(atfl_json);
|
||||||
}
|
}
|
||||||
@@ -225,7 +222,7 @@ public class NetworkTablesManager {
|
|||||||
if (ntInstance.isConnected()) {
|
if (ntInstance.isConnected()) {
|
||||||
var connections = ntInstance.getConnections();
|
var connections = ntInstance.getConnections();
|
||||||
if (connections.length > 0) {
|
if (connections.length > 0) {
|
||||||
subMap.put("address", connections[0].remote_ip + ":" + connections[0].remote_port);
|
subMap.put("address", connections[0].remoteIp + ":" + connections[0].remotePort);
|
||||||
}
|
}
|
||||||
subMap.put("clients", connections.length);
|
subMap.put("clients", connections.length);
|
||||||
}
|
}
|
||||||
@@ -270,7 +267,7 @@ public class NetworkTablesManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
HashMap<String, CameraConfiguration> cameraConfigs =
|
Map<String, CameraConfiguration> cameraConfigs =
|
||||||
ConfigManager.getInstance().getConfig().getCameraConfigurations();
|
ConfigManager.getInstance().getConfig().getCameraConfigurations();
|
||||||
String[] cameraNames =
|
String[] cameraNames =
|
||||||
cameraConfigs.entrySet().stream()
|
cameraConfigs.entrySet().stream()
|
||||||
@@ -358,7 +355,7 @@ public class NetworkTablesManager {
|
|||||||
ntInstance.stopClient();
|
ntInstance.stopClient();
|
||||||
String hostname = config.shouldManage ? config.hostname : CameraServerJNI.getHostname();
|
String hostname = config.shouldManage ? config.hostname : CameraServerJNI.getHostname();
|
||||||
logger.debug("Starting NT Client with hostname: " + hostname);
|
logger.debug("Starting NT Client with hostname: " + hostname);
|
||||||
ntInstance.startClient4(hostname);
|
ntInstance.startClient(hostname);
|
||||||
try {
|
try {
|
||||||
int t = Integer.parseInt(config.ntServerAddress);
|
int t = Integer.parseInt(config.ntServerAddress);
|
||||||
if (!m_isRetryingConnection) logger.info("Starting NT Client, server team is " + t);
|
if (!m_isRetryingConnection) logger.info("Starting NT Client, server team is " + t);
|
||||||
|
|||||||
@@ -17,16 +17,16 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.networktables;
|
package org.photonvision.common.dataflow.networktables;
|
||||||
|
|
||||||
import edu.wpi.first.cscore.CameraServerJNI;
|
|
||||||
import edu.wpi.first.networktables.IntegerPublisher;
|
|
||||||
import edu.wpi.first.networktables.NetworkTable;
|
|
||||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
|
||||||
import org.photonvision.common.configuration.NetworkConfig;
|
import org.photonvision.common.configuration.NetworkConfig;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.TimedTaskManager;
|
import org.photonvision.common.util.TimedTaskManager;
|
||||||
import org.photonvision.jni.TimeSyncClient;
|
import org.photonvision.jni.TimeSyncClient;
|
||||||
import org.photonvision.jni.TimeSyncServer;
|
import org.photonvision.jni.TimeSyncServer;
|
||||||
|
import org.wpilib.networktables.IntegerPublisher;
|
||||||
|
import org.wpilib.networktables.NetworkTable;
|
||||||
|
import org.wpilib.networktables.NetworkTableInstance;
|
||||||
|
import org.wpilib.vision.camera.CameraServerJNI;
|
||||||
|
|
||||||
public class TimeSyncManager {
|
public class TimeSyncManager {
|
||||||
private static final Logger logger = new Logger(TimeSyncManager.class, LogGroup.NetworkTables);
|
private static final Logger logger = new Logger(TimeSyncManager.class, LogGroup.NetworkTables);
|
||||||
@@ -66,7 +66,7 @@ public class TimeSyncManager {
|
|||||||
public synchronized long getOffset() {
|
public synchronized long getOffset() {
|
||||||
// if we're a client, return the offset to server time
|
// if we're a client, return the offset to server time
|
||||||
if (m_client != null) return m_client.getOffset();
|
if (m_client != null) return m_client.getOffset();
|
||||||
// if we're a server, our time (nt::Now) is the same as network time
|
// if we're a server, our time (wpi::nt::Now) is the same as network time
|
||||||
if (m_server != null) return 0;
|
if (m_server != null) return 0;
|
||||||
|
|
||||||
// ????? should never hit
|
// ????? should never hit
|
||||||
@@ -112,7 +112,7 @@ public class TimeSyncManager {
|
|||||||
var conns = ntInstance.getConnections();
|
var conns = ntInstance.getConnections();
|
||||||
|
|
||||||
if (conns.length > 0) {
|
if (conns.length > 0) {
|
||||||
var newServer = conns[0].remote_ip;
|
var newServer = conns[0].remoteIp;
|
||||||
if (!m_client.getServer().equals(newServer)) {
|
if (!m_client.getServer().equals(newServer)) {
|
||||||
logger.debug("Changing TimeSyncClient server to " + newServer);
|
logger.debug("Changing TimeSyncClient server to " + newServer);
|
||||||
m_client.setServer(newServer);
|
m_client.setServer(newServer);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user