mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-03 03:01:40 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
428f926ac2 | ||
|
|
4efeb3d412 | ||
|
|
6a2d83e19b | ||
|
|
1c0d92641f | ||
|
|
9653c46bdb | ||
|
|
3738e7821b | ||
|
|
0eb0a4e3c5 | ||
|
|
7666f152bb | ||
|
|
45a39f6609 |
22
.github/workflows/documentation.yml
vendored
22
.github/workflows/documentation.yml
vendored
@@ -68,10 +68,6 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
needs: [build-client, run_docs]
|
needs: [build-client, run_docs]
|
||||||
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
@@ -79,14 +75,12 @@ jobs:
|
|||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- run: find .
|
- run: find .
|
||||||
|
- name: copy file via ssh password
|
||||||
- name: Setup Pages
|
uses: appleboy/scp-action@v0.1.7
|
||||||
uses: actions/configure-pages@v4
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
with:
|
||||||
# Upload entire repository
|
host: ${{ secrets.WEBMASTER_SSH_HOST }}
|
||||||
path: '.'
|
username: ${{ secrets.WEBMASTER_SSH_USERNAME }}
|
||||||
- name: Deploy to GitHub Pages
|
password: ${{ secrets.WEBMASTER_SSH_KEY }}
|
||||||
id: deployment
|
port: ${{ secrets.WEBMASTER_SSH_PORT }}
|
||||||
uses: actions/deploy-pages@v4
|
source: "*"
|
||||||
|
target: /var/www/html/photonvision-docs/
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
|
|||||||
const calib = useCameraSettingsStore().getCalibrationCoeffs(format.resolution);
|
const calib = useCameraSettingsStore().getCalibrationCoeffs(format.resolution);
|
||||||
if (calib !== undefined) {
|
if (calib !== undefined) {
|
||||||
// For each error, square it, sum the squares, and divide by total points N
|
// For each error, square it, sum the squares, and divide by total points N
|
||||||
format.mean = calib.meanErrors.reduce((a, b) => a + b) / calib.meanErrors.length;
|
if (calib.meanErrors.length)
|
||||||
|
format.mean = calib.meanErrors.reduce((a, b) => a + b, 0) / calib.meanErrors.length;
|
||||||
|
else format.mean = NaN;
|
||||||
|
|
||||||
format.horizontalFOV =
|
format.horizontalFOV =
|
||||||
2 * Math.atan2(format.resolution.width / 2, calib.cameraIntrinsics.data[0]) * (180 / Math.PI);
|
2 * Math.atan2(format.resolution.width / 2, calib.cameraIntrinsics.data[0]) * (180 / Math.PI);
|
||||||
@@ -102,7 +104,7 @@ const downloadCalibBoard = () => {
|
|||||||
const yPos = chessboardStartY + squareY * squareSizeIn.value;
|
const yPos = chessboardStartY + squareY * squareSizeIn.value;
|
||||||
|
|
||||||
// Only draw the odd squares to create the chessboard pattern
|
// Only draw the odd squares to create the chessboard pattern
|
||||||
if ((xPos + yPos + 0.25) % 2 === 0) {
|
if (squareY % 2 != squareX % 2) {
|
||||||
doc.rect(xPos, yPos, squareSizeIn.value, squareSizeIn.value, "F");
|
doc.rect(xPos, yPos, squareSizeIn.value, squareSizeIn.value, "F");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +258,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
|||||||
>
|
>
|
||||||
<td>{{ getResolutionString(value.resolution) }}</td>
|
<td>{{ getResolutionString(value.resolution) }}</td>
|
||||||
<td>
|
<td>
|
||||||
{{ value.mean !== undefined ? (isNaN(value.mean) ? "NaN" : value.mean.toFixed(2) + "px") : "-" }}
|
{{ value.mean !== undefined ? (isNaN(value.mean) ? "Unknown" : value.mean.toFixed(2) + "px") : "-" }}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ value.horizontalFOV !== undefined ? value.horizontalFOV.toFixed(2) + "°" : "-" }}</td>
|
<td>{{ value.horizontalFOV !== undefined ? value.horizontalFOV.toFixed(2) + "°" : "-" }}</td>
|
||||||
<td>{{ value.verticalFOV !== undefined ? value.verticalFOV.toFixed(2) + "°" : "-" }}</td>
|
<td>{{ value.verticalFOV !== undefined ? value.verticalFOV.toFixed(2) + "°" : "-" }}</td>
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ const settingsHaveChanged = (): boolean => {
|
|||||||
a.shouldPublishProto !== b.shouldPublishProto ||
|
a.shouldPublishProto !== b.shouldPublishProto ||
|
||||||
a.networkManagerIface !== b.networkManagerIface ||
|
a.networkManagerIface !== b.networkManagerIface ||
|
||||||
a.setStaticCommand !== b.setStaticCommand ||
|
a.setStaticCommand !== b.setStaticCommand ||
|
||||||
a.setDHCPcommand !== b.setDHCPcommand
|
a.setDHCPcommand !== b.setDHCPcommand ||
|
||||||
|
a.matchCamerasOnlyByPath !== b.matchCamerasOnlyByPath
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,6 +78,7 @@ const saveGeneralSettings = () => {
|
|||||||
setStaticCommand: tempSettingsStruct.value.setStaticCommand || "",
|
setStaticCommand: tempSettingsStruct.value.setStaticCommand || "",
|
||||||
shouldManage: tempSettingsStruct.value.shouldManage,
|
shouldManage: tempSettingsStruct.value.shouldManage,
|
||||||
shouldPublishProto: tempSettingsStruct.value.shouldPublishProto,
|
shouldPublishProto: tempSettingsStruct.value.shouldPublishProto,
|
||||||
|
matchCamerasOnlyByPath: tempSettingsStruct.value.matchCamerasOnlyByPath,
|
||||||
staticIp: tempSettingsStruct.value.staticIp
|
staticIp: tempSettingsStruct.value.staticIp
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -137,6 +139,8 @@ watchEffect(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-card dark class="mb-3 pr-6 pb-3" style="background-color: #006492">
|
<v-card dark class="mb-3 pr-6 pb-3" style="background-color: #006492">
|
||||||
|
<v-card-title>Global Settings</v-card-title>
|
||||||
|
<v-divider />
|
||||||
<v-card-title>Networking</v-card-title>
|
<v-card-title>Networking</v-card-title>
|
||||||
<div class="ml-5">
|
<div class="ml-5">
|
||||||
<v-form ref="form" v-model="settingsValid">
|
<v-form ref="form" v-model="settingsValid">
|
||||||
@@ -254,6 +258,9 @@ watchEffect(() => {
|
|||||||
>
|
>
|
||||||
This mode is intended for debugging; it should be off for proper usage. PhotonLib will NOT work!
|
This mode is intended for debugging; it should be off for proper usage. PhotonLib will NOT work!
|
||||||
</v-banner>
|
</v-banner>
|
||||||
|
|
||||||
|
<v-divider />
|
||||||
|
<v-card-title>Miscellaneous</v-card-title>
|
||||||
<pv-switch
|
<pv-switch
|
||||||
v-model="tempSettingsStruct.shouldPublishProto"
|
v-model="tempSettingsStruct.shouldPublishProto"
|
||||||
label="Also Publish Protobuf"
|
label="Also Publish Protobuf"
|
||||||
@@ -272,6 +279,14 @@ watchEffect(() => {
|
|||||||
This mode is intended for debugging; it should be off for field use. You may notice a performance hit by using
|
This mode is intended for debugging; it should be off for field use. You may notice a performance hit by using
|
||||||
this mode.
|
this mode.
|
||||||
</v-banner>
|
</v-banner>
|
||||||
|
<pv-switch
|
||||||
|
v-model="tempSettingsStruct.matchCamerasOnlyByPath"
|
||||||
|
label="Match cameras by-path ONLY"
|
||||||
|
tooltip="ONLY match cameras by the USB port they're plugged into + (basename or USB VID/PID), and never only by the device product string"
|
||||||
|
class="mt-3 mb-2"
|
||||||
|
:label-cols="4"
|
||||||
|
/>
|
||||||
|
<v-divider class="mb-3" />
|
||||||
</v-form>
|
</v-form>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="accent"
|
color="accent"
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ export const useSettingsStore = defineStore("settings", {
|
|||||||
connName: "Example Wired Connection",
|
connName: "Example Wired Connection",
|
||||||
devName: "eth0"
|
devName: "eth0"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
networkingDisabled: false,
|
||||||
|
matchCamerasOnlyByPath: false
|
||||||
},
|
},
|
||||||
lighting: {
|
lighting: {
|
||||||
supported: true,
|
supported: true,
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export interface NetworkSettings {
|
|||||||
setDHCPcommand?: string;
|
setDHCPcommand?: string;
|
||||||
networkInterfaceNames: NetworkInterfaceType[];
|
networkInterfaceNames: NetworkInterfaceType[];
|
||||||
networkingDisabled: boolean;
|
networkingDisabled: boolean;
|
||||||
|
matchCamerasOnlyByPath: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConfigurableNetworkSettings = Omit<
|
export type ConfigurableNetworkSettings = Omit<
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||||
@@ -51,6 +52,12 @@ public class CameraConfiguration {
|
|||||||
|
|
||||||
@JsonIgnore public String[] otherPaths = {};
|
@JsonIgnore public String[] otherPaths = {};
|
||||||
|
|
||||||
|
@JsonProperty("usbVID")
|
||||||
|
public int usbVID = -1;
|
||||||
|
|
||||||
|
@JsonProperty("usbPID")
|
||||||
|
public int usbPID = -1;
|
||||||
|
|
||||||
public CameraType cameraType = CameraType.UsbCamera;
|
public CameraType cameraType = CameraType.UsbCamera;
|
||||||
public double FOV = 70;
|
public double FOV = 70;
|
||||||
public final List<CameraCalibrationCoefficients> calibrations;
|
public final List<CameraCalibrationCoefficients> calibrations;
|
||||||
@@ -98,7 +105,9 @@ public class CameraConfiguration {
|
|||||||
@JsonProperty("cameraType") CameraType cameraType,
|
@JsonProperty("cameraType") CameraType cameraType,
|
||||||
@JsonProperty("cameraQuirks") QuirkyCamera cameraQuirks,
|
@JsonProperty("cameraQuirks") QuirkyCamera cameraQuirks,
|
||||||
@JsonProperty("calibration") List<CameraCalibrationCoefficients> calibrations,
|
@JsonProperty("calibration") List<CameraCalibrationCoefficients> calibrations,
|
||||||
@JsonProperty("currentPipelineIndex") int currentPipelineIndex) {
|
@JsonProperty("currentPipelineIndex") int currentPipelineIndex,
|
||||||
|
@JsonProperty("usbVID") int usbVID,
|
||||||
|
@JsonProperty("usbPID") int usbPID) {
|
||||||
this.baseName = baseName;
|
this.baseName = baseName;
|
||||||
this.uniqueName = uniqueName;
|
this.uniqueName = uniqueName;
|
||||||
this.nickname = nickname;
|
this.nickname = nickname;
|
||||||
@@ -108,6 +117,8 @@ public class CameraConfiguration {
|
|||||||
this.cameraQuirks = cameraQuirks;
|
this.cameraQuirks = cameraQuirks;
|
||||||
this.calibrations = calibrations != null ? calibrations : new ArrayList<>();
|
this.calibrations = calibrations != null ? calibrations : new ArrayList<>();
|
||||||
this.currentPipelineIndex = currentPipelineIndex;
|
this.currentPipelineIndex = currentPipelineIndex;
|
||||||
|
this.usbPID = usbPID;
|
||||||
|
this.usbVID = usbVID;
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Creating camera configuration for "
|
"Creating camera configuration for "
|
||||||
@@ -156,6 +167,17 @@ public class CameraConfiguration {
|
|||||||
calibrations.add(calibration);
|
calibrations.add(calibration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a unique descriptor of the USB port this camera is attached to. EG
|
||||||
|
* "/dev/v4l/by-path/platform-fc800000.usb-usb-0:1.3:1.0-video-index0"
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public Optional<String> getUSBPath() {
|
||||||
|
return Arrays.stream(otherPaths).filter(path -> path.contains("/by-path/")).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "CameraConfiguration [baseName="
|
return "CameraConfiguration [baseName="
|
||||||
|
|||||||
@@ -20,6 +20,24 @@ package org.photonvision.common.configuration;
|
|||||||
public class HardwareSettings {
|
public class HardwareSettings {
|
||||||
public int ledBrightnessPercentage = 100;
|
public int ledBrightnessPercentage = 100;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ledBrightnessPercentage;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (obj == null) return false;
|
||||||
|
if (getClass() != obj.getClass()) return false;
|
||||||
|
HardwareSettings other = (HardwareSettings) obj;
|
||||||
|
if (ledBrightnessPercentage != other.ledBrightnessPercentage) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "HardwareSettings [ledBrightnessPercentage=" + ledBrightnessPercentage + "]";
|
return "HardwareSettings [ledBrightnessPercentage=" + ledBrightnessPercentage + "]";
|
||||||
|
|||||||
@@ -39,6 +39,12 @@ public class NetworkConfig {
|
|||||||
public boolean shouldManage;
|
public boolean shouldManage;
|
||||||
public boolean shouldPublishProto = false;
|
public boolean shouldPublishProto = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we should ONLY match cameras by path, and NEVER only by base-name. For now default to false
|
||||||
|
* to preserve old matching logic
|
||||||
|
*/
|
||||||
|
public boolean matchCamerasOnlyByPath = false;
|
||||||
|
|
||||||
@JsonIgnore public static final String NM_IFACE_STRING = "${interface}";
|
@JsonIgnore public static final String NM_IFACE_STRING = "${interface}";
|
||||||
@JsonIgnore public static final String NM_IP_STRING = "${ipaddr}";
|
@JsonIgnore public static final String NM_IP_STRING = "${ipaddr}";
|
||||||
|
|
||||||
@@ -76,7 +82,8 @@ public class NetworkConfig {
|
|||||||
@JsonProperty("shouldPublishProto") boolean shouldPublishProto,
|
@JsonProperty("shouldPublishProto") boolean shouldPublishProto,
|
||||||
@JsonProperty("networkManagerIface") String networkManagerIface,
|
@JsonProperty("networkManagerIface") String networkManagerIface,
|
||||||
@JsonProperty("setStaticCommand") String setStaticCommand,
|
@JsonProperty("setStaticCommand") String setStaticCommand,
|
||||||
@JsonProperty("setDHCPcommand") String setDHCPcommand) {
|
@JsonProperty("setDHCPcommand") String setDHCPcommand,
|
||||||
|
@JsonProperty("matchCamerasOnlyByPath") boolean matchCamerasOnlyByPath) {
|
||||||
this.ntServerAddress = ntServerAddress;
|
this.ntServerAddress = ntServerAddress;
|
||||||
this.connectionType = connectionType;
|
this.connectionType = connectionType;
|
||||||
this.staticIp = staticIp;
|
this.staticIp = staticIp;
|
||||||
@@ -86,6 +93,7 @@ public class NetworkConfig {
|
|||||||
this.networkManagerIface = networkManagerIface;
|
this.networkManagerIface = networkManagerIface;
|
||||||
this.setStaticCommand = setStaticCommand;
|
this.setStaticCommand = setStaticCommand;
|
||||||
this.setDHCPcommand = setDHCPcommand;
|
this.setDHCPcommand = setDHCPcommand;
|
||||||
|
this.matchCamerasOnlyByPath = matchCamerasOnlyByPath;
|
||||||
setShouldManage(shouldManage);
|
setShouldManage(shouldManage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ public class MrCalJNILoader extends PhotonJNICommon {
|
|||||||
"libcolamd",
|
"libcolamd",
|
||||||
"libccolamd",
|
"libccolamd",
|
||||||
"openblas",
|
"openblas",
|
||||||
|
"libwinpthread-1",
|
||||||
"libgcc_s_seh-1",
|
"libgcc_s_seh-1",
|
||||||
"libquadmath-0",
|
"libquadmath-0",
|
||||||
"libgfortran-5",
|
"libgfortran-5",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.photonvision.vision.camera;
|
|||||||
|
|
||||||
import edu.wpi.first.cscore.UsbCameraInfo;
|
import edu.wpi.first.cscore.UsbCameraInfo;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class CameraInfo extends UsbCameraInfo {
|
public class CameraInfo extends UsbCameraInfo {
|
||||||
public final CameraType cameraType;
|
public final CameraType cameraType;
|
||||||
@@ -68,6 +69,16 @@ public class CameraInfo extends UsbCameraInfo {
|
|||||||
return getBaseName().replaceAll(" ", "_");
|
return getBaseName().replaceAll(" ", "_");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a unique descriptor of the USB port this camera is attached to. EG
|
||||||
|
* "/dev/v4l/by-path/platform-fc800000.usb-usb-0:1.3:1.0-video-index0"
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Optional<String> getUSBPath() {
|
||||||
|
return Arrays.stream(otherPaths).filter(path -> path.contains("/by-path/")).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (o == this) return true;
|
if (o == this) return true;
|
||||||
@@ -79,4 +90,19 @@ public class CameraInfo extends UsbCameraInfo {
|
|||||||
&& productId == other.productId
|
&& productId == other.productId
|
||||||
&& vendorId == other.vendorId;
|
&& vendorId == other.vendorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CameraInfo [cameraType="
|
||||||
|
+ cameraType
|
||||||
|
+ "baseName="
|
||||||
|
+ getBaseName()
|
||||||
|
+ ", vid="
|
||||||
|
+ vendorId
|
||||||
|
+ ", pid="
|
||||||
|
+ productId
|
||||||
|
+ ", otherPaths="
|
||||||
|
+ Arrays.toString(otherPaths)
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,9 +49,17 @@ public class USBCameraSource extends VisionSource {
|
|||||||
super(config);
|
super(config);
|
||||||
|
|
||||||
logger = new Logger(USBCameraSource.class, config.nickname, LogGroup.Camera);
|
logger = new Logger(USBCameraSource.class, config.nickname, LogGroup.Camera);
|
||||||
camera = new UsbCamera(config.nickname, config.path);
|
// cscore will auto-reconnect to the camera path we give it. v4l does not guarantee that if i
|
||||||
|
// swap cameras around, the same /dev/videoN ID will be assigned to that camera. So instead
|
||||||
|
// default to pinning to a particular USB port, or by "path" (appears to be a global identifier)
|
||||||
|
// on Windows.
|
||||||
|
camera = new UsbCamera(config.nickname, config.getUSBPath().orElse(config.path));
|
||||||
cvSink = CameraServer.getVideo(this.camera);
|
cvSink = CameraServer.getVideo(this.camera);
|
||||||
|
|
||||||
|
// set vid/pid if not done already for future matching
|
||||||
|
if (config.usbVID < 0) config.usbVID = this.camera.getInfo().vendorId;
|
||||||
|
if (config.usbPID < 0) config.usbPID = this.camera.getInfo().productId;
|
||||||
|
|
||||||
if (getCameraConfiguration().cameraQuirks == null)
|
if (getCameraConfiguration().cameraQuirks == null)
|
||||||
getCameraConfiguration().cameraQuirks =
|
getCameraConfiguration().cameraQuirks =
|
||||||
QuirkyCamera.getQuirkyCamera(
|
QuirkyCamera.getQuirkyCamera(
|
||||||
@@ -395,6 +403,7 @@ public class USBCameraSource extends VisionSource {
|
|||||||
// Sort by resolution
|
// Sort by resolution
|
||||||
var sortedList =
|
var sortedList =
|
||||||
videoModesList.stream()
|
videoModesList.stream()
|
||||||
|
.distinct() // remove redundant video mode entries
|
||||||
.sorted(((a, b) -> (b.width + b.height) - (a.width + a.height)))
|
.sorted(((a, b) -> (b.width + b.height) - (a.width + a.height)))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
Collections.reverse(sortedList);
|
Collections.reverse(sortedList);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.common.configuration.ConfigManager;
|
import org.photonvision.common.configuration.ConfigManager;
|
||||||
@@ -164,7 +165,7 @@ public class VisionSourceManager {
|
|||||||
|
|
||||||
// Debug prints
|
// Debug prints
|
||||||
for (var info : connectedCameras) {
|
for (var info : connectedCameras) {
|
||||||
logger.info("Adding local video device - \"" + info.name + "\" at \"" + info.path + "\"");
|
logger.info("Detected unmatched physical camera: " + info.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!unmatchedLoadedConfigs.isEmpty())
|
if (!unmatchedLoadedConfigs.isEmpty())
|
||||||
@@ -216,6 +217,52 @@ public class VisionSourceManager {
|
|||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a predicate for checking cameras against a saved config.
|
||||||
|
*
|
||||||
|
* @param savedConfig The saved camera configuration to match against
|
||||||
|
* @param checkUSBPath If we should compare the USB port/bus IDs
|
||||||
|
* @param checkVidPid If we should compare USB VID and PID
|
||||||
|
* @param checkBaseName If we should compare {@link CameraInfo#getBaseName}
|
||||||
|
* @param checkPath If we should check {@link CameraInfo::path} (eg /dev/videoN on Linux, or
|
||||||
|
* ?/usb#vid_05c8&pid_03df&mi_00#7&fa76035&0&0000#{e5323777-f976-4f5b-9b55-b94699c46e44}\global
|
||||||
|
* on Windows)
|
||||||
|
*/
|
||||||
|
private final Predicate<CameraInfo> getCameraMatcher(
|
||||||
|
final CameraConfiguration savedConfig,
|
||||||
|
boolean checkUSBPath,
|
||||||
|
boolean checkVidPid,
|
||||||
|
boolean checkBaseName,
|
||||||
|
boolean checkPath) {
|
||||||
|
if (checkUSBPath && savedConfig.getUSBPath().isEmpty()) {
|
||||||
|
logger.debug(
|
||||||
|
"WARN: Camera has empty USB path, but asked to match by name: "
|
||||||
|
+ camCfgToString(savedConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (CameraInfo physicalCamera) -> {
|
||||||
|
var matches = true;
|
||||||
|
|
||||||
|
if (checkUSBPath) {
|
||||||
|
var savedPath = savedConfig.getUSBPath();
|
||||||
|
matches &= (savedPath.isPresent() && physicalCamera.getUSBPath().equals(savedPath));
|
||||||
|
}
|
||||||
|
if (checkBaseName) {
|
||||||
|
matches &= physicalCamera.getBaseName().equals(savedConfig.baseName);
|
||||||
|
}
|
||||||
|
if (checkVidPid) {
|
||||||
|
matches &=
|
||||||
|
(physicalCamera.vendorId == savedConfig.usbVID
|
||||||
|
&& physicalCamera.productId == savedConfig.usbPID);
|
||||||
|
}
|
||||||
|
if (checkPath) {
|
||||||
|
matches &= (physicalCamera.path.equals(savedConfig.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create {@link CameraConfiguration}s based on a list of detected USB cameras and the configs on
|
* Create {@link CameraConfiguration}s based on a list of detected USB cameras and the configs on
|
||||||
* disk.
|
* disk.
|
||||||
@@ -226,35 +273,111 @@ public class VisionSourceManager {
|
|||||||
*/
|
*/
|
||||||
public List<CameraConfiguration> matchCameras(
|
public List<CameraConfiguration> matchCameras(
|
||||||
List<CameraInfo> detectedCamInfos, List<CameraConfiguration> loadedCamConfigs) {
|
List<CameraInfo> detectedCamInfos, List<CameraConfiguration> loadedCamConfigs) {
|
||||||
|
return matchCameras(
|
||||||
|
detectedCamInfos,
|
||||||
|
loadedCamConfigs,
|
||||||
|
ConfigManager.getInstance().getConfig().getNetworkConfig().matchCamerasOnlyByPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String camCfgToString(CameraConfiguration c) {
|
||||||
|
return new StringBuilder()
|
||||||
|
.append("[baseName=")
|
||||||
|
.append(c.baseName)
|
||||||
|
.append(", uniqueName=")
|
||||||
|
.append(c.uniqueName)
|
||||||
|
.append(", otherPaths=")
|
||||||
|
.append(Arrays.toString(c.otherPaths))
|
||||||
|
.append(", vid=")
|
||||||
|
.append(c.usbVID)
|
||||||
|
.append(", pid=")
|
||||||
|
.append(c.usbPID)
|
||||||
|
.append("]")
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create {@link CameraConfiguration}s based on a list of detected USB cameras and the configs on
|
||||||
|
* disk.
|
||||||
|
*
|
||||||
|
* @param detectedCamInfos Information about currently connected USB cameras.
|
||||||
|
* @param loadedCamConfigs The USB {@link CameraConfiguration}s loaded from disk.
|
||||||
|
* @param matchCamerasOnlyByPath If we should never try to match only by (base name, vid, pid)
|
||||||
|
* @return the matched configurations.
|
||||||
|
*/
|
||||||
|
public List<CameraConfiguration> matchCameras(
|
||||||
|
List<CameraInfo> detectedCamInfos,
|
||||||
|
List<CameraConfiguration> loadedCamConfigs,
|
||||||
|
boolean matchCamerasOnlyByPath) {
|
||||||
var detectedCameraList = new ArrayList<>(detectedCamInfos);
|
var detectedCameraList = new ArrayList<>(detectedCamInfos);
|
||||||
ArrayList<CameraConfiguration> cameraConfigurations = new ArrayList<CameraConfiguration>();
|
ArrayList<CameraConfiguration> cameraConfigurations = new ArrayList<CameraConfiguration>();
|
||||||
ArrayList<CameraConfiguration> unloadedConfigs =
|
ArrayList<CameraConfiguration> unloadedConfigs =
|
||||||
new ArrayList<CameraConfiguration>(loadedCamConfigs);
|
new ArrayList<CameraConfiguration>(loadedCamConfigs);
|
||||||
|
|
||||||
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0)
|
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
|
||||||
cameraConfigurations.addAll(matchByPathByID(detectedCameraList, unloadedConfigs));
|
logger.info("Matching by usb port & name & USB VID/PID...");
|
||||||
else logger.debug("Skipping matchByPath no configs or cameras left to match");
|
cameraConfigurations.addAll(
|
||||||
|
matchCamerasByStrategy(detectedCameraList, unloadedConfigs, true, true, true, false));
|
||||||
|
} else
|
||||||
|
logger.debug("Skipping match by usb port/name/vid/pid, no configs or cameras left to match");
|
||||||
|
|
||||||
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0)
|
// On windows, the v4l path is actually useful and tells us the port the camera is physically
|
||||||
cameraConfigurations.addAll(matchByPath(detectedCameraList, unloadedConfigs));
|
// connected to which is neat
|
||||||
else logger.debug("Skipping matchByPath no configs or cameras left to match");
|
if (Platform.isWindows()) {
|
||||||
|
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
|
||||||
|
logger.info("Matching by windows-path & USB VID/PID only...");
|
||||||
|
cameraConfigurations.addAll(
|
||||||
|
matchCamerasByStrategy(detectedCameraList, unloadedConfigs, false, true, true, true));
|
||||||
|
} else
|
||||||
|
logger.debug(
|
||||||
|
"Skipping matching by windiws-path/name/vid/pid, no configs or cameras left to match");
|
||||||
|
}
|
||||||
|
|
||||||
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0)
|
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
|
||||||
cameraConfigurations.addAll(matchByName(detectedCameraList, unloadedConfigs));
|
logger.info("Matching by usb port & USB VID/PID...");
|
||||||
else logger.debug("Skipping matchByName no configs or cameras left to match");
|
cameraConfigurations.addAll(
|
||||||
|
matchCamerasByStrategy(detectedCameraList, unloadedConfigs, true, true, false, false));
|
||||||
|
} else logger.debug("Skipping match by port/vid/pid, no configs or cameras left to match");
|
||||||
|
|
||||||
if (detectedCameraList.size() > 0)
|
// handle disabling only-by-base-name matching
|
||||||
|
if (!matchCamerasOnlyByPath) {
|
||||||
|
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
|
||||||
|
logger.info("Matching by base-name & USB VID/PID only...");
|
||||||
|
cameraConfigurations.addAll(
|
||||||
|
matchCamerasByStrategy(detectedCameraList, unloadedConfigs, false, true, true, false));
|
||||||
|
} else
|
||||||
|
logger.debug("Skipping match by base-name/viid/pid, no configs or cameras left to match");
|
||||||
|
} else logger.info("Skipping match by filepath/vid/pid, disabled by user");
|
||||||
|
|
||||||
|
if (detectedCameraList.size() > 0) {
|
||||||
cameraConfigurations.addAll(
|
cameraConfigurations.addAll(
|
||||||
createConfigsForCameras(detectedCameraList, unloadedConfigs, cameraConfigurations));
|
createConfigsForCameras(detectedCameraList, unloadedConfigs, cameraConfigurations));
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug("Matched or created " + cameraConfigurations.size() + " camera configs!");
|
logger.debug("Matched or created " + cameraConfigurations.size() + " camera configs!");
|
||||||
return cameraConfigurations;
|
return cameraConfigurations;
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop over all the configs loaded from disk, attempting to match each camera
|
/**
|
||||||
// to a config by path-by-id on linux
|
* Abstractly match cameras
|
||||||
private List<CameraConfiguration> matchByPathByID(
|
*
|
||||||
List<CameraInfo> detectedCamInfos, List<CameraConfiguration> unloadedConfigs) {
|
* @param detectedCamInfos Physical cameras unmatched and attached to the device
|
||||||
|
* @param unloadedConfigs {@link CameraConfiguration}
|
||||||
|
* @param checkUSBPath If we should compare the USB port/bus IDs
|
||||||
|
* @param checkVidPid If we should compare USB VID and PID
|
||||||
|
* @param checkBaseName If we should check {@link CameraInfo::getBaseName}
|
||||||
|
* @param checkPath If we should check {@link CameraInfo::path} (eg /dev/videoN on Linux, or
|
||||||
|
* usb#vid_05c8&pid_03df&mi_00#7&fa76035&0&0000#{e5323777-f976-4f5b-9b55-b94699c46e44}\global
|
||||||
|
* on Windows). Note that path may change based on order cameras are plugged in/unplugged on
|
||||||
|
* Linux, and should not be trusted to remain the same.
|
||||||
|
* @return All matched or created new configs
|
||||||
|
*/
|
||||||
|
private List<CameraConfiguration> matchCamerasByStrategy(
|
||||||
|
List<CameraInfo> detectedCamInfos,
|
||||||
|
List<CameraConfiguration> unloadedConfigs,
|
||||||
|
boolean checkUSBPath,
|
||||||
|
boolean checkVidPid,
|
||||||
|
boolean checkBaseName,
|
||||||
|
boolean checkPath) {
|
||||||
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
|
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
|
||||||
List<CameraConfiguration> unloadedConfigsCopy =
|
List<CameraConfiguration> unloadedConfigsCopy =
|
||||||
new ArrayList<CameraConfiguration>(unloadedConfigs);
|
new ArrayList<CameraConfiguration>(unloadedConfigs);
|
||||||
@@ -262,111 +385,43 @@ public class VisionSourceManager {
|
|||||||
for (CameraConfiguration config : unloadedConfigsCopy) {
|
for (CameraConfiguration config : unloadedConfigsCopy) {
|
||||||
// Only run match path by id if the camera is not a CSI camera.
|
// Only run match path by id if the camera is not a CSI camera.
|
||||||
if (config.cameraType != CameraType.ZeroCopyPicam) {
|
if (config.cameraType != CameraType.ZeroCopyPicam) {
|
||||||
CameraInfo cameraInfo;
|
logger.debug(
|
||||||
if (config.otherPaths.length == 0) {
|
String.format(
|
||||||
logger.debug("No valid path-by-id found for config with name " + config.baseName);
|
"Trying to find a match for loaded camera %s by strategy (path %s vid/pid %s basename %s path %s) with camera config: %s",
|
||||||
} else {
|
config.baseName,
|
||||||
// attempt matching by path and basename
|
checkUSBPath,
|
||||||
logger.debug(
|
checkVidPid,
|
||||||
"Trying to find a match for loaded camera "
|
checkBaseName,
|
||||||
+ config.baseName
|
checkPath,
|
||||||
+ " with path-by-id "
|
camCfgToString(config)));
|
||||||
+ config.otherPaths[0]);
|
|
||||||
cameraInfo =
|
|
||||||
detectedCamInfos.stream()
|
|
||||||
.filter(
|
|
||||||
usbCameraInfo ->
|
|
||||||
usbCameraInfo.otherPaths.length != 0
|
|
||||||
&& usbCameraInfo.otherPaths[0].equals(config.otherPaths[0])
|
|
||||||
&& usbCameraInfo.getBaseName().equals(config.baseName))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
// If we actually matched a camera to a config, remove that camera from the list
|
// Get matcher and filter against it, picking out the first match
|
||||||
// and add it to the output
|
Predicate<CameraInfo> matches =
|
||||||
if (cameraInfo != null) {
|
getCameraMatcher(config, checkUSBPath, checkVidPid, checkBaseName, checkPath);
|
||||||
logger.debug("Matched the config for " + config.baseName + " to a physical camera!");
|
var cameraInfo = detectedCamInfos.stream().filter(matches).findFirst().orElse(null);
|
||||||
ret.add(mergeInfoIntoConfig(config, cameraInfo));
|
|
||||||
detectedCamInfos.remove(cameraInfo);
|
// If we actually matched a camera to a config, remove that camera from the list
|
||||||
unloadedConfigs.remove(config);
|
// and add it to the output
|
||||||
}
|
if (cameraInfo != null) {
|
||||||
|
logger.debug("Matched the config for " + config.baseName + " to a physical camera!");
|
||||||
|
ret.add(mergeInfoIntoConfig(config, cameraInfo));
|
||||||
|
detectedCamInfos.remove(cameraInfo);
|
||||||
|
unloadedConfigs.remove(config);
|
||||||
|
} else {
|
||||||
|
logger.debug("No camera found for the config " + config.baseName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CameraConfiguration> matchByPath(
|
/**
|
||||||
List<CameraInfo> detectedCamInfos, List<CameraConfiguration> unloadedConfigs) {
|
* Create new {@link CameraConfiguration}s for unmatched cameras, and assign them a unique name
|
||||||
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
|
* (unique in the set of (loaded configs, unloaded configs, loaded vision modules) at least)
|
||||||
List<CameraConfiguration> unloadedConfigsCopy =
|
*/
|
||||||
new ArrayList<CameraConfiguration>(unloadedConfigs);
|
|
||||||
// now attempt to match the cameras and configs remaining by normal path
|
|
||||||
for (CameraConfiguration config : unloadedConfigsCopy) {
|
|
||||||
CameraInfo cameraInfo;
|
|
||||||
|
|
||||||
// attempt matching by path and basename
|
|
||||||
logger.debug(
|
|
||||||
"Trying to find a match for loaded camera "
|
|
||||||
+ config.baseName
|
|
||||||
+ " with path "
|
|
||||||
+ config.path);
|
|
||||||
cameraInfo =
|
|
||||||
detectedCamInfos.stream()
|
|
||||||
.filter(
|
|
||||||
usbCameraInfo ->
|
|
||||||
usbCameraInfo.path.equals(config.path)
|
|
||||||
&& usbCameraInfo.getBaseName().equals(config.baseName))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
// If we actually matched a camera to a config, remove that camera from the list
|
|
||||||
// and add it to the output
|
|
||||||
if (cameraInfo != null) {
|
|
||||||
logger.debug("Matched the config for " + config.baseName + " to a physical camera!");
|
|
||||||
ret.add(mergeInfoIntoConfig(config, cameraInfo));
|
|
||||||
detectedCamInfos.remove(cameraInfo);
|
|
||||||
unloadedConfigs.remove(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try matching cameras to configs by name.
|
|
||||||
private List<CameraConfiguration> matchByName(
|
|
||||||
List<CameraInfo> detectedCamInfos, List<CameraConfiguration> unloadedConfigs) {
|
|
||||||
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
|
|
||||||
List<CameraConfiguration> unloadedConfigsCopy =
|
|
||||||
new ArrayList<CameraConfiguration>(unloadedConfigs);
|
|
||||||
// if both path and ID based matching fails, attempt basename only match
|
|
||||||
for (CameraConfiguration config : unloadedConfigsCopy) {
|
|
||||||
CameraInfo cameraInfo;
|
|
||||||
|
|
||||||
logger.debug("Trying to find a match for loaded camera with name " + config.baseName);
|
|
||||||
|
|
||||||
cameraInfo =
|
|
||||||
detectedCamInfos.stream()
|
|
||||||
.filter(CameraInfo -> CameraInfo.getBaseName().equals(config.baseName))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
// If we actually matched a camera to a config, remove that camera from the list
|
|
||||||
// and add it to the output
|
|
||||||
if (cameraInfo != null) {
|
|
||||||
logger.debug("Matched the config for " + config.baseName + " to a physical camera!");
|
|
||||||
ret.add(mergeInfoIntoConfig(config, cameraInfo));
|
|
||||||
detectedCamInfos.remove(cameraInfo);
|
|
||||||
unloadedConfigs.remove(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have any unmatched cameras left, create a new CameraConfiguration for
|
|
||||||
// them here.
|
|
||||||
private List<CameraConfiguration> createConfigsForCameras(
|
private List<CameraConfiguration> createConfigsForCameras(
|
||||||
List<CameraInfo> detectedCameraList,
|
List<CameraInfo> detectedCameraList,
|
||||||
List<CameraConfiguration> loadedCamConfigs,
|
List<CameraConfiguration> unloadedCamConfigs,
|
||||||
List<CameraConfiguration> loadedConfigs) {
|
List<CameraConfiguration> loadedConfigs) {
|
||||||
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
|
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@@ -377,7 +432,9 @@ public class VisionSourceManager {
|
|||||||
String uniqueName = info.getHumanReadableName();
|
String uniqueName = info.getHumanReadableName();
|
||||||
|
|
||||||
int suffix = 0;
|
int suffix = 0;
|
||||||
while (containsName(loadedConfigs, uniqueName) || containsName(uniqueName)) {
|
while (containsName(loadedConfigs, uniqueName)
|
||||||
|
|| containsName(uniqueName)
|
||||||
|
|| containsName(unloadedCamConfigs, uniqueName)) {
|
||||||
suffix++;
|
suffix++;
|
||||||
uniqueName = String.format("%s (%d)", uniqueName, suffix);
|
uniqueName = String.format("%s (%d)", uniqueName, suffix);
|
||||||
}
|
}
|
||||||
@@ -460,7 +517,7 @@ public class VisionSourceManager {
|
|||||||
List<CameraConfiguration> camConfigs) {
|
List<CameraConfiguration> camConfigs) {
|
||||||
var cameraSources = new ArrayList<VisionSource>();
|
var cameraSources = new ArrayList<VisionSource>();
|
||||||
for (var configuration : camConfigs) {
|
for (var configuration : camConfigs) {
|
||||||
logger.debug("Creating VisionSource for " + configuration);
|
logger.debug("Creating VisionSource for " + camCfgToString(configuration));
|
||||||
|
|
||||||
boolean is_pi = Platform.isRaspberryPi();
|
boolean is_pi = Platform.isRaspberryPi();
|
||||||
|
|
||||||
|
|||||||
@@ -139,8 +139,31 @@ public class ConfigTest {
|
|||||||
writer.write(str);
|
writer.write(str);
|
||||||
writer.flush();
|
writer.flush();
|
||||||
writer.close();
|
writer.close();
|
||||||
Assertions.assertDoesNotThrow(
|
CameraConfiguration result =
|
||||||
() -> JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class));
|
JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class);
|
||||||
|
|
||||||
|
tempFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJacksonAddUSBVIDPID() throws IOException {
|
||||||
|
var str =
|
||||||
|
"{\"baseName\":\"aaaaaa\",\"uniqueName\":\"aaaaaa\",\"nickname\":\"aaaaaa\",\"FOV\":70.0,\"path\":\"dev/vid\",\"cameraType\":\"UsbCamera\",\"currentPipelineIndex\":0,\"camPitch\":{\"radians\":0.0},\"calibrations\":[], \"usbVID\":3, \"usbPID\":4, \"cameraLEDs\":[]}";
|
||||||
|
File tempFile = File.createTempFile("test", ".json");
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
var writer = new FileWriter(tempFile);
|
||||||
|
writer.write(str);
|
||||||
|
writer.flush();
|
||||||
|
writer.close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
CameraConfiguration result =
|
||||||
|
JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class);
|
||||||
|
String ser = JacksonUtils.serializeToString(result);
|
||||||
|
System.out.println(ser);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
tempFile.delete();
|
tempFile.delete();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,9 @@ public class SQLConfigTest {
|
|||||||
CameraType.UsbCamera,
|
CameraType.UsbCamera,
|
||||||
QuirkyCamera.getQuirkyCamera(-1, -1),
|
QuirkyCamera.getQuirkyCamera(-1, -1),
|
||||||
List.of(),
|
List.of(),
|
||||||
0);
|
0,
|
||||||
|
-1,
|
||||||
|
-1);
|
||||||
testcamcfg.pipelineSettings =
|
testcamcfg.pipelineSettings =
|
||||||
List.of(
|
List.of(
|
||||||
new ReflectivePipelineSettings(),
|
new ReflectivePipelineSettings(),
|
||||||
|
|||||||
@@ -24,14 +24,20 @@ import java.util.ArrayList;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.common.configuration.ConfigManager;
|
import org.photonvision.common.configuration.ConfigManager;
|
||||||
|
import org.photonvision.common.logging.LogGroup;
|
||||||
|
import org.photonvision.common.logging.LogLevel;
|
||||||
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.vision.camera.CameraInfo;
|
import org.photonvision.vision.camera.CameraInfo;
|
||||||
import org.photonvision.vision.camera.CameraType;
|
import org.photonvision.vision.camera.CameraType;
|
||||||
|
|
||||||
public class VisionSourceManagerTest {
|
public class VisionSourceManagerTest {
|
||||||
@Test
|
@Test
|
||||||
public void visionSourceTest() {
|
public void visionSourceTest() {
|
||||||
|
Logger.setLevel(LogGroup.Camera, LogLevel.DEBUG);
|
||||||
|
|
||||||
var inst = new VisionSourceManager();
|
var inst = new VisionSourceManager();
|
||||||
var cameraInfos = new ArrayList<CameraInfo>();
|
var cameraInfos = new ArrayList<CameraInfo>();
|
||||||
|
ConfigManager.getInstance().clearConfig();
|
||||||
ConfigManager.getInstance().load();
|
ConfigManager.getInstance().load();
|
||||||
|
|
||||||
inst.tryMatchCamImpl(cameraInfos);
|
inst.tryMatchCamImpl(cameraInfos);
|
||||||
@@ -43,6 +49,8 @@ public class VisionSourceManagerTest {
|
|||||||
"thirdTestVideo",
|
"thirdTestVideo",
|
||||||
"dev/video1",
|
"dev/video1",
|
||||||
new String[] {"by-id/123"});
|
new String[] {"by-id/123"});
|
||||||
|
config3.usbVID = 3;
|
||||||
|
config3.usbPID = 4;
|
||||||
var config4 =
|
var config4 =
|
||||||
new CameraConfiguration(
|
new CameraConfiguration(
|
||||||
"fourthTestVideo",
|
"fourthTestVideo",
|
||||||
@@ -50,6 +58,8 @@ public class VisionSourceManagerTest {
|
|||||||
"fourthTestVideo",
|
"fourthTestVideo",
|
||||||
"dev/video2",
|
"dev/video2",
|
||||||
new String[] {"by-id/321"});
|
new String[] {"by-id/321"});
|
||||||
|
config4.usbVID = 5;
|
||||||
|
config4.usbPID = 6;
|
||||||
|
|
||||||
CameraInfo info1 = new CameraInfo(0, "dev/video0", "testVideo", new String[0], 1, 2);
|
CameraInfo info1 = new CameraInfo(0, "dev/video0", "testVideo", new String[0], 1, 2);
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class PhotonCamera:
|
|||||||
retVal.populateFromPacket(pkt)
|
retVal.populateFromPacket(pkt)
|
||||||
# NT4 allows us to correct the timestamp based on when the message was sent
|
# NT4 allows us to correct the timestamp based on when the message was sent
|
||||||
retVal.setTimestampSeconds(
|
retVal.setTimestampSeconds(
|
||||||
timestamp / 1e-6 - retVal.getLatencyMillis() / 1e-3
|
timestamp / 1e6 - retVal.getLatencyMillis() / 1e3
|
||||||
)
|
)
|
||||||
return retVal
|
return retVal
|
||||||
|
|
||||||
|
|||||||
@@ -408,8 +408,8 @@ public class PhotonPoseEstimator {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (estimatedPose.isEmpty()) {
|
if (estimatedPose.isPresent()) {
|
||||||
lastPose = null;
|
lastPose = estimatedPose.get().estimatedPose;
|
||||||
}
|
}
|
||||||
|
|
||||||
return estimatedPose;
|
return estimatedPose;
|
||||||
|
|||||||
@@ -186,6 +186,9 @@ std::optional<EstimatedRobotPose> PhotonPoseEstimator::Update(
|
|||||||
ret = std::nullopt;
|
ret = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
lastPose = ret.value().estimatedPose;
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ class Robot : public frc::TimedRobot {
|
|||||||
void TeleopPeriodic() override;
|
void TeleopPeriodic() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Change this to match the name of your camera
|
// Change this to match the name of your camera as shown in the web UI
|
||||||
photon::PhotonCamera camera{"photonvision"};
|
photon::PhotonCamera camera{"YOUR_CAMERA_NAME_HERE"};
|
||||||
// PID constants should be tuned per robot
|
// PID constants should be tuned per robot
|
||||||
frc::PIDController controller{.1, 0, 0};
|
frc::PIDController controller{.1, 0, 0};
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ public class Robot extends TimedRobot {
|
|||||||
// How far from the target we want to be
|
// How far from the target we want to be
|
||||||
final double GOAL_RANGE_METERS = Units.feetToMeters(3);
|
final double GOAL_RANGE_METERS = Units.feetToMeters(3);
|
||||||
|
|
||||||
// Change this to match the name of your camera
|
// Change this to match the name of your camera as shown in the web UI
|
||||||
PhotonCamera camera = new PhotonCamera("photonvision");
|
PhotonCamera camera = new PhotonCamera("YOUR_CAMERA_NAME_HERE");
|
||||||
|
|
||||||
// PID constants should be tuned per robot
|
// PID constants should be tuned per robot
|
||||||
final double LINEAR_P = 0.1;
|
final double LINEAR_P = 0.1;
|
||||||
|
|||||||
Reference in New Issue
Block a user