Skip to content

Commit 5d55d21

Browse files
authored
Another config matching bug (#1518)
This is quite an odd issue/fix. So this is what happened... Photonvision booted with the camera connected and the camera was working... After a short time the camera stopped working (for some reason maybe static, maybe temp, maybe wiring, idk). During this time pv showed Jul 04 06:25:18 BackLeft java[643]: [2024-07-04 06:25:18] [CSCore - PvCSCoreLogger] [ERROR] CS: ERROR 40: ioctl VIDIOC_QBUF failed at UsbCameraImpl.cpp:723: Invalid argument (UsbUtil.cpp:156) Jul 04 06:25:18 BackLeft java[643]: [2024-07-04 06:25:18] [CSCore - PvCSCoreLogger] [WARN] CS: WARNING 30: BackLeft: could not queue buffer 0 (UsbCameraImpl.cpp:724) I went over and played with the wire. The camera fully disconnected but it ended up "reconnecting" When the camera was "reconnected" photonvision detected a "new camera" except this time with no otherpaths (aka no usb path, or by id path). That resulted in pv creating a new camera configuration for a camera with no otherpaths Cscore then started to report errors that look like it attempted to connect to the same camera twice This fixes it by filtering out USB cameras that have no otherpath on linux.
1 parent 625dacb commit 5d55d21

File tree

4 files changed

+97
-53
lines changed

4 files changed

+97
-53
lines changed

photon-core/src/main/java/org/photonvision/common/configuration/CameraConfiguration.java

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,7 @@ public CameraConfiguration(
8484
this.calibrations = new ArrayList<>();
8585
this.otherPaths = alternates;
8686

87-
logger.debug(
88-
"Creating USB camera configuration for "
89-
+ cameraType
90-
+ " "
91-
+ baseName
92-
+ " (AKA "
93-
+ nickname
94-
+ ") at "
95-
+ path);
87+
logger.debug("Creating USB camera configuration for " + this.toShortString());
9688
}
9789

9890
@JsonCreator
@@ -120,15 +112,7 @@ public CameraConfiguration(
120112
this.usbPID = usbPID;
121113
this.usbVID = usbVID;
122114

123-
logger.debug(
124-
"Creating camera configuration for "
125-
+ cameraType
126-
+ " "
127-
+ baseName
128-
+ " (AKA "
129-
+ nickname
130-
+ ") at "
131-
+ path);
115+
logger.debug("Loaded camera configuration for " + toShortString());
132116
}
133117

134118
public void addPipelineSettings(List<CVPipelineSettings> settings) {
@@ -189,6 +173,30 @@ public Optional<String> getUSBPath() {
189173
return Arrays.stream(otherPaths).filter(path -> path.contains("/by-path/")).findFirst();
190174
}
191175

176+
public String toShortString() {
177+
return "CameraConfiguration [baseName="
178+
+ baseName
179+
+ ", uniqueName="
180+
+ uniqueName
181+
+ ", nickname="
182+
+ nickname
183+
+ ", path="
184+
+ path
185+
+ ", otherPaths="
186+
+ Arrays.toString(otherPaths)
187+
+ ", cameraType="
188+
+ cameraType
189+
+ ", cameraQuirks="
190+
+ cameraQuirks
191+
+ ", FOV="
192+
+ FOV
193+
+ "]"
194+
+ ", PID="
195+
+ usbPID
196+
+ ", VID="
197+
+ usbVID;
198+
}
199+
192200
@Override
193201
public String toString() {
194202
return "CameraConfiguration [baseName="

photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.photonvision.common.dataflow.DataChangeService;
3131
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
3232
import org.photonvision.common.hardware.Platform;
33+
import org.photonvision.common.hardware.Platform.OSType;
3334
import org.photonvision.common.logging.LogGroup;
3435
import org.photonvision.common.logging.Logger;
3536
import org.photonvision.common.util.TimedTaskManager;
@@ -128,20 +129,26 @@ protected List<VisionSource> tryMatchCamImpl() {
128129
return tryMatchCamImpl(null);
129130
}
130131

132+
protected List<VisionSource> tryMatchCamImpl(ArrayList<CameraInfo> cameraInfos) {
133+
return tryMatchCamImpl(cameraInfos, Platform.getCurrentPlatform());
134+
}
135+
131136
/**
132137
* @param cameraInfos Used to feed camera info for unit tests.
133138
* @return New VisionSources.
134139
*/
135-
protected List<VisionSource> tryMatchCamImpl(ArrayList<CameraInfo> cameraInfos) {
140+
protected List<VisionSource> tryMatchCamImpl(
141+
ArrayList<CameraInfo> cameraInfos, Platform platform) {
136142
boolean createSources = true;
137143
List<CameraInfo> connectedCameras;
138144
if (cameraInfos == null) {
139145
// Detect USB cameras using CSCore
140-
connectedCameras = new ArrayList<>(filterAllowedDevices(getConnectedUSBCameras()));
146+
connectedCameras = new ArrayList<>(filterAllowedDevices(getConnectedUSBCameras(), platform));
141147
// Detect CSI cameras using libcamera
142-
connectedCameras.addAll(new ArrayList<>(filterAllowedDevices(getConnectedCSICameras())));
148+
connectedCameras.addAll(
149+
new ArrayList<>(filterAllowedDevices(getConnectedCSICameras(), platform)));
143150
} else {
144-
connectedCameras = new ArrayList<>(filterAllowedDevices(cameraInfos));
151+
connectedCameras = new ArrayList<>(filterAllowedDevices(cameraInfos, platform));
145152
createSources =
146153
false; // Dont create sources if we are using supplied camerainfo for unit tests.
147154
}
@@ -162,15 +169,15 @@ protected List<VisionSource> tryMatchCamImpl(ArrayList<CameraInfo> cameraInfos)
162169
// All cameras are already loaded return no new sources.
163170
if (connectedCameras.isEmpty()) return null;
164171

165-
logger.debug("Matching " + connectedCameras.size() + " new cameras!");
172+
logger.debug("Matching " + connectedCameras.size() + " new camera(s)!");
166173

167174
// Debug prints
168175
for (var info : connectedCameras) {
169176
logger.info("Detected unmatched physical camera: " + info.toString());
170177
}
171178

172179
if (!unmatchedLoadedConfigs.isEmpty())
173-
logger.debug("Trying to match " + unmatchedLoadedConfigs.size() + " unmatched configs...");
180+
logger.debug("Trying to match " + unmatchedLoadedConfigs.size() + " unmatched config(s)...");
174181

175182
// Match camera configs to physical cameras
176183
List<CameraConfiguration> matchedCameras =
@@ -182,7 +189,7 @@ protected List<VisionSource> tryMatchCamImpl(ArrayList<CameraInfo> cameraInfos)
182189
() ->
183190
"After matching, "
184191
+ unmatchedLoadedConfigs.size()
185-
+ " configs remained unmatched. Is your camera disconnected?");
192+
+ " config(s) remained unmatched. Is your camera disconnected?");
186193
logger.warn(
187194
"Unloaded configs: "
188195
+ unmatchedLoadedConfigs.stream()
@@ -233,7 +240,7 @@ private final Predicate<CameraInfo> getCameraMatcher(
233240
if (checkUSBPath && savedConfig.getUSBPath().isEmpty()) {
234241
logger.debug(
235242
"WARN: Camera has empty USB path, but asked to match by name: "
236-
+ camCfgToString(savedConfig));
243+
+ savedConfig.toShortString());
237244
}
238245

239246
return (CameraInfo physicalCamera) -> {
@@ -277,22 +284,6 @@ public List<CameraConfiguration> matchCameras(
277284
ConfigManager.getInstance().getConfig().getNetworkConfig().matchCamerasOnlyByPath);
278285
}
279286

280-
private static final String camCfgToString(CameraConfiguration c) {
281-
return new StringBuilder()
282-
.append("[baseName=")
283-
.append(c.baseName)
284-
.append(", uniqueName=")
285-
.append(c.uniqueName)
286-
.append(", otherPaths=")
287-
.append(Arrays.toString(c.otherPaths))
288-
.append(", vid=")
289-
.append(c.usbVID)
290-
.append(", pid=")
291-
.append(c.usbPID)
292-
.append("]")
293-
.toString();
294-
}
295-
296287
/**
297288
* Create {@link CameraConfiguration}s based on a list of detected USB cameras and the configs on
298289
* disk.
@@ -423,7 +414,7 @@ private List<CameraConfiguration> matchCamerasByStrategy(
423414
logger.debug(
424415
String.format(
425416
"Trying to find a match for loaded camera %s (%s) with camera config: %s",
426-
config.baseName, config.uniqueName, camCfgToString(config)));
417+
config.baseName, config.uniqueName, config.toShortString()));
427418

428419
// Get matcher and filter against it, picking out the first match
429420
Predicate<CameraInfo> matches =
@@ -463,7 +454,7 @@ private List<CameraConfiguration> createConfigsForCameras(
463454
List<CameraConfiguration> loadedConfigs) {
464455
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
465456
logger.debug(
466-
"After matching loaded configs, these configs remained unmatched: "
457+
"After matching loaded configs, these cameras remained unmatched: "
467458
+ detectedCameraList.stream()
468459
.map(n -> String.valueOf(n))
469460
.collect(Collectors.joining("-", "{", "}")));
@@ -537,7 +528,7 @@ public void setIgnoredCamerasRegex(String ignoredCamerasRegex) {
537528
* @param allDevices
538529
* @return list of devices with blacklisted or ignore devices removed.
539530
*/
540-
private List<CameraInfo> filterAllowedDevices(List<CameraInfo> allDevices) {
531+
private List<CameraInfo> filterAllowedDevices(List<CameraInfo> allDevices, Platform platform) {
541532
List<CameraInfo> filteredDevices = new ArrayList<>();
542533
for (var device : allDevices) {
543534
if (deviceBlacklist.contains(device.name)) {
@@ -546,6 +537,13 @@ private List<CameraInfo> filterAllowedDevices(List<CameraInfo> allDevices) {
546537
} else if (device.name.matches(ignoredCamerasRegex)) {
547538
logger.trace("Skipping ignored device: \"" + device.name + "\" at \"" + device.path);
548539
} else if (device.getIsV4lCsiCamera()) {
540+
} else if (device.otherPaths.length == 0
541+
&& platform.osType == OSType.LINUX
542+
&& device.cameraType == CameraType.UsbCamera) {
543+
logger.trace(
544+
"Skipping device with no other paths: \"" + device.name + "\" at \"" + device.path);
545+
// If cscore hasnt passed this other paths aka a path by id or a path as in usb port then we
546+
// cant guarantee it is a valid camera.
549547
} else {
550548
filteredDevices.add(device);
551549
logger.trace(
@@ -559,8 +557,6 @@ private static List<VisionSource> loadVisionSourcesFromCamConfigs(
559557
List<CameraConfiguration> camConfigs, boolean createSources) {
560558
var cameraSources = new ArrayList<VisionSource>();
561559
for (var configuration : camConfigs) {
562-
logger.debug("Creating VisionSource for " + camCfgToString(configuration));
563-
564560
// In unit tests, create dummy
565561
if (!createSources) {
566562
cameraSources.add(new TestSource(configuration));
@@ -580,6 +576,7 @@ private static List<VisionSource> loadVisionSourcesFromCamConfigs(
580576
cameraSources.add(newCam);
581577
}
582578
}
579+
logger.debug("Creating VisionSource for " + configuration.toShortString());
583580
}
584581
return cameraSources;
585582
}

photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717

1818
package org.photonvision.vision.processes;
1919

20-
import static org.junit.jupiter.api.Assertions.assertEquals;
21-
import static org.junit.jupiter.api.Assertions.assertTrue;
20+
import static org.junit.jupiter.api.Assertions.*;
2221

2322
import java.util.ArrayList;
2423
import java.util.List;
2524
import org.junit.jupiter.api.Test;
2625
import org.photonvision.common.configuration.CameraConfiguration;
2726
import org.photonvision.common.configuration.ConfigManager;
27+
import org.photonvision.common.hardware.Platform;
2828
import org.photonvision.common.logging.LogGroup;
2929
import org.photonvision.common.logging.LogLevel;
3030
import org.photonvision.common.logging.Logger;
@@ -62,7 +62,8 @@ public void visionSourceTest() {
6262
config4.usbVID = 5;
6363
config4.usbPID = 6;
6464

65-
CameraInfo info1 = new CameraInfo(0, "dev/video0", "testVideo", new String[0], 1, 2);
65+
CameraInfo info1 =
66+
new CameraInfo(0, "dev/video0", "testVideo", new String[] {"/usb/path/0"}, 1, 2);
6667

6768
cameraInfos.add(info1);
6869

@@ -73,7 +74,8 @@ public void visionSourceTest() {
7374
assertTrue(inst.knownCameras.contains(info1));
7475
assertEquals(2, inst.unmatchedLoadedConfigs.size());
7576

76-
CameraInfo info2 = new CameraInfo(0, "dev/video1", "secondTestVideo", new String[0], 2, 3);
77+
CameraInfo info2 =
78+
new CameraInfo(0, "dev/video1", "secondTestVideo", new String[] {"/usb/path/1"}, 2, 3);
7779

7880
cameraInfos.add(info2);
7981

@@ -500,6 +502,43 @@ public void testCSICameraMatching() {
500502
}
501503
}
502504

505+
@Test
506+
public void testNoOtherPaths() {
507+
Logger.setLevel(LogGroup.Camera, LogLevel.DEBUG);
508+
509+
// List of known cameras
510+
var cameraInfos = new ArrayList<CameraInfo>();
511+
512+
var inst = new VisionSourceManager();
513+
ConfigManager.getInstance().clearConfig();
514+
ConfigManager.getInstance().load();
515+
ConfigManager.getInstance().getConfig().getNetworkConfig().matchCamerasOnlyByPath = false;
516+
517+
// Match empty camera infos
518+
inst.tryMatchCamImpl(cameraInfos);
519+
520+
CameraInfo info1 =
521+
new CameraInfo(0, "/dev/video0", "Arducam OV2311 USB Camera", new String[] {}, 3141, 25446);
522+
523+
cameraInfos.add(info1);
524+
525+
// Match two "new" cameras
526+
var ret1 = inst.tryMatchCamImpl(cameraInfos, Platform.LINUX_64);
527+
528+
// Our cameras should be "known"
529+
assertFalse(inst.knownCameras.contains(info1));
530+
assertEquals(0, inst.knownCameras.size());
531+
assertEquals(null, ret1);
532+
533+
// Match two "new" cameras
534+
var ret2 = inst.tryMatchCamImpl(cameraInfos, Platform.WINDOWS_64);
535+
536+
// Our cameras should be "known"
537+
assertTrue(inst.knownCameras.contains(info1));
538+
assertEquals(1, inst.knownCameras.size());
539+
assertEquals(1, ret2.size());
540+
}
541+
503542
@Test
504543
public void testIdenticalCameras() {
505544
Logger.setLevel(LogGroup.Camera, LogLevel.DEBUG);

photon-targeting/src/main/java/org/photonvision/common/hardware/Platform.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public enum Platform {
5454
LINUX_ARM32("Linux ARM32", "linuxarm32", false, OSType.LINUX, false), // ODROID XU4, C1+
5555
UNKNOWN("Unsupported Platform", "", false, OSType.UNKNOWN, false);
5656

57-
private enum OSType {
57+
public enum OSType {
5858
WINDOWS,
5959
LINUX,
6060
MACOS,
@@ -123,7 +123,7 @@ public static boolean isSupported() {
123123
private static final String UnknownPlatformString =
124124
String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH);
125125

126-
private static Platform getCurrentPlatform() {
126+
public static Platform getCurrentPlatform() {
127127
if (RuntimeDetector.isWindows()) {
128128
if (RuntimeDetector.is32BitIntel()) {
129129
return WINDOWS_32;

0 commit comments

Comments
 (0)