Compare commits

...

20 Commits

Author SHA1 Message Date
Craig Schardt
6a8d638853 Fix typehint problem with ndarrays (#1631)
mypy was inferring the wrong type for ndarrays in photonCameraSim.py.
This fixes the problem by adding typehints using numpy.typing.NDArray.
2024-12-09 05:21:37 +00:00
Craig Schardt
782929b006 Fix "Manage Device Networking" toggle being disabled incorrectly (#1620)
This fixes a bug introduced in #1592 that caused the Manage Device
Networking toggle to be disabled for systems where PhotonVision is
managing the network.

There is still a problem with the toggle defaulting to "off" and not
staying in the "on" position after settings are saved. I need help from
someone who understands the frontend to figure out why it keeps getting
set back to "off".

---------

Co-authored-by: Matt <matthew.morley.ca@gmail.com>
Co-authored-by: Cameron (3539) <theforgelover@gmail.com>
2024-12-07 04:30:41 +00:00
Craig Schardt
4997ad9115 Fix formating errors that are in master (#1627)
A few files with format mistakes were merged into master and they cause
spotless and wpiformat to fail. This PR fixes those files.
2024-12-06 22:21:05 +00:00
Matt
857a30d980 Pull image version from metadata file (#1599)
Closes #1554


![image](https://github.com/user-attachments/assets/fa51c0a3-a25e-4112-8ef2-990404c746d6)
2024-12-05 02:01:48 +00:00
Julius
a40e69cca0 Update docs to suggest JDK 17 (#1611)
#1609 
#1604
2024-12-04 03:27:10 +00:00
William Toth
e069a79a32 Check for exposure setting validity before accessing. (#1618)
If the exposure property for the generic USB camera settable was not
valid for one camera in the list, then the thread would crash/hang and
no cameras would show up in the list
2024-12-01 23:59:00 +00:00
Joseph Farkas
d9dfe15bfe Set loaded to false when JNI loading fails (#1614)
#1613
2024-12-01 16:19:31 +00:00
James Ward
1dbd2e5990 [python] Correct time units (#1605) 2024-11-28 16:12:52 +00:00
Matt
7e9da4133d (Mostly) allow reloading during calibration (#1593) 2024-11-26 03:25:50 +00:00
Gold856
163b5c9c81 Remove unused JNI artifacts (#1603)
Also removes unnecessary `_M_` prefix from artifacts.
2024-11-23 16:54:00 +00:00
Max Midgley
c6a3638a2f More obvious industrial SD card recommendation (#1601)
It wasn't hard to not notice or skip over the recommendation, or just
not take it seriously. So I added some banners to direct people to read
the quick start guide, and a banner to heavily suggest using an
industrial SD card, since they're just the best thing available right
now.
2024-11-23 07:39:42 +00:00
Jordan McMichael
44f78cb03e [Examples] Limit minimum battery voltage in sim to 0.1V (#1600)
Occasionally, the sim projects are capable of simulating current draw of
over 600A, which triggers a condition in
`BatterySim::calculateDefaultBatteryLoadedVoltage` that limits the
minimum measured battery voltage to 0V (to prevent it from going
negative).

When battery voltage measures 0, this causes NaN values to propagate
through the drivetrain model, making sim inoperable. Specifically, [this
is the
line](https://github.com/PhotonVision/photonvision/blob/master/photonlib-java-examples/aimandrange/src/main/java/frc/robot/subsystems/drivetrain/SwerveDriveSim.java#L452)
that causes the initial NaN values in simulation.

This PR is posed as a patch to ensure that simulation doesn't break.
2024-11-22 00:21:27 +00:00
Lucien Morey
61552ad6ca Tidy up of python autogenerated messages (#1594)
This fixes mostly formatting issues so there is no longer any diff with
things after rerunning the generation script. Yes, most of this can be
fixed by running wpiformat but that doesn't exist as a pre-commit hook
atm, so I think this is nicer.

It also removes any reference to the java encode/decode within the
python message gen
2024-11-21 04:43:33 +00:00
Lucien Morey
fa66ed866c add file marker for type checking (#1598)
I think this is the correct way to do this based on my understanding of
the guide to making a [pep561 compliant
package](https://mypy.readthedocs.io/en/stable/installed_packages.html#creating-pep-561-compatible-packages)
and this example in
[black](https://github.com/psf/black/pull/1395/files). Given that I get
all the type-checking info from a local installation, I don't know if
this is testable until it's published on Pypi.

resolves #1210
2024-11-21 04:43:05 +00:00
Gold856
08b4bd1f03 Update to WPILib beta 2 (#1588)
Resolves #1547.
2024-11-21 04:42:30 +00:00
Lucien Morey
c536a1c312 add missing log and use logger over print (#1596)
This is closer to a port of the Java/C++ stuff and will mean we get a
more standard print output with the rest of the logging in the lib
2024-11-21 03:01:08 +00:00
Matt
adb18fe711 Refactor program configuration broadcast hashmap spaghetti (#1592)
WAS: we used raw hash-maps to encode program state
S/B: we use Jackson to do this encoding for us for free. We have
Objects, and we should use them to represent structured data.

---------

Co-authored-by: Craig Schardt <crschardt@fastem.com>
2024-11-19 05:22:33 +00:00
Matt Morley
7d1e748b0e Enable merge groups 2024-11-18 21:15:40 -08:00
James Ward
3a9d22c76b Handle remote UUID mismatch properly (#1590)
Check for no response
Only throw if incorrect version, and not when missing

Resolves #1569
2024-11-18 21:28:07 -05:00
Dualfuel671
417e1a65b6 Simple proof reading (#1591)
Just correcting some keystroke errors.

---------

Co-authored-by: Craig Schardt <crschardt@fastem.com>
2024-11-18 17:54:24 -06:00
97 changed files with 2279 additions and 1004 deletions

View File

@@ -16,6 +16,7 @@ on:
- '**'
- '!docs/**'
- '.github/**'
merge_group:
jobs:
build-client:

View File

@@ -16,6 +16,7 @@ on:
- '**'
- '!docs/**'
- '.github/**'
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}

View File

@@ -16,6 +16,7 @@ on:
- '**'
- '!docs/**'
- '.github/**'
merge_group:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:

View File

@@ -11,6 +11,7 @@ on:
paths:
- 'docs/**'
- '.github/**'
merge_group:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -18,6 +18,7 @@ on:
- '**'
- '!docs/**'
- '.github/**'
merge_group:
jobs:
buildAndDeploy:

View File

@@ -20,6 +20,8 @@ modifiableFileExclude {
\.ico$
\.rknn$
gradlew
photon-lib/py/photonlibpy/generated/
photon-targeting/src/generated/
}
includeProject {

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-1"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
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,7 +30,7 @@ ext.allOutputsFolder = file("$project.buildDir/outputs")
apply from: "versioningHelper.gradle"
ext {
wpilibVersion = "2025.1.1-beta-1"
wpilibVersion = "2025.1.1-beta-2"
wpimathVersion = wpilibVersion
openCVYear = "2024"
openCVversion = "4.8.0-4"
@@ -109,7 +109,7 @@ spotless {
}
wrapper {
gradleVersion '8.4'
gradleVersion '8.11'
}
ext.getCurrentArch = {

View File

@@ -8,14 +8,14 @@ You do not need to install PhotonVision on a Windows PC in order to access the w
## Installing Java
PhotonVision requires a JDK installed and on the system path. JDK 11 is needed (different versions will not work). If you don't have JDK 11 already, run the following to install it:
PhotonVision requires a JDK installed and on the system path. JDK 17 is needed (different versions will not work). If you don't have JDK 17 already, run the following to install it:
```
$ sudo apt-get install openjdk-11-jdk
$ sudo apt-get install openjdk-17-jdk
```
:::{warning}
Using a JDK other than JDK11 will cause issues when running PhotonVision and is not supported.
Using a JDK other than JDK17 will cause issues when running PhotonVision and is not supported.
:::
## Downloading the Latest Stable Release of PhotonVision

View File

@@ -5,17 +5,17 @@ Due to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscor
:::
:::{note}
You do not need to install PhotonVision on a Windows PC in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi).
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.
## Installing Java
PhotonVision requires a JDK installed and on the system path. JDK 11 is needed (different versions will not work). You may already have this if you have installed WPILib. If not, [download and install it from here](https://adoptium.net/temurin/releases?version=11).
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 2025+. If not, [download and install it from here](https://adoptium.net/temurin/releases?version=17).
:::{warning}
Using a JDK other than JDK11 will cause issues when running PhotonVision and is not supported.
Using a JDK other than JDK17 will cause issues when running PhotonVision and is not supported.
:::
## Downloading the Latest Stable Release of PhotonVision

View File

@@ -12,10 +12,14 @@ Bonjour provides more stable networking when using Windows PCs. Install [Bonjour
## Installing Java
PhotonVision requires a JDK installed and on the system path. **JDK 11 is needed** (different versions will not work). You may already have this if you have installed WPILib, but ensure that running `java -version` shows JDK 11. If not, [download and install it from here](https://adoptium.net/temurin/releases?version=11) and ensure that the new JDK is being used.
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`:
```
> ./gradlew run "-Dorg.gradle.java.home=C:\Users\Public\wpilib\2025\jdk"
```
:::{warning}
Using a JDK other than JDK11 will cause issues when running PhotonVision and is not supported.
Using a JDK other than WPILIB's JDK17 will cause issues when running PhotonVision and is not supported.
:::
## Downloading the Latest Stable Release of PhotonVision

View File

@@ -1,5 +1,10 @@
# Selecting Hardware
:::{note}
It is highly recommended that you read the {ref}`quick start guide<docs/quick-start/common-setups:Common Hardware Setups>`, and use the hardware recommended there that
is not touched on here.
:::
In order to use PhotonVision, you need a coprocessor and a camera. Other than the recommended hardware found in the {ref}`quick start guide<docs/quick-start/common-setups:Common Hardware Setups>`, this page will help you select hardware that should work for photonvision even though it is not supported/recommended.
## Choosing a Coprocessor

View File

@@ -13,6 +13,11 @@ The Orange Pi 5 is the only currently supported device for object detection.
## SD Cards
:::{important}
It is highly recommended that you use an industrial micro SD card, as they offer far greater protection against corruption from improper shutdowns, like most cards
face every time the robot is turned off.
:::
- 8GB or larger micro SD card
- Many teams have found that an industrial micro sd card are much more stable in competition. One example is the SanDisk industrial 16GB micro SD card.

View File

@@ -88,4 +88,4 @@ The address in the code above (`photonvision.local`) is the hostname of the copr
## Camera Stream Ports
The camera streams start at they begin at 1181 with two ports for each camera (ex. 1181 and 1182 for camera one, 1183 and 1184 for camera two, etc.). The easiest way to identify the port of the camera that you want is by double clicking on the stream, which opens it in a separate page. The port will be listed below the stream.
The camera streams start at 1181 with two ports for each camera (ex. 1181 and 1182 for camera one, 1183 and 1184 for camera two, etc.). The easiest way to identify the port of the camera that you want is by double clicking on the stream, which opens it in a separate page. The port will be listed below the stream.

View File

@@ -8,7 +8,7 @@ In order for photonvision to connect to the roborio it needs to know your team n
### Camera Nickname
You **must** nickname your cameras in photonvision to ensure that every camera has a unique name. This is how we will identify cameras in robot code. The camera can be nickname using the edit button next to the camera name in the upper right of the Dashboard tab.
You **must** nickname your cameras in PhotonVision to ensure that every camera has a unique name. This is how you will identify cameras in robot code. The camera can be nicknamed using the edit button next to the camera name in the upper right of the Dashboard tab.
```{image} images/editCameraName.png
:align: center
@@ -38,7 +38,7 @@ When detecting AprilTags, it's important to minimize 'motion blur' as much as po
- Fixes
- Lower your exposure as low as possible. Using gain and brightness to account for lack of brightness.
- Other Options:
- Don't use/rely vision measurements while moving.
- Don't use/rely on vision measurements while moving.
```{image} images/motionblur.png
:align: center
@@ -51,7 +51,7 @@ When using an Orange Pi 5 with an OV9782 teams will usually change the following
- Resolution:
- Resolutions higher than 640x640 may not result in any higher detection accuracy and may lower {ref}`performance<docs/objectDetection/about-object-detection:Letterboxing>`.
- Confidence:
- 0.75 - 0.95 Lower values are fpr detecting warn game pieces or less ideal game pieces. Higher for less warn, more ideal game pieces.
- 0.75 - 0.95 Lower values are for detecting worn game pieces or less ideal game pieces. Higher for less worn, more ideal game pieces.
- White Balance Temperature:
- Adjust this to achieve better color accuracy. This may be needed to increase confidence.
- Set arducam specific camera type selector to OV9782

View File

@@ -6,3 +6,4 @@ org.gradle.jvmargs= \
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
org.ysb33r.gradle.doxygen.download.url=https://frcmaven.wpi.edu/artifactory/generic-release-mirror/doxygen

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=permwrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

7
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

22
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

View File

@@ -157,7 +157,10 @@ const downloadCalibBoard = () => {
doc.save(`calibrationTarget-${CalibrationBoardTypes[boardType.value]}.pdf`);
};
const isCalibrating = ref(false);
const isCalibrating = computed(
() => useCameraSettingsStore().currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.Calib3d
);
const startCalibration = () => {
useCameraSettingsStore().startPnPCalibration({
squareSizeIn: squareSizeIn.value,
@@ -170,13 +173,15 @@ const startCalibration = () => {
});
// The Start PnP method already handles updating the backend so only a store update is required
useCameraSettingsStore().currentCameraSettings.currentPipelineIndex = WebsocketPipelineType.Calib3d;
isCalibrating.value = true;
// isCalibrating.value = true;
calibCanceled.value = false;
};
const showCalibEndDialog = ref(false);
const calibCanceled = ref(false);
const calibSuccess = ref<boolean | undefined>(undefined);
const endCalibration = () => {
calibSuccess.value = undefined;
if (!useStateStore().calibrationData.hasEnoughImages) {
calibCanceled.value = true;
}
@@ -192,7 +197,8 @@ const endCalibration = () => {
calibSuccess.value = false;
})
.finally(() => {
isCalibrating.value = false;
// isCalibrating.value = false;
// backend deals with this for us
});
};
@@ -245,6 +251,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
<v-row style="display: flex; flex-direction: column" class="mt-4">
<v-card-subtitle v-show="!isCalibrating" class="pl-3 pa-0 ma-0"> Configure New Calibration</v-card-subtitle>
<v-form ref="form" v-model="settingsValid" class="pl-4 mb-10 pr-5">
<!-- TODO: the default videoFormatIndex is 0, but the list of unique video mode indexes might not include 0. getUniqueVideoResolutionStrings indexing is also different from the normal video mode indexing -->
<pv-select
v-model="useStateStore().calibrationData.videoFormatIndex"
label="Resolution"
@@ -492,10 +499,12 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
process.</v-card-text
>
</template>
<template v-else-if="isCalibrating">
<!-- No result reported yet -->
<template v-else-if="calibSuccess === undefined">
<v-progress-circular indeterminate :size="70" :width="8" color="accent" />
<v-card-text>Camera is being calibrated. This process may take several minutes...</v-card-text>
</template>
<!-- Got positive result -->
<template v-else-if="calibSuccess">
<v-icon color="green" size="70"> mdi-check-bold </v-icon>
<v-card-text>

View File

@@ -21,12 +21,8 @@ import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.networking.NetworkMode;
import org.photonvision.common.util.file.JacksonUtils;
public class NetworkConfig {
// Can be an integer team number, or an IP address
@@ -89,15 +85,19 @@ public class NetworkConfig {
setShouldManage(shouldManage);
}
public Map<String, Object> toHashMap() {
try {
var ret = new ObjectMapper().convertValue(this, JacksonUtils.UIMap.class);
ret.put("canManage", this.deviceCanManageNetwork());
return ret;
} catch (Exception e) {
e.printStackTrace();
return new HashMap<>();
}
public NetworkConfig(NetworkConfig config) {
this(
config.ntServerAddress,
config.connectionType,
config.staticIp,
config.hostname,
config.runNTServer,
config.shouldManage,
config.shouldPublishProto,
config.networkManagerIface,
config.setStaticCommand,
config.setDHCPcommand,
config.matchCamerasOnlyByPath);
}
@JsonIgnore
@@ -110,18 +110,12 @@ public class NetworkConfig {
return "\"" + networkManagerIface + "\"";
}
@JsonIgnore
public boolean shouldManage() {
return this.shouldManage;
}
@JsonIgnore
public void setShouldManage(boolean shouldManage) {
this.shouldManage = shouldManage && this.deviceCanManageNetwork();
}
@JsonIgnore
private boolean deviceCanManageNetwork() {
protected boolean deviceCanManageNetwork() {
return Platform.isLinux();
}

View File

@@ -21,19 +21,6 @@ import edu.wpi.first.apriltag.AprilTagFieldLayout;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.photonvision.PhotonVersion;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.networking.NetworkManager;
import org.photonvision.common.networking.NetworkUtils;
import org.photonvision.common.util.SerializationUtils;
import org.photonvision.mrcal.MrCalJNILoader;
import org.photonvision.raspi.LibCameraJNILoader;
import org.photonvision.vision.calibration.UICameraCalibrationCoefficients;
import org.photonvision.vision.camera.QuirkyCamera;
import org.photonvision.vision.processes.VisionModule;
import org.photonvision.vision.processes.VisionModuleManager;
import org.photonvision.vision.processes.VisionSource;
public class PhotonConfiguration {
@@ -124,81 +111,6 @@ public class PhotonConfiguration {
return cameraConfigurations.remove(name) != null;
}
public Map<String, Object> toHashMap() {
Map<String, Object> map = new HashMap<>();
var settingsSubmap = new HashMap<String, Object>();
// Hack active interfaces into networkSettings
var netConfigMap = networkConfig.toHashMap();
netConfigMap.put("networkInterfaceNames", NetworkUtils.getAllActiveWiredInterfaces());
netConfigMap.put("networkingDisabled", NetworkManager.getInstance().networkingIsDisabled);
settingsSubmap.put("networkSettings", netConfigMap);
var lightingConfig = new UILightingConfig();
lightingConfig.brightness = hardwareSettings.ledBrightnessPercentage;
lightingConfig.supported = !hardwareConfig.ledPins.isEmpty();
settingsSubmap.put("lighting", SerializationUtils.objectToHashMap(lightingConfig));
// General Settings
var generalSubmap = new HashMap<String, Object>();
generalSubmap.put("version", PhotonVersion.versionString);
generalSubmap.put(
"gpuAcceleration",
LibCameraJNILoader.isSupported()
? "Zerocopy Libcamera Working"
: ""); // TODO add support for other types of GPU accel
generalSubmap.put("mrCalWorking", MrCalJNILoader.getInstance().isLoaded());
generalSubmap.put("availableModels", NeuralNetworkModelManager.getInstance().getModels());
generalSubmap.put(
"supportedBackends", NeuralNetworkModelManager.getInstance().getSupportedBackends());
generalSubmap.put(
"hardwareModel",
hardwareConfig.deviceName.isEmpty()
? Platform.getHardwareModel()
: hardwareConfig.deviceName);
generalSubmap.put("hardwarePlatform", Platform.getPlatformName());
settingsSubmap.put("general", generalSubmap);
// AprilTagFieldLayout
settingsSubmap.put("atfl", this.atfl);
map.put(
"cameraSettings",
VisionModuleManager.getInstance().getModules().stream()
.map(VisionModule::toUICameraConfig)
.map(SerializationUtils::objectToHashMap)
.collect(Collectors.toList()));
map.put("settings", settingsSubmap);
return map;
}
public static class UILightingConfig {
public int brightness = 0;
public boolean supported = true;
}
public static class UICameraConfiguration {
@SuppressWarnings("unused")
public double fov;
public String nickname;
public String uniqueName;
public HashMap<String, Object> currentPipelineSettings;
public int currentPipelineIndex;
public List<String> pipelineNicknames;
public HashMap<Integer, HashMap<String, Object>> videoFormatList;
public int outputStreamPort;
public int inputStreamPort;
public List<UICameraCalibrationCoefficients> calibrations;
public boolean isFovConfigurable = true;
public QuirkyCamera cameraQuirks;
public boolean isCSICamera;
public double minExposureRaw;
public double maxExposureRaw;
public double minWhiteBalanceTemp;
public double maxWhiteBalanceTemp;
}
@Override
public String toString() {
return "PhotonConfiguration [\n hardwareConfig="

View File

@@ -32,6 +32,7 @@ import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.configuration.NetworkConfig;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.dataflow.websocket.UIPhotonConfiguration;
import org.photonvision.common.hardware.HardwareManager;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.LogLevel;
@@ -165,7 +166,8 @@ public class NetworkTablesManager {
DataChangeService.getInstance()
.publishEvent(
new OutgoingUIEvent<>(
"fullsettings", ConfigManager.getInstance().getConfig().toHashMap()));
"fullsettings",
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
} catch (IOException e) {
logger.error("Error deserializing atfl!");
logger.error(atfl_json);

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.dataflow.websocket;
import java.util.HashMap;
import java.util.List;
import org.photonvision.vision.calibration.UICameraCalibrationCoefficients;
import org.photonvision.vision.camera.QuirkyCamera;
public class UICameraConfiguration {
@SuppressWarnings("unused")
public double fov;
public String nickname;
public String uniqueName;
public HashMap<String, Object> currentPipelineSettings;
public int currentPipelineIndex;
public List<String> pipelineNicknames;
public HashMap<Integer, HashMap<String, Object>> videoFormatList;
public int outputStreamPort;
public int inputStreamPort;
public List<UICameraCalibrationCoefficients> calibrations;
public boolean isFovConfigurable = true;
public QuirkyCamera cameraQuirks;
public boolean isCSICamera;
public double minExposureRaw;
public double maxExposureRaw;
public double minWhiteBalanceTemp;
public double maxWhiteBalanceTemp;
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.dataflow.websocket;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class UIGeneralSettings {
public UIGeneralSettings(
String version,
String gpuAcceleration,
boolean mrCalWorking,
Map<String, ArrayList<String>> availableModels,
List<String> supportedBackends,
String hardwareModel,
String hardwarePlatform) {
this.version = version;
this.gpuAcceleration = gpuAcceleration;
this.mrCalWorking = mrCalWorking;
this.availableModels = availableModels;
this.supportedBackends = supportedBackends;
this.hardwareModel = hardwareModel;
this.hardwarePlatform = hardwarePlatform;
}
public String version;
public String gpuAcceleration;
public boolean mrCalWorking;
public Map<String, ArrayList<String>> availableModels;
public List<String> supportedBackends;
public String hardwareModel;
public String hardwarePlatform;
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.dataflow.websocket;
public class UILightingConfig {
public UILightingConfig(int brightness, boolean supported) {
this.brightness = brightness;
this.supported = supported;
}
public int brightness = 0;
public boolean supported = true;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.dataflow.websocket;
import java.util.List;
import org.photonvision.common.configuration.NetworkConfig;
import org.photonvision.common.networking.NetworkUtils.NMDeviceInfo;
public class UINetConfig extends NetworkConfig {
public UINetConfig(
NetworkConfig config, List<NMDeviceInfo> networkInterfaceNames, boolean networkingDisabled) {
super(config);
this.networkInterfaceNames = networkInterfaceNames;
this.networkingDisabled = networkingDisabled;
this.canManage = this.deviceCanManageNetwork();
}
public List<NMDeviceInfo> networkInterfaceNames;
public boolean networkingDisabled;
public boolean canManage;
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.dataflow.websocket;
import java.util.List;
import java.util.stream.Collectors;
import org.photonvision.PhotonVersion;
import org.photonvision.common.configuration.NeuralNetworkModelManager;
import org.photonvision.common.configuration.PhotonConfiguration;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.networking.NetworkManager;
import org.photonvision.common.networking.NetworkUtils;
import org.photonvision.mrcal.MrCalJNILoader;
import org.photonvision.raspi.LibCameraJNILoader;
import org.photonvision.vision.processes.VisionModule;
import org.photonvision.vision.processes.VisionModuleManager;
public class UIPhotonConfiguration {
public List<UICameraConfiguration> cameraSettings;
public UIProgramSettings settings;
public UIPhotonConfiguration(
UIProgramSettings settings, List<UICameraConfiguration> cameraSettings) {
this.cameraSettings = cameraSettings;
this.settings = settings;
}
public static UIPhotonConfiguration programStateToUi(PhotonConfiguration c) {
return new UIPhotonConfiguration(
new UIProgramSettings(
new UINetConfig(
c.getNetworkConfig(),
NetworkUtils.getAllActiveWiredInterfaces(),
NetworkManager.getInstance().networkingIsDisabled),
new UILightingConfig(
c.getHardwareSettings().ledBrightnessPercentage,
!c.getHardwareConfig().ledPins.isEmpty()),
new UIGeneralSettings(
PhotonVersion.versionString,
// TODO add support for other types of GPU accel
LibCameraJNILoader.isSupported() ? "Zerocopy Libcamera Working" : "",
MrCalJNILoader.getInstance().isLoaded(),
NeuralNetworkModelManager.getInstance().getModels(),
NeuralNetworkModelManager.getInstance().getSupportedBackends(),
c.getHardwareConfig().deviceName.isEmpty()
? Platform.getHardwareModel()
: c.getHardwareConfig().deviceName,
Platform.getPlatformName()),
c.getApriltagFieldLayout()),
VisionModuleManager.getInstance().getModules().stream()
.map(VisionModule::toUICameraConfig)
.collect(Collectors.toList()));
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.dataflow.websocket;
import edu.wpi.first.apriltag.AprilTagFieldLayout;
public class UIProgramSettings {
public UIProgramSettings(
UINetConfig networkSettings,
UILightingConfig lighting,
UIGeneralSettings general,
AprilTagFieldLayout atfl) {
this.networkSettings = networkSettings;
this.lighting = lighting;
this.general = general;
this.atfl = atfl;
}
public UINetConfig networkSettings;
public UILightingConfig lighting;
public UIGeneralSettings general;
public AprilTagFieldLayout atfl;
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.hardware;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
/**
* Our blessed images inject the current version via this build workflow:
* https://github.com/PhotonVision/photon-image-modifier/blob/2e5ddb6b599df0be921c12c8dbe7b939ecd7f615/.github/workflows/main.yml#L67
*
* <p>This class provides a convienent abstraction around this
*/
public class OsImageVersion {
private static final Logger logger = new Logger(OsImageVersion.class, LogGroup.General);
private static Path imageVersionFile = Path.of("/opt/photonvision/image-version");
public static final Optional<String> IMAGE_VERSION = getImageVersion();
private static Optional<String> getImageVersion() {
if (!imageVersionFile.toFile().exists()) {
logger.warn(
"Photon cannot locate base OS image version metadata at " + imageVersionFile.toString());
return Optional.empty();
}
try {
return Optional.of(Files.readString(imageVersionFile).strip());
} catch (IOException e) {
logger.error("Couldn't read image-version file", e);
}
return Optional.empty();
}
}

View File

@@ -113,7 +113,7 @@ public class NetworkManager {
}
public void reinitialize() {
initialize(ConfigManager.getInstance().getConfig().getNetworkConfig().shouldManage());
initialize(ConfigManager.getInstance().getConfig().getNetworkConfig().shouldManage);
DataChangeService.getInstance()
.publishEvent(

View File

@@ -69,7 +69,8 @@ public abstract class PhotonJNICommon {
logger.error("Couldn't load shared object " + libraryName, e);
e.printStackTrace();
// logger.error(System.getProperty("java.library.path"));
break;
instance.setLoaded(false);
return;
}
}
instance.setLoaded(true);

View File

@@ -95,9 +95,11 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
// first.
var autoExpProp = findProperty("exposure_auto", "auto_exposure");
exposureAbsProp = expProp.get();
this.minExposure = exposureAbsProp.getMin();
this.maxExposure = exposureAbsProp.getMax();
if (expProp.isPresent()) {
exposureAbsProp = expProp.get();
this.minExposure = exposureAbsProp.getMin();
this.maxExposure = exposureAbsProp.getMax();
}
if (autoExpProp.isPresent()) {
autoExposureProp = autoExpProp.get();
@@ -184,7 +186,7 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
softSet("auto_exposure_bias", 12);
softSet("iso_sensitivity_auto", 1);
softSet("iso_sensitivity", 1); // Manual ISO adjustment by default
autoExposureProp.set(PROP_AUTO_EXPOSURE_ENABLED);
if (autoExposureProp != null) autoExposureProp.set(PROP_AUTO_EXPOSURE_ENABLED);
}
}

View File

@@ -26,6 +26,7 @@ import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.dataflow.websocket.UIPhotonConfiguration;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.vision.pipeline.*;
@@ -231,7 +232,8 @@ public class PipelineManager {
DataChangeService.getInstance()
.publishEvent(
new OutgoingUIEvent<>(
"fullsettings", ConfigManager.getInstance().getConfig().toHashMap()));
"fullsettings",
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
}
/**

View File

@@ -30,13 +30,14 @@ import java.util.stream.Collectors;
import org.opencv.core.Size;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.configuration.PhotonConfiguration;
import org.photonvision.common.dataflow.CVPipelineResultConsumer;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.dataflow.networktables.NTDataPublisher;
import org.photonvision.common.dataflow.statusLEDs.StatusLEDConsumer;
import org.photonvision.common.dataflow.websocket.UICameraConfiguration;
import org.photonvision.common.dataflow.websocket.UIDataPublisher;
import org.photonvision.common.dataflow.websocket.UIPhotonConfiguration;
import org.photonvision.common.hardware.HardwareManager;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
@@ -489,7 +490,8 @@ public class VisionModule {
DataChangeService.getInstance()
.publishEvent(
new OutgoingUIEvent<>(
"fullsettings", ConfigManager.getInstance().getConfig().toHashMap()));
"fullsettings",
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
}
void saveAndBroadcastSelective(WsContext originContext, String propertyName, Object value) {
@@ -516,8 +518,8 @@ public class VisionModule {
saveAndBroadcastAll();
}
public PhotonConfiguration.UICameraConfiguration toUICameraConfig() {
var ret = new PhotonConfiguration.UICameraConfiguration();
public UICameraConfiguration toUICameraConfig() {
var ret = new UICameraConfiguration();
ret.fov = visionSource.getSettables().getFOV();
ret.isCSICamera = visionSource.getCameraConfiguration().cameraType == CameraType.ZeroCopyPicam;

View File

@@ -29,6 +29,7 @@ import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.dataflow.websocket.UIPhotonConfiguration;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.hardware.Platform.OSType;
import org.photonvision.common.logging.LogGroup;
@@ -122,7 +123,8 @@ public class VisionSourceManager {
DataChangeService.getInstance()
.publishEvent(
new OutgoingUIEvent<>(
"fullsettings", ConfigManager.getInstance().getConfig().toHashMap()));
"fullsettings",
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
}
protected List<VisionSource> tryMatchCamImpl() {

View File

@@ -2,7 +2,7 @@
plugins {
id 'java'
id "org.ysb33r.doxygen" version "0.7.0"
id "org.ysb33r.doxygen" version "1.0.4"
}
@@ -36,15 +36,16 @@ doxygen {
String arch = System.getProperty("os.arch");
if (arch.equals("x86_64") || arch.equals("amd64")) {
executables {
doxygen version : '1.9.4',
baseURI : 'https://frcmaven.wpi.edu/artifactory/generic-release-mirror/doxygen'
doxygen {
executableByVersion('1.12.0')
}
}
}
}
doxygen {
generate_html true
html_extra_stylesheet 'theme.css'
option 'generate_html', true
option 'html_extra_stylesheet', 'theme.css'
cppProjectZips.each {
dependsOn it
@@ -53,126 +54,37 @@ doxygen {
cppIncludeRoots.add(it.absolutePath)
}
}
cppIncludeRoots << '../ntcore/build/generated/main/native/include/'
if (project.hasProperty('docWarningsAsErrors')) {
// Eigen
exclude 'Eigen/**'
exclude 'unsupported/**'
// LLVM
exclude 'wpi/AlignOf.h'
exclude 'wpi/Casting.h'
exclude 'wpi/Chrono.h'
exclude 'wpi/Compiler.h'
exclude 'wpi/ConvertUTF.h'
exclude 'wpi/DenseMap.h'
exclude 'wpi/DenseMapInfo.h'
exclude 'wpi/Endian.h'
exclude 'wpi/EpochTracker.h'
exclude 'wpi/Errc.h'
exclude 'wpi/Errno.h'
exclude 'wpi/ErrorHandling.h'
exclude 'wpi/bit.h'
exclude 'wpi/fs.h'
exclude 'wpi/FunctionExtras.h'
exclude 'wpi/function_ref.h'
exclude 'wpi/Hashing.h'
exclude 'wpi/iterator.h'
exclude 'wpi/iterator_range.h'
exclude 'wpi/ManagedStatic.h'
exclude 'wpi/MapVector.h'
exclude 'wpi/MathExtras.h'
exclude 'wpi/MemAlloc.h'
exclude 'wpi/PointerIntPair.h'
exclude 'wpi/PointerLikeTypeTraits.h'
exclude 'wpi/PointerUnion.h'
exclude 'wpi/raw_os_ostream.h'
exclude 'wpi/raw_ostream.h'
exclude 'wpi/SmallPtrSet.h'
exclude 'wpi/SmallSet.h'
exclude 'wpi/SmallString.h'
exclude 'wpi/SmallVector.h'
exclude 'wpi/StringExtras.h'
exclude 'wpi/StringMap.h'
exclude 'wpi/SwapByteOrder.h'
exclude 'wpi/type_traits.h'
exclude 'wpi/VersionTuple.h'
exclude 'wpi/WindowsError.h'
// fmtlib
exclude 'fmt/**'
// libuv
exclude 'uv.h'
exclude 'uv/**'
exclude 'wpinet/uv/**'
// json
exclude 'wpi/adl_serializer.h'
exclude 'wpi/byte_container_with_subtype.h'
exclude 'wpi/detail/**'
exclude 'wpi/json.h'
exclude 'wpi/json_fwd.h'
exclude 'wpi/ordered_map.h'
exclude 'wpi/thirdparty/**'
// memory
exclude 'wpi/memory/**'
// mpack
exclude 'wpi/mpack.h'
// units
exclude 'units/**'
}
//TODO: building memory docs causes search to break
exclude 'wpi/memory/**'
exclude '*.pb.h'
// Save space by excluding protobuf and eigen
exclude 'Eigen/**'
exclude 'google/protobuf/**'
aliases 'effects=\\par <i>Effects:</i>^^',
'notes=\\par <i>Notes:</i>^^',
'requires=\\par <i>Requires:</i>^^',
'requiredbe=\\par <i>Required Behavior:</i>^^',
'concept{2}=<a href=\"md_doc_concepts.html#\1\">\2</a>',
'defaultbe=\\par <i>Default Behavior:</i>^^'
case_sense_names false
extension_mapping 'inc=C++', 'no_extension=C++'
extract_all true
extract_static true
file_patterns '*'
full_path_names true
generate_html true
generate_latex false
generate_treeview true
html_extra_stylesheet 'theme.css'
html_timestamp true
javadoc_autobrief true
project_name 'PhotonVision C++'
project_logo '../photon-client/src/assets/images/logoSmall.svg'
project_number pubVersion
quiet true
recursive true
strip_code_comments false
strip_from_inc_path cppIncludeRoots as String[]
strip_from_path cppIncludeRoots as String[]
use_mathjax true
warnings false
warn_if_incomplete_doc true
warn_if_undocumented false
warn_no_paramdoc true
option 'case_sense_names', false
option 'extension_mapping', 'inc=C++ no_extension=C++'
option 'extract_all', true
option 'extract_static', true
option 'file_patterns', '*'
option 'full_path_names', true
option 'generate_html', true
option 'generate_latex', false
option 'generate_treeview', true
option 'html_extra_stylesheet', 'theme.css'
option 'html_timestamp', true
option 'javadoc_autobrief', true
option 'project_name', 'PhotonVision C++'
option 'project_logo', '../docs/source/assets/RoundLogo.png'
option 'project_number', pubVersion
option 'quiet', true
option 'recursive', true
option 'strip_code_comments', false
option 'strip_from_inc_path', cppIncludeRoots
option 'strip_from_path', cppIncludeRoots
option 'use_mathjax', true
option 'warnings', false
option 'warn_if_incomplete_doc', true
option 'warn_if_undocumented', false
option 'warn_no_paramdoc', true
//enable doxygen preprocessor expansion of WPI_DEPRECATED to fix MotorController docs
enable_preprocessing true
macro_expansion true
expand_only_predef true
predefined "WPI_DEPRECATED(x)=[[deprecated(x)]]\"\\\n" +
option 'enable_preprocessing', true
option 'macro_expansion', true
option 'expand_only_predef', true
option 'predefined', "WPI_DEPRECATED(x)=[[deprecated(x)]]\"\\\n" +
"\"__cplusplus\"\\\n" +
"\"HAL_ENUM(name)=enum name : int32_t"

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
import logging
import math
from typing import Any
@@ -11,6 +12,8 @@ from .rotTrlTransform3d import RotTrlTransform3d
NWU_TO_EDN = Rotation3d(np.array([[0, -1, 0], [0, 0, -1], [1, 0, 0]]))
EDN_TO_NWU = Rotation3d(np.array([[0, 0, 1], [-1, 0, 0], [0, -1, 0]]))
logger = logging.getLogger(__name__)
class OpenCVHelp:
@staticmethod
@@ -243,7 +246,7 @@ class OpenCVHelp:
# solvePnP failed
if reprojectionError is None or math.isnan(reprojectionError[0, 0]):
print("SolvePNP_Square failed!")
logger.error("SolvePNP_Square failed!")
return None
if alt:
@@ -303,6 +306,7 @@ class OpenCVHelp:
)
if math.isnan(error):
logger.error("SolvePNP_SQPNP failed!")
return None
# We have no alternative so set it to best as well

View File

@@ -42,9 +42,7 @@ class PhotonPipelineResultSerde:
ret = Packet()
# metadata is of non-intrinsic type PhotonPipelineMetadata
ret.encodeBytes(
PhotonPipelineMetadata.photonStruct.pack(value.metadata).getData()
)
ret.encodeBytes(PhotonPipelineMetadata.photonStruct.pack(value.metadata).getData())
# targets is a custom VLA!
ret.encodeList(value.targets, PhotonTrackedTarget.photonStruct)

View File

@@ -17,10 +17,11 @@ class NTTopicSet:
different for sim vs. real camera
"""
def __init__(self, tableName: str, cameraName: str) -> None:
instance = nt.NetworkTableInstance.getDefault()
photonvision_root_table = instance.getTable(tableName)
self.subTable = photonvision_root_table.getSubTable(cameraName)
def __init__(
self,
ntSubTable: nt.NetworkTable,
) -> None:
self.subTable = ntSubTable
def updateEntries(self) -> None:
options = nt.PubSubOptions()

View File

@@ -298,43 +298,42 @@ class PhotonCamera:
# Check mdef UUID
localUUID = PhotonPipelineResult.photonStruct.MESSAGE_VERSION
remoteUUID = str(self._rawBytesEntry.getTopic().getProperty("message_uuid"))
remoteUUID = self._rawBytesEntry.getTopic().getProperty("message_uuid")
if not remoteUUID:
if remoteUUID is None:
wpilib.reportWarning(
f"PhotonVision coprocessor at path {self._path} has not reported a message interface UUID - is your coprocessor's camera started?",
True,
)
else:
# ntcore hands us a JSON string with leading/trailing quotes - remove those
remoteUUID = str(remoteUUID).replace('"', "")
assert isinstance(remoteUUID, str)
# ntcore hands us a JSON string with leading/trailing quotes - remove those
remoteUUID = remoteUUID.replace('"', "")
if localUUID != remoteUUID:
# Verified version mismatch
if localUUID != remoteUUID:
# Verified version mismatch
bfw = """
\n\n\n
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
>>>
>>> You are running an incompatible version
>>> of PhotonVision on your coprocessor!
>>>
>>> This is neither tested nor supported.
>>> You MUST update PhotonVision,
>>> PhotonLib, or both.
>>>
>>> Your code will now crash.
>>> We hope your day gets better.
>>>
>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\n\n
"""
bfw = """
\n\n\n
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
>>>
>>> You are running an incompatible version
>>> of PhotonVision on your coprocessor!
>>>
>>> This is neither tested nor supported.
>>> You MUST update PhotonVision,
>>> PhotonLib, or both.
>>>
>>> Your code will now crash.
>>> We hope your day gets better.
>>>
>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\n\n
"""
wpilib.reportWarning(bfw)
wpilib.reportWarning(bfw)
errText = f"Photonlibpy version {PHOTONLIB_VERSION} (With message UUID {localUUID}) does not match coprocessor version {versionString} (with message UUID {remoteUUID}). Please install photonlibpy version {versionString}, or update your coprocessor to {PHOTONLIB_VERSION}."
wpilib.reportError(errText, True)
raise Exception(errText)
errText = f"Photonlibpy version {PHOTONLIB_VERSION} (With message UUID {localUUID}) does not match coprocessor version {versionString} (with message UUID {remoteUUID}). Please install photonlibpy version {versionString}, or update your coprocessor to {PHOTONLIB_VERSION}."
wpilib.reportError(errText, True)
raise Exception(errText)

View File

@@ -0,0 +1 @@

View File

@@ -60,11 +60,10 @@ class PhotonCameraSim:
self.videoSimRawEnabled: bool = False
self.videoSimWireframeEnabled: bool = False
self.videoSimWireframeResolution: float = 0.1
self.videoSimProcEnabled: bool = (
False # TODO switch this back to default True when the functionality is enabled
)
# TODO switch this back to default True when the functionality is enabled
self.videoSimProcEnabled: bool = False
self.heartbeatCounter: int = 0
self.nextNtEntryTime = int(wpilib.Timer.getFPGATimestamp() * 1e6)
self.nextNtEntryTime = wpilib.Timer.getFPGATimestamp()
self.tagLayout = AprilTagFieldLayout.loadField(AprilTagField.k2024Crescendo)
self.cam = camera
@@ -95,7 +94,7 @@ class PhotonCameraSim:
(self.prop.getResWidth(), self.prop.getResHeight())
)
self.ts = NTTopicSet("photonvision", self.cam.getName())
self.ts = NTTopicSet(self.cam._cameraTable)
self.ts.updateEntries()
# Handle this last explicitly for this function signature because the other constructor is called in the initialiser list
@@ -173,20 +172,20 @@ class PhotonCameraSim:
def consumeNextEntryTime(self) -> float | None:
"""Determine if this camera should process a new frame based on performance metrics and the time
since the last update. This returns an Optional which is either empty if no update should occur
or a Long of the timestamp in microseconds of when the frame which should be received by NT. If
or a float of the timestamp in seconds of when the frame which should be received by NT. If
a timestamp is returned, the last frame update time becomes that timestamp.
:returns: Optional long which is empty while blocked or the NT entry timestamp in microseconds if
:returns: Optional float which is empty while blocked or the NT entry timestamp in seconds if
ready
"""
# check if this camera is ready for another frame update
now = int(wpilib.Timer.getFPGATimestamp() * 1e6)
timestamp = 0
now = wpilib.Timer.getFPGATimestamp()
timestamp = 0.0
iter = 0
# prepare next latest update
while now >= self.nextNtEntryTime:
timestamp = int(self.nextNtEntryTime)
frameTime = int(self.prop.estSecUntilNextFrame() * 1e6)
timestamp = self.nextNtEntryTime
frameTime = self.prop.estSecUntilNextFrame()
self.nextNtEntryTime += frameTime
# if frame time is very small, avoid blocking
@@ -432,7 +431,9 @@ class PhotonCameraSim:
)
def submitProcessedFrame(
self, result: PhotonPipelineResult, receiveTimestamp: float | None
self,
result: PhotonPipelineResult,
receiveTimestamp_us: float | None = None,
):
"""Simulate one processed frame of vision data, putting one result to NT. Image capture timestamp
overrides :meth:`.PhotonPipelineResult.getTimestampSeconds` for more
@@ -441,44 +442,45 @@ class PhotonCameraSim:
:param result: The pipeline result to submit
:param receiveTimestamp: The (sim) timestamp when this result was read by NT in microseconds. If not passed image capture time is assumed be (current time - latency)
"""
if receiveTimestamp is None:
receiveTimestamp = wpilib.Timer.getFPGATimestamp() * 1e6
receiveTimestamp = int(receiveTimestamp)
if receiveTimestamp_us is None:
receiveTimestamp_us = wpilib.Timer.getFPGATimestamp() * 1e6
receiveTimestamp_us = int(receiveTimestamp_us)
self.ts.latencyMillisEntry.set(result.getLatencyMillis(), receiveTimestamp)
self.ts.latencyMillisEntry.set(result.getLatencyMillis(), receiveTimestamp_us)
newPacket = PhotonPipelineResult.photonStruct.pack(result)
self.ts.rawBytesEntry.set(newPacket.getData(), receiveTimestamp)
self.ts.rawBytesEntry.set(newPacket.getData(), receiveTimestamp_us)
hasTargets = result.hasTargets()
self.ts.hasTargetEntry.set(hasTargets, receiveTimestamp)
self.ts.hasTargetEntry.set(hasTargets, receiveTimestamp_us)
if not hasTargets:
self.ts.targetPitchEntry.set(0.0, receiveTimestamp)
self.ts.targetYawEntry.set(0.0, receiveTimestamp)
self.ts.targetAreaEntry.set(0.0, receiveTimestamp)
self.ts.targetPoseEntry.set(Transform3d(), receiveTimestamp)
self.ts.targetSkewEntry.set(0.0, receiveTimestamp)
self.ts.targetPitchEntry.set(0.0, receiveTimestamp_us)
self.ts.targetYawEntry.set(0.0, receiveTimestamp_us)
self.ts.targetAreaEntry.set(0.0, receiveTimestamp_us)
self.ts.targetPoseEntry.set(Transform3d(), receiveTimestamp_us)
self.ts.targetSkewEntry.set(0.0, receiveTimestamp_us)
else:
bestTarget = result.getBestTarget()
assert bestTarget
self.ts.targetPitchEntry.set(bestTarget.getPitch(), receiveTimestamp)
self.ts.targetYawEntry.set(bestTarget.getYaw(), receiveTimestamp)
self.ts.targetAreaEntry.set(bestTarget.getArea(), receiveTimestamp)
self.ts.targetSkewEntry.set(bestTarget.getSkew(), receiveTimestamp)
self.ts.targetPitchEntry.set(bestTarget.getPitch(), receiveTimestamp_us)
self.ts.targetYawEntry.set(bestTarget.getYaw(), receiveTimestamp_us)
self.ts.targetAreaEntry.set(bestTarget.getArea(), receiveTimestamp_us)
self.ts.targetSkewEntry.set(bestTarget.getSkew(), receiveTimestamp_us)
self.ts.targetPoseEntry.set(
bestTarget.getBestCameraToTarget(), receiveTimestamp
bestTarget.getBestCameraToTarget(), receiveTimestamp_us
)
intrinsics = self.prop.getIntrinsics()
intrinsicsView = intrinsics.flatten().tolist()
self.ts.cameraIntrinsicsPublisher.set(intrinsicsView, receiveTimestamp)
intrinsics = self.prop.getIntrinsics()
intrinsicsView = intrinsics.flatten().tolist()
self.ts.cameraIntrinsicsPublisher.set(intrinsicsView, receiveTimestamp_us)
distortion = self.prop.getDistCoeffs()
distortionView = distortion.flatten().tolist()
self.ts.cameraDistortionPublisher.set(distortionView, receiveTimestamp)
distortion = self.prop.getDistCoeffs()
distortionView = distortion.flatten().tolist()
self.ts.cameraDistortionPublisher.set(distortionView, receiveTimestamp_us)
self.ts.heartbeatPublisher.set(self.heartbeatCounter, receiveTimestamp)
self.ts.heartbeatPublisher.set(self.heartbeatCounter, receiveTimestamp_us)
self.heartbeatCounter += 1
self.ts.subTable.getInstance().flush()
self.ts.subTable.getInstance().flush()

View File

@@ -4,11 +4,14 @@ import typing
import cv2 as cv
import numpy as np
import numpy.typing as npt
from wpimath.geometry import Rotation2d, Rotation3d, Translation3d
from wpimath.units import hertz, seconds
from ..estimation import RotTrlTransform3d
logger = logging.getLogger(__name__)
class SimCameraProperties:
"""Calibration and performance values for this camera.
@@ -29,8 +32,8 @@ class SimCameraProperties:
"""Default constructor which is the same as {@link #PERFECT_90DEG}"""
self.resWidth: int = -1
self.resHeight: int = -1
self.camIntrinsics: np.ndarray = np.zeros((3, 3)) # [3,3]
self.distCoeffs: np.ndarray = np.zeros((8, 1)) # [8,1]
self.camIntrinsics: npt.NDArray[np.floating] = np.zeros((3, 3)) # [3,3]
self.distCoeffs: npt.NDArray[np.floating] = np.zeros((8, 1)) # [8,1]
self.avgErrorPx: float = 0.0
self.errorStdDevPx: float = 0.0
self.frameSpeed: seconds = 0.0
@@ -46,7 +49,7 @@ class SimCameraProperties:
) -> None:
if fovDiag.degrees() < 1.0 or fovDiag.degrees() > 179.0:
fovDiag = Rotation2d.fromDegrees(max(min(fovDiag.degrees(), 179.0), 1.0))
logging.error("Requested invalid FOV! Clamping between (1, 179) degrees...")
logger.error("Requested invalid FOV! Clamping between (1, 179) degrees...")
resDiag = math.sqrt(width * width + height * height)
diagRatio = math.tan(fovDiag.radians() / 2.0)
@@ -78,7 +81,6 @@ class SimCameraProperties:
newCamIntrinsics: np.ndarray,
newDistCoeffs: np.ndarray,
) -> None:
self.resWidth = width
self.resHeight = height
self.camIntrinsics = newCamIntrinsics
@@ -171,10 +173,10 @@ class SimCameraProperties:
def getAspectRatio(self) -> float:
return 1.0 * self.resWidth / self.resHeight
def getIntrinsics(self) -> np.ndarray:
def getIntrinsics(self) -> npt.NDArray[np.floating]:
return self.camIntrinsics
def getDistCoeffs(self) -> np.ndarray:
def getDistCoeffs(self) -> npt.NDArray[np.floating]:
return self.distCoeffs
def getFPS(self) -> hertz:
@@ -353,7 +355,6 @@ class SimCameraProperties:
# find intersections
for i, normal in enumerate(self.viewplanes):
# // we want to know the value of t when the line intercepts this plane
# // parametrized: v = t * ab + a, where v lies on the plane
# // we can find the projection of a onto the plane normal
@@ -465,7 +466,7 @@ class SimCameraProperties:
def estSecUntilNextFrame(self) -> seconds:
"""
:returns: Estimate how long until the next frame should be processed in milliseconds
:returns: Estimate how long until the next frame should be processed in seconds
"""
# // exceptional processing latency blocks the next frame
return self.frameSpeed + max(0.0, self.estLatency() - self.frameSpeed)

View File

@@ -305,7 +305,7 @@ class VisionSystemSim:
timestampNt = optTimestamp
latency = camSim.prop.estLatency()
# the image capture timestamp in seconds of this result
timestampCapture = timestampNt * 1.0e-6 - latency
timestampCapture = timestampNt - latency
# use camera pose from the image capture timestamp
lateRobotPose = self.getRobotPose(timestampCapture)
@@ -318,7 +318,8 @@ class VisionSystemSim:
# process a PhotonPipelineResult with visible targets
camResult = camSim.process(latency, lateCameraPose, allTargets)
# publish this info to NT at estimated timestamp of receive
camSim.submitProcessedFrame(camResult, timestampNt)
# needs a timestamp in microseconds
camSim.submitProcessedFrame(camResult, timestampNt * 1.0e6)
# display debug results
for tgt in camResult.getTargets():
trf = tgt.getBestCameraToTarget()

View File

@@ -55,6 +55,7 @@ descriptionStr = f"Pure-python implementation of PhotonLib for interfacing with
setup(
name="photonlibpy",
packages=find_packages(),
package_data={"photonlibpy": ["py.typed"]},
version=versionString,
install_requires=[
"numpy~=2.1",

View File

@@ -1,9 +1,8 @@
import math
import ntcore as nt
import pytest
from photonlibpy.estimation import TargetModel, VisionEstimation
from photonlibpy.photonCamera import PhotonCamera, setVersionCheckEnabled
from photonlibpy.photonCamera import PhotonCamera
from photonlibpy.simulation import PhotonCameraSim, VisionSystemSim, VisionTargetSim
from robotpy_apriltag import AprilTag, AprilTagFieldLayout
from wpimath.geometry import (
@@ -18,12 +17,6 @@ from wpimath.geometry import (
from wpimath.units import feetToMeters, meters
@pytest.fixture(autouse=True)
def setupCommon() -> None:
nt.NetworkTableInstance.getDefault().startServer()
setVersionCheckEnabled(False)
def test_VisibilityCupidShuffle() -> None:
targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi))
@@ -32,6 +25,8 @@ def test_VisibilityCupidShuffle() -> None:
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, Transform3d())
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
visionSysSim.addVisionTargets(
@@ -93,6 +88,8 @@ def test_NotVisibleVert1() -> None:
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, Transform3d())
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
visionSysSim.addVisionTargets(
@@ -128,6 +125,8 @@ def test_NotVisibleVert2() -> None:
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, robotToCamera)
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(
4774, 4774, fovDiag=Rotation2d.fromDegrees(80.0)
)
@@ -156,6 +155,8 @@ def test_NotVisibleTargetSize() -> None:
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, Transform3d())
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
cameraSim.setMinTargetAreaPixels(20.0)
visionSysSim.addVisionTargets(
@@ -183,6 +184,8 @@ def test_NotVisibleTooFarLeds() -> None:
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, Transform3d())
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
cameraSim.setMinTargetAreaPixels(1.0)
cameraSim.setMaxSightRange(10.0)
@@ -216,6 +219,9 @@ def test_YawAngles(expected_yaw) -> None:
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, Transform3d())
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
cameraSim.setMinTargetAreaPixels(0.0)
visionSysSim.addVisionTargets(
@@ -250,6 +256,9 @@ def test_PitchAngles(expected_pitch) -> None:
camera = PhotonCamera("camera")
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, Transform3d())
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(
640, 480, fovDiag=Rotation2d.fromDegrees(120.0)
)
@@ -316,8 +325,10 @@ def test_distanceCalc(distParam, pitchParam, heightParam) -> None:
)
camera = PhotonCamera("camera")
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, Transform3d())
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(
640, 480, fovDiag=Rotation2d.fromDegrees(160.0)
)
@@ -354,6 +365,9 @@ def test_MultipleTargets() -> None:
camera = PhotonCamera("camera")
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, Transform3d())
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
cameraSim.setMinTargetAreaPixels(20.0)
@@ -451,6 +465,9 @@ def test_PoseEstimation() -> None:
camera = PhotonCamera("camera")
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, Transform3d())
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(90.0))
cameraSim.setMinTargetAreaPixels(20.0)
@@ -525,6 +542,9 @@ def test_PoseEstimationRotated() -> None:
camera = PhotonCamera("camera")
cameraSim = PhotonCameraSim(camera)
visionSysSim.addCamera(cameraSim, robotToCamera)
# Set massive FPS so timing isn't an issue
cameraSim.prop.setFPS(1e6)
cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(90.0))
cameraSim.setMinTargetAreaPixels(20.0)

View File

@@ -305,6 +305,7 @@ void PhotonCamera::VerifyVersion() {
FRC_ReportError(frc::warn::Warning,
"Cannot find property message_uuid for PhotonCamera {}",
path);
return;
}
std::string remote_uuid{remote_uuid_json};

View File

@@ -6,6 +6,8 @@ bool:
cpp_type: bool
java_decode_method: decodeBoolean
java_encode_shim: encodeBoolean
python_decode_shim: decodeBoolean
python_encode_shim: encodeBoolean
int16:
len: 2
java_type: short
@@ -13,27 +15,37 @@ int16:
java_decode_method: decodeShort
java_list_decode_method: decodeShortList
java_encode_shim: encodeShort
python_decode_shim: decodeShort
python_encode_shim: encodeShort
int32:
len: 4
java_type: int
cpp_type: int32_t
java_decode_method: decodeInt
java_encode_shim: encodeInt
python_decode_shim: decodeInt
python_encode_shim: encodeInt
int64:
len: 8
java_type: long
cpp_type: int64_t
java_decode_method: decodeLong
java_encode_shim: encodeLong
python_decode_shim: decodeLong
python_encode_shim: encodeLong
float32:
len: 4
java_type: float
cpp_type: float
java_decode_method: decodeFloat
java_encode_shim: encodeFloat
python_decode_shim: decodeFloat
python_encode_shim: encodeFloat
float64:
len: 8
java_type: double
cpp_type: double
java_decode_method: decodeDouble
java_encode_shim: encodeDouble
python_decode_shim: decodeDouble
python_encode_shim: encodeDouble

View File

@@ -16,7 +16,7 @@
java_encode_shim: PacketUtils.packTransform3d
cpp_type: frc::Transform3d
cpp_include: "<frc/geometry/Transform3d.h>"
python_decode_shim: packet.decodeTransform
python_decode_shim: decodeTransform
python_encode_shim: encodeTransform
java_import: edu.wpi.first.math.geometry.Transform3d
# shim since we expect fields to at least exist

View File

@@ -54,26 +54,26 @@ public class {{ name }}Serde implements PacketSerde<{{name}}> {
@Override
public void pack(Packet packet, {{ name }} value) {
{%- for field in fields -%}
{%- if field.type | is_shimmed %}
{%- if field.type | is_shimmed %}
{{ get_message_by_name(field.type).java_encode_shim }}(packet, value.{{ field.name }});
{%- elif field.optional == True %}
{%- elif field.optional == True %}
// {{ field.name }} is optional! it better not be a VLA too
packet.encodeOptional(value.{{ field.name }});
{%- elif field.vla == True and field.type | is_intrinsic %}
{%- elif field.vla == True and field.type | is_intrinsic %}
// {{ field.name }} is a intrinsic VLA!
packet.encode(value.{{ field.name }});
{%- elif field.vla == True %}
{%- elif field.vla == True %}
// {{ field.name }} is a custom VLA!
packet.encodeList(value.{{ field.name }});
{%- elif field.type | is_intrinsic %}
{%- elif field.type | is_intrinsic %}
// field {{ field.name }} is of intrinsic type {{ field.type }}
packet.encode(({{ type_map[field.type].java_type }}) value.{{ field.name }});
{%- else %}
{%- else %}
// field {{ field.name }} is of non-intrinsic type {{ field.type }}
{{ field.type }}.photonStruct.pack(packet, value.{{ field.name }});
{%- endif %}
{%- if not loop.last %}
{% endif -%}
{%- endif %}
{%- if not loop.last %}
{% endif -%}
{% endfor%}
}
@@ -81,26 +81,26 @@ public class {{ name }}Serde implements PacketSerde<{{name}}> {
public {{ name }} unpack(Packet packet) {
var ret = new {{ name }}();
{% for field in fields -%}
{%- if field.type | is_shimmed %}
{%- if field.type | is_shimmed %}
ret.{{ field.name }} = {{ get_message_by_name(field.type).java_decode_shim }}(packet);
{%- elif field.optional == True %}
{%- elif field.optional == True %}
// {{ field.name }} is optional! it better not be a VLA too
ret.{{ field.name }} = packet.decodeOptional({{ field.type }}.photonStruct);
{%- elif field.vla == True and not field.type | is_intrinsic %}
{%- elif field.vla == True and not field.type | is_intrinsic %}
// {{ field.name }} is a custom VLA!
ret.{{ field.name }} = packet.decodeList({{ field.type }}.photonStruct);
{%- elif field.vla == True and field.type | is_intrinsic %}
{%- elif field.vla == True and field.type | is_intrinsic %}
// {{ field.name }} is a custom VLA!
ret.{{ field.name }} = packet.decode{{ type_map[field.type].java_type.title() }}List();
{%- elif field.type | is_intrinsic %}
{%- elif field.type | is_intrinsic %}
// {{ field.name }} is of intrinsic type {{ field.type }}
ret.{{field.name}} = packet.{{ type_map[field.type].java_decode_method }}();
{%- else %}
{%- else %}
// {{ field.name }} is of non-intrinsic type {{ field.type }}
ret.{{field.name}} = {{ field.type }}.photonStruct.unpack(packet);
{%- endif %}
{%- if not loop.last %}
{% endif -%}
{%- endif %}
{%- if not loop.last %}
{% endif -%}
{% endfor%}
return ret;
@@ -125,4 +125,4 @@ public class {{ name }}Serde implements PacketSerde<{{name}}> {
{%- endfor%}
};
}
}
}{{'\n'}}

View File

@@ -24,21 +24,21 @@ namespace photon {
using StructType = SerdeType<{{ name }}>;
void StructType::Pack(Packet& packet, const {{ name }}& value) {
{% for field in fields -%}
packet.Pack<{{ field | get_qualified_name }}>(value.{{ field.name }});
{%- if not loop.last %}
{% endif -%}
{% endfor %}
{% for field in fields -%}
packet.Pack<{{ field | get_qualified_name }}>(value.{{ field.name }});
{%- if not loop.last %}
{% endif -%}
{% endfor %}
}
{{ name }} StructType::Unpack(Packet& packet) {
return {{ name }}{ {{ name }}_PhotonStruct{
{% for field in fields -%}
.{{ field.name}} = packet.Unpack<{{ field | get_qualified_name }}>(),
{%- if not loop.last %}
{% endif -%}
{% endfor %}
}};
return {{ name }}{ {{ name }}_PhotonStruct{
{% for field in fields -%}
.{{ field.name}} = packet.Unpack<{{ field | get_qualified_name }}>(),
{%- if not loop.last %}
{% endif -%}
{% endfor %}
}};
}
} // namespace photon
} // namespace photon{{'\n'}}

View File

@@ -48,4 +48,4 @@ struct WPILIB_DLLEXPORT SerdeType<{{ name }}> {
static_assert(photon::PhotonStructSerializable<photon::{{ name }}>);
} // namespace photon
} // namespace photon{{'\n'}}

View File

@@ -44,7 +44,7 @@ class {{ name }}Serde:
MESSAGE_FORMAT = "{{ message_fmt }}"
@staticmethod
def pack(value: '{{ name }}' ) -> 'Packet':
def pack(value: "{{ name }}") -> "Packet":
ret = Packet()
{% for field in fields -%}
{%- if field.type | is_shimmed %}
@@ -60,7 +60,7 @@ class {{ name }}Serde:
ret.encode{{ type_map[field.type].java_type.title() }}List(value.{{ field.name }})
{%- elif field.type | is_intrinsic %}
# {{ field.name }} is of intrinsic type {{ field.type }}
ret.{{ type_map[field.type].java_encode_shim }}(value.{{field.name}})
ret.{{ type_map[field.type].python_encode_shim }}(value.{{field.name}})
{%- else %}
# {{ field.name }} is of non-intrinsic type {{ field.type }}
ret.encodeBytes({{ field.type }}.photonStruct.pack(value.{{field.name}}).getData())
@@ -70,13 +70,12 @@ class {{ name }}Serde:
{% endfor%}
return ret
@staticmethod
def unpack(packet: 'Packet') -> '{{ name }}':
def unpack(packet: "Packet") -> "{{ name }}":
ret = {{ name }}()
{% for field in fields -%}
{%- if field.type | is_shimmed %}
ret.{{ field.name }} = {{ get_message_by_name(field.type).python_decode_shim }}()
ret.{{ field.name }} = packet.{{ get_message_by_name(field.type).python_decode_shim }}()
{%- elif field.optional == True %}
# {{ field.name }} is optional! it better not be a VLA too
ret.{{ field.name }} = packet.decodeOptional({{ field.type }}.photonStruct)
@@ -88,7 +87,7 @@ class {{ name }}Serde:
ret.{{ field.name }} = packet.decode{{ type_map[field.type].java_type.title() }}List()
{%- elif field.type | is_intrinsic %}
# {{ field.name }} is of intrinsic type {{ field.type }}
ret.{{field.name}} = packet.{{ type_map[field.type].java_decode_method }}()
ret.{{field.name}} = packet.{{ type_map[field.type].python_decode_shim }}()
{%- else %}
# {{ field.name }} is of non-intrinsic type {{ field.type }}
ret.{{field.name}} = {{ field.type }}.photonStruct.unpack(packet)
@@ -101,4 +100,4 @@ class {{ name }}Serde:
# Hack ourselves into the base class
{{ name }}.photonStruct = {{ name }}Serde()
{{ name }}.photonStruct = {{ name }}Serde(){{'\n'}}

View File

@@ -36,4 +36,4 @@ struct {{ name }}_PhotonStruct {
friend bool operator==({{ name }}_PhotonStruct const&, {{ name }}_PhotonStruct const&) = default;
};
} // namespace photon
} // namespace photon{{'\n'}}

View File

@@ -31,6 +31,7 @@ import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.configuration.NeuralNetworkModelManager;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.hardware.HardwareManager;
import org.photonvision.common.hardware.OsImageVersion;
import org.photonvision.common.hardware.PiVersion;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.KernelLogLogger;
@@ -353,10 +354,14 @@ public class Main {
logger.info(
"Starting PhotonVision version "
+ PhotonVersion.versionString
+ " on "
+ " on platform "
+ Platform.getPlatformName()
+ (Platform.isRaspberryPi() ? (" (Pi " + PiVersion.getPiVersion() + ")") : ""));
if (OsImageVersion.IMAGE_VERSION.isPresent()) {
logger.info("PhotonVision image version: " + OsImageVersion.IMAGE_VERSION.get());
}
try {
if (!handleArgs(args)) {
System.exit(1);

View File

@@ -27,6 +27,7 @@ import org.photonvision.common.dataflow.events.DataChangeEvent;
import org.photonvision.common.dataflow.events.IncomingWebSocketEvent;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.dataflow.websocket.UIPhotonConfiguration;
import org.photonvision.common.logging.Logger;
public class UIInboundSubscriber extends DataChangeSubscriber {
@@ -43,7 +44,8 @@ public class UIInboundSubscriber extends DataChangeSubscriber {
if (incomingWSEvent.propertyName.equals("userConnected")
|| incomingWSEvent.propertyName.equals("sendFullSettings")) {
// Send full settings
var settings = ConfigManager.getInstance().getConfig().toHashMap();
var settings =
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig());
var message =
new OutgoingUIEvent<>("fullsettings", settings, incomingWSEvent.originContext);
DataChangeService.getInstance().publishEvent(message);

View File

@@ -25,13 +25,7 @@ import edu.wpi.first.apriltag.jni.AprilTagJNI;
import edu.wpi.first.cscore.CameraServerJNI;
import edu.wpi.first.cscore.OpenCvLoader;
import edu.wpi.first.hal.JNIWrapper;
import edu.wpi.first.math.jni.ArmFeedforwardJNI;
import edu.wpi.first.math.jni.DAREJNI;
import edu.wpi.first.math.jni.EigenJNI;
import edu.wpi.first.math.jni.Ellipse2dJNI;
import edu.wpi.first.math.jni.Pose3dJNI;
import edu.wpi.first.math.jni.StateSpaceUtilJNI;
import edu.wpi.first.math.jni.TrajectoryUtilJNI;
import edu.wpi.first.math.jni.WPIMathJNI;
import edu.wpi.first.net.WPINetJNI;
import edu.wpi.first.networktables.NetworkTablesJNI;
import edu.wpi.first.util.CombinedRuntimeLoader;
@@ -48,18 +42,8 @@ public class WpilibLoader {
OpenCvLoader.Helper.setExtractOnStaticLoad(false);
JNIWrapper.Helper.setExtractOnStaticLoad(false);
WPINetJNI.Helper.setExtractOnStaticLoad(false);
WPIMathJNI.Helper.setExtractOnStaticLoad(false);
AprilTagJNI.Helper.setExtractOnStaticLoad(false);
// wpimathjni is a bit odd, it's all in the wpimathjni shared lib, but the java side stuff has
// been split.
ArmFeedforwardJNI.Helper.setExtractOnStaticLoad(false);
DAREJNI.Helper.setExtractOnStaticLoad(false);
EigenJNI.Helper.setExtractOnStaticLoad(false);
Ellipse2dJNI.Helper.setExtractOnStaticLoad(false);
Pose3dJNI.Helper.setExtractOnStaticLoad(false);
StateSpaceUtilJNI.Helper.setExtractOnStaticLoad(false);
TrajectoryUtilJNI.Helper.setExtractOnStaticLoad(false);
try {
CombinedRuntimeLoader.loadLibraries(
WpilibLoader.class,

View File

@@ -34,6 +34,7 @@ import edu.wpi.first.util.struct.Struct;
* Auto-generated serialization/deserialization helper for MultiTargetPNPResult
*/
public class MultiTargetPNPResultSerde implements PacketSerde<MultiTargetPNPResult> {
@Override
public final String getInterfaceUUID() { return "541096947e9f3ca2d3f425ff7b04aa7b"; }
@Override
@@ -79,6 +80,7 @@ public class MultiTargetPNPResultSerde implements PacketSerde<MultiTargetPNPResu
@Override
public Struct<?>[] getNestedWpilibMessages() {
return new Struct<?>[] {
};
}
}

View File

@@ -34,6 +34,7 @@ import edu.wpi.first.util.struct.Struct;
* Auto-generated serialization/deserialization helper for PhotonPipelineMetadata
*/
public class PhotonPipelineMetadataSerde implements PacketSerde<PhotonPipelineMetadata> {
@Override
public final String getInterfaceUUID() { return "ac0a45f686457856fb30af77699ea356"; }
@Override
@@ -84,12 +85,14 @@ public class PhotonPipelineMetadataSerde implements PacketSerde<PhotonPipelineMe
@Override
public PacketSerde<?>[] getNestedPhotonMessages() {
return new PacketSerde<?>[] {
};
}
@Override
public Struct<?>[] getNestedWpilibMessages() {
return new Struct<?>[] {
};
}
}

View File

@@ -34,6 +34,7 @@ import edu.wpi.first.util.struct.Struct;
* Auto-generated serialization/deserialization helper for PhotonPipelineResult
*/
public class PhotonPipelineResultSerde implements PacketSerde<PhotonPipelineResult> {
@Override
public final String getInterfaceUUID() { return "4b2ff16a964b5e2bf04be0c1454d91c4"; }
@Override
@@ -78,13 +79,14 @@ public class PhotonPipelineResultSerde implements PacketSerde<PhotonPipelineResu
@Override
public PacketSerde<?>[] getNestedPhotonMessages() {
return new PacketSerde<?>[] {
MultiTargetPNPResult.photonStruct,PhotonPipelineMetadata.photonStruct,PhotonTrackedTarget.photonStruct
PhotonPipelineMetadata.photonStruct,MultiTargetPNPResult.photonStruct,PhotonTrackedTarget.photonStruct
};
}
@Override
public Struct<?>[] getNestedWpilibMessages() {
return new Struct<?>[] {
};
}
}

View File

@@ -34,6 +34,7 @@ import edu.wpi.first.math.geometry.Transform3d;
* Auto-generated serialization/deserialization helper for PhotonTrackedTarget
*/
public class PhotonTrackedTargetSerde implements PacketSerde<PhotonTrackedTarget> {
@Override
public final String getInterfaceUUID() { return "cc6dbb5c5c1e0fa808108019b20863f1"; }
@Override

View File

@@ -34,6 +34,7 @@ import edu.wpi.first.math.geometry.Transform3d;
* Auto-generated serialization/deserialization helper for PnpResult
*/
public class PnpResultSerde implements PacketSerde<PnpResult> {
@Override
public final String getInterfaceUUID() { return "ae4d655c0a3104d88df4f5db144c1e86"; }
@Override
@@ -86,6 +87,7 @@ public class PnpResultSerde implements PacketSerde<PnpResult> {
@Override
public PacketSerde<?>[] getNestedPhotonMessages() {
return new PacketSerde<?>[] {
};
}

View File

@@ -34,6 +34,7 @@ import edu.wpi.first.util.struct.Struct;
* Auto-generated serialization/deserialization helper for TargetCorner
*/
public class TargetCornerSerde implements PacketSerde<TargetCorner> {
@Override
public final String getInterfaceUUID() { return "16f6ac0dedc8eaccb951f4895d9e18b6"; }
@Override
@@ -72,12 +73,14 @@ public class TargetCornerSerde implements PacketSerde<TargetCorner> {
@Override
public PacketSerde<?>[] getNestedPhotonMessages() {
return new PacketSerde<?>[] {
};
}
@Override
public Struct<?>[] getNestedWpilibMessages() {
return new Struct<?>[] {
};
}
}

View File

@@ -15,8 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include "photon/serde/MultiTargetPNPResultSerde.h"
@@ -30,10 +29,10 @@ void StructType::Pack(Packet& packet, const MultiTargetPNPResult& value) {
}
MultiTargetPNPResult StructType::Unpack(Packet& packet) {
return MultiTargetPNPResult{MultiTargetPNPResult_PhotonStruct{
.estimatedPose = packet.Unpack<photon::PnpResult>(),
.fiducialIDsUsed = packet.Unpack<std::vector<int16_t>>(),
return MultiTargetPNPResult{ MultiTargetPNPResult_PhotonStruct{
.estimatedPose = packet.Unpack<photon::PnpResult>(),
.fiducialIDsUsed = packet.Unpack<std::vector<int16_t>>(),
}};
}
} // namespace photon
} // namespace photon

View File

@@ -15,8 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include "photon/serde/PhotonPipelineMetadataSerde.h"
@@ -32,12 +31,12 @@ void StructType::Pack(Packet& packet, const PhotonPipelineMetadata& value) {
}
PhotonPipelineMetadata StructType::Unpack(Packet& packet) {
return PhotonPipelineMetadata{PhotonPipelineMetadata_PhotonStruct{
.sequenceID = packet.Unpack<int64_t>(),
.captureTimestampMicros = packet.Unpack<int64_t>(),
.publishTimestampMicros = packet.Unpack<int64_t>(),
.timeSinceLastPong = packet.Unpack<int64_t>(),
return PhotonPipelineMetadata{ PhotonPipelineMetadata_PhotonStruct{
.sequenceID = packet.Unpack<int64_t>(),
.captureTimestampMicros = packet.Unpack<int64_t>(),
.publishTimestampMicros = packet.Unpack<int64_t>(),
.timeSinceLastPong = packet.Unpack<int64_t>(),
}};
}
} // namespace photon
} // namespace photon

View File

@@ -15,8 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include "photon/serde/PhotonPipelineResultSerde.h"
@@ -27,17 +26,15 @@ using StructType = SerdeType<PhotonPipelineResult>;
void StructType::Pack(Packet& packet, const PhotonPipelineResult& value) {
packet.Pack<photon::PhotonPipelineMetadata>(value.metadata);
packet.Pack<std::vector<photon::PhotonTrackedTarget>>(value.targets);
packet.Pack<std::optional<photon::MultiTargetPNPResult>>(
value.multitagResult);
packet.Pack<std::optional<photon::MultiTargetPNPResult>>(value.multitagResult);
}
PhotonPipelineResult StructType::Unpack(Packet& packet) {
return PhotonPipelineResult{PhotonPipelineResult_PhotonStruct{
.metadata = packet.Unpack<photon::PhotonPipelineMetadata>(),
.targets = packet.Unpack<std::vector<photon::PhotonTrackedTarget>>(),
.multitagResult =
packet.Unpack<std::optional<photon::MultiTargetPNPResult>>(),
return PhotonPipelineResult{ PhotonPipelineResult_PhotonStruct{
.metadata = packet.Unpack<photon::PhotonPipelineMetadata>(),
.targets = packet.Unpack<std::vector<photon::PhotonTrackedTarget>>(),
.multitagResult = packet.Unpack<std::optional<photon::MultiTargetPNPResult>>(),
}};
}
} // namespace photon
} // namespace photon

View File

@@ -15,8 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include "photon/serde/PhotonTrackedTargetSerde.h"
@@ -40,20 +39,20 @@ void StructType::Pack(Packet& packet, const PhotonTrackedTarget& value) {
}
PhotonTrackedTarget StructType::Unpack(Packet& packet) {
return PhotonTrackedTarget{PhotonTrackedTarget_PhotonStruct{
.yaw = packet.Unpack<double>(),
.pitch = packet.Unpack<double>(),
.area = packet.Unpack<double>(),
.skew = packet.Unpack<double>(),
.fiducialId = packet.Unpack<int32_t>(),
.objDetectId = packet.Unpack<int32_t>(),
.objDetectConf = packet.Unpack<float>(),
.bestCameraToTarget = packet.Unpack<frc::Transform3d>(),
.altCameraToTarget = packet.Unpack<frc::Transform3d>(),
.poseAmbiguity = packet.Unpack<double>(),
.minAreaRectCorners = packet.Unpack<std::vector<photon::TargetCorner>>(),
.detectedCorners = packet.Unpack<std::vector<photon::TargetCorner>>(),
return PhotonTrackedTarget{ PhotonTrackedTarget_PhotonStruct{
.yaw = packet.Unpack<double>(),
.pitch = packet.Unpack<double>(),
.area = packet.Unpack<double>(),
.skew = packet.Unpack<double>(),
.fiducialId = packet.Unpack<int32_t>(),
.objDetectId = packet.Unpack<int32_t>(),
.objDetectConf = packet.Unpack<float>(),
.bestCameraToTarget = packet.Unpack<frc::Transform3d>(),
.altCameraToTarget = packet.Unpack<frc::Transform3d>(),
.poseAmbiguity = packet.Unpack<double>(),
.minAreaRectCorners = packet.Unpack<std::vector<photon::TargetCorner>>(),
.detectedCorners = packet.Unpack<std::vector<photon::TargetCorner>>(),
}};
}
} // namespace photon
} // namespace photon

View File

@@ -15,8 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include "photon/serde/PnpResultSerde.h"
@@ -33,13 +32,13 @@ void StructType::Pack(Packet& packet, const PnpResult& value) {
}
PnpResult StructType::Unpack(Packet& packet) {
return PnpResult{PnpResult_PhotonStruct{
.best = packet.Unpack<frc::Transform3d>(),
.alt = packet.Unpack<frc::Transform3d>(),
.bestReprojErr = packet.Unpack<double>(),
.altReprojErr = packet.Unpack<double>(),
.ambiguity = packet.Unpack<double>(),
return PnpResult{ PnpResult_PhotonStruct{
.best = packet.Unpack<frc::Transform3d>(),
.alt = packet.Unpack<frc::Transform3d>(),
.bestReprojErr = packet.Unpack<double>(),
.altReprojErr = packet.Unpack<double>(),
.ambiguity = packet.Unpack<double>(),
}};
}
} // namespace photon
} // namespace photon

View File

@@ -15,8 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include "photon/serde/TargetCornerSerde.h"
@@ -30,10 +29,10 @@ void StructType::Pack(Packet& packet, const TargetCorner& value) {
}
TargetCorner StructType::Unpack(Packet& packet) {
return TargetCorner{TargetCorner_PhotonStruct{
.x = packet.Unpack<double>(),
.y = packet.Unpack<double>(),
return TargetCorner{ TargetCorner_PhotonStruct{
.x = packet.Unpack<double>(),
.y = packet.Unpack<double>(),
}};
}
} // namespace photon
} // namespace photon

View File

@@ -17,8 +17,7 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include <wpi/SymbolExports.h>
@@ -31,6 +30,7 @@
#include <stdint.h>
#include <vector>
namespace photon {
template <>
@@ -40,15 +40,13 @@ struct WPILIB_DLLEXPORT SerdeType<MultiTargetPNPResult> {
}
static constexpr std::string_view GetSchema() {
return "PnpResult:ae4d655c0a3104d88df4f5db144c1e86 estimatedPose;int16 "
"fiducialIDsUsed[?];";
return "PnpResult:ae4d655c0a3104d88df4f5db144c1e86 estimatedPose;int16 fiducialIDsUsed[?];";
}
static photon::MultiTargetPNPResult Unpack(photon::Packet& packet);
static void Pack(photon::Packet& packet,
const photon::MultiTargetPNPResult& value);
static void Pack(photon::Packet& packet, const photon::MultiTargetPNPResult& value);
};
static_assert(photon::PhotonStructSerializable<photon::MultiTargetPNPResult>);
} // namespace photon
} // namespace photon

View File

@@ -17,8 +17,7 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include <wpi/SymbolExports.h>
@@ -29,6 +28,7 @@
// Includes for dependant types
#include <stdint.h>
namespace photon {
template <>
@@ -38,15 +38,13 @@ struct WPILIB_DLLEXPORT SerdeType<PhotonPipelineMetadata> {
}
static constexpr std::string_view GetSchema() {
return "int64 sequenceID;int64 captureTimestampMicros;int64 "
"publishTimestampMicros;int64 timeSinceLastPong;";
return "int64 sequenceID;int64 captureTimestampMicros;int64 publishTimestampMicros;int64 timeSinceLastPong;";
}
static photon::PhotonPipelineMetadata Unpack(photon::Packet& packet);
static void Pack(photon::Packet& packet,
const photon::PhotonPipelineMetadata& value);
static void Pack(photon::Packet& packet, const photon::PhotonPipelineMetadata& value);
};
static_assert(photon::PhotonStructSerializable<photon::PhotonPipelineMetadata>);
} // namespace photon
} // namespace photon

View File

@@ -17,8 +17,7 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include <wpi/SymbolExports.h>
@@ -34,6 +33,7 @@
#include <stdint.h>
#include <vector>
namespace photon {
template <>
@@ -43,18 +43,13 @@ struct WPILIB_DLLEXPORT SerdeType<PhotonPipelineResult> {
}
static constexpr std::string_view GetSchema() {
return "PhotonPipelineMetadata:ac0a45f686457856fb30af77699ea356 "
"metadata;PhotonTrackedTarget:cc6dbb5c5c1e0fa808108019b20863f1 "
"targets[?];optional "
"MultiTargetPNPResult:541096947e9f3ca2d3f425ff7b04aa7b "
"multitagResult;";
return "PhotonPipelineMetadata:ac0a45f686457856fb30af77699ea356 metadata;PhotonTrackedTarget:cc6dbb5c5c1e0fa808108019b20863f1 targets[?];optional MultiTargetPNPResult:541096947e9f3ca2d3f425ff7b04aa7b multitagResult;";
}
static photon::PhotonPipelineResult Unpack(photon::Packet& packet);
static void Pack(photon::Packet& packet,
const photon::PhotonPipelineResult& value);
static void Pack(photon::Packet& packet, const photon::PhotonPipelineResult& value);
};
static_assert(photon::PhotonStructSerializable<photon::PhotonPipelineResult>);
} // namespace photon
} // namespace photon

View File

@@ -17,8 +17,7 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include <wpi/SymbolExports.h>
@@ -32,6 +31,7 @@
#include <stdint.h>
#include <vector>
namespace photon {
template <>
@@ -41,19 +41,13 @@ struct WPILIB_DLLEXPORT SerdeType<PhotonTrackedTarget> {
}
static constexpr std::string_view GetSchema() {
return "float64 yaw;float64 pitch;float64 area;float64 skew;int32 "
"fiducialId;int32 objDetectId;float32 objDetectConf;Transform3d "
"bestCameraToTarget;Transform3d altCameraToTarget;float64 "
"poseAmbiguity;TargetCorner:16f6ac0dedc8eaccb951f4895d9e18b6 "
"minAreaRectCorners[?];TargetCorner:"
"16f6ac0dedc8eaccb951f4895d9e18b6 detectedCorners[?];";
return "float64 yaw;float64 pitch;float64 area;float64 skew;int32 fiducialId;int32 objDetectId;float32 objDetectConf;Transform3d bestCameraToTarget;Transform3d altCameraToTarget;float64 poseAmbiguity;TargetCorner:16f6ac0dedc8eaccb951f4895d9e18b6 minAreaRectCorners[?];TargetCorner:16f6ac0dedc8eaccb951f4895d9e18b6 detectedCorners[?];";
}
static photon::PhotonTrackedTarget Unpack(photon::Packet& packet);
static void Pack(photon::Packet& packet,
const photon::PhotonTrackedTarget& value);
static void Pack(photon::Packet& packet, const photon::PhotonTrackedTarget& value);
};
static_assert(photon::PhotonStructSerializable<photon::PhotonTrackedTarget>);
} // namespace photon
} // namespace photon

View File

@@ -17,8 +17,7 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include <wpi/SymbolExports.h>
@@ -30,6 +29,7 @@
#include <frc/geometry/Transform3d.h>
#include <stdint.h>
namespace photon {
template <>
@@ -39,8 +39,7 @@ struct WPILIB_DLLEXPORT SerdeType<PnpResult> {
}
static constexpr std::string_view GetSchema() {
return "Transform3d best;Transform3d alt;float64 bestReprojErr;float64 "
"altReprojErr;float64 ambiguity;";
return "Transform3d best;Transform3d alt;float64 bestReprojErr;float64 altReprojErr;float64 ambiguity;";
}
static photon::PnpResult Unpack(photon::Packet& packet);
@@ -49,4 +48,4 @@ struct WPILIB_DLLEXPORT SerdeType<PnpResult> {
static_assert(photon::PhotonStructSerializable<photon::PnpResult>);
} // namespace photon
} // namespace photon

View File

@@ -17,8 +17,7 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
#include <wpi/SymbolExports.h>
@@ -29,6 +28,7 @@
// Includes for dependant types
#include <stdint.h>
namespace photon {
template <>
@@ -47,4 +47,4 @@ struct WPILIB_DLLEXPORT SerdeType<TargetCorner> {
static_assert(photon::PhotonStructSerializable<photon::TargetCorner>);
} // namespace photon
} // namespace photon

View File

@@ -17,15 +17,13 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
// Includes for dependant types
#include "photon/targeting/PnpResult.h"
#include <stdint.h>
#include <vector>
#include "photon/targeting/PnpResult.h"
namespace photon {
@@ -33,8 +31,7 @@ struct MultiTargetPNPResult_PhotonStruct {
photon::PnpResult estimatedPose;
std::vector<int16_t> fiducialIDsUsed;
friend bool operator==(MultiTargetPNPResult_PhotonStruct const&,
MultiTargetPNPResult_PhotonStruct const&) = default;
friend bool operator==(MultiTargetPNPResult_PhotonStruct const&, MultiTargetPNPResult_PhotonStruct const&) = default;
};
} // namespace photon
} // namespace photon

View File

@@ -17,12 +17,12 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
// Includes for dependant types
#include <stdint.h>
namespace photon {
struct PhotonPipelineMetadata_PhotonStruct {
@@ -31,8 +31,7 @@ struct PhotonPipelineMetadata_PhotonStruct {
int64_t publishTimestampMicros;
int64_t timeSinceLastPong;
friend bool operator==(PhotonPipelineMetadata_PhotonStruct const&,
PhotonPipelineMetadata_PhotonStruct const&) = default;
friend bool operator==(PhotonPipelineMetadata_PhotonStruct const&, PhotonPipelineMetadata_PhotonStruct const&) = default;
};
} // namespace photon
} // namespace photon

View File

@@ -17,18 +17,16 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
// Includes for dependant types
#include <stdint.h>
#include <optional>
#include <vector>
#include "photon/targeting/MultiTargetPNPResult.h"
#include "photon/targeting/PhotonPipelineMetadata.h"
#include "photon/targeting/PhotonTrackedTarget.h"
#include <optional>
#include <stdint.h>
#include <vector>
namespace photon {
@@ -37,8 +35,7 @@ struct PhotonPipelineResult_PhotonStruct {
std::vector<photon::PhotonTrackedTarget> targets;
std::optional<photon::MultiTargetPNPResult> multitagResult;
friend bool operator==(PhotonPipelineResult_PhotonStruct const&,
PhotonPipelineResult_PhotonStruct const&) = default;
friend bool operator==(PhotonPipelineResult_PhotonStruct const&, PhotonPipelineResult_PhotonStruct const&) = default;
};
} // namespace photon
} // namespace photon

View File

@@ -17,17 +17,14 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
// Includes for dependant types
#include "photon/targeting/TargetCorner.h"
#include <frc/geometry/Transform3d.h>
#include <stdint.h>
#include <vector>
#include <frc/geometry/Transform3d.h>
#include "photon/targeting/TargetCorner.h"
namespace photon {
@@ -45,8 +42,7 @@ struct PhotonTrackedTarget_PhotonStruct {
std::vector<photon::TargetCorner> minAreaRectCorners;
std::vector<photon::TargetCorner> detectedCorners;
friend bool operator==(PhotonTrackedTarget_PhotonStruct const&,
PhotonTrackedTarget_PhotonStruct const&) = default;
friend bool operator==(PhotonTrackedTarget_PhotonStruct const&, PhotonTrackedTarget_PhotonStruct const&) = default;
};
} // namespace photon
} // namespace photon

View File

@@ -17,13 +17,12 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
// Includes for dependant types
#include <frc/geometry/Transform3d.h>
#include <stdint.h>
#include <frc/geometry/Transform3d.h>
namespace photon {
@@ -34,8 +33,7 @@ struct PnpResult_PhotonStruct {
double altReprojErr;
double ambiguity;
friend bool operator==(PnpResult_PhotonStruct const&,
PnpResult_PhotonStruct const&) = default;
friend bool operator==(PnpResult_PhotonStruct const&, PnpResult_PhotonStruct const&) = default;
};
} // namespace photon
} // namespace photon

View File

@@ -17,20 +17,19 @@
#pragma once
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
// NOT MODIFY
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
// Includes for dependant types
#include <stdint.h>
namespace photon {
struct TargetCorner_PhotonStruct {
double x;
double y;
friend bool operator==(TargetCorner_PhotonStruct const&,
TargetCorner_PhotonStruct const&) = default;
friend bool operator==(TargetCorner_PhotonStruct const&, TargetCorner_PhotonStruct const&) = default;
};
} // namespace photon
} // namespace photon

View File

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

View File

@@ -125,7 +125,9 @@ void Robot::SimulationPeriodic() {
units::ampere_t totalCurrent = drivetrain.GetCurrentDraw();
units::volt_t loadedBattVolts =
frc::sim::BatterySim::Calculate({totalCurrent});
frc::sim::RoboRioSim::SetVInVoltage(loadedBattVolts);
// Using max(0.1, voltage) here isn't a *physically correct* solution,
// but it avoids problems with battery voltage measuring 0.
frc::sim::RoboRioSim::SetVInVoltage(units::math::max(0.1_V, loadedBattVolts));
}
#ifndef RUNNING_FRC_TESTS

View File

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

View File

@@ -115,7 +115,9 @@ void Robot::SimulationPeriodic() {
units::ampere_t totalCurrent = drivetrain.GetCurrentDraw();
units::volt_t loadedBattVolts =
frc::sim::BatterySim::Calculate({totalCurrent});
frc::sim::RoboRioSim::SetVInVoltage(loadedBattVolts);
// Using max(0.1, voltage) here isn't a *physically correct* solution,
// but it avoids problems with battery voltage measuring 0.
frc::sim::RoboRioSim::SetVInVoltage(units::math::max(0.1_V, loadedBattVolts));
}
#ifndef RUNNING_FRC_TESTS

View File

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

View File

@@ -105,7 +105,10 @@ void Robot::SimulationPeriodic() {
units::ampere_t totalCurrent = drivetrain.GetCurrentDraw();
units::volt_t loadedBattVolts =
frc::sim::BatterySim::Calculate({totalCurrent});
frc::sim::RoboRioSim::SetVInVoltage(loadedBattVolts);
// Using max(0.1, voltage) here isn't a *physically correct* solution,
// but it avoids problems with battery voltage measuring 0.
frc::sim::RoboRioSim::SetVInVoltage(units::math::max(0.1_V, loadedBattVolts));
}
#ifndef RUNNING_FRC_TESTS

View File

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

View File

@@ -147,8 +147,12 @@ public class Robot extends TimedRobot {
debugField.getObject("EstimatedRobotModules").setPoses(drivetrain.getModulePoses());
// Calculate battery voltage sag due to current draw
RoboRioSim.setVInVoltage(
BatterySim.calculateDefaultBatteryLoadedVoltage(drivetrain.getCurrentDraw()));
var batteryVoltage =
BatterySim.calculateDefaultBatteryLoadedVoltage(drivetrain.getCurrentDraw());
// Using max(0.1, voltage) here isn't a *physically correct* solution,
// but it avoids problems with battery voltage measuring 0.
RoboRioSim.setVInVoltage(Math.max(0.1, batteryVoltage));
}
public void resetPose() {

View File

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

View File

@@ -129,8 +129,12 @@ public class Robot extends TimedRobot {
debugField.getObject("EstimatedRobotModules").setPoses(drivetrain.getModulePoses());
// Calculate battery voltage sag due to current draw
RoboRioSim.setVInVoltage(
BatterySim.calculateDefaultBatteryLoadedVoltage(drivetrain.getCurrentDraw()));
var batteryVoltage =
BatterySim.calculateDefaultBatteryLoadedVoltage(drivetrain.getCurrentDraw());
// Using max(0.1, voltage) here isn't a *physically correct* solution,
// but it avoids problems with battery voltage measuring 0.
RoboRioSim.setVInVoltage(Math.max(0.1, batteryVoltage));
}
public void resetPose() {

View File

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

View File

@@ -127,8 +127,12 @@ public class Robot extends TimedRobot {
gpLauncher.simulationPeriodic();
// Calculate battery voltage sag due to current draw
RoboRioSim.setVInVoltage(
BatterySim.calculateDefaultBatteryLoadedVoltage(drivetrain.getCurrentDraw()));
var batteryVoltage =
BatterySim.calculateDefaultBatteryLoadedVoltage(drivetrain.getCurrentDraw());
// Using max(0.1, voltage) here isn't a *physically correct* solution,
// but it avoids problems with battery voltage measuring 0.
RoboRioSim.setVInVoltage(Math.max(0.1, batteryVoltage));
}
public void resetPose() {

View File

@@ -110,7 +110,7 @@ ext.createComponentZipTasks = { components, names, base, type, project, func ->
description = 'Creates component archive for platform ' + key
destinationDirectory = outputsFolder
archiveClassifier = key
archiveBaseName = '_M_' + base
archiveBaseName = base
duplicatesStrategy = 'exclude'
from(licenseFile) {

View File

@@ -7,9 +7,6 @@ def baseArtifactId = nativeName
def artifactGroupId = 'org.photonvision'
def zipBaseName = "_GROUP_org_photonvision_${baseArtifactId}_ID_${baseArtifactId}-cpp_CLS"
def jniBaseName = "_GROUP_edu_wpi_first_${nativeName}_ID_${nativeName}-jni_CLS"
def jniCvStaticBaseName = "_GROUP_edu_wpi_first_${nativeName}_ID_${nativeName}-jnicvstatic_CLS"
def licenseFile = ext.licenseFile
// Quick hack to make this name visible to photon-lib for combined
ext.zipBaseName = zipBaseName
@@ -80,29 +77,6 @@ model {
"${nativeName}JNI"
], zipBaseName, Zip, project, includeStandardZipFormat)
// From https://github.com/wpilibsuite/allwpilib/blob/1c220ebc607daa8da7d983b8f17bc40323633cb2/shared/jni/publish.gradle#L80C9-L100C11
def jniTaskList = createComponentZipTasks($.components, ["${nativeName}JNI"], jniBaseName, Jar, project, { task, value ->
value.each { binary ->
if (binary.buildable) {
if (binary instanceof SharedLibraryBinarySpec) {
task.dependsOn binary.tasks.link
def hashFile = new File(binary.sharedLibraryFile.parentFile.absolutePath, "${binary.component.baseName}.hash")
task.outputs.file(hashFile)
task.inputs.file(binary.sharedLibraryFile)
task.from(hashFile) {
into nativeUtils.getPlatformPath(binary)
}
task.doFirst {
hashFile.text = MessageDigest.getInstance("MD5").digest(binary.sharedLibraryFile.bytes).encodeHex().toString()
}
task.from(binary.sharedLibraryFile) {
into nativeUtils.getPlatformPath(binary)
}
}
}
}
})
publications {
cpp(MavenPublication) {
cppTaskList.each {
@@ -115,15 +89,6 @@ model {
groupId artifactGroupId
version pubVersion
}
jni(MavenPublication) {
jniTaskList.each {
artifact it
}
artifactId = "${baseArtifactId}-jni"
groupId artifactGroupId
version pubVersion
}
}
repositories {