mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-26 01:51:40 +00:00
Compare commits
20 Commits
v2025.0.0-
...
v2025.0.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81076375b8 | ||
|
|
66f369f3a9 | ||
|
|
7cba7b432d | ||
|
|
dd98d96d7e | ||
|
|
8ede892c14 | ||
|
|
08c62ab8cd | ||
|
|
e8efef476b | ||
|
|
c6403a65d2 | ||
|
|
6a8d638853 | ||
|
|
782929b006 | ||
|
|
4997ad9115 | ||
|
|
857a30d980 | ||
|
|
a40e69cca0 | ||
|
|
e069a79a32 | ||
|
|
d9dfe15bfe | ||
|
|
1dbd2e5990 | ||
|
|
7e9da4133d | ||
|
|
163b5c9c81 | ||
|
|
c6a3638a2f | ||
|
|
44f78cb03e |
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -6,16 +6,8 @@ on:
|
||||
branches: [ master ]
|
||||
tags:
|
||||
- 'v*'
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '.github/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '.github/**'
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
@@ -479,6 +471,12 @@ jobs:
|
||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-6/photonvision_opi5max.img.xz
|
||||
cpu: cortex-a8
|
||||
image_additional_mb: 1024
|
||||
- os: ubuntu-22.04
|
||||
artifact-name: LinuxArm64
|
||||
image_suffix: rock5c
|
||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.0-beta-7/photonvision_rock5c.img.xz
|
||||
cpu: cortex-a8
|
||||
image_additional_mb: 1024
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: "Build image - ${{ matrix.image_url }}"
|
||||
@@ -560,6 +558,7 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
**/*orangepi5*.xz
|
||||
**/*rock5*.xz
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -567,6 +566,7 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
**/!(*orangepi5*).xz
|
||||
**/!(*rock5*).xz
|
||||
**/*.jar
|
||||
**/photonlib*.json
|
||||
**/photonlib*.zip
|
||||
|
||||
8
.github/workflows/lint-format.yml
vendored
8
.github/workflows/lint-format.yml
vendored
@@ -6,16 +6,8 @@ on:
|
||||
branches: [ master ]
|
||||
tags:
|
||||
- 'v*'
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '.github/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '.github/**'
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
|
||||
8
.github/workflows/photon-code-docs.yml
vendored
8
.github/workflows/photon-code-docs.yml
vendored
@@ -6,16 +6,8 @@ on:
|
||||
branches: [ master ]
|
||||
tags:
|
||||
- 'v*'
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '.github/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '.github/**'
|
||||
merge_group:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
|
||||
6
.github/workflows/photonvision-docs.yml
vendored
6
.github/workflows/photonvision-docs.yml
vendored
@@ -3,14 +3,8 @@ name: PhotonVision Sphinx Documentation Checks
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '.github/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '.github/**'
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
|
||||
8
.github/workflows/python.yml
vendored
8
.github/workflows/python.yml
vendored
@@ -8,16 +8,8 @@ on:
|
||||
branches: [ master ]
|
||||
tags:
|
||||
- 'v*'
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '.github/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '.github/**'
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -17,7 +17,7 @@ If you are interested in contributing code or documentation to the project, plea
|
||||
## Documentation
|
||||
|
||||
- Our main documentation page: [docs.photonvision.org](https://docs.photonvision.org)
|
||||
- Photon UI demo: [demo.photonvision.org](https://demo.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-client/))
|
||||
- Photon UI demo: [http://photonvision.global/](http://photonvision.global/) (or [manual link](https://photonvision.github.io/photonvision/built-client/))
|
||||
- Javadocs: [javadocs.photonvision.org](https://javadocs.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-docs/javadoc/))
|
||||
- C++ Doxygen [cppdocs.photonvision.org](https://cppdocs.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-docs/doxygen/html/))
|
||||
|
||||
|
||||
14
build.gradle
14
build.gradle
@@ -5,7 +5,7 @@ plugins {
|
||||
id "cpp"
|
||||
id "com.diffplug.spotless" version "6.24.0"
|
||||
id "edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin" version "2020.2"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
|
||||
id 'edu.wpi.first.WpilibTools' version '1.3.0'
|
||||
id 'com.google.protobuf' version '0.9.3' apply false
|
||||
id 'edu.wpi.first.GradleJni' version '1.1.0'
|
||||
@@ -30,16 +30,16 @@ ext.allOutputsFolder = file("$project.buildDir/outputs")
|
||||
apply from: "versioningHelper.gradle"
|
||||
|
||||
ext {
|
||||
wpilibVersion = "2025.1.1-beta-2"
|
||||
wpilibVersion = "2025.1.1-beta-3"
|
||||
wpimathVersion = wpilibVersion
|
||||
openCVYear = "2024"
|
||||
openCVversion = "4.8.0-4"
|
||||
openCVYear = "2025"
|
||||
openCVversion = "4.10.0-3"
|
||||
joglVersion = "2.4.0"
|
||||
javalinVersion = "5.6.2"
|
||||
libcameraDriverVersion = "dev-v2023.1.0-15-gc8988b3"
|
||||
rknnVersion = "dev-v2024.0.1-4-g0db16ac"
|
||||
libcameraDriverVersion = "v2025.0.0"
|
||||
rknnVersion = "v2025.0.0"
|
||||
frcYear = "2025"
|
||||
mrcalVersion = "dev-v2024.0.0-27-g41d7868";
|
||||
mrcalVersion = "v2025.0.0";
|
||||
|
||||
|
||||
pubVersion = versionString
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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-3) Either ensure the only Java on your PATH is the WPILIB Java or specify it to gradle with `-Dorg.gradle.java.home=C:\Users\Public\wpilib\2025\jdk`:
|
||||
|
||||
```
|
||||
> ./gradlew run "-Dorg.gradle.java.home=C:\Users\Public\wpilib\2025\jdk"
|
||||
```
|
||||
|
||||
:::{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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,7 +8,7 @@ If you’re not using cameras in 3D mode, calibration is optional, but it can st
|
||||
|
||||
## Print the Calibration Target
|
||||
|
||||
- Downloaded from our [demo site](https://demo.photonvision.org/#/cameras), or directly from your coprocessors cameras tab.
|
||||
- Downloaded from our [demo site](http://photonvision.global/#/cameras), or directly from your coprocessors cameras tab.
|
||||
- Use the Charuco calibration board:
|
||||
- Board Type: Charuco
|
||||
- Tag Family: 4x4
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -70,16 +70,12 @@ public class ConfigManager {
|
||||
if (INSTANCE == null) {
|
||||
Path rootFolder = PathManager.getInstance().getRootFolder();
|
||||
switch (m_saveStrat) {
|
||||
case SQL:
|
||||
INSTANCE = new ConfigManager(rootFolder, new SqlConfigProvider(rootFolder));
|
||||
break;
|
||||
case LEGACY:
|
||||
INSTANCE = new ConfigManager(rootFolder, new LegacyConfigProvider(rootFolder));
|
||||
break;
|
||||
case ATOMIC_ZIP:
|
||||
// not yet done, fall through
|
||||
default:
|
||||
break;
|
||||
case SQL -> INSTANCE = new ConfigManager(rootFolder, new SqlConfigProvider(rootFolder));
|
||||
case LEGACY ->
|
||||
INSTANCE = new ConfigManager(rootFolder, new LegacyConfigProvider(rootFolder));
|
||||
case ATOMIC_ZIP -> {
|
||||
// TODO: Not done yet
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
|
||||
@@ -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
|
||||
@@ -104,17 +100,6 @@ public class NetworkConfig {
|
||||
config.matchCamerasOnlyByPath);
|
||||
}
|
||||
|
||||
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<>();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getPhysicalInterfaceName() {
|
||||
return this.networkManagerIface;
|
||||
@@ -125,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -205,13 +205,11 @@ public class NeuralNetworkModelManager {
|
||||
|
||||
try {
|
||||
switch (backend.get()) {
|
||||
case RKNN:
|
||||
case RKNN -> {
|
||||
models.get(backend.get()).add(new RknnModel(model, labels));
|
||||
logger.info(
|
||||
"Loaded model " + model.getName() + " for backend " + backend.get().toString());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Failed to load model " + model.getName(), e);
|
||||
|
||||
@@ -599,9 +599,9 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
result.getString(Columns.CAM_PIPELINE_JSONS), dummyList.getClass());
|
||||
|
||||
List<CVPipelineSettings> loadedSettings = new ArrayList<>();
|
||||
for (var str : pipelineSettings) {
|
||||
if (str instanceof String) {
|
||||
loadedSettings.add(JacksonUtils.deserialize((String) str, CVPipelineSettings.class));
|
||||
for (var setting : pipelineSettings) {
|
||||
if (setting instanceof String str) {
|
||||
loadedSettings.add(JacksonUtils.deserialize(str, CVPipelineSettings.class));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,10 @@ public class UINetConfig extends NetworkConfig {
|
||||
super(config);
|
||||
this.networkInterfaceNames = networkInterfaceNames;
|
||||
this.networkingDisabled = networkingDisabled;
|
||||
this.canManage = this.deviceCanManageNetwork();
|
||||
}
|
||||
|
||||
public List<NMDeviceInfo> networkInterfaceNames;
|
||||
public boolean networkingDisabled;
|
||||
public boolean canManage;
|
||||
}
|
||||
|
||||
@@ -242,22 +242,7 @@ public class PigpioSocket {
|
||||
waveSendOnce(waveformId);
|
||||
}
|
||||
} else {
|
||||
String error = "";
|
||||
switch (waveformId) {
|
||||
case PI_EMPTY_WAVEFORM:
|
||||
error = "Waveform empty";
|
||||
break;
|
||||
case PI_TOO_MANY_CBS:
|
||||
error = "Too many CBS";
|
||||
break;
|
||||
case PI_TOO_MANY_OOL:
|
||||
error = "Too many OOL";
|
||||
break;
|
||||
case PI_NO_WAVEFORM_ID:
|
||||
error = "No waveform ID";
|
||||
break;
|
||||
}
|
||||
logger.error("Failed to send wave: " + error);
|
||||
logger.error("Failed to send wave: " + getMessageForError(waveformId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -134,25 +134,17 @@ public class VisionLED {
|
||||
var newLedModeRaw = (int) entryNotification.valueData.value.getInteger();
|
||||
logger.debug("Got LED mode " + newLedModeRaw);
|
||||
if (newLedModeRaw != currentLedMode.value) {
|
||||
VisionLEDMode newLedMode;
|
||||
switch (newLedModeRaw) {
|
||||
case -1:
|
||||
newLedMode = VisionLEDMode.kDefault;
|
||||
break;
|
||||
case 0:
|
||||
newLedMode = VisionLEDMode.kOff;
|
||||
break;
|
||||
case 1:
|
||||
newLedMode = VisionLEDMode.kOn;
|
||||
break;
|
||||
case 2:
|
||||
newLedMode = VisionLEDMode.kBlink;
|
||||
break;
|
||||
default:
|
||||
logger.warn("User supplied invalid LED mode, falling back to Default");
|
||||
newLedMode = VisionLEDMode.kDefault;
|
||||
break;
|
||||
}
|
||||
VisionLEDMode newLedMode =
|
||||
switch (newLedModeRaw) {
|
||||
case -1 -> newLedMode = VisionLEDMode.kDefault;
|
||||
case 0 -> newLedMode = VisionLEDMode.kOff;
|
||||
case 1 -> newLedMode = VisionLEDMode.kOn;
|
||||
case 2 -> newLedMode = VisionLEDMode.kBlink;
|
||||
default -> {
|
||||
logger.warn("User supplied invalid LED mode, falling back to Default");
|
||||
yield VisionLEDMode.kDefault;
|
||||
}
|
||||
};
|
||||
setInternal(newLedMode, true);
|
||||
|
||||
if (modeConsumer != null) modeConsumer.accept(newLedMode.value);
|
||||
@@ -164,18 +156,10 @@ public class VisionLED {
|
||||
|
||||
if (fromNT) {
|
||||
switch (newLedMode) {
|
||||
case kDefault:
|
||||
setStateImpl(pipelineModeSupplier.getAsBoolean());
|
||||
break;
|
||||
case kOff:
|
||||
setStateImpl(false);
|
||||
break;
|
||||
case kOn:
|
||||
setStateImpl(true);
|
||||
break;
|
||||
case kBlink:
|
||||
blinkImpl(85, -1);
|
||||
break;
|
||||
case kDefault -> setStateImpl(pipelineModeSupplier.getAsBoolean());
|
||||
case kOff -> setStateImpl(false);
|
||||
case kOn -> setStateImpl(true);
|
||||
case kBlink -> blinkImpl(85, -1);
|
||||
}
|
||||
currentLedMode = newLedMode;
|
||||
logger.info(
|
||||
@@ -183,18 +167,10 @@ public class VisionLED {
|
||||
} else {
|
||||
if (currentLedMode == VisionLEDMode.kDefault) {
|
||||
switch (newLedMode) {
|
||||
case kDefault:
|
||||
setStateImpl(pipelineModeSupplier.getAsBoolean());
|
||||
break;
|
||||
case kOff:
|
||||
setStateImpl(false);
|
||||
break;
|
||||
case kOn:
|
||||
setStateImpl(true);
|
||||
break;
|
||||
case kBlink:
|
||||
blinkImpl(85, -1);
|
||||
break;
|
||||
case kDefault -> setStateImpl(pipelineModeSupplier.getAsBoolean());
|
||||
case kOff -> setStateImpl(false);
|
||||
case kOn -> setStateImpl(true);
|
||||
case kBlink -> blinkImpl(85, -1);
|
||||
}
|
||||
}
|
||||
logger.info("Changing LED internal state to " + newLedMode.toString());
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
package org.photonvision.common.logging;
|
||||
|
||||
import edu.wpi.first.util.RuntimeDetector;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.util.TimedTaskManager;
|
||||
import org.photonvision.jni.QueuedFileLogger;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class KernelLogLogger {
|
||||
Logger logger = new Logger(KernelLogLogger.class, LogGroup.General);
|
||||
|
||||
public KernelLogLogger() {
|
||||
if (RuntimeDetector.isLinux()) {
|
||||
if (Platform.isLinux()) {
|
||||
listener = new QueuedFileLogger("/var/log/kern.log");
|
||||
} else {
|
||||
System.out.println("NOT for klogs");
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -51,16 +51,9 @@ public abstract class NumberCouple<T extends Number> {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof NumberCouple)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var couple = (NumberCouple) obj;
|
||||
if (!couple.first.equals(first)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return couple.second.equals(second);
|
||||
return obj instanceof NumberCouple<?> couple
|
||||
&& couple.first.equals(first)
|
||||
&& couple.second.equals(second);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ import edu.wpi.first.cscore.CvSink;
|
||||
import edu.wpi.first.cscore.UsbCamera;
|
||||
import edu.wpi.first.cscore.VideoException;
|
||||
import edu.wpi.first.cscore.VideoProperty;
|
||||
import edu.wpi.first.util.RuntimeDetector;
|
||||
import java.util.*;
|
||||
import org.photonvision.common.configuration.CameraConfiguration;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.vision.camera.CameraQuirk;
|
||||
@@ -113,7 +113,7 @@ public class USBCameraSource extends VisionSource {
|
||||
GenericUSBCameraSettables settables;
|
||||
|
||||
if (quirks.hasQuirk(CameraQuirk.LifeCamControls)) {
|
||||
if (RuntimeDetector.isWindows()) {
|
||||
if (Platform.isWindows()) {
|
||||
logger.debug("Using Microsoft Lifecam 3000 Windows-Specific Settables");
|
||||
settables = new LifeCam3kWindowsCameraSettables(config, camera);
|
||||
} else {
|
||||
@@ -124,7 +124,7 @@ public class USBCameraSource extends VisionSource {
|
||||
logger.debug("Using PlayStation Eye Camera Settables");
|
||||
settables = new PsEyeCameraSettables(config, camera);
|
||||
} else if (quirks.hasQuirk(CameraQuirk.ArduOV2311Controls)) {
|
||||
if (RuntimeDetector.isWindows()) {
|
||||
if (Platform.isWindows()) {
|
||||
logger.debug("Using Arducam OV2311 Windows-Specific Settables");
|
||||
settables = new ArduOV2311WindowsCameraSettables(config, camera);
|
||||
} else {
|
||||
|
||||
@@ -135,20 +135,14 @@ public class MJPGFrameConsumer implements AutoCloseable {
|
||||
}
|
||||
|
||||
private static String pixelFormatToString(PixelFormat pixelFormat) {
|
||||
switch (pixelFormat) {
|
||||
case kMJPEG:
|
||||
return "MJPEG";
|
||||
case kYUYV:
|
||||
return "YUYV";
|
||||
case kRGB565:
|
||||
return "RGB565";
|
||||
case kBGR:
|
||||
return "BGR";
|
||||
case kGray:
|
||||
return "Gray";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
return switch (pixelFormat) {
|
||||
case kMJPEG -> "MJPEG";
|
||||
case kYUYV -> "YUYV";
|
||||
case kRGB565 -> "RGB565";
|
||||
case kBGR -> "BGR";
|
||||
case kGray -> "Gray";
|
||||
case kUYVY, kUnknown, kY16, kBGRA -> "Unknown";
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -158,18 +158,19 @@ public class Contour implements Releasable {
|
||||
double massX = (x0A + x0B) / 2;
|
||||
double massY = (y0A + y0B) / 2;
|
||||
switch (intersectionDirection) {
|
||||
case Up:
|
||||
case None -> {}
|
||||
case Up -> {
|
||||
if (intersectionY < massY) isIntersecting = true;
|
||||
break;
|
||||
case Down:
|
||||
}
|
||||
case Down -> {
|
||||
if (intersectionY > massY) isIntersecting = true;
|
||||
break;
|
||||
case Left:
|
||||
}
|
||||
case Left -> {
|
||||
if (intersectionX < massX) isIntersecting = true;
|
||||
break;
|
||||
case Right:
|
||||
}
|
||||
case Right -> {
|
||||
if (intersectionX > massX) isIntersecting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
intersectMatA.release();
|
||||
intersectMatB.release();
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
|
||||
package org.photonvision.vision.opencv;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
|
||||
public enum ContourShape {
|
||||
Circle(0),
|
||||
Custom(-1),
|
||||
@@ -32,15 +29,12 @@ public enum ContourShape {
|
||||
this.sides = sides;
|
||||
}
|
||||
|
||||
private static final HashMap<Integer, ContourShape> sidesToValueMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (var value : EnumSet.allOf(ContourShape.class)) {
|
||||
sidesToValueMap.put(value.sides, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static ContourShape fromSides(int sides) {
|
||||
return sidesToValueMap.get(sides);
|
||||
return switch (sides) {
|
||||
case 0 -> Circle;
|
||||
case 3 -> Triangle;
|
||||
case 4 -> Quadrilateral;
|
||||
default -> Custom;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,13 +48,14 @@ public class Draw2dCrosshairPipe
|
||||
double scale = params.frameStaticProperties.imageWidth / (double) params.divisor.value / 32.0;
|
||||
|
||||
switch (params.robotOffsetPointMode) {
|
||||
case Single:
|
||||
case None -> {}
|
||||
case Single -> {
|
||||
if (params.singleOffsetPoint.x != 0 && params.singleOffsetPoint.y != 0) {
|
||||
x = params.singleOffsetPoint.x;
|
||||
y = params.singleOffsetPoint.y;
|
||||
}
|
||||
break;
|
||||
case Dual:
|
||||
}
|
||||
case Dual -> {
|
||||
if (!in.getRight().isEmpty()) {
|
||||
var target = in.getRight().get(0);
|
||||
if (target != null) {
|
||||
@@ -65,7 +66,7 @@ public class Draw2dCrosshairPipe
|
||||
y = offsetCrosshair.y;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
x /= (double) params.divisor.value;
|
||||
|
||||
@@ -50,23 +50,7 @@ public class FindPolygonPipe
|
||||
|
||||
private CVShape getShape(Contour in) {
|
||||
int corners = getCorners(in);
|
||||
|
||||
/*The contourShape enum has predefined shapes for Circles, Triangles, and Quads
|
||||
meaning any shape not fitting in those predefined shapes must be a custom shape.
|
||||
*/
|
||||
if (ContourShape.fromSides(corners) == null) {
|
||||
return new CVShape(in, ContourShape.Custom);
|
||||
}
|
||||
switch (ContourShape.fromSides(corners)) {
|
||||
case Circle:
|
||||
return new CVShape(in, ContourShape.Circle);
|
||||
case Triangle:
|
||||
return new CVShape(in, ContourShape.Triangle);
|
||||
case Quadrilateral:
|
||||
return new CVShape(in, ContourShape.Quadrilateral);
|
||||
}
|
||||
|
||||
return new CVShape(in, ContourShape.Custom);
|
||||
return new CVShape(in, ContourShape.fromSides(corners));
|
||||
}
|
||||
|
||||
private int getCorners(Contour contour) {
|
||||
|
||||
@@ -85,19 +85,29 @@ public class ArucoPipeline extends CVPipeline<CVPipelineResult, ArucoPipelineSet
|
||||
// 2023/other: best guess is 6in
|
||||
double tagWidth = Units.inchesToMeters(6);
|
||||
TargetModel tagModel = TargetModel.kAprilTag16h5;
|
||||
switch (settings.tagFamily) {
|
||||
case kTag36h11:
|
||||
// 2024 tag, 6.5in
|
||||
params.tagFamily = Objdetect.DICT_APRILTAG_36h11;
|
||||
tagWidth = Units.inchesToMeters(6.5);
|
||||
tagModel = TargetModel.kAprilTag36h11;
|
||||
break;
|
||||
case kTag25h9:
|
||||
params.tagFamily = Objdetect.DICT_APRILTAG_25h9;
|
||||
break;
|
||||
default:
|
||||
params.tagFamily = Objdetect.DICT_APRILTAG_16h5;
|
||||
}
|
||||
|
||||
params.tagFamily =
|
||||
switch (settings.tagFamily) {
|
||||
case kTag36h11 -> {
|
||||
// 2024 tag, 6.5in
|
||||
tagWidth = Units.inchesToMeters(6.5);
|
||||
tagModel = TargetModel.kAprilTag36h11;
|
||||
yield Objdetect.DICT_APRILTAG_36h11;
|
||||
}
|
||||
case kTag25h9 -> Objdetect.DICT_APRILTAG_25h9;
|
||||
// TODO: explicitly drop support for these
|
||||
case kTag16h5,
|
||||
kTagCircle21h7,
|
||||
kTagCircle49h12,
|
||||
kTagCustom48h11,
|
||||
kTagStandard41h12,
|
||||
kTagStandard52h13 -> {
|
||||
// 2024 tag, 6.5in
|
||||
tagWidth = Units.inchesToMeters(6.5);
|
||||
tagModel = TargetModel.kAprilTag36h11;
|
||||
yield Objdetect.DICT_APRILTAG_36h11;
|
||||
}
|
||||
};
|
||||
|
||||
int threshMinSize = Math.max(3, settings.threshWinSizes.getFirst());
|
||||
settings.threshWinSizes.setFirst(threshMinSize);
|
||||
|
||||
@@ -241,41 +241,38 @@ public class PipelineManager {
|
||||
* recreation after changing pipeline type
|
||||
*/
|
||||
private void recreateUserPipeline() {
|
||||
// Cleanup potential old native resources before swapping over from a user
|
||||
// pipeline
|
||||
// Cleanup potential old native resources before swapping over from a user pipeline
|
||||
if (currentUserPipeline != null && !(currentPipelineIndex < 0)) {
|
||||
currentUserPipeline.release();
|
||||
}
|
||||
|
||||
var desiredPipelineSettings = userPipelineSettings.get(currentPipelineIndex);
|
||||
switch (desiredPipelineSettings.pipelineType) {
|
||||
case Reflective:
|
||||
case Reflective -> {
|
||||
logger.debug("Creating Reflective pipeline");
|
||||
currentUserPipeline =
|
||||
new ReflectivePipeline((ReflectivePipelineSettings) desiredPipelineSettings);
|
||||
break;
|
||||
case ColoredShape:
|
||||
}
|
||||
case ColoredShape -> {
|
||||
logger.debug("Creating ColoredShape pipeline");
|
||||
currentUserPipeline =
|
||||
new ColoredShapePipeline((ColoredShapePipelineSettings) desiredPipelineSettings);
|
||||
break;
|
||||
case AprilTag:
|
||||
}
|
||||
case AprilTag -> {
|
||||
logger.debug("Creating AprilTag pipeline");
|
||||
currentUserPipeline =
|
||||
new AprilTagPipeline((AprilTagPipelineSettings) desiredPipelineSettings);
|
||||
break;
|
||||
|
||||
case Aruco:
|
||||
}
|
||||
case Aruco -> {
|
||||
logger.debug("Creating Aruco Pipeline");
|
||||
currentUserPipeline = new ArucoPipeline((ArucoPipelineSettings) desiredPipelineSettings);
|
||||
break;
|
||||
case ObjectDetection:
|
||||
}
|
||||
case ObjectDetection -> {
|
||||
logger.debug("Creating ObjectDetection Pipeline");
|
||||
currentUserPipeline =
|
||||
new ObjectDetectionPipeline((ObjectDetectionPipelineSettings) desiredPipelineSettings);
|
||||
default:
|
||||
// Can be calib3d or drivermode, both of which are special cases
|
||||
break;
|
||||
}
|
||||
case Calib3d, DriverMode -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,44 +339,40 @@ public class PipelineManager {
|
||||
}
|
||||
|
||||
private CVPipelineSettings createSettingsForType(PipelineType type, String nickname) {
|
||||
CVPipelineSettings newSettings;
|
||||
switch (type) {
|
||||
case Reflective:
|
||||
{
|
||||
var added = new ReflectivePipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
case ColoredShape:
|
||||
{
|
||||
var added = new ColoredShapePipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
case AprilTag:
|
||||
{
|
||||
var added = new AprilTagPipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
case Aruco:
|
||||
{
|
||||
var added = new ArucoPipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
case ObjectDetection:
|
||||
{
|
||||
var added = new ObjectDetectionPipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
default:
|
||||
{
|
||||
logger.error("Got invalid pipeline type: " + type);
|
||||
return null;
|
||||
}
|
||||
case Reflective -> {
|
||||
var added = new ReflectivePipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
case ColoredShape -> {
|
||||
var added = new ColoredShapePipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
case AprilTag -> {
|
||||
var added = new AprilTagPipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
case Aruco -> {
|
||||
var added = new ArucoPipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
case ObjectDetection -> {
|
||||
var added = new ObjectDetectionPipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
case Calib3d, DriverMode -> {
|
||||
logger.error("Got invalid pipeline type: " + type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// This can never happen, this is here to satisfy the compiler.
|
||||
throw new IllegalStateException("Got impossible pipeline type: " + type);
|
||||
}
|
||||
|
||||
private void addPipelineInternal(CVPipelineSettings settings) {
|
||||
|
||||
@@ -587,11 +587,9 @@ public class VisionModule {
|
||||
|
||||
// Pipelines like DriverMode and Calibrate3dPipeline have null output frames
|
||||
if (result.inputAndOutputFrame != null
|
||||
&& (pipelineManager.getCurrentPipelineSettings() instanceof AdvancedPipelineSettings)) {
|
||||
streamRunnable.updateData(
|
||||
result.inputAndOutputFrame,
|
||||
(AdvancedPipelineSettings) pipelineManager.getCurrentPipelineSettings(),
|
||||
result.targets);
|
||||
&& (pipelineManager.getCurrentPipelineSettings()
|
||||
instanceof AdvancedPipelineSettings settings)) {
|
||||
streamRunnable.updateData(result.inputAndOutputFrame, settings, result.targets);
|
||||
// The streamRunnable manages releasing in this case
|
||||
} else {
|
||||
consumeResults(result.inputAndOutputFrame, result.targets);
|
||||
@@ -615,9 +613,9 @@ public class VisionModule {
|
||||
}
|
||||
|
||||
public void setTargetModel(TargetModel targetModel) {
|
||||
var settings = pipelineManager.getCurrentPipeline().getSettings();
|
||||
if (settings instanceof ReflectivePipelineSettings) {
|
||||
((ReflectivePipelineSettings) settings).targetModel = targetModel;
|
||||
var pipelineSettings = pipelineManager.getCurrentPipeline().getSettings();
|
||||
if (pipelineSettings instanceof ReflectivePipelineSettings settings) {
|
||||
settings.targetModel = targetModel;
|
||||
saveAndBroadcastAll();
|
||||
} else {
|
||||
logger.error("Cannot set target model of non-reflective pipe! Ignoring...");
|
||||
|
||||
@@ -55,11 +55,8 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
|
||||
@Override
|
||||
public void onDataChangeEvent(DataChangeEvent<?> event) {
|
||||
if (event instanceof IncomingWebSocketEvent) {
|
||||
var wsEvent = (IncomingWebSocketEvent<?>) event;
|
||||
|
||||
// Camera index -1 means a "multicast event" (i.e. the event is received by all
|
||||
// cameras)
|
||||
if (event instanceof IncomingWebSocketEvent wsEvent) {
|
||||
// Camera index -1 means a "multicast event" (i.e. the event is received by all cameras)
|
||||
if (wsEvent.cameraIndex != null
|
||||
&& (wsEvent.cameraIndex == parentModule.moduleIndex || wsEvent.cameraIndex == -1)) {
|
||||
logger.trace("Got PSC event - propName: " + wsEvent.propertyName);
|
||||
@@ -93,120 +90,32 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
var currentSettings = change.getCurrentSettings();
|
||||
var originContext = change.getOriginContext();
|
||||
switch (propName) {
|
||||
case "pipelineName": // rename current pipeline
|
||||
logger.info("Changing nick to " + newPropValue);
|
||||
parentModule.pipelineManager.getCurrentPipelineSettings().pipelineNickname =
|
||||
(String) newPropValue;
|
||||
parentModule.saveAndBroadcastAll();
|
||||
continue;
|
||||
case "newPipelineInfo": // add new pipeline
|
||||
var typeName = (Pair<String, PipelineType>) newPropValue;
|
||||
var type = typeName.getRight();
|
||||
var name = typeName.getLeft();
|
||||
|
||||
logger.info("Adding a " + type + " pipeline with name " + name);
|
||||
|
||||
var addedSettings = parentModule.pipelineManager.addPipeline(type);
|
||||
addedSettings.pipelineNickname = name;
|
||||
parentModule.saveAndBroadcastAll();
|
||||
continue;
|
||||
case "deleteCurrPipeline":
|
||||
var indexToDelete = parentModule.pipelineManager.getRequestedIndex();
|
||||
logger.info("Deleting current pipe at index " + indexToDelete);
|
||||
int newIndex = parentModule.pipelineManager.removePipeline(indexToDelete);
|
||||
parentModule.setPipeline(newIndex);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
continue;
|
||||
case "changePipeline": // change active pipeline
|
||||
var index = (Integer) newPropValue;
|
||||
if (index == parentModule.pipelineManager.getRequestedIndex()) {
|
||||
logger.debug("Skipping pipeline change, index " + index + " already active");
|
||||
continue;
|
||||
case "pipelineName" -> newPipelineNickname((String) newPropValue);
|
||||
case "newPipelineInfo" -> newPipelineInfo((Pair<String, PipelineType>) newPropValue);
|
||||
case "deleteCurrPipeline" -> deleteCurrPipeline();
|
||||
case "changePipeline" -> changePipeline((Integer) newPropValue);
|
||||
case "startCalibration" -> startCalibration((Map<String, Object>) newPropValue);
|
||||
case "saveInputSnapshot" -> parentModule.saveInputSnapshot();
|
||||
case "saveOutputSnapshot" -> parentModule.saveOutputSnapshot();
|
||||
case "takeCalSnapshot" -> parentModule.takeCalibrationSnapshot();
|
||||
case "duplicatePipeline" -> duplicatePipeline((Integer) newPropValue);
|
||||
case "calibrationUploaded" -> {
|
||||
if (newPropValue instanceof CameraCalibrationCoefficients newCal) {
|
||||
parentModule.addCalibrationToConfig(newCal);
|
||||
} else {
|
||||
logger.warn("Received invalid calibration data");
|
||||
}
|
||||
parentModule.setPipeline(index);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
continue;
|
||||
case "startCalibration":
|
||||
try {
|
||||
var data =
|
||||
JacksonUtils.deserialize(
|
||||
(Map<String, Object>) newPropValue, UICalibrationData.class);
|
||||
parentModule.startCalibration(data);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
} catch (Exception e) {
|
||||
logger.error("Error deserailizing start-cal request", e);
|
||||
}
|
||||
case "robotOffsetPoint" -> {
|
||||
if (currentSettings instanceof AdvancedPipelineSettings curAdvSettings) {
|
||||
robotOffsetPoint(curAdvSettings, (Integer) newPropValue);
|
||||
}
|
||||
continue;
|
||||
case "saveInputSnapshot":
|
||||
parentModule.saveInputSnapshot();
|
||||
continue;
|
||||
case "saveOutputSnapshot":
|
||||
parentModule.saveOutputSnapshot();
|
||||
continue;
|
||||
case "takeCalSnapshot":
|
||||
parentModule.takeCalibrationSnapshot();
|
||||
continue;
|
||||
case "duplicatePipeline":
|
||||
int idx = parentModule.pipelineManager.duplicatePipeline((Integer) newPropValue);
|
||||
parentModule.setPipeline(idx);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
continue;
|
||||
case "calibrationUploaded":
|
||||
if (newPropValue instanceof CameraCalibrationCoefficients)
|
||||
parentModule.addCalibrationToConfig((CameraCalibrationCoefficients) newPropValue);
|
||||
continue;
|
||||
case "robotOffsetPoint":
|
||||
if (currentSettings instanceof AdvancedPipelineSettings) {
|
||||
var curAdvSettings = (AdvancedPipelineSettings) currentSettings;
|
||||
var offsetOperation = RobotOffsetPointOperation.fromIndex((int) newPropValue);
|
||||
var latestTarget = parentModule.lastPipelineResultBestTarget;
|
||||
|
||||
if (latestTarget != null) {
|
||||
var newPoint = latestTarget.getTargetOffsetPoint();
|
||||
|
||||
switch (curAdvSettings.offsetRobotOffsetMode) {
|
||||
case Single:
|
||||
if (offsetOperation == RobotOffsetPointOperation.ROPO_CLEAR) {
|
||||
curAdvSettings.offsetSinglePoint = new Point();
|
||||
} else if (offsetOperation == RobotOffsetPointOperation.ROPO_TAKESINGLE) {
|
||||
curAdvSettings.offsetSinglePoint = newPoint;
|
||||
}
|
||||
break;
|
||||
case Dual:
|
||||
if (offsetOperation == RobotOffsetPointOperation.ROPO_CLEAR) {
|
||||
curAdvSettings.offsetDualPointA = new Point();
|
||||
curAdvSettings.offsetDualPointAArea = 0;
|
||||
curAdvSettings.offsetDualPointB = new Point();
|
||||
curAdvSettings.offsetDualPointBArea = 0;
|
||||
} else {
|
||||
// update point and area
|
||||
switch (offsetOperation) {
|
||||
case ROPO_TAKEFIRSTDUAL:
|
||||
curAdvSettings.offsetDualPointA = newPoint;
|
||||
curAdvSettings.offsetDualPointAArea = latestTarget.getArea();
|
||||
break;
|
||||
case ROPO_TAKESECONDDUAL:
|
||||
curAdvSettings.offsetDualPointB = newPoint;
|
||||
curAdvSettings.offsetDualPointBArea = latestTarget.getArea();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
case "changePipelineType":
|
||||
}
|
||||
case "changePipelineType" -> {
|
||||
parentModule.changePipelineType((Integer) newPropValue);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
continue;
|
||||
case "isDriverMode":
|
||||
parentModule.setDriverMode((Boolean) newPropValue);
|
||||
continue;
|
||||
}
|
||||
case "isDriverMode" -> parentModule.setDriverMode((Boolean) newPropValue);
|
||||
}
|
||||
|
||||
// special case for camera settables
|
||||
@@ -249,6 +158,104 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
}
|
||||
}
|
||||
|
||||
public void newPipelineNickname(String newNickname) {
|
||||
logger.info("Changing pipeline nickname to " + newNickname);
|
||||
parentModule.pipelineManager.getCurrentPipelineSettings().pipelineNickname = newNickname;
|
||||
parentModule.saveAndBroadcastAll();
|
||||
}
|
||||
|
||||
public void newPipelineInfo(Pair<String, PipelineType> typeName) {
|
||||
var type = typeName.getRight();
|
||||
var name = typeName.getLeft();
|
||||
|
||||
logger.info("Adding a " + type + " pipeline with name " + name);
|
||||
|
||||
var addedSettings = parentModule.pipelineManager.addPipeline(type);
|
||||
addedSettings.pipelineNickname = name;
|
||||
parentModule.saveAndBroadcastAll();
|
||||
}
|
||||
|
||||
public void deleteCurrPipeline() {
|
||||
var indexToDelete = parentModule.pipelineManager.getRequestedIndex();
|
||||
logger.info("Deleting current pipe at index " + indexToDelete);
|
||||
int newIndex = parentModule.pipelineManager.removePipeline(indexToDelete);
|
||||
parentModule.setPipeline(newIndex);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
}
|
||||
|
||||
public void changePipeline(int index) {
|
||||
if (index == parentModule.pipelineManager.getRequestedIndex()) {
|
||||
logger.debug("Skipping pipeline change, index " + index + " already active");
|
||||
return;
|
||||
}
|
||||
parentModule.setPipeline(index);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
}
|
||||
|
||||
public void startCalibration(Map<String, Object> data) {
|
||||
try {
|
||||
var deserialized = JacksonUtils.deserialize(data, UICalibrationData.class);
|
||||
parentModule.startCalibration(deserialized);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
} catch (Exception e) {
|
||||
logger.error("Error deserailizing start-calibration request", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void duplicatePipeline(int index) {
|
||||
var newIndex = parentModule.pipelineManager.duplicatePipeline(index);
|
||||
parentModule.setPipeline(newIndex);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
}
|
||||
|
||||
public void robotOffsetPoint(AdvancedPipelineSettings curAdvSettings, int offsetIndex) {
|
||||
RobotOffsetPointOperation offsetOperation = RobotOffsetPointOperation.fromIndex(offsetIndex);
|
||||
|
||||
var latestTarget = parentModule.lastPipelineResultBestTarget;
|
||||
if (latestTarget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newPoint = latestTarget.getTargetOffsetPoint();
|
||||
switch (curAdvSettings.offsetRobotOffsetMode) {
|
||||
case Single -> {
|
||||
switch (offsetOperation) {
|
||||
case CLEAR -> curAdvSettings.offsetSinglePoint = new Point();
|
||||
case TAKE_SINGLE -> curAdvSettings.offsetSinglePoint = newPoint;
|
||||
case TAKE_FIRST_DUAL, TAKE_SECOND_DUAL -> {
|
||||
logger.warn("Dual point operation in single point mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
case Dual -> {
|
||||
switch (offsetOperation) {
|
||||
case CLEAR -> {
|
||||
curAdvSettings.offsetDualPointA = new Point();
|
||||
curAdvSettings.offsetDualPointAArea = 0;
|
||||
curAdvSettings.offsetDualPointB = new Point();
|
||||
curAdvSettings.offsetDualPointBArea = 0;
|
||||
}
|
||||
case TAKE_FIRST_DUAL -> {
|
||||
// update point and area
|
||||
curAdvSettings.offsetDualPointA = newPoint;
|
||||
curAdvSettings.offsetDualPointAArea = latestTarget.getArea();
|
||||
}
|
||||
case TAKE_SECOND_DUAL -> {
|
||||
// update point and area
|
||||
curAdvSettings.offsetDualPointB = newPoint;
|
||||
curAdvSettings.offsetDualPointBArea = latestTarget.getArea();
|
||||
}
|
||||
case TAKE_SINGLE -> {
|
||||
logger.warn("Single point operation in dual point mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
case None -> {
|
||||
logger.warn("Robot offset point operation requested, but no offset mode set");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a property in the given object using reflection. This method should not be
|
||||
* used generally and is only known to be correct in the context of `onDataChangeEvent`.
|
||||
@@ -281,8 +288,8 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
} else if (propType.equals(Integer.TYPE)) {
|
||||
propField.setInt(currentSettings, (Integer) newPropValue);
|
||||
} else if (propType.equals(Boolean.TYPE)) {
|
||||
if (newPropValue instanceof Integer) {
|
||||
propField.setBoolean(currentSettings, (Integer) newPropValue != 0);
|
||||
if (newPropValue instanceof Integer intValue) {
|
||||
propField.setBoolean(currentSettings, intValue != 0);
|
||||
} else {
|
||||
propField.setBoolean(currentSettings, (Boolean) newPropValue);
|
||||
}
|
||||
|
||||
@@ -84,8 +84,7 @@ public class VisionRunner {
|
||||
|
||||
frameSupplier.requestFrameThresholdType(wantedProcessType);
|
||||
var settings = pipeline.getSettings();
|
||||
if (settings instanceof AdvancedPipelineSettings) {
|
||||
var advanced = (AdvancedPipelineSettings) settings;
|
||||
if (settings instanceof AdvancedPipelineSettings advanced) {
|
||||
var hsvParams =
|
||||
new HSVPipe.HSVParams(
|
||||
advanced.hsvHue, advanced.hsvSaturation, advanced.hsvValue, advanced.hueInverted);
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
package org.photonvision.vision.target;
|
||||
|
||||
public enum RobotOffsetPointOperation {
|
||||
ROPO_CLEAR(0),
|
||||
ROPO_TAKESINGLE(1),
|
||||
ROPO_TAKEFIRSTDUAL(2),
|
||||
ROPO_TAKESECONDDUAL(3);
|
||||
CLEAR(0),
|
||||
TAKE_SINGLE(1),
|
||||
TAKE_FIRST_DUAL(2),
|
||||
TAKE_SECOND_DUAL(3);
|
||||
|
||||
public final int index;
|
||||
|
||||
@@ -29,17 +29,12 @@ public enum RobotOffsetPointOperation {
|
||||
}
|
||||
|
||||
public static RobotOffsetPointOperation fromIndex(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return ROPO_CLEAR;
|
||||
case 1:
|
||||
return ROPO_TAKESINGLE;
|
||||
case 2:
|
||||
return ROPO_TAKEFIRSTDUAL;
|
||||
case 3:
|
||||
return ROPO_TAKESECONDDUAL;
|
||||
default:
|
||||
return ROPO_CLEAR;
|
||||
}
|
||||
return switch (index) {
|
||||
case 0 -> CLEAR;
|
||||
case 1 -> TAKE_SINGLE;
|
||||
case 2 -> TAKE_FIRST_DUAL;
|
||||
case 3 -> TAKE_SECOND_DUAL;
|
||||
default -> CLEAR;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
|
||||
@@ -31,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
|
||||
@@ -80,7 +81,6 @@ class SimCameraProperties:
|
||||
newCamIntrinsics: np.ndarray,
|
||||
newDistCoeffs: np.ndarray,
|
||||
) -> None:
|
||||
|
||||
self.resWidth = width
|
||||
self.resHeight = height
|
||||
self.camIntrinsics = newCamIntrinsics
|
||||
@@ -173,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:
|
||||
@@ -355,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
|
||||
@@ -467,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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -298,17 +298,12 @@ public class PhotonCamera implements AutoCloseable {
|
||||
*/
|
||||
public VisionLEDMode getLEDMode() {
|
||||
int value = (int) ledModeState.get(-1);
|
||||
switch (value) {
|
||||
case 0:
|
||||
return VisionLEDMode.kOff;
|
||||
case 1:
|
||||
return VisionLEDMode.kOn;
|
||||
case 2:
|
||||
return VisionLEDMode.kBlink;
|
||||
case -1:
|
||||
default:
|
||||
return VisionLEDMode.kDefault;
|
||||
}
|
||||
return switch (value) {
|
||||
case 0 -> VisionLEDMode.kOff;
|
||||
case 1 -> VisionLEDMode.kOn;
|
||||
case 2 -> VisionLEDMode.kBlink;
|
||||
default -> VisionLEDMode.kDefault;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -345,46 +345,35 @@ public class PhotonPoseEstimator {
|
||||
PhotonPipelineResult cameraResult,
|
||||
Optional<Matrix<N3, N3>> cameraMatrix,
|
||||
Optional<Matrix<N8, N1>> distCoeffs,
|
||||
PoseStrategy strat) {
|
||||
Optional<EstimatedRobotPose> estimatedPose = Optional.empty();
|
||||
switch (strat) {
|
||||
case LOWEST_AMBIGUITY:
|
||||
estimatedPose = lowestAmbiguityStrategy(cameraResult);
|
||||
break;
|
||||
case CLOSEST_TO_CAMERA_HEIGHT:
|
||||
estimatedPose = closestToCameraHeightStrategy(cameraResult);
|
||||
break;
|
||||
case CLOSEST_TO_REFERENCE_POSE:
|
||||
estimatedPose = closestToReferencePoseStrategy(cameraResult, referencePose);
|
||||
break;
|
||||
case CLOSEST_TO_LAST_POSE:
|
||||
setReferencePose(lastPose);
|
||||
estimatedPose = closestToReferencePoseStrategy(cameraResult, referencePose);
|
||||
break;
|
||||
case AVERAGE_BEST_TARGETS:
|
||||
estimatedPose = averageBestTargetsStrategy(cameraResult);
|
||||
break;
|
||||
case MULTI_TAG_PNP_ON_RIO:
|
||||
if (cameraMatrix.isEmpty()) {
|
||||
DriverStation.reportWarning(
|
||||
"Camera matrix is empty for multi-tag-on-rio",
|
||||
Thread.currentThread().getStackTrace());
|
||||
} else if (distCoeffs.isEmpty()) {
|
||||
DriverStation.reportWarning(
|
||||
"Camera matrix is empty for multi-tag-on-rio",
|
||||
Thread.currentThread().getStackTrace());
|
||||
} else {
|
||||
estimatedPose = multiTagOnRioStrategy(cameraResult, cameraMatrix, distCoeffs);
|
||||
}
|
||||
break;
|
||||
case MULTI_TAG_PNP_ON_COPROCESSOR:
|
||||
estimatedPose = multiTagOnCoprocStrategy(cameraResult);
|
||||
break;
|
||||
default:
|
||||
DriverStation.reportError(
|
||||
"[PhotonPoseEstimator] Unknown Position Estimation Strategy!", false);
|
||||
return Optional.empty();
|
||||
}
|
||||
PoseStrategy strategy) {
|
||||
Optional<EstimatedRobotPose> estimatedPose =
|
||||
switch (strategy) {
|
||||
case LOWEST_AMBIGUITY -> lowestAmbiguityStrategy(cameraResult);
|
||||
case CLOSEST_TO_CAMERA_HEIGHT -> closestToCameraHeightStrategy(cameraResult);
|
||||
case CLOSEST_TO_REFERENCE_POSE ->
|
||||
closestToReferencePoseStrategy(cameraResult, referencePose);
|
||||
case CLOSEST_TO_LAST_POSE -> {
|
||||
setReferencePose(lastPose);
|
||||
yield closestToReferencePoseStrategy(cameraResult, referencePose);
|
||||
}
|
||||
case AVERAGE_BEST_TARGETS -> averageBestTargetsStrategy(cameraResult);
|
||||
case MULTI_TAG_PNP_ON_RIO -> {
|
||||
if (cameraMatrix.isEmpty()) {
|
||||
DriverStation.reportWarning(
|
||||
"Camera matrix is empty for multi-tag-on-rio",
|
||||
Thread.currentThread().getStackTrace());
|
||||
yield Optional.empty();
|
||||
} else if (distCoeffs.isEmpty()) {
|
||||
DriverStation.reportWarning(
|
||||
"Camera matrix is empty for multi-tag-on-rio",
|
||||
Thread.currentThread().getStackTrace());
|
||||
yield Optional.empty();
|
||||
} else {
|
||||
yield multiTagOnRioStrategy(cameraResult, cameraMatrix, distCoeffs);
|
||||
}
|
||||
}
|
||||
case MULTI_TAG_PNP_ON_COPROCESSOR -> multiTagOnCoprocStrategy(cameraResult);
|
||||
};
|
||||
|
||||
if (estimatedPose.isPresent()) {
|
||||
lastPose = estimatedPose.get().estimatedPose;
|
||||
|
||||
@@ -84,11 +84,9 @@ public class VisionTargetSim {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj instanceof VisionTargetSim) {
|
||||
var o = (VisionTargetSim) obj;
|
||||
return pose.equals(o.pose) && model.equals(o.model);
|
||||
}
|
||||
return false;
|
||||
return this == obj
|
||||
&& obj instanceof VisionTargetSim o
|
||||
&& pose.equals(o.pose)
|
||||
&& model.equals(o.model);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id "application"
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
id 'com.gradleup.shadow' version '8.3.4'
|
||||
id "com.github.node-gradle.node" version "7.0.1"
|
||||
id "org.hidetake.ssh" version "2.11.2"
|
||||
id 'edu.wpi.first.WpilibTools' version '1.3.0'
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -99,10 +99,9 @@ public class DataSocketHandler {
|
||||
objectMapper.readValue(context.data(), new TypeReference<>() {});
|
||||
|
||||
// Special case the current camera index
|
||||
var camIndexValue = deserializedData.get("cameraIndex");
|
||||
Integer cameraIndex = null;
|
||||
if (camIndexValue instanceof Integer) {
|
||||
cameraIndex = (Integer) camIndexValue;
|
||||
if (deserializedData.get("cameraIndex") instanceof Integer camIndexValue) {
|
||||
cameraIndex = camIndexValue;
|
||||
deserializedData.remove("cameraIndex");
|
||||
}
|
||||
|
||||
@@ -128,216 +127,182 @@ public class DataSocketHandler {
|
||||
}
|
||||
|
||||
switch (socketMessageType) {
|
||||
case SMT_DRIVERMODE:
|
||||
{
|
||||
// TODO: what is this event?
|
||||
var data = (Boolean) entryValue;
|
||||
var dmIsDriverEvent =
|
||||
new IncomingWebSocketEvent<Boolean>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"isDriverMode",
|
||||
data,
|
||||
cameraIndex,
|
||||
context);
|
||||
case SMT_DRIVERMODE -> {
|
||||
// TODO: what is this event?
|
||||
var data = (Boolean) entryValue;
|
||||
var dmIsDriverEvent =
|
||||
new IncomingWebSocketEvent<Boolean>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"isDriverMode",
|
||||
data,
|
||||
cameraIndex,
|
||||
context);
|
||||
|
||||
dcService.publishEvents(dmIsDriverEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_CHANGECAMERANAME:
|
||||
{
|
||||
var ccnEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"cameraNickname",
|
||||
(String) entryValue,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(ccnEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_CHANGEPIPELINENAME:
|
||||
{
|
||||
var cpnEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"pipelineName",
|
||||
(String) entryValue,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(cpnEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_ADDNEWPIPELINE:
|
||||
{
|
||||
// HashMap<String, Object> data = (HashMap<String,
|
||||
// Object>) entryValue;
|
||||
// var type = (PipelineType)
|
||||
// data.get("pipelineType");
|
||||
// var name = (String) data.get("pipelineName");
|
||||
var arr = (ArrayList<Object>) entryValue;
|
||||
var name = (String) arr.get(0);
|
||||
var type = PipelineType.values()[(Integer) arr.get(1) + 2];
|
||||
dcService.publishEvents(dmIsDriverEvent);
|
||||
}
|
||||
case SMT_CHANGECAMERANAME -> {
|
||||
var ccnEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"cameraNickname",
|
||||
(String) entryValue,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(ccnEvent);
|
||||
}
|
||||
case SMT_CHANGEPIPELINENAME -> {
|
||||
var cpnEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"pipelineName",
|
||||
(String) entryValue,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(cpnEvent);
|
||||
}
|
||||
case SMT_ADDNEWPIPELINE -> {
|
||||
// HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
|
||||
// var type = (PipelineType) data.get("pipelineType");
|
||||
// var name = (String) data.get("pipelineName");
|
||||
var arr = (ArrayList<Object>) entryValue;
|
||||
var name = (String) arr.get(0);
|
||||
var type = PipelineType.values()[(Integer) arr.get(1) + 2];
|
||||
|
||||
var newPipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"newPipelineInfo",
|
||||
Pair.of(name, type),
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(newPipelineEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_CHANGEBRIGHTNESS:
|
||||
{
|
||||
HardwareManager.getInstance()
|
||||
.setBrightnessPercent(Integer.parseInt(entryValue.toString()));
|
||||
break;
|
||||
}
|
||||
case SMT_DUPLICATEPIPELINE:
|
||||
{
|
||||
var pipeIndex = (Integer) entryValue;
|
||||
var newPipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"newPipelineInfo",
|
||||
Pair.of(name, type),
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(newPipelineEvent);
|
||||
}
|
||||
case SMT_CHANGEBRIGHTNESS -> {
|
||||
HardwareManager.getInstance()
|
||||
.setBrightnessPercent(Integer.parseInt(entryValue.toString()));
|
||||
}
|
||||
case SMT_DUPLICATEPIPELINE -> {
|
||||
var pipeIndex = (Integer) entryValue;
|
||||
|
||||
logger.info("Duplicating pipe@index" + pipeIndex + " for camera " + cameraIndex);
|
||||
logger.info("Duplicating pipe@index" + pipeIndex + " for camera " + cameraIndex);
|
||||
|
||||
var newPipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"duplicatePipeline",
|
||||
pipeIndex,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(newPipelineEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_DELETECURRENTPIPELINE:
|
||||
{
|
||||
var deleteCurrentPipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"deleteCurrPipeline",
|
||||
0,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(deleteCurrentPipelineEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_ROBOTOFFSETPOINT:
|
||||
{
|
||||
var robotOffsetPointEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"robotOffsetPoint",
|
||||
(Integer) entryValue,
|
||||
cameraIndex,
|
||||
null);
|
||||
dcService.publishEvent(robotOffsetPointEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_CURRENTCAMERA:
|
||||
{
|
||||
var changeCurrentCameraEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_OTHER, "changeUICamera", (Integer) entryValue);
|
||||
dcService.publishEvent(changeCurrentCameraEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_CURRENTPIPELINE:
|
||||
{
|
||||
var changePipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"changePipeline",
|
||||
(Integer) entryValue,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(changePipelineEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_STARTPNPCALIBRATION:
|
||||
{
|
||||
var changePipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"startCalibration",
|
||||
(Map) entryValue,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(changePipelineEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_SAVEINPUTSNAPSHOT:
|
||||
{
|
||||
var takeInputSnapshotEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"saveInputSnapshot",
|
||||
0,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(takeInputSnapshotEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_SAVEOUTPUTSNAPSHOT:
|
||||
{
|
||||
var takeOutputSnapshotEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"saveOutputSnapshot",
|
||||
0,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(takeOutputSnapshotEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_TAKECALIBRATIONSNAPSHOT:
|
||||
{
|
||||
var takeCalSnapshotEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"takeCalSnapshot",
|
||||
0,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(takeCalSnapshotEvent);
|
||||
break;
|
||||
}
|
||||
case SMT_PIPELINESETTINGCHANGE:
|
||||
{
|
||||
HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
|
||||
var newPipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"duplicatePipeline",
|
||||
pipeIndex,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(newPipelineEvent);
|
||||
}
|
||||
case SMT_DELETECURRENTPIPELINE -> {
|
||||
var deleteCurrentPipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"deleteCurrPipeline",
|
||||
0,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(deleteCurrentPipelineEvent);
|
||||
}
|
||||
case SMT_ROBOTOFFSETPOINT -> {
|
||||
var robotOffsetPointEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"robotOffsetPoint",
|
||||
(Integer) entryValue,
|
||||
cameraIndex,
|
||||
null);
|
||||
dcService.publishEvent(robotOffsetPointEvent);
|
||||
}
|
||||
case SMT_CURRENTCAMERA -> {
|
||||
var changeCurrentCameraEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_OTHER, "changeUICamera", (Integer) entryValue);
|
||||
dcService.publishEvent(changeCurrentCameraEvent);
|
||||
}
|
||||
case SMT_CURRENTPIPELINE -> {
|
||||
var changePipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"changePipeline",
|
||||
(Integer) entryValue,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(changePipelineEvent);
|
||||
}
|
||||
case SMT_STARTPNPCALIBRATION -> {
|
||||
var changePipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"startCalibration",
|
||||
(Map) entryValue,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(changePipelineEvent);
|
||||
}
|
||||
case SMT_SAVEINPUTSNAPSHOT -> {
|
||||
var takeInputSnapshotEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"saveInputSnapshot",
|
||||
0,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(takeInputSnapshotEvent);
|
||||
}
|
||||
case SMT_SAVEOUTPUTSNAPSHOT -> {
|
||||
var takeOutputSnapshotEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"saveOutputSnapshot",
|
||||
0,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(takeOutputSnapshotEvent);
|
||||
}
|
||||
case SMT_TAKECALIBRATIONSNAPSHOT -> {
|
||||
var takeCalSnapshotEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"takeCalSnapshot",
|
||||
0,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(takeCalSnapshotEvent);
|
||||
}
|
||||
case SMT_PIPELINESETTINGCHANGE -> {
|
||||
HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
|
||||
|
||||
if (data.size() >= 2) {
|
||||
var cameraIndex2 = (int) data.get("cameraIndex");
|
||||
for (var dataEntry : data.entrySet()) {
|
||||
if (dataEntry.getKey().equals("cameraIndex")) {
|
||||
continue;
|
||||
}
|
||||
var pipelineSettingChangeEvent =
|
||||
new IncomingWebSocketEvent(
|
||||
DataChangeDestination.DCD_ACTIVEPIPELINESETTINGS,
|
||||
dataEntry.getKey(),
|
||||
dataEntry.getValue(),
|
||||
cameraIndex2,
|
||||
context);
|
||||
dcService.publishEvent(pipelineSettingChangeEvent);
|
||||
if (data.size() >= 2) {
|
||||
var cameraIndex2 = (int) data.get("cameraIndex");
|
||||
for (var dataEntry : data.entrySet()) {
|
||||
if (dataEntry.getKey().equals("cameraIndex")) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
logger.warn("Unknown message for PSC: " + data.keySet().iterator().next());
|
||||
var pipelineSettingChangeEvent =
|
||||
new IncomingWebSocketEvent(
|
||||
DataChangeDestination.DCD_ACTIVEPIPELINESETTINGS,
|
||||
dataEntry.getKey(),
|
||||
dataEntry.getValue(),
|
||||
cameraIndex2,
|
||||
context);
|
||||
dcService.publishEvent(pipelineSettingChangeEvent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SMT_CHANGEPIPELINETYPE:
|
||||
{
|
||||
var changePipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"changePipelineType",
|
||||
(Integer) entryValue,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(changePipelineEvent);
|
||||
break;
|
||||
} else {
|
||||
logger.warn("Unknown message for PSC: " + data.keySet().iterator().next());
|
||||
}
|
||||
}
|
||||
case SMT_CHANGEPIPELINETYPE -> {
|
||||
var changePipelineEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"changePipelineType",
|
||||
(Integer) entryValue,
|
||||
cameraIndex,
|
||||
context);
|
||||
dcService.publishEvent(changePipelineEvent);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to parse message!", e);
|
||||
|
||||
@@ -39,8 +39,7 @@ public class UIInboundSubscriber extends DataChangeSubscriber {
|
||||
|
||||
@Override
|
||||
public void onDataChangeEvent(DataChangeEvent<?> event) {
|
||||
if (event instanceof IncomingWebSocketEvent) {
|
||||
var incomingWSEvent = (IncomingWebSocketEvent<?>) event;
|
||||
if (event instanceof IncomingWebSocketEvent incomingWSEvent) {
|
||||
if (incomingWSEvent.propertyName.equals("userConnected")
|
||||
|| incomingWSEvent.propertyName.equals("sendFullSettings")) {
|
||||
// Send full settings
|
||||
|
||||
@@ -44,11 +44,9 @@ class UIOutboundSubscriber extends DataChangeSubscriber {
|
||||
|
||||
@Override
|
||||
public void onDataChangeEvent(DataChangeEvent event) {
|
||||
if (event instanceof OutgoingUIEvent) {
|
||||
var thisEvent = (OutgoingUIEvent) event;
|
||||
if (event instanceof OutgoingUIEvent thisEvent) {
|
||||
try {
|
||||
if (event.data instanceof HashMap) {
|
||||
var data = (HashMap) event.data;
|
||||
if (event.data instanceof HashMap data) {
|
||||
socketHandler.broadcastMessage(data, thisEvent.originContext);
|
||||
} else {
|
||||
socketHandler.broadcastMessage(event.data, thisEvent.originContext);
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
package org.photonvision.common.hardware;
|
||||
|
||||
import edu.wpi.first.util.RuntimeDetector;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
@@ -148,6 +148,11 @@ public enum Platform {
|
||||
return currentPlatform.isSupported;
|
||||
}
|
||||
|
||||
public static boolean isAthena() {
|
||||
File runRobotFile = new File("/usr/local/frc/bin/frcRunRobot.sh");
|
||||
return runRobotFile.exists();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
// Debug info related to unknown platforms for debug help
|
||||
@@ -158,10 +163,24 @@ public enum Platform {
|
||||
private static final String UnknownDeviceModelString = "Unknown";
|
||||
|
||||
public static Platform getCurrentPlatform() {
|
||||
if (RuntimeDetector.isWindows()) {
|
||||
if (RuntimeDetector.is32BitIntel()) {
|
||||
String OS_NAME;
|
||||
if (Platform.OS_NAME != null) {
|
||||
OS_NAME = Platform.OS_NAME;
|
||||
} else {
|
||||
OS_NAME = System.getProperty("os.name");
|
||||
}
|
||||
|
||||
String OS_ARCH;
|
||||
if (Platform.OS_ARCH != null) {
|
||||
OS_ARCH = Platform.OS_ARCH;
|
||||
} else {
|
||||
OS_ARCH = System.getProperty("os.arch");
|
||||
}
|
||||
|
||||
if (OS_NAME.startsWith("Windows")) {
|
||||
if (OS_ARCH.equals("x86") || OS_ARCH.equals("i386")) {
|
||||
return WINDOWS_32;
|
||||
} else if (RuntimeDetector.is64BitIntel()) {
|
||||
} else if (OS_ARCH.equals("amd64") || OS_ARCH.equals("x86_64")) {
|
||||
return WINDOWS_64;
|
||||
} else {
|
||||
// please don't try this
|
||||
@@ -169,41 +188,41 @@ public enum Platform {
|
||||
}
|
||||
}
|
||||
|
||||
if (RuntimeDetector.isMac()) {
|
||||
if (OS_NAME.startsWith("Mac")) {
|
||||
// TODO - once we have real support, this might have to be more granular
|
||||
return MACOS;
|
||||
}
|
||||
|
||||
if (RuntimeDetector.isLinux()) {
|
||||
if (OS_NAME.startsWith("Linux")) {
|
||||
if (isPiSBC()) {
|
||||
if (RuntimeDetector.isArm32()) {
|
||||
if (OS_ARCH.equals("arm") || OS_ARCH.equals("arm32")) {
|
||||
return LINUX_RASPBIAN32;
|
||||
} else if (RuntimeDetector.isArm64()) {
|
||||
} else if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) {
|
||||
return LINUX_RASPBIAN64;
|
||||
} else {
|
||||
// Unknown/exotic installation
|
||||
return UNKNOWN;
|
||||
}
|
||||
} else if (isJetsonSBC()) {
|
||||
if (RuntimeDetector.isArm64()) {
|
||||
if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) {
|
||||
// TODO - do we need to check OS version?
|
||||
return LINUX_AARCH64;
|
||||
} else {
|
||||
// Unknown/exotic installation
|
||||
return UNKNOWN;
|
||||
}
|
||||
} else if (RuntimeDetector.is64BitIntel()) {
|
||||
} else if (OS_ARCH.equals("amd64") || OS_ARCH.equals("x86_64")) {
|
||||
return LINUX_64;
|
||||
} else if (RuntimeDetector.is32BitIntel()) {
|
||||
} else if (OS_ARCH.equals("x86") || OS_ARCH.equals("i386")) {
|
||||
return LINUX_32;
|
||||
} else if (RuntimeDetector.isArm64()) {
|
||||
} else if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) {
|
||||
// TODO - os detection needed?
|
||||
if (isOrangePi()) {
|
||||
return LINUX_RK3588_64;
|
||||
} else {
|
||||
return LINUX_AARCH64;
|
||||
}
|
||||
} else if (RuntimeDetector.isArm32()) {
|
||||
} else if (OS_ARCH.equals("arm") || OS_ARCH.equals("arm32")) {
|
||||
return LINUX_ARM32;
|
||||
} else {
|
||||
// Unknown or otherwise unsupported platform
|
||||
|
||||
@@ -31,16 +31,11 @@ public enum VisionLEDMode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (this) {
|
||||
case kDefault:
|
||||
return "Default";
|
||||
case kOff:
|
||||
return "Off";
|
||||
case kOn:
|
||||
return "On";
|
||||
case kBlink:
|
||||
return "Blink";
|
||||
}
|
||||
return "";
|
||||
return switch (this) {
|
||||
case kDefault -> "Default";
|
||||
case kOff -> "Off";
|
||||
case kOn -> "On";
|
||||
case kBlink -> "Blink";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,11 +175,10 @@ public class TargetModel {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj instanceof TargetModel) {
|
||||
var o = (TargetModel) obj;
|
||||
return vertices.equals(o.vertices) && isPlanar == o.isPlanar && isSpherical == o.isSpherical;
|
||||
}
|
||||
return false;
|
||||
return this == obj
|
||||
&& obj instanceof TargetModel o
|
||||
&& vertices.equals(o.vertices)
|
||||
&& isPlanar == o.isPlanar
|
||||
&& isSpherical == o.isSpherical;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -11,8 +11,8 @@ repositories {
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useDevelopment = false
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
|
||||
|
||||
// Define my targets (RoboRIO) and artifacts (deployable files)
|
||||
// This is added by GradleRIO's backing project DeployUtils.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -11,8 +11,8 @@ repositories {
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useDevelopment = false
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
|
||||
|
||||
// Define my targets (RoboRIO) and artifacts (deployable files)
|
||||
// This is added by GradleRIO's backing project DeployUtils.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -11,8 +11,8 @@ repositories {
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useDevelopment = false
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
|
||||
|
||||
// Define my targets (RoboRIO) and artifacts (deployable files)
|
||||
// This is added by GradleRIO's backing project DeployUtils.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
|
||||
}
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
def ROBOT_MAIN_CLASS = "frc.robot.Main"
|
||||
|
||||
@@ -13,8 +13,8 @@ repositories {
|
||||
}
|
||||
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
|
||||
|
||||
|
||||
// Define my targets (RoboRIO) and artifacts (deployable files)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
|
||||
}
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
def ROBOT_MAIN_CLASS = "frc.robot.Main"
|
||||
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
|
||||
|
||||
|
||||
// Define my targets (RoboRIO) and artifacts (deployable files)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -26,14 +26,35 @@ package frc.robot;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import edu.wpi.first.math.geometry.Pose3d;
|
||||
import edu.wpi.first.math.geometry.Rotation2d;
|
||||
import java.util.List;
|
||||
import org.junit.Test;
|
||||
import org.photonvision.PhotonCamera;
|
||||
import org.photonvision.simulation.PhotonCameraSim;
|
||||
import org.photonvision.simulation.SimCameraProperties;
|
||||
import org.photonvision.timesync.TimeSyncSingleton;
|
||||
|
||||
public class JniLoadTest {
|
||||
@Test
|
||||
public void smoketest() {
|
||||
public void smoketestTimeSync() {
|
||||
if (!TimeSyncSingleton.load()) {
|
||||
fail("Could not load TimeSync JNI????????");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void smoketestPhotonCameraSim() {
|
||||
// This will trigger a force load of OpenCV internally - good to smoketest
|
||||
var camera = new PhotonCamera("Hellowo");
|
||||
var cameraProp = new SimCameraProperties();
|
||||
cameraProp.setCalibration(320, 240, Rotation2d.fromDegrees(90));
|
||||
|
||||
var sim = new PhotonCameraSim(camera, cameraProp);
|
||||
sim.enableDrawWireframe(true);
|
||||
sim.enableProcessedStream(true);
|
||||
sim.enableRawStream(true);
|
||||
|
||||
sim.process(0, new Pose3d(), List.of());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-3"
|
||||
}
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
def ROBOT_MAIN_CLASS = "frc.robot.Main"
|
||||
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-2"
|
||||
wpi.versions.wpilibVersion = "2025.1.1-beta-3"
|
||||
wpi.versions.wpimathVersion = "2025.1.1-beta-3"
|
||||
|
||||
|
||||
// Define my targets (RoboRIO) and artifacts (deployable files)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -4,8 +4,8 @@ apply plugin: 'jacoco'
|
||||
apply plugin: 'com.google.protobuf'
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
def baseArtifactId = nativeName
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user