Skip to content

Commit

Permalink
Merge branch 'PhotonVision:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
r4stered authored Dec 8, 2023
2 parents 4087d3b + 6db5bc5 commit f2c5932
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ static class TableKeys {
static final String CAM_UNIQUE_NAME = "unique_name";
static final String CONFIG_JSON = "config_json";
static final String DRIVERMODE_JSON = "drivermode_json";
static final String OTHERPATHS_JSON = "otherpaths_json";
static final String PIPELINE_JSONS = "pipeline_jsons";

static final String NETWORK_CONFIG = "networkConfig";
Expand Down Expand Up @@ -147,6 +148,7 @@ private void initDatabase() {
+ " unique_name TINYTEXT PRIMARY KEY,\n"
+ " config_json text NOT NULL,\n"
+ " drivermode_json text NOT NULL,\n"
+ " otherpaths_json text NOT NULL,\n"
+ " pipeline_jsons mediumtext NOT NULL\n"
+ ");";
createCameraTableStatement.execute(sql);
Expand Down Expand Up @@ -295,8 +297,8 @@ private void saveCameras(Connection conn) {
try {
// Replace this camera's row with the new settings
var sqlString =
"REPLACE INTO cameras (unique_name, config_json, drivermode_json, pipeline_jsons) VALUES "
+ "(?,?,?,?);";
"REPLACE INTO cameras (unique_name, config_json, drivermode_json, otherpaths_json, pipeline_jsons) VALUES "
+ "(?,?,?,?,?);";

for (var c : config.getCameraConfigurations().entrySet()) {
PreparedStatement statement = conn.prepareStatement(sqlString);
Expand All @@ -305,6 +307,7 @@ private void saveCameras(Connection conn) {
statement.setString(1, c.getKey());
statement.setString(2, JacksonUtils.serializeToString(config));
statement.setString(3, JacksonUtils.serializeToString(config.driveModeSettings));
statement.setString(4, JacksonUtils.serializeToString(config.otherPaths));

// Serializing a list of abstract classes sucks. Instead, make it into an array
// of strings, which we can later unpack back into individual settings
Expand All @@ -321,7 +324,7 @@ private void saveCameras(Connection conn) {
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
statement.setString(4, JacksonUtils.serializeToString(settings));
statement.setString(5, JacksonUtils.serializeToString(settings));

statement.executeUpdate();
}
Expand Down Expand Up @@ -455,10 +458,11 @@ private HashMap<String, CameraConfiguration> loadCameraConfigs(Connection conn)
query =
conn.prepareStatement(
String.format(
"SELECT %s, %s, %s, %s FROM cameras",
"SELECT %s, %s, %s, %s, %s FROM cameras",
TableKeys.CAM_UNIQUE_NAME,
TableKeys.CONFIG_JSON,
TableKeys.DRIVERMODE_JSON,
TableKeys.OTHERPATHS_JSON,
TableKeys.PIPELINE_JSONS));

var result = query.executeQuery();
Expand All @@ -474,6 +478,8 @@ private HashMap<String, CameraConfiguration> loadCameraConfigs(Connection conn)
var driverMode =
JacksonUtils.deserialize(
result.getString(TableKeys.DRIVERMODE_JSON), DriverModePipelineSettings.class);
var otherPaths =
JacksonUtils.deserialize(result.getString(TableKeys.OTHERPATHS_JSON), String[].class);
List<?> pipelineSettings =
JacksonUtils.deserialize(
result.getString(TableKeys.PIPELINE_JSONS), dummyList.getClass());
Expand All @@ -487,6 +493,7 @@ private HashMap<String, CameraConfiguration> loadCameraConfigs(Connection conn)

config.pipelineSettings = loadedSettings;
config.driveModeSettings = driverMode;
config.otherPaths = otherPaths;
loadedConfigurations.put(uniqueName, config);
}
} catch (SQLException | IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,52 +196,112 @@ protected List<VisionSource> tryMatchUSBCamImpl(boolean createSources) {
* @param loadedUsbCamConfigs The USB {@link CameraConfiguration}s loaded from disk.
* @return the matched configurations.
*/
private List<CameraConfiguration> matchUSBCameras(
protected List<CameraConfiguration> matchUSBCameras(
List<UsbCameraInfo> detectedCamInfos, List<CameraConfiguration> loadedUsbCamConfigs) {
var detectedCameraList = new ArrayList<>(detectedCamInfos);
ArrayList<CameraConfiguration> cameraConfigurations = new ArrayList<>();

// loop over all the configs loaded from disk
List<CameraConfiguration> unmatchedAfterByID = new ArrayList<>(loadedUsbCamConfigs);

// loop over all the configs loaded from disk, attempting to match each camera
// to a config by path-by-id on linux
for (CameraConfiguration config : loadedUsbCamConfigs) {
UsbCameraInfo cameraInfo;

// attempt matching by path and basename
logger.debug(
"Trying to find a match for loaded camera "
+ config.baseName
+ " with path "
+ config.path);
cameraInfo =
detectedCameraList.stream()
.filter(
usbCameraInfo ->
usbCameraInfo.path.equals(config.path)
&& cameraNameToBaseName(usbCameraInfo.name).equals(config.baseName))
.findFirst()
.orElse(null);

// if path based fails, attempt basename only match
if (cameraInfo == null) {
logger.debug("Failed to match by path and name, falling back to name-only match");
if (config.otherPaths.length == 0) {
logger.debug("No valid path-by-id found for config with name " + config.baseName);
} else {
// attempt matching by path and basename
logger.debug(
"Trying to find a match for loaded camera "
+ config.baseName
+ " with path-by-id "
+ config.otherPaths[0]);
cameraInfo =
detectedCameraList.stream()
.filter(
usbCameraInfo ->
usbCameraInfo.otherPaths.length != 0
&& usbCameraInfo.otherPaths[0].equals(config.otherPaths[0])
&& cameraNameToBaseName(usbCameraInfo.name).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!");
detectedCameraList.remove(cameraInfo);
unmatchedAfterByID.remove(config);
cameraConfigurations.add(mergeInfoIntoConfig(config, cameraInfo));
}
}
}

if (!unmatchedAfterByID.isEmpty() && !detectedCameraList.isEmpty()) {
logger.debug("Match by path-by-id failed, falling back to path-only matching");

List<CameraConfiguration> unmatchedAfterByPath = new ArrayList<>(loadedUsbCamConfigs);

// now attempt to match the cameras and configs remaining by normal path
for (CameraConfiguration config : unmatchedAfterByID) {
UsbCameraInfo cameraInfo;

// attempt matching by path and basename
logger.debug(
"Trying to find a match for loaded camera "
+ config.baseName
+ " with path "
+ config.path);
cameraInfo =
detectedCameraList.stream()
.filter(
usbCameraInfo ->
cameraNameToBaseName(usbCameraInfo.name).equals(config.baseName))
usbCameraInfo.path.equals(config.path)
&& cameraNameToBaseName(usbCameraInfo.name).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!");
detectedCameraList.remove(cameraInfo);
unmatchedAfterByPath.remove(config);
cameraConfigurations.add(mergeInfoIntoConfig(config, cameraInfo));
}
}

// 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!");
detectedCameraList.remove(cameraInfo);
cameraConfigurations.add(mergeInfoIntoConfig(config, cameraInfo));
if (!unmatchedAfterByPath.isEmpty() && !detectedCameraList.isEmpty()) {
logger.debug("Match by ID and path failed, falling back to name-only matching");

// if both path and ID based matching fails, attempt basename only match
for (CameraConfiguration config : unmatchedAfterByPath) {
UsbCameraInfo cameraInfo;

logger.debug("Trying to find a match for loaded camera with name " + config.baseName);

cameraInfo =
detectedCameraList.stream()
.filter(
usbCameraInfo ->
cameraNameToBaseName(usbCameraInfo.name).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!");
detectedCameraList.remove(cameraInfo);
cameraConfigurations.add(mergeInfoIntoConfig(config, cameraInfo));
}
}
}
}

// If we have any unmatched cameras left, create a new CameraConfiguration for them here.
// If we have any unmatched cameras left, create a new CameraConfiguration for
// them here.
logger.debug(
"After matching loaded configs " + detectedCameraList.size() + " cameras were unmatched.");
for (UsbCameraInfo info : detectedCameraList) {
Expand All @@ -250,7 +310,7 @@ && cameraNameToBaseName(usbCameraInfo.name).equals(config.baseName))
String uniqueName = baseNameToUniqueName(baseName);

int suffix = 0;
while (containsName(cameraConfigurations, uniqueName)) {
while (containsName(cameraConfigurations, uniqueName) || containsName(uniqueName)) {
suffix++;
uniqueName = String.format("%s (%d)", uniqueName, suffix);
}
Expand Down Expand Up @@ -283,6 +343,27 @@ private CameraConfiguration mergeInfoIntoConfig(CameraConfiguration cfg, UsbCame
cfg.path = info.path;
}

if (cfg.otherPaths.length != info.otherPaths.length) {
logger.debug(
"Updating otherPath config from "
+ Arrays.toString(cfg.otherPaths)
+ " to "
+ Arrays.toString(info.otherPaths));
cfg.otherPaths = info.otherPaths.clone();
} else {
for (int i = 0; i < info.otherPaths.length; i++) {
if (!cfg.otherPaths[i].equals(info.otherPaths[i])) {
logger.debug(
"Updating otherPath config from "
+ Arrays.toString(cfg.otherPaths)
+ " to "
+ Arrays.toString(info.otherPaths));
cfg.otherPaths = info.otherPaths.clone();
break;
}
}
}

return cfg;
}

Expand All @@ -309,7 +390,7 @@ private List<UsbCameraInfo> filterAllowedDevices(List<UsbCameraInfo> allDevices)

private boolean usbCamEquals(UsbCameraInfo a, UsbCameraInfo b) {
return a.path.equals(b.path)
&& a.dev == b.dev
// && a.dev == b.dev (dev is not constant in Windows)
&& a.name.equals(b.name)
&& a.productId == b.productId
&& a.vendorId == b.vendorId;
Expand Down Expand Up @@ -363,4 +444,15 @@ private boolean containsName(
return configList.stream()
.anyMatch(configuration -> configuration.uniqueName.equals(uniqueName));
}

/**
* Check if the current list of known cameras contains the given unique name.
*
* @param uniqueName The unique name.
* @return If the list of cameras contains the unique name.
*/
private boolean containsName(final String uniqueName) {
return VisionModuleManager.getInstance().getModules().stream()
.anyMatch(camera -> camera.visionSource.cameraConfiguration.uniqueName.equals(uniqueName));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,90 @@ public void visionSourceTest() {
ConfigManager.getInstance().load();

inst.tryMatchUSBCamImpl();
var config = new CameraConfiguration("secondTestVideo", "dev/video1");
var config3 =
new CameraConfiguration(
"secondTestVideo",
"secondTestVideo1",
"secondTestVideo1",
"dev/video1",
new String[] {"by-id/123"});
var config4 =
new CameraConfiguration(
"secondTestVideo",
"secondTestVideo2",
"secondTestVideo2",
"dev/video2",
new String[] {"by-id/321"});

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.registerLoadedConfigs(config3, config4);

inst.tryMatchUSBCamImpl(false);

assertTrue(inst.knownUsbCameras.contains(info1));
assertEquals(1, inst.unmatchedLoadedConfigs.size());
assertEquals(2, inst.unmatchedLoadedConfigs.size());

UsbCameraInfo info2 = new UsbCameraInfo(0, "dev/video1", "testVideo", new String[0], 1, 2);

UsbCameraInfo info2 =
new UsbCameraInfo(0, "dev/video1", "secondTestVideo", new String[0], 2, 1);
infoList.add(info2);

var cams = inst.matchUSBCameras(infoList, inst.unmatchedLoadedConfigs);

// assertEquals("testVideo (1)", cams.get(0).uniqueName); // Proper suffixing

inst.tryMatchUSBCamImpl(false);

assertTrue(inst.knownUsbCameras.contains(info2));
assertEquals(2, inst.unmatchedLoadedConfigs.size());

UsbCameraInfo info3 =
new UsbCameraInfo(0, "dev/video2", "secondTestVideo", new String[] {"by-id/123"}, 2, 1);

UsbCameraInfo info4 =
new UsbCameraInfo(0, "dev/video3", "secondTestVideo", new String[] {"by-id/321"}, 3, 1);

infoList.add(info4);

cams = inst.matchUSBCameras(infoList, inst.unmatchedLoadedConfigs);

var cam4 =
cams.stream()
.filter(
cam -> cam.otherPaths.length > 0 && cam.otherPaths[0].equals(config4.otherPaths[0]))
.findFirst()
.orElse(null);
// If this is null, cam4 got matched to config3 instead of config4

assertEquals(cam4.nickname, config4.nickname);

infoList.add(info3);

cams = inst.matchUSBCameras(infoList, inst.unmatchedLoadedConfigs);

inst.tryMatchUSBCamImpl(false);

assertTrue(inst.knownUsbCameras.contains(info2));
assertEquals(2, inst.knownUsbCameras.size());
assertTrue(inst.knownUsbCameras.contains(info3));

var cam3 =
cams.stream()
.filter(
cam -> cam.otherPaths.length > 0 && cam.otherPaths[0].equals(config3.otherPaths[0]))
.findFirst()
.orElse(null);
cam4 =
cams.stream()
.filter(
cam -> cam.otherPaths.length > 0 && cam.otherPaths[0].equals(config4.otherPaths[0]))
.findFirst()
.orElse(null);

assertEquals(cam3.nickname, config3.nickname);
assertEquals(cam4.nickname, config4.nickname);
assertEquals(4, inst.knownUsbCameras.size());
assertEquals(0, inst.unmatchedLoadedConfigs.size());
}
}

0 comments on commit f2c5932

Please sign in to comment.