Compare commits

...

12 Commits

Author SHA1 Message Date
Craig Schardt
e506ac6b28 Fix release action glob exclusion (#1644)
In softprops/action-gh-release, when using an glob pattern to exclude files, all of the files to exclude must be specified on one line. Splitting them to multiple lines causes all files to match one of the two lines. 

To combine on one line, use `|` between the patterns.
2024-12-20 17:11:13 -06:00
Matt
88bc63cf82 Bump Athena image to 2025 (#1643) 2024-12-20 13:12:01 -08:00
Nolan Brown
a05542c06c [docs] Document VSCode tests and gradle tasks (#1641) 2024-12-20 09:27:29 -08:00
Jade
ffc4e06ac6 Move to main over master (#1642)
Resolves https://github.com/PhotonVision/photonvision/issues/251

---------

Signed-off-by: Jade Turner <spacey-sooty@proton.me>
2024-12-20 09:24:52 -08:00
Jade
81076375b8 Bump to WPILib beta 3 and OpenCV 4.10 (#1638)
Signed-off-by: Jade Turner <spacey-sooty@proton.me>
Co-authored-by: Matt <matthew.morley.ca@gmail.com>
2024-12-19 20:54:48 -05:00
Matt
66f369f3a9 Remove selective builds (#1639)
Selective builds breaks my ability to require that checks pass before
merging. It's not worth the bite for very few docs-only PRs, given
github limitations on needing to skip INDIVIDUAL JOB STEPS on required
CI jobs.

Maybe we should move to gitlab 💀
2024-12-19 05:32:22 +00:00
Matt
7cba7b432d Add camera sim smoketest (#1637)
Paranoia test inspired by
https://github.com/PhotonVision/photonvision/issues/1635 .
2024-12-19 04:51:02 +00:00
Jade
dd98d96d7e Actually remove all RuntimeDetector usage (#1636)
Signed-off-by: Jade Turner <spacey-sooty@proton.me>
2024-12-19 04:47:29 +00:00
Jade
8ede892c14 Remove usage of RuntimeDetector (#1536)
Signed-off-by: Jade Turner <spacey-sooty@proton.me>
2024-12-17 17:11:05 +00:00
Christopher Mahoney
08c62ab8cd Java 17 (#1440)
![image](https://github.com/user-attachments/assets/d4e4226f-74b0-4ded-87b4-ac4bf2f1ac34)

JDK 11 References
https://github.com/search?q=org%3APhotonVision+jdk-11&type=code

JDK 17 References
https://github.com/search?q=org%3APhotonVision+jdk-17&type=code

TODO List (things we might need to update in other repos):
- []
ab5fa98d72/photonvision/src/modules/photonvision/start_chroot_script (L29)
- []
7f8d225445/stage2/01-sys-tweaks/00-packages (L34)
- []
ab5fa98d72/photonvision/src/modules/photonvision/install.sh (L11)
- []
a4e7ecb6e3/Makefile (L14)

Closes https://github.com/PhotonVision/photonvision/pull/1069

---------

Signed-off-by: Jade Turner <spacey-sooty@proton.me>
Co-authored-by: Jade <spacey-sooty@proton.me>
2024-12-17 17:01:19 +00:00
Matt
e8efef476b We have followers around the whole globe (#1622)
Co-authored-by: Vasista Vovveti <vasistavovveti@gmail.com>
2024-12-11 23:47:18 -05:00
Griffin Della Grotte
c6403a65d2 Add Rock 5C Release (#1617) 2024-12-11 06:47:16 +00:00
62 changed files with 648 additions and 777 deletions

View File

@@ -1,21 +1,13 @@
name: Build
on:
# Run on pushes to master and pushed tags, and on pull requests against master, but ignore the docs folder
# Run on pushes to main and pushed tags, and on pull requests against main, but ignore the docs folder
push:
branches: [ master ]
branches: [ main ]
tags:
- 'v*'
paths:
- '**'
- '!docs/**'
- '.github/**'
pull_request:
branches: [ master ]
paths:
- '**'
- '!docs/**'
- '.github/**'
branches: [ main ]
merge_group:
jobs:
@@ -213,7 +205,7 @@ jobs:
fail-fast: false
matrix:
include:
- container: wpilib/roborio-cross-ubuntu:2024-22.04
- container: wpilib/roborio-cross-ubuntu:2025-24.04
artifact-name: Athena
build-options: "-Ponlylinuxathena"
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
@@ -479,6 +471,12 @@ jobs:
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-6/photonvision_opi5max.img.xz
cpu: cortex-a8
image_additional_mb: 1024
- os: ubuntu-22.04
artifact-name: LinuxArm64
image_suffix: rock5c
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-7/photonvision_rock5c.img.xz
cpu: cortex-a8
image_additional_mb: 1024
runs-on: ${{ matrix.os }}
name: "Build image - ${{ matrix.image_url }}"
@@ -559,14 +557,14 @@ jobs:
- uses: softprops/action-gh-release@v2.0.9
with:
files: |
**/*orangepi5*.xz
**/@(*orangepi5*|*rock5*).xz
if: startsWith(github.ref, 'refs/tags/v')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: softprops/action-gh-release@v2.0.9
with:
files: |
**/!(*orangepi5*).xz
**/!(*orangepi5*|*rock5*).xz
**/*.jar
**/photonlib*.json
**/photonlib*.zip

View File

@@ -1,21 +1,13 @@
name: Lint and Format
on:
# Run on pushes to master and pushed tags, and on pull requests against master, but ignore the docs folder
# Run on pushes to main and pushed tags, and on pull requests against main, but ignore the docs folder
push:
branches: [ master ]
branches: [ main ]
tags:
- 'v*'
paths:
- '**'
- '!docs/**'
- '.github/**'
pull_request:
branches: [ master ]
paths:
- '**'
- '!docs/**'
- '.github/**'
branches: [ main ]
merge_group:
concurrency:
@@ -32,7 +24,7 @@ jobs:
run: |
git fetch --prune --unshallow
git checkout -b pr
git branch -f master origin/master
git branch -f main origin/main
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
@@ -92,6 +84,6 @@ jobs:
run: |
git fetch --prune --unshallow
git checkout -b pr
git branch -f master origin/master
git branch -f main origin/main
- name: Check index.html not changed
run: git --no-pager diff --exit-code origin/master photon-server/src/main/resources/web/index.html
run: git --no-pager diff --exit-code origin/main photon-server/src/main/resources/web/index.html

View File

@@ -1,21 +1,13 @@
name: Photon Code Documentation
on:
# Run on pushes to master and pushed tags, and on pull requests against master, but ignore the docs folder
# Run on pushes to main and pushed tags, and on pull requests against main, but ignore the docs folder
push:
branches: [ master ]
branches: [ main ]
tags:
- 'v*'
paths:
- '**'
- '!docs/**'
- '.github/**'
pull_request:
branches: [ master ]
paths:
- '**'
- '!docs/**'
- '.github/**'
branches: [ main ]
merge_group:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
@@ -86,7 +78,7 @@ jobs:
- run: find .
- name: copy file via ssh password
if: github.ref == 'refs/heads/master'
if: github.ref == 'refs/heads/main'
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.WEBMASTER_SSH_HOST }}

View File

@@ -3,14 +3,8 @@ name: PhotonVision Sphinx Documentation Checks
on:
push:
branches: [ master ]
paths:
- 'docs/**'
- '.github/**'
pull_request:
branches: [ master ]
paths:
- 'docs/**'
- '.github/**'
merge_group:
env:

View File

@@ -8,16 +8,8 @@ on:
branches: [ master ]
tags:
- 'v*'
paths:
- '**'
- '!docs/**'
- '.github/**'
pull_request:
branches: [ master ]
paths:
- '**'
- '!docs/**'
- '.github/**'
merge_group:
jobs:

View File

@@ -1,6 +1,6 @@
# PhotonVision
[![CI](https://github.com/PhotonVision/photonvision/workflows/CI/badge.svg)](https://github.com/PhotonVision/photonvision/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/PhotonVision/photonvision/branch/master/graph/badge.svg)](https://codecov.io/gh/PhotonVision/photonvision) [![Discord](https://img.shields.io/discord/725836368059826228?color=%23738ADB&label=Join%20our%20Discord&logo=discord&logoColor=white)](https://discord.gg/wYxTwym)
[![CI](https://github.com/PhotonVision/photonvision/workflows/CI/badge.svg)](https://github.com/PhotonVision/photonvision/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/PhotonVision/photonvision/branch/main/graph/badge.svg)](https://codecov.io/gh/PhotonVision/photonvision) [![Discord](https://img.shields.io/discord/725836368059826228?color=%23738ADB&label=Join%20our%20Discord&logo=discord&logoColor=white)](https://discord.gg/wYxTwym)
PhotonVision is the free, fast, and easy-to-use computer vision solution for the *FIRST* Robotics Competition. You can read an overview of our features [on our website](https://photonvision.org). You can find our comprehensive documentation [here](https://docs.photonvision.org).
@@ -17,7 +17,7 @@ If you are interested in contributing code or documentation to the project, plea
## Documentation
- Our main documentation page: [docs.photonvision.org](https://docs.photonvision.org)
- Photon UI demo: [demo.photonvision.org](https://demo.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-client/))
- Photon UI demo: [http://photonvision.global/](http://photonvision.global/) (or [manual link](https://photonvision.github.io/photonvision/built-client/))
- Javadocs: [javadocs.photonvision.org](https://javadocs.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-docs/javadoc/))
- C++ Doxygen [cppdocs.photonvision.org](https://cppdocs.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-docs/doxygen/html/))
@@ -67,7 +67,7 @@ sudo apt install libcholmod3 liblapack3 libsuitesparseconfig5
PhotonVision was forked from [Chameleon Vision](https://github.com/Chameleon-Vision/chameleon-vision/). Thank you to everyone who worked on the original project.
* [WPILib](https://github.com/wpilibsuite) - Specifically [cscore](https://github.com/wpilibsuite/allwpilib/tree/master/cscore), [CameraServer](https://github.com/wpilibsuite/allwpilib/tree/master/cameraserver), [NTCore](https://github.com/wpilibsuite/allwpilib/tree/master/ntcore), and [OpenCV](https://github.com/wpilibsuite/thirdparty-opencv).
* [WPILib](https://github.com/wpilibsuite) - Specifically [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore), [CameraServer](https://github.com/wpilibsuite/allwpilib/tree/main/cameraserver), [NTCore](https://github.com/wpilibsuite/allwpilib/tree/main/ntcore), and [OpenCV](https://github.com/wpilibsuite/thirdparty-opencv).
* [Apache Commons](https://commons.apache.org/) - Specifically [Commons Math](https://commons.apache.org/proper/commons-math/), and [Commons Lang](https://commons.apache.org/proper/commons-lang/)

View File

@@ -5,7 +5,7 @@ plugins {
id "cpp"
id "com.diffplug.spotless" version "6.24.0"
id "edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin" version "2020.2"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
id 'edu.wpi.first.WpilibTools' version '1.3.0'
id 'com.google.protobuf' version '0.9.3' apply false
id 'edu.wpi.first.GradleJni' version '1.1.0'
@@ -30,16 +30,16 @@ ext.allOutputsFolder = file("$project.buildDir/outputs")
apply from: "versioningHelper.gradle"
ext {
wpilibVersion = "2025.1.1-beta-2"
wpilibVersion = "2025.1.1-beta-3"
wpimathVersion = wpilibVersion
openCVYear = "2024"
openCVversion = "4.8.0-4"
openCVYear = "2025"
openCVversion = "4.10.0-3"
joglVersion = "2.4.0"
javalinVersion = "5.6.2"
libcameraDriverVersion = "dev-v2023.1.0-15-gc8988b3"
rknnVersion = "dev-v2024.0.1-4-g0db16ac"
libcameraDriverVersion = "v2025.0.0"
rknnVersion = "v2025.0.0"
frcYear = "2025"
mrcalVersion = "dev-v2024.0.0-27-g41d7868";
mrcalVersion = "v2025.0.0";
pubVersion = versionString

View File

@@ -45,7 +45,7 @@ extensions = [
ogp_site_url = "https://docs.photonvision.org/en/latest/"
ogp_site_name = "PhotonVision Documentation"
ogp_image = "https://raw.githubusercontent.com/PhotonVision/photonvision-docs/master/source/assets/RectLogo.png"
ogp_image = "https://raw.githubusercontent.com/PhotonVision/photonvision-docs/main/source/assets/RectLogo.png"
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]

View File

@@ -1,12 +1,12 @@
# Installing Pre-Release Versions
Pre-release/development version of PhotonVision can be tested by installing/downloading artifacts from Github Actions (see below), which are built automatically on commits to open pull requests and to PhotonVision's `master` branch, or by {ref}`compiling PhotonVision locally <docs/contributing/building-photon:Build Instructions>`.
Pre-release/development version of PhotonVision can be tested by installing/downloading artifacts from Github Actions (see below), which are built automatically on commits to open pull requests and to PhotonVision's `main` branch, or by {ref}`compiling PhotonVision locally <docs/contributing/building-photon:Build Instructions>`.
:::{warning}
If testing a pre-release version of PhotonVision with a robot, PhotonLib must be updated to match the version downloaded! If not, packet schema definitions may not match and unexpected things will occur. To update PhotonLib, refer to {ref}`installing specific version of PhotonLib<docs/programming/photonlib/adding-vendordep:Install Specific Version - Java/C++>`.
:::
GitHub Actions builds pre-release version of PhotonVision automatically on PRs and on each commit merged to master. To test a particular commit to master, navigate to the [PhotonVision commit list](https://github.com/PhotonVision/photonvision/commits/master/) and click on the check mark (below). Scroll to "Build / Build fat JAR - PLATFORM", click details, and then summary. From here, JAR and image files can be downloaded to be flashed or uploaded using "Offline Update".
GitHub Actions builds pre-release version of PhotonVision automatically on PRs and on each commit merged to main. To test a particular commit to main, navigate to the [PhotonVision commit list](https://github.com/PhotonVision/photonvision/commits/main/) and click on the check mark (below). Scroll to "Build / Build fat JAR - PLATFORM", click details, and then summary. From here, JAR and image files can be downloaded to be flashed or uploaded using "Offline Update".
```{image} images/gh_actions_1.png
:alt: Github Actions Badge

View File

@@ -12,7 +12,7 @@ Bonjour provides more stable networking when using Windows PCs. Install [Bonjour
## Installing Java
PhotonVision requires a JDK installed and on the system path. **JDK 17 is needed. Windows Users must use the JDK that ships with WPILib.** [Download and install it from here.](https://github.com/wpilibsuite/allwpilib/releases/tag/v2025.1.1-beta-2) Either ensure the only Java on your PATH is the WPILIB Java or specify it to gradle with `-Dorg.gradle.java.home=C:\Users\Public\wpilib\2025\jdk`:
PhotonVision requires a JDK installed and on the system path. **JDK 17 is needed. Windows Users must use the JDK that ships with WPILib.** [Download and install it from here.](https://github.com/wpilibsuite/allwpilib/releases/tag/v2025.1.1-beta-3) Either ensure the only Java on your PATH is the WPILIB Java or specify it to gradle with `-Dorg.gradle.java.home=C:\Users\Public\wpilib\2025\jdk`:
```
> ./gradlew run "-Dorg.gradle.java.home=C:\Users\Public\wpilib\2025\jdk"

View File

@@ -21,7 +21,7 @@ AprilTag pipelines come with reasonable defaults to get you up and running with
### Target Family
Target families are defined by two numbers (before and after the h). The first number is the number of bits the tag is able to encode (which means more tags are available in the respective family) and the second is the hamming distance. Hamming distance describes the ability for error correction while identifying tag ids. A high hamming distance generally means that it will be easier for a tag to be identified even if there are errors. However, as hamming distance increases, the number of available tags decreases. The 2024 FRC game will be using 36h11 tags, which can be found [here](https://github.com/AprilRobotics/apriltag-imgs/tree/master/tag36h11).
Target families are defined by two numbers (before and after the h). The first number is the number of bits the tag is able to encode (which means more tags are available in the respective family) and the second is the hamming distance. Hamming distance describes the ability for error correction while identifying tag ids. A high hamming distance generally means that it will be easier for a tag to be identified even if there are errors. However, as hamming distance increases, the number of available tags decreases. The 2024 FRC game will be using 36h11 tags, which can be found [here](https://github.com/AprilRobotics/apriltag-imgs/tree/main/tag36h11).
### Decimate

View File

@@ -83,7 +83,7 @@ More info on what these parameters mean can be found in [OpenCV's docs](https://
Below these outputs are the snapshots collected for calibration, along with a per-snapshot mean reprojection error. A snapshot with a larger reprojection error might indicate a bad snapshot, due to effects such as motion blur or misidentified chessboard corners.
Calibration images can also be extracted from the downloaded JSON file using [this Python script](https://raw.githubusercontent.com/PhotonVision/photonvision/master/devTools/calibrationUtils.py). This script will unpack calibration images, and also generate a VNL file for use [with mrcal](https://mrcal.secretsauce.net/).
Calibration images can also be extracted from the downloaded JSON file using [this Python script](https://raw.githubusercontent.com/PhotonVision/photonvision/main/devTools/calibrationUtils.py). This script will unpack calibration images, and also generate a VNL file for use [with mrcal](https://mrcal.secretsauce.net/).
```
python3 /path/to/calibrationUtils.py path/to/photon_calibration.json /path/to/output/folder

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

View File

@@ -167,30 +167,31 @@ repositories {
}
```
### VSCode Test Runner Extension
With the VSCode [Extension Pack for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack), you can get the Test Runner for Java and Gradle for Java extensions. This lets you easily run specific tests through the IDE:
```{image} assets/vscode-runner-tests.png
:alt: An image showing how unit tests can be ran in VSCode through the Test Runner for Java extension.
```
To correctly run PhotonVision tests this way, you must [delegate the tests to Gradle](https://code.visualstudio.com/docs/java/java-build#_delegate-tests-to-gradle). Debugging tests like this will [**not** currently](https://github.com/microsoft/build-server-for-gradle/issues/119) collect outputs.
### Debugging PhotonVision Running Locally
One way is by running the program using gradle with the {code}`--debug-jvm` flag. Run the program with {code}`./gradlew run --debug-jvm`, and attach to it with VSCode by adding the following to {code}`launch.json`. Note args can be passed with {code}`--args="foobar"`.
Unit tests can instead be debugged through the ``test`` Gradle task for a specific subproject in VSCode, found in the Gradle tab:
```
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Attach to Remote Program",
"request": "attach",
"hostName": "localhost",
"port": "5005",
"projectName": "photon-core",
}
]
}
```{image} assets/vscode-gradle-tests.png
:alt: An image showing how unit tests can be debugged in VSCode through the Gradle for Java extension.
```
PhotonVision can also be run using the gradle tasks plugin with {code}`"args": "--debug-jvm"` added to launch.json.
However, this will run all tests in a subproject.
Similarly, a local instance of PhotonVision can be debugged in the same way using the Gradle ``run`` task. In both cases, additional arguments can be specified:
```{image} assets/vscode-gradle-args.png
:alt: An image showing how VSCode gradle tasks can specify additional arguments.
```
### Debugging PhotonVision Running on a CoProcessor

View File

@@ -1,6 +1,6 @@
# Combining Aiming and Getting in Range
The following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/master/photonlib-java-examples/aimandrange)/[C++](https://github.com/PhotonVision/photonvision/tree/master/photonlib-cpp-examples/aimandrange)).
The following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/aimandrange)/[C++](https://github.com/PhotonVision/photonvision/tree/main/photonlib-cpp-examples/aimandrange)).
## Knowledge and Equipment Needed

View File

@@ -1,6 +1,6 @@
# Aiming at a Target
The following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/master/photonlib-java-examples/aimattarget)).
The following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/aimattarget)).
## Knowledge and Equipment Needed

View File

@@ -1,6 +1,6 @@
# Using WPILib Pose Estimation, Simulation, and PhotonVision Together
The following example comes from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/master/photonlib-java-examples/poseest)/[C++](https://github.com/PhotonVision/photonvision/tree/master/photonlib-cpp-examples/poseest)/[Python](https://github.com/PhotonVision/photonvision/tree/master/photonlib-python-examples/poseest)). Full code is available at that links.
The following example comes from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/poseest)/[C++](https://github.com/PhotonVision/photonvision/tree/main/photonlib-cpp-examples/poseest)/[Python](https://github.com/PhotonVision/photonvision/tree/main/photonlib-python-examples/poseest)). Full code is available at that links.
## Knowledge and Equipment Needed

View File

@@ -47,6 +47,6 @@ Coming soon!
PhotonVision currently ONLY supports YOLOv5 models trained and converted to `.rknn` format for RK3588 CPUs! Other models require different post-processing code and will NOT work. The model conversion process is also highly particular. Proceed with care.
:::
Our [pre-trained NOTE model](https://github.com/PhotonVision/photonvision/blob/master/photon-server/src/main/resources/models/note-640-640-yolov5s.rknn) is automatically extracted from the JAR when PhotonVision starts, only if a file named “note-640-640-yolov5s.rknn” and "labels.txt" does not exist in the folder `photonvision_config/models/`. This technically allows power users to replace the model and label files with new ones without rebuilding Photon from source and uploading a new JAR.
Our [pre-trained NOTE model](https://github.com/PhotonVision/photonvision/blob/main/photon-server/src/main/resources/models/note-640-640-yolov5s.rknn) is automatically extracted from the JAR when PhotonVision starts, only if a file named “note-640-640-yolov5s.rknn” and "labels.txt" does not exist in the folder `photonvision_config/models/`. This technically allows power users to replace the model and label files with new ones without rebuilding Photon from source and uploading a new JAR.
Use a program like WinSCP or FileZilla to access your coprocessor's filesystem, and copy the new `.rknn` model file into /home/pi. Next, SSH into the coprocessor and `sudo mv /path/to/new/model.rknn /opt/photonvision/photonvision_config/models/note-640-640-yolov5s.rknn`. Repeat this process with the labels file, which should contain one line per label the model outputs with no training newline. Next, restart PhotonVision via the web UI.

View File

@@ -60,7 +60,7 @@ Use the `getLatestResult()`/`GetLatestResult()` (Java and C++ respectively) to o
```
:::{note}
Unlike other vision software solutions, using the latest result guarantees that all information is from the same timestamp. This is achievable because the PhotonVision backend sends a byte-packed string of data which is then deserialized by PhotonLib to get target data. For more information, check out the [PhotonLib source code](https://github.com/PhotonVision/photonvision/tree/master/photon-lib).
Unlike other vision software solutions, using the latest result guarantees that all information is from the same timestamp. This is achievable because the PhotonVision backend sends a byte-packed string of data which is then deserialized by PhotonLib to get target data. For more information, check out the [PhotonLib source code](https://github.com/PhotonVision/photonvision/tree/main/photon-lib).
:::
## Checking for Existence of Targets

View File

@@ -8,7 +8,7 @@ If youre not using cameras in 3D mode, calibration is optional, but it can st
## Print the Calibration Target
- Downloaded from our [demo site](https://demo.photonvision.org/#/cameras), or directly from your coprocessors cameras tab.
- Downloaded from our [demo site](http://photonvision.global/#/cameras), or directly from your coprocessors cameras tab.
- Use the Charuco calibration board:
- Board Type: Charuco
- Tag Family: 4x4

View File

@@ -29,7 +29,7 @@ Limelights have a different installation processes. Simply connect the limelight
Unless otherwise noted in release notes or if updating from the prior years version, to update PhotonVision after the initial installation, use the offline update option in the settings page with the downloaded jar file from the latest release.
:::{note}
Limelight 2, 2+, and 3 will need a [custom hardware config file](https://github.com/PhotonVision/photonvision/tree/master/docs/source/docs/advanced-installation/sw_install/files) for lighting to work. Currently only limelight 2 and 2+ files are available.
Limelight 2, 2+, and 3 will need a [custom hardware config file](https://github.com/PhotonVision/photonvision/tree/main/docs/source/docs/advanced-installation/sw_install/files) for lighting to work. Currently only limelight 2 and 2+ files are available.
:::
:::{note}

View File

@@ -70,16 +70,12 @@ public class ConfigManager {
if (INSTANCE == null) {
Path rootFolder = PathManager.getInstance().getRootFolder();
switch (m_saveStrat) {
case SQL:
INSTANCE = new ConfigManager(rootFolder, new SqlConfigProvider(rootFolder));
break;
case LEGACY:
INSTANCE = new ConfigManager(rootFolder, new LegacyConfigProvider(rootFolder));
break;
case ATOMIC_ZIP:
// not yet done, fall through
default:
break;
case SQL -> INSTANCE = new ConfigManager(rootFolder, new SqlConfigProvider(rootFolder));
case LEGACY ->
INSTANCE = new ConfigManager(rootFolder, new LegacyConfigProvider(rootFolder));
case ATOMIC_ZIP -> {
// TODO: Not done yet
}
}
}
return INSTANCE;

View File

@@ -205,13 +205,11 @@ public class NeuralNetworkModelManager {
try {
switch (backend.get()) {
case RKNN:
case RKNN -> {
models.get(backend.get()).add(new RknnModel(model, labels));
logger.info(
"Loaded model " + model.getName() + " for backend " + backend.get().toString());
break;
default:
break;
}
}
} catch (IllegalArgumentException e) {
logger.error("Failed to load model " + model.getName(), e);

View File

@@ -599,9 +599,9 @@ public class SqlConfigProvider extends ConfigProvider {
result.getString(Columns.CAM_PIPELINE_JSONS), dummyList.getClass());
List<CVPipelineSettings> loadedSettings = new ArrayList<>();
for (var str : pipelineSettings) {
if (str instanceof String) {
loadedSettings.add(JacksonUtils.deserialize((String) str, CVPipelineSettings.class));
for (var setting : pipelineSettings) {
if (setting instanceof String str) {
loadedSettings.add(JacksonUtils.deserialize(str, CVPipelineSettings.class));
}
}

View File

@@ -242,22 +242,7 @@ public class PigpioSocket {
waveSendOnce(waveformId);
}
} else {
String error = "";
switch (waveformId) {
case PI_EMPTY_WAVEFORM:
error = "Waveform empty";
break;
case PI_TOO_MANY_CBS:
error = "Too many CBS";
break;
case PI_TOO_MANY_OOL:
error = "Too many OOL";
break;
case PI_NO_WAVEFORM_ID:
error = "No waveform ID";
break;
}
logger.error("Failed to send wave: " + error);
logger.error("Failed to send wave: " + getMessageForError(waveformId));
}
}

View File

@@ -134,25 +134,17 @@ public class VisionLED {
var newLedModeRaw = (int) entryNotification.valueData.value.getInteger();
logger.debug("Got LED mode " + newLedModeRaw);
if (newLedModeRaw != currentLedMode.value) {
VisionLEDMode newLedMode;
switch (newLedModeRaw) {
case -1:
newLedMode = VisionLEDMode.kDefault;
break;
case 0:
newLedMode = VisionLEDMode.kOff;
break;
case 1:
newLedMode = VisionLEDMode.kOn;
break;
case 2:
newLedMode = VisionLEDMode.kBlink;
break;
default:
logger.warn("User supplied invalid LED mode, falling back to Default");
newLedMode = VisionLEDMode.kDefault;
break;
}
VisionLEDMode newLedMode =
switch (newLedModeRaw) {
case -1 -> newLedMode = VisionLEDMode.kDefault;
case 0 -> newLedMode = VisionLEDMode.kOff;
case 1 -> newLedMode = VisionLEDMode.kOn;
case 2 -> newLedMode = VisionLEDMode.kBlink;
default -> {
logger.warn("User supplied invalid LED mode, falling back to Default");
yield VisionLEDMode.kDefault;
}
};
setInternal(newLedMode, true);
if (modeConsumer != null) modeConsumer.accept(newLedMode.value);
@@ -164,18 +156,10 @@ public class VisionLED {
if (fromNT) {
switch (newLedMode) {
case kDefault:
setStateImpl(pipelineModeSupplier.getAsBoolean());
break;
case kOff:
setStateImpl(false);
break;
case kOn:
setStateImpl(true);
break;
case kBlink:
blinkImpl(85, -1);
break;
case kDefault -> setStateImpl(pipelineModeSupplier.getAsBoolean());
case kOff -> setStateImpl(false);
case kOn -> setStateImpl(true);
case kBlink -> blinkImpl(85, -1);
}
currentLedMode = newLedMode;
logger.info(
@@ -183,18 +167,10 @@ public class VisionLED {
} else {
if (currentLedMode == VisionLEDMode.kDefault) {
switch (newLedMode) {
case kDefault:
setStateImpl(pipelineModeSupplier.getAsBoolean());
break;
case kOff:
setStateImpl(false);
break;
case kOn:
setStateImpl(true);
break;
case kBlink:
blinkImpl(85, -1);
break;
case kDefault -> setStateImpl(pipelineModeSupplier.getAsBoolean());
case kOff -> setStateImpl(false);
case kOn -> setStateImpl(true);
case kBlink -> blinkImpl(85, -1);
}
}
logger.info("Changing LED internal state to " + newLedMode.toString());

View File

@@ -17,7 +17,7 @@
package org.photonvision.common.logging;
import edu.wpi.first.util.RuntimeDetector;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.util.TimedTaskManager;
import org.photonvision.jni.QueuedFileLogger;
@@ -39,7 +39,7 @@ public class KernelLogLogger {
Logger logger = new Logger(KernelLogLogger.class, LogGroup.General);
public KernelLogLogger() {
if (RuntimeDetector.isLinux()) {
if (Platform.isLinux()) {
listener = new QueuedFileLogger("/var/log/kern.log");
} else {
System.out.println("NOT for klogs");

View File

@@ -51,16 +51,9 @@ public abstract class NumberCouple<T extends Number> {
@Override
public boolean equals(Object obj) {
if (!(obj instanceof NumberCouple)) {
return false;
}
var couple = (NumberCouple) obj;
if (!couple.first.equals(first)) {
return false;
}
return couple.second.equals(second);
return obj instanceof NumberCouple<?> couple
&& couple.first.equals(first)
&& couple.second.equals(second);
}
@JsonIgnore

View File

@@ -22,9 +22,9 @@ import edu.wpi.first.cscore.CvSink;
import edu.wpi.first.cscore.UsbCamera;
import edu.wpi.first.cscore.VideoException;
import edu.wpi.first.cscore.VideoProperty;
import edu.wpi.first.util.RuntimeDetector;
import java.util.*;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.vision.camera.CameraQuirk;
@@ -113,7 +113,7 @@ public class USBCameraSource extends VisionSource {
GenericUSBCameraSettables settables;
if (quirks.hasQuirk(CameraQuirk.LifeCamControls)) {
if (RuntimeDetector.isWindows()) {
if (Platform.isWindows()) {
logger.debug("Using Microsoft Lifecam 3000 Windows-Specific Settables");
settables = new LifeCam3kWindowsCameraSettables(config, camera);
} else {
@@ -124,7 +124,7 @@ public class USBCameraSource extends VisionSource {
logger.debug("Using PlayStation Eye Camera Settables");
settables = new PsEyeCameraSettables(config, camera);
} else if (quirks.hasQuirk(CameraQuirk.ArduOV2311Controls)) {
if (RuntimeDetector.isWindows()) {
if (Platform.isWindows()) {
logger.debug("Using Arducam OV2311 Windows-Specific Settables");
settables = new ArduOV2311WindowsCameraSettables(config, camera);
} else {

View File

@@ -135,20 +135,14 @@ public class MJPGFrameConsumer implements AutoCloseable {
}
private static String pixelFormatToString(PixelFormat pixelFormat) {
switch (pixelFormat) {
case kMJPEG:
return "MJPEG";
case kYUYV:
return "YUYV";
case kRGB565:
return "RGB565";
case kBGR:
return "BGR";
case kGray:
return "Gray";
default:
return "Unknown";
}
return switch (pixelFormat) {
case kMJPEG -> "MJPEG";
case kYUYV -> "YUYV";
case kRGB565 -> "RGB565";
case kBGR -> "BGR";
case kGray -> "Gray";
case kUYVY, kUnknown, kY16, kBGRA -> "Unknown";
};
}
@Override

View File

@@ -158,18 +158,19 @@ public class Contour implements Releasable {
double massX = (x0A + x0B) / 2;
double massY = (y0A + y0B) / 2;
switch (intersectionDirection) {
case Up:
case None -> {}
case Up -> {
if (intersectionY < massY) isIntersecting = true;
break;
case Down:
}
case Down -> {
if (intersectionY > massY) isIntersecting = true;
break;
case Left:
}
case Left -> {
if (intersectionX < massX) isIntersecting = true;
break;
case Right:
}
case Right -> {
if (intersectionX > massX) isIntersecting = true;
break;
}
}
intersectMatA.release();
intersectMatB.release();

View File

@@ -17,9 +17,6 @@
package org.photonvision.vision.opencv;
import java.util.EnumSet;
import java.util.HashMap;
public enum ContourShape {
Circle(0),
Custom(-1),
@@ -32,15 +29,12 @@ public enum ContourShape {
this.sides = sides;
}
private static final HashMap<Integer, ContourShape> sidesToValueMap = new HashMap<>();
static {
for (var value : EnumSet.allOf(ContourShape.class)) {
sidesToValueMap.put(value.sides, value);
}
}
public static ContourShape fromSides(int sides) {
return sidesToValueMap.get(sides);
return switch (sides) {
case 0 -> Circle;
case 3 -> Triangle;
case 4 -> Quadrilateral;
default -> Custom;
};
}
}

View File

@@ -48,13 +48,14 @@ public class Draw2dCrosshairPipe
double scale = params.frameStaticProperties.imageWidth / (double) params.divisor.value / 32.0;
switch (params.robotOffsetPointMode) {
case Single:
case None -> {}
case Single -> {
if (params.singleOffsetPoint.x != 0 && params.singleOffsetPoint.y != 0) {
x = params.singleOffsetPoint.x;
y = params.singleOffsetPoint.y;
}
break;
case Dual:
}
case Dual -> {
if (!in.getRight().isEmpty()) {
var target = in.getRight().get(0);
if (target != null) {
@@ -65,7 +66,7 @@ public class Draw2dCrosshairPipe
y = offsetCrosshair.y;
}
}
break;
}
}
x /= (double) params.divisor.value;

View File

@@ -50,23 +50,7 @@ public class FindPolygonPipe
private CVShape getShape(Contour in) {
int corners = getCorners(in);
/*The contourShape enum has predefined shapes for Circles, Triangles, and Quads
meaning any shape not fitting in those predefined shapes must be a custom shape.
*/
if (ContourShape.fromSides(corners) == null) {
return new CVShape(in, ContourShape.Custom);
}
switch (ContourShape.fromSides(corners)) {
case Circle:
return new CVShape(in, ContourShape.Circle);
case Triangle:
return new CVShape(in, ContourShape.Triangle);
case Quadrilateral:
return new CVShape(in, ContourShape.Quadrilateral);
}
return new CVShape(in, ContourShape.Custom);
return new CVShape(in, ContourShape.fromSides(corners));
}
private int getCorners(Contour contour) {

View File

@@ -85,19 +85,29 @@ public class ArucoPipeline extends CVPipeline<CVPipelineResult, ArucoPipelineSet
// 2023/other: best guess is 6in
double tagWidth = Units.inchesToMeters(6);
TargetModel tagModel = TargetModel.kAprilTag16h5;
switch (settings.tagFamily) {
case kTag36h11:
// 2024 tag, 6.5in
params.tagFamily = Objdetect.DICT_APRILTAG_36h11;
tagWidth = Units.inchesToMeters(6.5);
tagModel = TargetModel.kAprilTag36h11;
break;
case kTag25h9:
params.tagFamily = Objdetect.DICT_APRILTAG_25h9;
break;
default:
params.tagFamily = Objdetect.DICT_APRILTAG_16h5;
}
params.tagFamily =
switch (settings.tagFamily) {
case kTag36h11 -> {
// 2024 tag, 6.5in
tagWidth = Units.inchesToMeters(6.5);
tagModel = TargetModel.kAprilTag36h11;
yield Objdetect.DICT_APRILTAG_36h11;
}
case kTag25h9 -> Objdetect.DICT_APRILTAG_25h9;
// TODO: explicitly drop support for these
case kTag16h5,
kTagCircle21h7,
kTagCircle49h12,
kTagCustom48h11,
kTagStandard41h12,
kTagStandard52h13 -> {
// 2024 tag, 6.5in
tagWidth = Units.inchesToMeters(6.5);
tagModel = TargetModel.kAprilTag36h11;
yield Objdetect.DICT_APRILTAG_36h11;
}
};
int threshMinSize = Math.max(3, settings.threshWinSizes.getFirst());
settings.threshWinSizes.setFirst(threshMinSize);

View File

@@ -241,41 +241,38 @@ public class PipelineManager {
* recreation after changing pipeline type
*/
private void recreateUserPipeline() {
// Cleanup potential old native resources before swapping over from a user
// pipeline
// Cleanup potential old native resources before swapping over from a user pipeline
if (currentUserPipeline != null && !(currentPipelineIndex < 0)) {
currentUserPipeline.release();
}
var desiredPipelineSettings = userPipelineSettings.get(currentPipelineIndex);
switch (desiredPipelineSettings.pipelineType) {
case Reflective:
case Reflective -> {
logger.debug("Creating Reflective pipeline");
currentUserPipeline =
new ReflectivePipeline((ReflectivePipelineSettings) desiredPipelineSettings);
break;
case ColoredShape:
}
case ColoredShape -> {
logger.debug("Creating ColoredShape pipeline");
currentUserPipeline =
new ColoredShapePipeline((ColoredShapePipelineSettings) desiredPipelineSettings);
break;
case AprilTag:
}
case AprilTag -> {
logger.debug("Creating AprilTag pipeline");
currentUserPipeline =
new AprilTagPipeline((AprilTagPipelineSettings) desiredPipelineSettings);
break;
case Aruco:
}
case Aruco -> {
logger.debug("Creating Aruco Pipeline");
currentUserPipeline = new ArucoPipeline((ArucoPipelineSettings) desiredPipelineSettings);
break;
case ObjectDetection:
}
case ObjectDetection -> {
logger.debug("Creating ObjectDetection Pipeline");
currentUserPipeline =
new ObjectDetectionPipeline((ObjectDetectionPipelineSettings) desiredPipelineSettings);
default:
// Can be calib3d or drivermode, both of which are special cases
break;
}
case Calib3d, DriverMode -> {}
}
}
@@ -342,44 +339,40 @@ public class PipelineManager {
}
private CVPipelineSettings createSettingsForType(PipelineType type, String nickname) {
CVPipelineSettings newSettings;
switch (type) {
case Reflective:
{
var added = new ReflectivePipelineSettings();
added.pipelineNickname = nickname;
return added;
}
case ColoredShape:
{
var added = new ColoredShapePipelineSettings();
added.pipelineNickname = nickname;
return added;
}
case AprilTag:
{
var added = new AprilTagPipelineSettings();
added.pipelineNickname = nickname;
return added;
}
case Aruco:
{
var added = new ArucoPipelineSettings();
added.pipelineNickname = nickname;
return added;
}
case ObjectDetection:
{
var added = new ObjectDetectionPipelineSettings();
added.pipelineNickname = nickname;
return added;
}
default:
{
logger.error("Got invalid pipeline type: " + type);
return null;
}
case Reflective -> {
var added = new ReflectivePipelineSettings();
added.pipelineNickname = nickname;
return added;
}
case ColoredShape -> {
var added = new ColoredShapePipelineSettings();
added.pipelineNickname = nickname;
return added;
}
case AprilTag -> {
var added = new AprilTagPipelineSettings();
added.pipelineNickname = nickname;
return added;
}
case Aruco -> {
var added = new ArucoPipelineSettings();
added.pipelineNickname = nickname;
return added;
}
case ObjectDetection -> {
var added = new ObjectDetectionPipelineSettings();
added.pipelineNickname = nickname;
return added;
}
case Calib3d, DriverMode -> {
logger.error("Got invalid pipeline type: " + type);
return null;
}
}
// This can never happen, this is here to satisfy the compiler.
throw new IllegalStateException("Got impossible pipeline type: " + type);
}
private void addPipelineInternal(CVPipelineSettings settings) {

View File

@@ -587,11 +587,9 @@ public class VisionModule {
// Pipelines like DriverMode and Calibrate3dPipeline have null output frames
if (result.inputAndOutputFrame != null
&& (pipelineManager.getCurrentPipelineSettings() instanceof AdvancedPipelineSettings)) {
streamRunnable.updateData(
result.inputAndOutputFrame,
(AdvancedPipelineSettings) pipelineManager.getCurrentPipelineSettings(),
result.targets);
&& (pipelineManager.getCurrentPipelineSettings()
instanceof AdvancedPipelineSettings settings)) {
streamRunnable.updateData(result.inputAndOutputFrame, settings, result.targets);
// The streamRunnable manages releasing in this case
} else {
consumeResults(result.inputAndOutputFrame, result.targets);
@@ -615,9 +613,9 @@ public class VisionModule {
}
public void setTargetModel(TargetModel targetModel) {
var settings = pipelineManager.getCurrentPipeline().getSettings();
if (settings instanceof ReflectivePipelineSettings) {
((ReflectivePipelineSettings) settings).targetModel = targetModel;
var pipelineSettings = pipelineManager.getCurrentPipeline().getSettings();
if (pipelineSettings instanceof ReflectivePipelineSettings settings) {
settings.targetModel = targetModel;
saveAndBroadcastAll();
} else {
logger.error("Cannot set target model of non-reflective pipe! Ignoring...");

View File

@@ -55,11 +55,8 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
@Override
public void onDataChangeEvent(DataChangeEvent<?> event) {
if (event instanceof IncomingWebSocketEvent) {
var wsEvent = (IncomingWebSocketEvent<?>) event;
// Camera index -1 means a "multicast event" (i.e. the event is received by all
// cameras)
if (event instanceof IncomingWebSocketEvent wsEvent) {
// Camera index -1 means a "multicast event" (i.e. the event is received by all cameras)
if (wsEvent.cameraIndex != null
&& (wsEvent.cameraIndex == parentModule.moduleIndex || wsEvent.cameraIndex == -1)) {
logger.trace("Got PSC event - propName: " + wsEvent.propertyName);
@@ -93,120 +90,32 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
var currentSettings = change.getCurrentSettings();
var originContext = change.getOriginContext();
switch (propName) {
case "pipelineName": // rename current pipeline
logger.info("Changing nick to " + newPropValue);
parentModule.pipelineManager.getCurrentPipelineSettings().pipelineNickname =
(String) newPropValue;
parentModule.saveAndBroadcastAll();
continue;
case "newPipelineInfo": // add new pipeline
var typeName = (Pair<String, PipelineType>) newPropValue;
var type = typeName.getRight();
var name = typeName.getLeft();
logger.info("Adding a " + type + " pipeline with name " + name);
var addedSettings = parentModule.pipelineManager.addPipeline(type);
addedSettings.pipelineNickname = name;
parentModule.saveAndBroadcastAll();
continue;
case "deleteCurrPipeline":
var indexToDelete = parentModule.pipelineManager.getRequestedIndex();
logger.info("Deleting current pipe at index " + indexToDelete);
int newIndex = parentModule.pipelineManager.removePipeline(indexToDelete);
parentModule.setPipeline(newIndex);
parentModule.saveAndBroadcastAll();
continue;
case "changePipeline": // change active pipeline
var index = (Integer) newPropValue;
if (index == parentModule.pipelineManager.getRequestedIndex()) {
logger.debug("Skipping pipeline change, index " + index + " already active");
continue;
case "pipelineName" -> newPipelineNickname((String) newPropValue);
case "newPipelineInfo" -> newPipelineInfo((Pair<String, PipelineType>) newPropValue);
case "deleteCurrPipeline" -> deleteCurrPipeline();
case "changePipeline" -> changePipeline((Integer) newPropValue);
case "startCalibration" -> startCalibration((Map<String, Object>) newPropValue);
case "saveInputSnapshot" -> parentModule.saveInputSnapshot();
case "saveOutputSnapshot" -> parentModule.saveOutputSnapshot();
case "takeCalSnapshot" -> parentModule.takeCalibrationSnapshot();
case "duplicatePipeline" -> duplicatePipeline((Integer) newPropValue);
case "calibrationUploaded" -> {
if (newPropValue instanceof CameraCalibrationCoefficients newCal) {
parentModule.addCalibrationToConfig(newCal);
} else {
logger.warn("Received invalid calibration data");
}
parentModule.setPipeline(index);
parentModule.saveAndBroadcastAll();
continue;
case "startCalibration":
try {
var data =
JacksonUtils.deserialize(
(Map<String, Object>) newPropValue, UICalibrationData.class);
parentModule.startCalibration(data);
parentModule.saveAndBroadcastAll();
} catch (Exception e) {
logger.error("Error deserailizing start-cal request", e);
}
case "robotOffsetPoint" -> {
if (currentSettings instanceof AdvancedPipelineSettings curAdvSettings) {
robotOffsetPoint(curAdvSettings, (Integer) newPropValue);
}
continue;
case "saveInputSnapshot":
parentModule.saveInputSnapshot();
continue;
case "saveOutputSnapshot":
parentModule.saveOutputSnapshot();
continue;
case "takeCalSnapshot":
parentModule.takeCalibrationSnapshot();
continue;
case "duplicatePipeline":
int idx = parentModule.pipelineManager.duplicatePipeline((Integer) newPropValue);
parentModule.setPipeline(idx);
parentModule.saveAndBroadcastAll();
continue;
case "calibrationUploaded":
if (newPropValue instanceof CameraCalibrationCoefficients)
parentModule.addCalibrationToConfig((CameraCalibrationCoefficients) newPropValue);
continue;
case "robotOffsetPoint":
if (currentSettings instanceof AdvancedPipelineSettings) {
var curAdvSettings = (AdvancedPipelineSettings) currentSettings;
var offsetOperation = RobotOffsetPointOperation.fromIndex((int) newPropValue);
var latestTarget = parentModule.lastPipelineResultBestTarget;
if (latestTarget != null) {
var newPoint = latestTarget.getTargetOffsetPoint();
switch (curAdvSettings.offsetRobotOffsetMode) {
case Single:
if (offsetOperation == RobotOffsetPointOperation.ROPO_CLEAR) {
curAdvSettings.offsetSinglePoint = new Point();
} else if (offsetOperation == RobotOffsetPointOperation.ROPO_TAKESINGLE) {
curAdvSettings.offsetSinglePoint = newPoint;
}
break;
case Dual:
if (offsetOperation == RobotOffsetPointOperation.ROPO_CLEAR) {
curAdvSettings.offsetDualPointA = new Point();
curAdvSettings.offsetDualPointAArea = 0;
curAdvSettings.offsetDualPointB = new Point();
curAdvSettings.offsetDualPointBArea = 0;
} else {
// update point and area
switch (offsetOperation) {
case ROPO_TAKEFIRSTDUAL:
curAdvSettings.offsetDualPointA = newPoint;
curAdvSettings.offsetDualPointAArea = latestTarget.getArea();
break;
case ROPO_TAKESECONDDUAL:
curAdvSettings.offsetDualPointB = newPoint;
curAdvSettings.offsetDualPointBArea = latestTarget.getArea();
break;
default:
break;
}
}
break;
default:
break;
}
}
}
continue;
case "changePipelineType":
}
case "changePipelineType" -> {
parentModule.changePipelineType((Integer) newPropValue);
parentModule.saveAndBroadcastAll();
continue;
case "isDriverMode":
parentModule.setDriverMode((Boolean) newPropValue);
continue;
}
case "isDriverMode" -> parentModule.setDriverMode((Boolean) newPropValue);
}
// special case for camera settables
@@ -249,6 +158,104 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
}
}
public void newPipelineNickname(String newNickname) {
logger.info("Changing pipeline nickname to " + newNickname);
parentModule.pipelineManager.getCurrentPipelineSettings().pipelineNickname = newNickname;
parentModule.saveAndBroadcastAll();
}
public void newPipelineInfo(Pair<String, PipelineType> typeName) {
var type = typeName.getRight();
var name = typeName.getLeft();
logger.info("Adding a " + type + " pipeline with name " + name);
var addedSettings = parentModule.pipelineManager.addPipeline(type);
addedSettings.pipelineNickname = name;
parentModule.saveAndBroadcastAll();
}
public void deleteCurrPipeline() {
var indexToDelete = parentModule.pipelineManager.getRequestedIndex();
logger.info("Deleting current pipe at index " + indexToDelete);
int newIndex = parentModule.pipelineManager.removePipeline(indexToDelete);
parentModule.setPipeline(newIndex);
parentModule.saveAndBroadcastAll();
}
public void changePipeline(int index) {
if (index == parentModule.pipelineManager.getRequestedIndex()) {
logger.debug("Skipping pipeline change, index " + index + " already active");
return;
}
parentModule.setPipeline(index);
parentModule.saveAndBroadcastAll();
}
public void startCalibration(Map<String, Object> data) {
try {
var deserialized = JacksonUtils.deserialize(data, UICalibrationData.class);
parentModule.startCalibration(deserialized);
parentModule.saveAndBroadcastAll();
} catch (Exception e) {
logger.error("Error deserailizing start-calibration request", e);
}
}
public void duplicatePipeline(int index) {
var newIndex = parentModule.pipelineManager.duplicatePipeline(index);
parentModule.setPipeline(newIndex);
parentModule.saveAndBroadcastAll();
}
public void robotOffsetPoint(AdvancedPipelineSettings curAdvSettings, int offsetIndex) {
RobotOffsetPointOperation offsetOperation = RobotOffsetPointOperation.fromIndex(offsetIndex);
var latestTarget = parentModule.lastPipelineResultBestTarget;
if (latestTarget == null) {
return;
}
var newPoint = latestTarget.getTargetOffsetPoint();
switch (curAdvSettings.offsetRobotOffsetMode) {
case Single -> {
switch (offsetOperation) {
case CLEAR -> curAdvSettings.offsetSinglePoint = new Point();
case TAKE_SINGLE -> curAdvSettings.offsetSinglePoint = newPoint;
case TAKE_FIRST_DUAL, TAKE_SECOND_DUAL -> {
logger.warn("Dual point operation in single point mode");
}
}
}
case Dual -> {
switch (offsetOperation) {
case CLEAR -> {
curAdvSettings.offsetDualPointA = new Point();
curAdvSettings.offsetDualPointAArea = 0;
curAdvSettings.offsetDualPointB = new Point();
curAdvSettings.offsetDualPointBArea = 0;
}
case TAKE_FIRST_DUAL -> {
// update point and area
curAdvSettings.offsetDualPointA = newPoint;
curAdvSettings.offsetDualPointAArea = latestTarget.getArea();
}
case TAKE_SECOND_DUAL -> {
// update point and area
curAdvSettings.offsetDualPointB = newPoint;
curAdvSettings.offsetDualPointBArea = latestTarget.getArea();
}
case TAKE_SINGLE -> {
logger.warn("Single point operation in dual point mode");
}
}
}
case None -> {
logger.warn("Robot offset point operation requested, but no offset mode set");
}
}
}
/**
* Sets the value of a property in the given object using reflection. This method should not be
* used generally and is only known to be correct in the context of `onDataChangeEvent`.
@@ -281,8 +288,8 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
} else if (propType.equals(Integer.TYPE)) {
propField.setInt(currentSettings, (Integer) newPropValue);
} else if (propType.equals(Boolean.TYPE)) {
if (newPropValue instanceof Integer) {
propField.setBoolean(currentSettings, (Integer) newPropValue != 0);
if (newPropValue instanceof Integer intValue) {
propField.setBoolean(currentSettings, intValue != 0);
} else {
propField.setBoolean(currentSettings, (Boolean) newPropValue);
}

View File

@@ -84,8 +84,7 @@ public class VisionRunner {
frameSupplier.requestFrameThresholdType(wantedProcessType);
var settings = pipeline.getSettings();
if (settings instanceof AdvancedPipelineSettings) {
var advanced = (AdvancedPipelineSettings) settings;
if (settings instanceof AdvancedPipelineSettings advanced) {
var hsvParams =
new HSVPipe.HSVParams(
advanced.hsvHue, advanced.hsvSaturation, advanced.hsvValue, advanced.hueInverted);

View File

@@ -17,10 +17,10 @@
package org.photonvision.vision.target;
public enum RobotOffsetPointOperation {
ROPO_CLEAR(0),
ROPO_TAKESINGLE(1),
ROPO_TAKEFIRSTDUAL(2),
ROPO_TAKESECONDDUAL(3);
CLEAR(0),
TAKE_SINGLE(1),
TAKE_FIRST_DUAL(2),
TAKE_SECOND_DUAL(3);
public final int index;
@@ -29,17 +29,12 @@ public enum RobotOffsetPointOperation {
}
public static RobotOffsetPointOperation fromIndex(int index) {
switch (index) {
case 0:
return ROPO_CLEAR;
case 1:
return ROPO_TAKESINGLE;
case 2:
return ROPO_TAKEFIRSTDUAL;
case 3:
return ROPO_TAKESECONDDUAL;
default:
return ROPO_CLEAR;
}
return switch (index) {
case 0 -> CLEAR;
case 1 -> TAKE_SINGLE;
case 2 -> TAKE_FIRST_DUAL;
case 3 -> TAKE_SECOND_DUAL;
default -> CLEAR;
};
}
}

View File

@@ -29,7 +29,7 @@ if m:
split = gitDescribeResult.split("-")
if len(split) == 3:
year, commits, sha = split
# Chop off leading v from "v2024.1.2", and use "post" for commits to master since
# Chop off leading v from "v2024.1.2", and use "post" for commits to main since
versionString = f"{year[1:]}post{commits}"
print("using dev release " + versionString)
else:

View File

@@ -298,17 +298,12 @@ public class PhotonCamera implements AutoCloseable {
*/
public VisionLEDMode getLEDMode() {
int value = (int) ledModeState.get(-1);
switch (value) {
case 0:
return VisionLEDMode.kOff;
case 1:
return VisionLEDMode.kOn;
case 2:
return VisionLEDMode.kBlink;
case -1:
default:
return VisionLEDMode.kDefault;
}
return switch (value) {
case 0 -> VisionLEDMode.kOff;
case 1 -> VisionLEDMode.kOn;
case 2 -> VisionLEDMode.kBlink;
default -> VisionLEDMode.kDefault;
};
}
/**

View File

@@ -345,46 +345,35 @@ public class PhotonPoseEstimator {
PhotonPipelineResult cameraResult,
Optional<Matrix<N3, N3>> cameraMatrix,
Optional<Matrix<N8, N1>> distCoeffs,
PoseStrategy strat) {
Optional<EstimatedRobotPose> estimatedPose = Optional.empty();
switch (strat) {
case LOWEST_AMBIGUITY:
estimatedPose = lowestAmbiguityStrategy(cameraResult);
break;
case CLOSEST_TO_CAMERA_HEIGHT:
estimatedPose = closestToCameraHeightStrategy(cameraResult);
break;
case CLOSEST_TO_REFERENCE_POSE:
estimatedPose = closestToReferencePoseStrategy(cameraResult, referencePose);
break;
case CLOSEST_TO_LAST_POSE:
setReferencePose(lastPose);
estimatedPose = closestToReferencePoseStrategy(cameraResult, referencePose);
break;
case AVERAGE_BEST_TARGETS:
estimatedPose = averageBestTargetsStrategy(cameraResult);
break;
case MULTI_TAG_PNP_ON_RIO:
if (cameraMatrix.isEmpty()) {
DriverStation.reportWarning(
"Camera matrix is empty for multi-tag-on-rio",
Thread.currentThread().getStackTrace());
} else if (distCoeffs.isEmpty()) {
DriverStation.reportWarning(
"Camera matrix is empty for multi-tag-on-rio",
Thread.currentThread().getStackTrace());
} else {
estimatedPose = multiTagOnRioStrategy(cameraResult, cameraMatrix, distCoeffs);
}
break;
case MULTI_TAG_PNP_ON_COPROCESSOR:
estimatedPose = multiTagOnCoprocStrategy(cameraResult);
break;
default:
DriverStation.reportError(
"[PhotonPoseEstimator] Unknown Position Estimation Strategy!", false);
return Optional.empty();
}
PoseStrategy strategy) {
Optional<EstimatedRobotPose> estimatedPose =
switch (strategy) {
case LOWEST_AMBIGUITY -> lowestAmbiguityStrategy(cameraResult);
case CLOSEST_TO_CAMERA_HEIGHT -> closestToCameraHeightStrategy(cameraResult);
case CLOSEST_TO_REFERENCE_POSE ->
closestToReferencePoseStrategy(cameraResult, referencePose);
case CLOSEST_TO_LAST_POSE -> {
setReferencePose(lastPose);
yield closestToReferencePoseStrategy(cameraResult, referencePose);
}
case AVERAGE_BEST_TARGETS -> averageBestTargetsStrategy(cameraResult);
case MULTI_TAG_PNP_ON_RIO -> {
if (cameraMatrix.isEmpty()) {
DriverStation.reportWarning(
"Camera matrix is empty for multi-tag-on-rio",
Thread.currentThread().getStackTrace());
yield Optional.empty();
} else if (distCoeffs.isEmpty()) {
DriverStation.reportWarning(
"Camera matrix is empty for multi-tag-on-rio",
Thread.currentThread().getStackTrace());
yield Optional.empty();
} else {
yield multiTagOnRioStrategy(cameraResult, cameraMatrix, distCoeffs);
}
}
case MULTI_TAG_PNP_ON_COPROCESSOR -> multiTagOnCoprocStrategy(cameraResult);
};
if (estimatedPose.isPresent()) {
lastPose = estimatedPose.get().estimatedPose;

View File

@@ -84,11 +84,9 @@ public class VisionTargetSim {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj instanceof VisionTargetSim) {
var o = (VisionTargetSim) obj;
return pose.equals(o.pose) && model.equals(o.model);
}
return false;
return this == obj
&& obj instanceof VisionTargetSim o
&& pose.equals(o.pose)
&& model.equals(o.model);
}
}

View File

@@ -1,6 +1,6 @@
plugins {
id "application"
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'com.gradleup.shadow' version '8.3.4'
id "com.github.node-gradle.node" version "7.0.1"
id "org.hidetake.ssh" version "2.11.2"
id 'edu.wpi.first.WpilibTools' version '1.3.0'

View File

@@ -99,10 +99,9 @@ public class DataSocketHandler {
objectMapper.readValue(context.data(), new TypeReference<>() {});
// Special case the current camera index
var camIndexValue = deserializedData.get("cameraIndex");
Integer cameraIndex = null;
if (camIndexValue instanceof Integer) {
cameraIndex = (Integer) camIndexValue;
if (deserializedData.get("cameraIndex") instanceof Integer camIndexValue) {
cameraIndex = camIndexValue;
deserializedData.remove("cameraIndex");
}
@@ -128,216 +127,182 @@ public class DataSocketHandler {
}
switch (socketMessageType) {
case SMT_DRIVERMODE:
{
// TODO: what is this event?
var data = (Boolean) entryValue;
var dmIsDriverEvent =
new IncomingWebSocketEvent<Boolean>(
DataChangeDestination.DCD_ACTIVEMODULE,
"isDriverMode",
data,
cameraIndex,
context);
case SMT_DRIVERMODE -> {
// TODO: what is this event?
var data = (Boolean) entryValue;
var dmIsDriverEvent =
new IncomingWebSocketEvent<Boolean>(
DataChangeDestination.DCD_ACTIVEMODULE,
"isDriverMode",
data,
cameraIndex,
context);
dcService.publishEvents(dmIsDriverEvent);
break;
}
case SMT_CHANGECAMERANAME:
{
var ccnEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"cameraNickname",
(String) entryValue,
cameraIndex,
context);
dcService.publishEvent(ccnEvent);
break;
}
case SMT_CHANGEPIPELINENAME:
{
var cpnEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"pipelineName",
(String) entryValue,
cameraIndex,
context);
dcService.publishEvent(cpnEvent);
break;
}
case SMT_ADDNEWPIPELINE:
{
// HashMap<String, Object> data = (HashMap<String,
// Object>) entryValue;
// var type = (PipelineType)
// data.get("pipelineType");
// var name = (String) data.get("pipelineName");
var arr = (ArrayList<Object>) entryValue;
var name = (String) arr.get(0);
var type = PipelineType.values()[(Integer) arr.get(1) + 2];
dcService.publishEvents(dmIsDriverEvent);
}
case SMT_CHANGECAMERANAME -> {
var ccnEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"cameraNickname",
(String) entryValue,
cameraIndex,
context);
dcService.publishEvent(ccnEvent);
}
case SMT_CHANGEPIPELINENAME -> {
var cpnEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"pipelineName",
(String) entryValue,
cameraIndex,
context);
dcService.publishEvent(cpnEvent);
}
case SMT_ADDNEWPIPELINE -> {
// HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
// var type = (PipelineType) data.get("pipelineType");
// var name = (String) data.get("pipelineName");
var arr = (ArrayList<Object>) entryValue;
var name = (String) arr.get(0);
var type = PipelineType.values()[(Integer) arr.get(1) + 2];
var newPipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"newPipelineInfo",
Pair.of(name, type),
cameraIndex,
context);
dcService.publishEvent(newPipelineEvent);
break;
}
case SMT_CHANGEBRIGHTNESS:
{
HardwareManager.getInstance()
.setBrightnessPercent(Integer.parseInt(entryValue.toString()));
break;
}
case SMT_DUPLICATEPIPELINE:
{
var pipeIndex = (Integer) entryValue;
var newPipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"newPipelineInfo",
Pair.of(name, type),
cameraIndex,
context);
dcService.publishEvent(newPipelineEvent);
}
case SMT_CHANGEBRIGHTNESS -> {
HardwareManager.getInstance()
.setBrightnessPercent(Integer.parseInt(entryValue.toString()));
}
case SMT_DUPLICATEPIPELINE -> {
var pipeIndex = (Integer) entryValue;
logger.info("Duplicating pipe@index" + pipeIndex + " for camera " + cameraIndex);
logger.info("Duplicating pipe@index" + pipeIndex + " for camera " + cameraIndex);
var newPipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"duplicatePipeline",
pipeIndex,
cameraIndex,
context);
dcService.publishEvent(newPipelineEvent);
break;
}
case SMT_DELETECURRENTPIPELINE:
{
var deleteCurrentPipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"deleteCurrPipeline",
0,
cameraIndex,
context);
dcService.publishEvent(deleteCurrentPipelineEvent);
break;
}
case SMT_ROBOTOFFSETPOINT:
{
var robotOffsetPointEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"robotOffsetPoint",
(Integer) entryValue,
cameraIndex,
null);
dcService.publishEvent(robotOffsetPointEvent);
break;
}
case SMT_CURRENTCAMERA:
{
var changeCurrentCameraEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_OTHER, "changeUICamera", (Integer) entryValue);
dcService.publishEvent(changeCurrentCameraEvent);
break;
}
case SMT_CURRENTPIPELINE:
{
var changePipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"changePipeline",
(Integer) entryValue,
cameraIndex,
context);
dcService.publishEvent(changePipelineEvent);
break;
}
case SMT_STARTPNPCALIBRATION:
{
var changePipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"startCalibration",
(Map) entryValue,
cameraIndex,
context);
dcService.publishEvent(changePipelineEvent);
break;
}
case SMT_SAVEINPUTSNAPSHOT:
{
var takeInputSnapshotEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"saveInputSnapshot",
0,
cameraIndex,
context);
dcService.publishEvent(takeInputSnapshotEvent);
break;
}
case SMT_SAVEOUTPUTSNAPSHOT:
{
var takeOutputSnapshotEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"saveOutputSnapshot",
0,
cameraIndex,
context);
dcService.publishEvent(takeOutputSnapshotEvent);
break;
}
case SMT_TAKECALIBRATIONSNAPSHOT:
{
var takeCalSnapshotEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"takeCalSnapshot",
0,
cameraIndex,
context);
dcService.publishEvent(takeCalSnapshotEvent);
break;
}
case SMT_PIPELINESETTINGCHANGE:
{
HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
var newPipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"duplicatePipeline",
pipeIndex,
cameraIndex,
context);
dcService.publishEvent(newPipelineEvent);
}
case SMT_DELETECURRENTPIPELINE -> {
var deleteCurrentPipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"deleteCurrPipeline",
0,
cameraIndex,
context);
dcService.publishEvent(deleteCurrentPipelineEvent);
}
case SMT_ROBOTOFFSETPOINT -> {
var robotOffsetPointEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"robotOffsetPoint",
(Integer) entryValue,
cameraIndex,
null);
dcService.publishEvent(robotOffsetPointEvent);
}
case SMT_CURRENTCAMERA -> {
var changeCurrentCameraEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_OTHER, "changeUICamera", (Integer) entryValue);
dcService.publishEvent(changeCurrentCameraEvent);
}
case SMT_CURRENTPIPELINE -> {
var changePipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"changePipeline",
(Integer) entryValue,
cameraIndex,
context);
dcService.publishEvent(changePipelineEvent);
}
case SMT_STARTPNPCALIBRATION -> {
var changePipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"startCalibration",
(Map) entryValue,
cameraIndex,
context);
dcService.publishEvent(changePipelineEvent);
}
case SMT_SAVEINPUTSNAPSHOT -> {
var takeInputSnapshotEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"saveInputSnapshot",
0,
cameraIndex,
context);
dcService.publishEvent(takeInputSnapshotEvent);
}
case SMT_SAVEOUTPUTSNAPSHOT -> {
var takeOutputSnapshotEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"saveOutputSnapshot",
0,
cameraIndex,
context);
dcService.publishEvent(takeOutputSnapshotEvent);
}
case SMT_TAKECALIBRATIONSNAPSHOT -> {
var takeCalSnapshotEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"takeCalSnapshot",
0,
cameraIndex,
context);
dcService.publishEvent(takeCalSnapshotEvent);
}
case SMT_PIPELINESETTINGCHANGE -> {
HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
if (data.size() >= 2) {
var cameraIndex2 = (int) data.get("cameraIndex");
for (var dataEntry : data.entrySet()) {
if (dataEntry.getKey().equals("cameraIndex")) {
continue;
}
var pipelineSettingChangeEvent =
new IncomingWebSocketEvent(
DataChangeDestination.DCD_ACTIVEPIPELINESETTINGS,
dataEntry.getKey(),
dataEntry.getValue(),
cameraIndex2,
context);
dcService.publishEvent(pipelineSettingChangeEvent);
if (data.size() >= 2) {
var cameraIndex2 = (int) data.get("cameraIndex");
for (var dataEntry : data.entrySet()) {
if (dataEntry.getKey().equals("cameraIndex")) {
continue;
}
} else {
logger.warn("Unknown message for PSC: " + data.keySet().iterator().next());
var pipelineSettingChangeEvent =
new IncomingWebSocketEvent(
DataChangeDestination.DCD_ACTIVEPIPELINESETTINGS,
dataEntry.getKey(),
dataEntry.getValue(),
cameraIndex2,
context);
dcService.publishEvent(pipelineSettingChangeEvent);
}
break;
}
case SMT_CHANGEPIPELINETYPE:
{
var changePipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"changePipelineType",
(Integer) entryValue,
cameraIndex,
context);
dcService.publishEvent(changePipelineEvent);
break;
} else {
logger.warn("Unknown message for PSC: " + data.keySet().iterator().next());
}
}
case SMT_CHANGEPIPELINETYPE -> {
var changePipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"changePipelineType",
(Integer) entryValue,
cameraIndex,
context);
dcService.publishEvent(changePipelineEvent);
}
}
} catch (Exception e) {
logger.error("Failed to parse message!", e);

View File

@@ -39,8 +39,7 @@ public class UIInboundSubscriber extends DataChangeSubscriber {
@Override
public void onDataChangeEvent(DataChangeEvent<?> event) {
if (event instanceof IncomingWebSocketEvent) {
var incomingWSEvent = (IncomingWebSocketEvent<?>) event;
if (event instanceof IncomingWebSocketEvent incomingWSEvent) {
if (incomingWSEvent.propertyName.equals("userConnected")
|| incomingWSEvent.propertyName.equals("sendFullSettings")) {
// Send full settings

View File

@@ -44,11 +44,9 @@ class UIOutboundSubscriber extends DataChangeSubscriber {
@Override
public void onDataChangeEvent(DataChangeEvent event) {
if (event instanceof OutgoingUIEvent) {
var thisEvent = (OutgoingUIEvent) event;
if (event instanceof OutgoingUIEvent thisEvent) {
try {
if (event.data instanceof HashMap) {
var data = (HashMap) event.data;
if (event.data instanceof HashMap data) {
socketHandler.broadcastMessage(data, thisEvent.originContext);
} else {
socketHandler.broadcastMessage(event.data, thisEvent.originContext);

View File

@@ -17,8 +17,8 @@
package org.photonvision.common.hardware;
import edu.wpi.first.util.RuntimeDetector;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -148,6 +148,11 @@ public enum Platform {
return currentPlatform.isSupported;
}
public static boolean isAthena() {
File runRobotFile = new File("/usr/local/frc/bin/frcRunRobot.sh");
return runRobotFile.exists();
}
//////////////////////////////////////////////////////
// Debug info related to unknown platforms for debug help
@@ -158,10 +163,24 @@ public enum Platform {
private static final String UnknownDeviceModelString = "Unknown";
public static Platform getCurrentPlatform() {
if (RuntimeDetector.isWindows()) {
if (RuntimeDetector.is32BitIntel()) {
String OS_NAME;
if (Platform.OS_NAME != null) {
OS_NAME = Platform.OS_NAME;
} else {
OS_NAME = System.getProperty("os.name");
}
String OS_ARCH;
if (Platform.OS_ARCH != null) {
OS_ARCH = Platform.OS_ARCH;
} else {
OS_ARCH = System.getProperty("os.arch");
}
if (OS_NAME.startsWith("Windows")) {
if (OS_ARCH.equals("x86") || OS_ARCH.equals("i386")) {
return WINDOWS_32;
} else if (RuntimeDetector.is64BitIntel()) {
} else if (OS_ARCH.equals("amd64") || OS_ARCH.equals("x86_64")) {
return WINDOWS_64;
} else {
// please don't try this
@@ -169,41 +188,41 @@ public enum Platform {
}
}
if (RuntimeDetector.isMac()) {
if (OS_NAME.startsWith("Mac")) {
// TODO - once we have real support, this might have to be more granular
return MACOS;
}
if (RuntimeDetector.isLinux()) {
if (OS_NAME.startsWith("Linux")) {
if (isPiSBC()) {
if (RuntimeDetector.isArm32()) {
if (OS_ARCH.equals("arm") || OS_ARCH.equals("arm32")) {
return LINUX_RASPBIAN32;
} else if (RuntimeDetector.isArm64()) {
} else if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) {
return LINUX_RASPBIAN64;
} else {
// Unknown/exotic installation
return UNKNOWN;
}
} else if (isJetsonSBC()) {
if (RuntimeDetector.isArm64()) {
if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) {
// TODO - do we need to check OS version?
return LINUX_AARCH64;
} else {
// Unknown/exotic installation
return UNKNOWN;
}
} else if (RuntimeDetector.is64BitIntel()) {
} else if (OS_ARCH.equals("amd64") || OS_ARCH.equals("x86_64")) {
return LINUX_64;
} else if (RuntimeDetector.is32BitIntel()) {
} else if (OS_ARCH.equals("x86") || OS_ARCH.equals("i386")) {
return LINUX_32;
} else if (RuntimeDetector.isArm64()) {
} else if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) {
// TODO - os detection needed?
if (isOrangePi()) {
return LINUX_RK3588_64;
} else {
return LINUX_AARCH64;
}
} else if (RuntimeDetector.isArm32()) {
} else if (OS_ARCH.equals("arm") || OS_ARCH.equals("arm32")) {
return LINUX_ARM32;
} else {
// Unknown or otherwise unsupported platform

View File

@@ -31,16 +31,11 @@ public enum VisionLEDMode {
@Override
public String toString() {
switch (this) {
case kDefault:
return "Default";
case kOff:
return "Off";
case kOn:
return "On";
case kBlink:
return "Blink";
}
return "";
return switch (this) {
case kDefault -> "Default";
case kOff -> "Off";
case kOn -> "On";
case kBlink -> "Blink";
};
}
}

View File

@@ -175,11 +175,10 @@ public class TargetModel {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj instanceof TargetModel) {
var o = (TargetModel) obj;
return vertices.equals(o.vertices) && isPlanar == o.isPlanar && isSpherical == o.isSpherical;
}
return false;
return this == obj
&& obj instanceof TargetModel o
&& vertices.equals(o.vertices)
&& isPlanar == o.isPlanar
&& isSpherical == o.isSpherical;
}
}

View File

@@ -1,7 +1,7 @@
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
}
repositories {
@@ -11,8 +11,8 @@ repositories {
wpi.maven.useLocal = false
wpi.maven.useDevelopment = false
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
// Define my targets (RoboRIO) and artifacts (deployable files)
// This is added by GradleRIO's backing project DeployUtils.

View File

@@ -1,7 +1,7 @@
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
}
repositories {
@@ -11,8 +11,8 @@ repositories {
wpi.maven.useLocal = false
wpi.maven.useDevelopment = false
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
// Define my targets (RoboRIO) and artifacts (deployable files)
// This is added by GradleRIO's backing project DeployUtils.

View File

@@ -1,7 +1,7 @@
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
}
repositories {
@@ -11,8 +11,8 @@ repositories {
wpi.maven.useLocal = false
wpi.maven.useDevelopment = false
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
// Define my targets (RoboRIO) and artifacts (deployable files)
// This is added by GradleRIO's backing project DeployUtils.

View File

@@ -1,10 +1,10 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
def ROBOT_MAIN_CLASS = "frc.robot.Main"
@@ -13,8 +13,8 @@ repositories {
}
wpi.maven.useDevelopment = true
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
// Define my targets (RoboRIO) and artifacts (deployable files)

View File

@@ -1,16 +1,16 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
def ROBOT_MAIN_CLASS = "frc.robot.Main"
wpi.maven.useDevelopment = true
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
// Define my targets (RoboRIO) and artifacts (deployable files)

View File

@@ -26,14 +26,35 @@ package frc.robot;
import static org.junit.Assert.fail;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation2d;
import java.util.List;
import org.junit.Test;
import org.photonvision.PhotonCamera;
import org.photonvision.simulation.PhotonCameraSim;
import org.photonvision.simulation.SimCameraProperties;
import org.photonvision.timesync.TimeSyncSingleton;
public class JniLoadTest {
@Test
public void smoketest() {
public void smoketestTimeSync() {
if (!TimeSyncSingleton.load()) {
fail("Could not load TimeSync JNI????????");
}
}
@Test
public void smoketestPhotonCameraSim() {
// This will trigger a force load of OpenCV internally - good to smoketest
var camera = new PhotonCamera("Hellowo");
var cameraProp = new SimCameraProperties();
cameraProp.setCalibration(320, 240, Rotation2d.fromDegrees(90));
var sim = new PhotonCameraSim(camera, cameraProp);
sim.enableDrawWireframe(true);
sim.enableProcessedStream(true);
sim.enableRawStream(true);
sim.process(0, new Pose3d(), List.of());
}
}

View File

@@ -1,16 +1,16 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
def ROBOT_MAIN_CLASS = "frc.robot.Main"
wpi.maven.useDevelopment = true
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
// Define my targets (RoboRIO) and artifacts (deployable files)

View File

@@ -1,7 +1,7 @@
#!/bin/bash
# The install script is now in photon-image-modifier
# this downloads and runs that install script for people using the old short URL
wget -q https://raw.githubusercontent.com/PhotonVision/photon-image-modifier/master/install.sh -O ./real_install.sh
wget -q https://raw.githubusercontent.com/PhotonVision/photon-image-modifier/main/install.sh -O ./real_install.sh
chmod +x ./real_install.sh
./real_install.sh "$@"
rm ./real_install.sh

View File

@@ -4,8 +4,8 @@ apply plugin: 'jacoco'
apply plugin: 'com.google.protobuf'
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
def baseArtifactId = nativeName