diff --git a/photon-client/src/components/settings/NetworkingCard.vue b/photon-client/src/components/settings/NetworkingCard.vue
index 1b3b7bb56..e3b80e2d9 100644
--- a/photon-client/src/components/settings/NetworkingCard.vue
+++ b/photon-client/src/components/settings/NetworkingCard.vue
@@ -281,11 +281,29 @@ watchEffect(() => {
+
+ Physical cameras will be strictly matched to camera configurations using physical USB port they are plugged
+ into, in addition to device name and other USB metadata. Additionally, no new cameras are allowed to be added.
+ This setting is useful for guaranteeing that an already known and configured camera can never be matched as an
+ "unknown"/"new" camera, which resets pipelines and calibration data.
+
+ Cameras will NOT be matched if they change USB ports, and new cameras plugged into this coprocessor will NOT
+ be automatically recognized or configured for vision processing.
+
+ To add a new camera to this coprocessor, disable this setting, connect the camera, and re-enable.
+
This also disables creating new CameraConfigurations for detected "new" cameras.
*/
public boolean matchCamerasOnlyByPath = false;
diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/CameraInfo.java b/photon-core/src/main/java/org/photonvision/vision/camera/CameraInfo.java
index e7a9f4b04..32441a308 100644
--- a/photon-core/src/main/java/org/photonvision/vision/camera/CameraInfo.java
+++ b/photon-core/src/main/java/org/photonvision/vision/camera/CameraInfo.java
@@ -101,6 +101,8 @@ public class CameraInfo extends UsbCameraInfo {
+ vendorId
+ ", pid="
+ productId
+ + ", path="
+ + path
+ ", otherPaths="
+ Arrays.toString(otherPaths)
+ "]";
diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/USBCameraSource.java b/photon-core/src/main/java/org/photonvision/vision/camera/USBCameraSource.java
index 1a29a9af2..dbecd1ef5 100644
--- a/photon-core/src/main/java/org/photonvision/vision/camera/USBCameraSource.java
+++ b/photon-core/src/main/java/org/photonvision/vision/camera/USBCameraSource.java
@@ -57,8 +57,8 @@ public class USBCameraSource extends VisionSource {
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 (config.usbVID <= 0) config.usbVID = this.camera.getInfo().vendorId;
+ if (config.usbPID <= 0) config.usbPID = this.camera.getInfo().productId;
if (getCameraConfiguration().cameraQuirks == null)
getCameraConfiguration().cameraQuirks =
diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java
index d5ceda85a..ef06d474b 100644
--- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java
+++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java
@@ -317,8 +317,7 @@ public class VisionSourceManager {
logger.info("Matching by usb port & name & USB VID/PID...");
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");
+ }
// On windows, the v4l path is actually useful and tells us the port the camera is physically
// connected to which is neat
@@ -327,16 +326,23 @@ public class VisionSourceManager {
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) {
logger.info("Matching by usb port & USB VID/PID...");
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");
+ }
+
+ // Legacy migration -- VID/PID will be unset, so we have to try with our most relaxed strategy
+ // at least once. We _should_ still have a valid USB path (assuming cameras have not moved), so
+ // try that first, then fallback to base name only beloow
+ if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
+ logger.info("Matching by base-name & usb port...");
+ cameraConfigurations.addAll(
+ matchCamerasByStrategy(detectedCameraList, unloadedConfigs, true, false, true, false));
+ }
// handle disabling only-by-base-name matching
if (!matchCamerasOnlyByPath) {
@@ -344,13 +350,29 @@ public class VisionSourceManager {
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");
+ }
+
+ // Legacy migration for if no USB VID/PID set
+ if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
+ logger.info("Matching by base-name only...");
+ cameraConfigurations.addAll(
+ matchCamerasByStrategy(detectedCameraList, unloadedConfigs, false, false, true, false));
+ }
} else logger.info("Skipping match by filepath/vid/pid, disabled by user");
if (detectedCameraList.size() > 0) {
- cameraConfigurations.addAll(
- createConfigsForCameras(detectedCameraList, unloadedConfigs, cameraConfigurations));
+ // handle disabling only-by-base-name matching
+ if (!matchCamerasOnlyByPath) {
+ cameraConfigurations.addAll(
+ createConfigsForCameras(detectedCameraList, unloadedConfigs, cameraConfigurations));
+ } else {
+ logger.warn(
+ "Not creating 'new' Photon CameraConfigurations for ["
+ + detectedCamInfos.stream()
+ .map(CameraInfo::toString)
+ .collect(Collectors.joining(";"))
+ + "], disabled by user");
+ }
}
logger.debug("Matched or created " + cameraConfigurations.size() + " camera configs!");