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 4ee25354d2..c17715cc40 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 @@ -142,7 +142,15 @@ protected List tryMatchUSBCamImpl(boolean createSources) { logger.debug("Trying to match " + usbCamConfigs.size() + " unmatched configs..."); // Match camera configs to physical cameras - var matchedCameras = matchUSBCameras(notYetLoadedCams, unmatchedLoadedConfigs); + + List matchedCameras; + + if (!createSources) { + matchedCameras = matchUSBCameras(notYetLoadedCams, unmatchedLoadedConfigs, false); + } else { + matchedCameras = matchUSBCameras(notYetLoadedCams, unmatchedLoadedConfigs); + } + unmatchedLoadedConfigs.removeAll(matchedCameras); if (!unmatchedLoadedConfigs.isEmpty() && !hasWarned) { logger.warn( @@ -198,6 +206,22 @@ protected List tryMatchUSBCamImpl(boolean createSources) { */ private List matchUSBCameras( List detectedCamInfos, List loadedUsbCamConfigs) { + return matchUSBCameras(detectedCamInfos, loadedUsbCamConfigs, true); + } + + /** + * 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 loadedUsbCamConfigs The USB {@link CameraConfiguration}s loaded from disk. + * @param useJNI If false, this is a unit test and the JNI should not be used for CSI devices. + * @return the matched configurations. + */ + private List matchUSBCameras( + List detectedCamInfos, + List loadedUsbCamConfigs, + boolean useJNI) { var detectedCameraList = new ArrayList<>(detectedCamInfos); ArrayList cameraConfigurations = new ArrayList<>(); @@ -260,7 +284,11 @@ && cameraNameToBaseName(usbCameraInfo.name).equals(config.baseName)) // HACK -- for picams, we want to use the camera model String nickname = uniqueName; if (isCsiCamera(info)) { - nickname = LibCameraJNI.getSensorModel().toString(); + if (useJNI) { + nickname = LibCameraJNI.getSensorModel().toString(); + } else { + nickname = "CSICAM-DEV"; + } } CameraConfiguration configuration = @@ -302,30 +330,36 @@ private List filterAllowedDevices(List allDevices) List paths = new ArrayList<>(); - // Use the other paths to filter out devices that share the same path other than the index - // select only the lowest index. - // A ov5647 on a raspi 5 would show another path as platform-1000880000.pisp_be-video-index0, - // platform-1000880000.pisp_be-video-index4, and platform-1000880000.pisp_be-video-index5. - // This code will remove "indexX" from all the other paths from all the devices and make sure - // that we only take one camera stream from each device the stream with the lowest index. - for (String p : device.otherPaths) { - paths.add(p.split("index")[0]); - } boolean skip = false; - for (var otherDevice : filteredDevices) { - List otherPaths = new ArrayList<>(); - for (String p : otherDevice.otherPaths) { - otherPaths.add(p.split("index")[0]); + if (device.otherPaths.length != 0) { + // Use the other paths to filter out devices that share the same path other than the index + // select only the lowest index. + // A ov5647 on a raspi 5 would show another path as + // platform-1000880000.pisp_be-video-index0, + // platform-1000880000.pisp_be-video-index4, and platform-1000880000.pisp_be-video-index5. + // This code will remove "indexX" from all the other paths from all the devices and make + // sure + // that we only take one camera stream from each device the stream with the lowest index. + for (String p : device.otherPaths) { + paths.add(p.split("index")[0]); } - if (paths.containsAll(otherPaths)) { - if (otherDevice.dev >= device.dev) { - badDevices.add(otherDevice); - } else { - skip = true; - break; + for (var otherDevice : filteredDevices) { + if (otherDevice.otherPaths.length == 0) continue; + List otherPaths = new ArrayList<>(); + for (String p : otherDevice.otherPaths) { + otherPaths.add(p.split("index")[0]); + } + if (paths.containsAll(otherPaths)) { + if (otherDevice.dev >= device.dev) { + badDevices.add(otherDevice); + } else { + skip = true; + break; + } } } } + filteredDevices.removeAll(badDevices); if (deviceBlacklist.contains(device.name)) { logger.trace( diff --git a/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java b/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java index 2c64a9a622..159e6ec951 100644 --- a/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java +++ b/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java @@ -35,23 +35,108 @@ public void visionSourceTest() { ConfigManager.getInstance().load(); inst.tryMatchUSBCamImpl(); - var config = new CameraConfiguration("secondTestVideo", "dev/video1"); - UsbCameraInfo info1 = new UsbCameraInfo(0, "dev/video0", "testVideo", new String[0], 1, 2); + var config = new CameraConfiguration("secondTestVideo", "/dev/video1"); + + UsbCameraInfo info1 = new UsbCameraInfo(0, "/dev/video0", "testVideo", new String[0], 1, 2); infoList.add(info1); inst.registerLoadedConfigs(config); - var sources = inst.tryMatchUSBCamImpl(false); + inst.tryMatchUSBCamImpl(false); assertTrue(inst.knownUsbCameras.contains(info1)); assertEquals(1, inst.unmatchedLoadedConfigs.size()); UsbCameraInfo info2 = - new UsbCameraInfo(0, "dev/video1", "secondTestVideo", new String[0], 2, 1); + new UsbCameraInfo(1, "/dev/video1", "secondTestVideo", new String[0], 2, 1); infoList.add(info2); + inst.tryMatchUSBCamImpl(false); assertTrue(inst.knownUsbCameras.contains(info2)); assertEquals(2, inst.knownUsbCameras.size()); assertEquals(0, inst.unmatchedLoadedConfigs.size()); + + UsbCameraInfo info3 = + new UsbCameraInfo( + 2, + "/dev/video2", + "Left Camera", + new String[] { + "/dev/v4l/by-id/usb-Arducam_Technology_Co.__Ltd._Left_Camera_12345-video-index0", + "/dev/v4l/by-path/platform-xhci-hcd.0-usb-0:2:1.0-video-index0" + }, + 3, + 4); + infoList.add(info3); + inst.tryMatchUSBCamImpl(false); + + assertTrue(inst.knownUsbCameras.contains(info3)); + assertEquals(3, inst.knownUsbCameras.size()); + assertEquals(0, inst.unmatchedLoadedConfigs.size()); + + UsbCameraInfo info4 = + new UsbCameraInfo( + 3, + "dev/video3", + "Right Camera", + new String[] { + "/dev/v4l/by-id/usb-Arducam_Technology_Co.__Ltd._Right_Camera_123456-video-index0", + "/dev/v4l/by-path/platform-xhci-hcd.1-usb-0:1:1.0-video-index0" + }, + 5, + 6); + infoList.add(info4); + inst.tryMatchUSBCamImpl(false); + + assertTrue(inst.knownUsbCameras.contains(info4)); + assertEquals(4, inst.knownUsbCameras.size()); + assertEquals(0, inst.unmatchedLoadedConfigs.size()); + + // RPI 5 CSI Tests + + UsbCameraInfo info5 = + new UsbCameraInfo( + 4, + "dev/video4", + "CSICAM-DEV", // Typically rp1-cfe for unit test changed to CSICAM-DEV + new String[] {"/dev/v4l/by-path/platform-1f00110000.csi-video-index0"}, + 7, + 8); + infoList.add(info5); + inst.tryMatchUSBCamImpl(false); + + assertTrue(inst.knownUsbCameras.contains(info5)); + assertEquals(5, inst.knownUsbCameras.size()); + assertEquals(0, inst.unmatchedLoadedConfigs.size()); + + UsbCameraInfo info6 = + new UsbCameraInfo( + 5, + "dev/video8", + "CSICAM-DEV", // Typically rp1-cfe for unit test changed to CSICAM-DEV + new String[] {"/dev/v4l/by-path/platform-1f00110000.csi-video-index4"}, + 9, + 10); + infoList.add(info6); + inst.tryMatchUSBCamImpl(false); + + assertTrue(!inst.knownUsbCameras.contains(info6)); // This camera should not be recognized/used. + assertEquals(5, inst.knownUsbCameras.size()); + assertEquals(0, inst.unmatchedLoadedConfigs.size()); + + UsbCameraInfo info7 = + new UsbCameraInfo( + 6, + "dev/video9", + "CSICAM-DEV", // Typically rp1-cfe for unit test changed to CSICAM-DEV + new String[] {"/dev/v4l/by-path/platform-1f00110000.csi-video-index5"}, + 11, + 12); + infoList.add(info7); + inst.tryMatchUSBCamImpl(false); + + assertTrue(!inst.knownUsbCameras.contains(info7)); // This camera should not be recognized/used. + assertEquals(5, inst.knownUsbCameras.size()); + assertEquals(0, inst.unmatchedLoadedConfigs.size()); } }