mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-21 01:01:41 +00:00
Merge branch 'dev' of https://github.com/Chameleon-Vision/chameleon-vision into dev
This commit is contained in:
@@ -3,6 +3,7 @@ package com.chameleonvision;
|
||||
import com.chameleonvision.config.ConfigManager;
|
||||
import com.chameleonvision.network.NetworkManager;
|
||||
import com.chameleonvision.util.Platform;
|
||||
import com.chameleonvision.util.ShellExec;
|
||||
import com.chameleonvision.util.Utilities;
|
||||
import com.chameleonvision.vision.VisionManager;
|
||||
import com.chameleonvision.web.Server;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.chameleonvision.config;
|
||||
|
||||
import com.chameleonvision.util.FileHelper;
|
||||
import com.chameleonvision.util.JacksonHelper;
|
||||
import com.chameleonvision.vision.pipeline.CVPipelineSettings;
|
||||
|
||||
@@ -17,12 +18,22 @@ public class CameraConfig {
|
||||
private final String cameraConfigName;
|
||||
private final CameraJsonConfig preliminaryConfig;
|
||||
|
||||
private final Path configFolderPath;
|
||||
private final Path configPath;
|
||||
private final Path driverModePath;
|
||||
final Path pipelineFolderPath;
|
||||
|
||||
public final PipelineConfig pipelineConfig;
|
||||
|
||||
CameraConfig(CameraJsonConfig config) {
|
||||
preliminaryConfig = config;
|
||||
cameraConfigName = preliminaryConfig.name.replace(' ', '_');
|
||||
pipelineConfig = new PipelineConfig(this);
|
||||
|
||||
configFolderPath = Paths.get(camerasConfigFolderPath.toString(), cameraConfigName);
|
||||
configPath = Paths.get(configFolderPath.toString(), "camera.json");
|
||||
driverModePath = Paths.get(configFolderPath.toString(), "drivermode.json");
|
||||
pipelineFolderPath = Paths.get(configFolderPath.toString(), "pipelines");
|
||||
}
|
||||
|
||||
public FullCameraConfiguration load() {
|
||||
@@ -37,9 +48,9 @@ public class CameraConfig {
|
||||
private CameraJsonConfig loadConfig() {
|
||||
CameraJsonConfig config = preliminaryConfig;
|
||||
try {
|
||||
config = JacksonHelper.deserializer(getConfigPath(), CameraJsonConfig.class);
|
||||
config = JacksonHelper.deserializer(configPath, CameraJsonConfig.class);
|
||||
} catch (IOException e) {
|
||||
System.err.printf("Failed to load camera config: %s - using default.\n", getConfigPath().toString());
|
||||
System.err.printf("Failed to load camera config: %s - using default.\n", configPath.toString());
|
||||
}
|
||||
return config;
|
||||
}
|
||||
@@ -48,18 +59,19 @@ public class CameraConfig {
|
||||
CVPipelineSettings driverMode = new CVPipelineSettings();
|
||||
driverMode.nickname = "DRIVERMODE";
|
||||
try {
|
||||
driverMode = JacksonHelper.deserializer(getDriverModePath(), CVPipelineSettings.class);
|
||||
driverMode = JacksonHelper.deserializer(driverModePath, CVPipelineSettings.class);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to load camera drivermode: " + getDriverModePath().toString());
|
||||
System.err.println("Failed to load camera drivermode: " + driverModePath.toString());
|
||||
}
|
||||
return driverMode;
|
||||
}
|
||||
|
||||
void saveConfig(CameraJsonConfig config) {
|
||||
try {
|
||||
JacksonHelper.serializer(getConfigPath(), config);
|
||||
JacksonHelper.serializer(configPath, config);
|
||||
FileHelper.setFilePerms(configPath);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to save camera config file: " + getConfigPath().toString());
|
||||
System.err.println("Failed to save camera config file: " + configPath.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,20 +81,22 @@ public class CameraConfig {
|
||||
|
||||
public void saveDriverMode(CVPipelineSettings driverMode) {
|
||||
try {
|
||||
JacksonHelper.serializer(getDriverModePath(), driverMode);
|
||||
JacksonHelper.serializer(driverModePath, driverMode);
|
||||
FileHelper.setFilePerms(driverModePath);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to save camera drivermode file: " + getDriverModePath().toString());
|
||||
System.err.println("Failed to save camera drivermode file: " + driverModePath.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void checkFolder() {
|
||||
if (!getConfigFolderExists()) {
|
||||
try {
|
||||
if (!(new File(getConfigFolderPath().toUri()).mkdirs())) {
|
||||
System.err.println("Failed to create camera config folder: " + getConfigFolderPath().toString());
|
||||
if (!(new File(configFolderPath.toUri()).mkdirs())) {
|
||||
System.err.println("Failed to create camera config folder: " + configFolderPath.toString());
|
||||
}
|
||||
FileHelper.setFilePerms(configFolderPath);
|
||||
} catch(Exception e) {
|
||||
System.err.println("Failed to create camera config folder: " + getConfigFolderPath().toString());
|
||||
System.err.println("Failed to create camera config folder: " + configFolderPath.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,9 +104,10 @@ public class CameraConfig {
|
||||
private void checkConfig() {
|
||||
if (!configExists()) {
|
||||
try {
|
||||
JacksonHelper.serializer(getConfigPath(), preliminaryConfig);
|
||||
JacksonHelper.serializer(configPath, preliminaryConfig);
|
||||
FileHelper.setFilePerms(configPath);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to create camera config file: " + getConfigPath().toString());
|
||||
System.err.println("Failed to create camera config file: " + configPath.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,38 +117,23 @@ public class CameraConfig {
|
||||
try {
|
||||
CVPipelineSettings newDriverModeSettings = new CVPipelineSettings();
|
||||
newDriverModeSettings.nickname = "DRIVERMODE";
|
||||
JacksonHelper.serializer(getDriverModePath(), newDriverModeSettings);
|
||||
JacksonHelper.serializer(driverModePath, newDriverModeSettings);
|
||||
FileHelper.setFilePerms(driverModePath);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to create camera drivermode file: " + getDriverModePath().toString());
|
||||
System.err.println("Failed to create camera drivermode file: " + driverModePath.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Path getConfigFolderPath() {
|
||||
return Paths.get(camerasConfigFolderPath.toString(), cameraConfigName);
|
||||
}
|
||||
|
||||
private Path getConfigPath() {
|
||||
return Paths.get(getConfigFolderPath().toString(), "camera.json");
|
||||
}
|
||||
|
||||
private Path getDriverModePath() {
|
||||
return Paths.get(getConfigFolderPath().toString(), "drivermode.json");
|
||||
}
|
||||
|
||||
private boolean getConfigFolderExists() {
|
||||
return Files.exists(getConfigFolderPath());
|
||||
}
|
||||
|
||||
Path getPipelineFolderPath() {
|
||||
return Paths.get(getConfigFolderPath().toString(), "pipelines");
|
||||
return Files.exists(configFolderPath);
|
||||
}
|
||||
|
||||
private boolean configExists() {
|
||||
return getConfigFolderExists() && Files.exists(getConfigPath());
|
||||
return getConfigFolderExists() && Files.exists(configPath);
|
||||
}
|
||||
|
||||
private boolean driverModeExists() {
|
||||
return getConfigFolderExists() && Files.exists(getDriverModePath());
|
||||
return getConfigFolderExists() && Files.exists(driverModePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.chameleonvision.config;
|
||||
|
||||
import com.chameleonvision.util.ProgramDirectoryUtilities;
|
||||
import com.chameleonvision.util.JacksonHelper;
|
||||
import com.chameleonvision.Main;
|
||||
import com.chameleonvision.util.*;
|
||||
import com.chameleonvision.vision.pipeline.CVPipelineSettings;
|
||||
|
||||
import java.io.File;
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
public class ConfigManager {
|
||||
private ConfigManager() {}
|
||||
|
||||
static final Path SettingsPath = Paths.get(ProgramDirectoryUtilities.getProgramDirectory(), "settings");
|
||||
public static final Path SettingsPath = Paths.get(ProgramDirectoryUtilities.getProgramDirectory(), "settings");
|
||||
private static final Path settingsFilePath = Paths.get(SettingsPath.toString(), "settings.json");
|
||||
|
||||
private static final LinkedHashMap<String, CameraConfig> cameraConfigs = new LinkedHashMap<>();
|
||||
@@ -33,6 +33,9 @@ public class ConfigManager {
|
||||
System.err.println("Failed to create settings folder: " + SettingsPath.toString());
|
||||
}
|
||||
Files.createDirectory(SettingsPath);
|
||||
if (!Platform.CurrentPlatform.isWindows()) {
|
||||
new ShellExec().executeBashCommand("sudo chmod -R 0777 " + SettingsPath.toString());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if(!(e instanceof java.nio.file.FileAlreadyExistsException))
|
||||
e.printStackTrace();
|
||||
@@ -45,6 +48,7 @@ public class ConfigManager {
|
||||
if (settingsFileEmpty || !settingsFileExists()) {
|
||||
try {
|
||||
JacksonHelper.serializer(settingsFilePath, settings);
|
||||
FileHelper.setFilePerms(settingsFilePath);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -66,6 +70,7 @@ public class ConfigManager {
|
||||
private static void saveSettingsFile() {
|
||||
try {
|
||||
JacksonHelper.serializer(settingsFilePath, settings);
|
||||
FileHelper.setFilePerms(settingsFilePath);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to save settings.json!");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.chameleonvision.config;
|
||||
|
||||
import com.chameleonvision.util.FileHelper;
|
||||
import com.chameleonvision.util.JacksonHelper;
|
||||
import com.chameleonvision.vision.pipeline.*;
|
||||
import com.chameleonvision.vision.pipeline.impl.CVPipeline2dSettings;
|
||||
@@ -29,15 +30,20 @@ public class PipelineConfig {
|
||||
}
|
||||
|
||||
private void checkFolder() {
|
||||
if ( !(new File(cameraConfig.getPipelineFolderPath().toUri()).mkdirs())) {
|
||||
if (Files.notExists(cameraConfig.getPipelineFolderPath())) {
|
||||
if ( !(new File(cameraConfig.pipelineFolderPath.toUri()).mkdirs())) {
|
||||
if (Files.notExists(cameraConfig.pipelineFolderPath)) {
|
||||
System.err.println("Failed to create pipelines folder.");
|
||||
}
|
||||
}
|
||||
try {
|
||||
FileHelper.setFilePerms(cameraConfig.pipelineFolderPath);
|
||||
} catch (IOException e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private File[] getPipelineFiles() {
|
||||
return new File(cameraConfig.getPipelineFolderPath().toUri()).listFiles();
|
||||
return new File(cameraConfig.pipelineFolderPath.toUri()).listFiles();
|
||||
}
|
||||
|
||||
private boolean folderHasPipelines() {
|
||||
@@ -59,7 +65,7 @@ public class PipelineConfig {
|
||||
String pipelineName = setting.nickname.replace(' ', '_');
|
||||
String prefix = ((setting instanceof CVPipeline2dSettings) ? CVPipeline2DPrefix : CVPipeline3DPrefix) + "-";
|
||||
String fullFileName = prefix + pipelineName + ".json";
|
||||
return Path.of(cameraConfig.getPipelineFolderPath().toString(), fullFileName);
|
||||
return Path.of(cameraConfig.pipelineFolderPath.toString(), fullFileName);
|
||||
}
|
||||
|
||||
private boolean pipelineExists(CVPipelineSettings setting) {
|
||||
@@ -73,12 +79,14 @@ public class PipelineConfig {
|
||||
if (settings instanceof CVPipeline3dSettings) {
|
||||
try {
|
||||
JacksonHelper.serializer(path, settings);
|
||||
FileHelper.setFilePerms(path);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if (settings instanceof CVPipeline2dSettings) {
|
||||
try {
|
||||
JacksonHelper.serializer(path, settings);
|
||||
FileHelper.setFilePerms(path);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.chameleonvision.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFileAttributes;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class FileHelper {
|
||||
private FileHelper() {}
|
||||
|
||||
private static final Set<PosixFilePermission> allReadWriteExecutePerms = new HashSet<>(Arrays.asList(PosixFilePermission.values()));
|
||||
|
||||
public static void setFilePerms(Path path) throws IOException {
|
||||
if (!Platform.CurrentPlatform.isWindows()) {
|
||||
Set<PosixFilePermission> perms = Files.readAttributes(path, PosixFileAttributes.class).permissions();
|
||||
if (!perms.equals(allReadWriteExecutePerms)) {
|
||||
Files.setPosixFilePermissions(path, perms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ public enum Platform {
|
||||
}
|
||||
}
|
||||
|
||||
public static Platform getCurrentPlatform() {
|
||||
private static Platform getCurrentPlatform() {
|
||||
if (OS_NAME.contains("Windows")) {
|
||||
if (OS_ARCH.equals("amd64")) return Platform.WINDOWS_64;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,43 @@ public class ShellExec {
|
||||
this.readError = readError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a bash command. We can handle complex bash commands including
|
||||
* multiple executions (; | && ||), quotes, expansions ($), escapes (\), e.g.:
|
||||
* "cd /abc/def; mv ghi 'older ghi '$(whoami)"
|
||||
* @param command
|
||||
* @return true if bash got started, but your command may have failed.
|
||||
*/
|
||||
public int executeBashCommand(String command) throws IOException {
|
||||
boolean wait = true;
|
||||
boolean success = false;
|
||||
Runtime r = Runtime.getRuntime();
|
||||
// Use bash -c so we can handle things like multi commands separated by ; and
|
||||
// things like quotes, $, |, and \. My tests show that command comes as
|
||||
// one argument to bash, so we do not need to quote it to make it one thing.
|
||||
// Also, exec may object if it does not have an executable file as the first thing,
|
||||
// so having bash here makes it happy provided bash is installed and in path.
|
||||
String[] commands = {"bash", "-c", command};
|
||||
|
||||
Process process = r.exec(commands);
|
||||
|
||||
// Consume streams, older jvm's had a memory leak if streams were not read,
|
||||
// some other jvm+OS combinations may block unless streams are consumed.
|
||||
errorGobbler = new StreamGobbler(process.getErrorStream(), readError);
|
||||
outputGobbler = new StreamGobbler(process.getInputStream(), readOutput);
|
||||
errorGobbler.start();
|
||||
outputGobbler.start();
|
||||
|
||||
exitCode = 0;
|
||||
if (wait) {
|
||||
try {
|
||||
process.waitFor();
|
||||
exitCode = process.exitValue();
|
||||
} catch (InterruptedException ignored) { }
|
||||
}
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in current folder, and wait for process to end
|
||||
* @param command command ("c:/some/folder/script.bat" or "some/folder/script.sh")
|
||||
|
||||
@@ -51,6 +51,8 @@ public class VisionProcess {
|
||||
private NetworkTableEntry ntValidEntry;
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private long lastUIUpdateMs = 0;
|
||||
|
||||
VisionProcess(USBCameraCapture cameraCapture, String name, List<CVPipelineSettings> loadedPipelineSettings) {
|
||||
this.cameraCapture = cameraCapture;
|
||||
|
||||
@@ -144,22 +146,34 @@ public class VisionProcess {
|
||||
}
|
||||
|
||||
private void updateUI(CVPipelineResult data) {
|
||||
if(cameraCapture.getProperties().name.equals(ConfigManager.settings.currentCamera)) {
|
||||
HashMap<String, Object> WebSend = new HashMap<>();
|
||||
HashMap<String, Object> point = new HashMap<>();
|
||||
HashMap<String, Object> calculated = new HashMap<>();
|
||||
List<Double> center = new ArrayList<>();
|
||||
if (data.hasTarget) {
|
||||
if(data instanceof CVPipeline2d.CVPipeline2dResult) {
|
||||
CVPipeline2d.CVPipeline2dResult result = (CVPipeline2d.CVPipeline2dResult) data;
|
||||
CVPipeline2d.Target2d bestTarget = result.targets.get(0);
|
||||
center.add(bestTarget.rawPoint.center.x);
|
||||
center.add(bestTarget.rawPoint.center.y);
|
||||
calculated.put("pitch", bestTarget.pitch);
|
||||
calculated.put("yaw", bestTarget.yaw);
|
||||
calculated.put("area", bestTarget.area);
|
||||
} else if (data instanceof CVPipeline3d.CVPipeline3dResult) {
|
||||
// TODO: (2.1) 3d stuff in UI
|
||||
// 30 "FPS" update rate
|
||||
long currentMillis = System.currentTimeMillis();
|
||||
if (currentMillis - lastUIUpdateMs > 1000/30) {
|
||||
lastUIUpdateMs = currentMillis;
|
||||
|
||||
if(cameraCapture.getProperties().name.equals(ConfigManager.settings.currentCamera)) {
|
||||
HashMap<String, Object> WebSend = new HashMap<>();
|
||||
HashMap<String, Object> point = new HashMap<>();
|
||||
HashMap<String, Object> calculated = new HashMap<>();
|
||||
List<Double> center = new ArrayList<>();
|
||||
if (data.hasTarget) {
|
||||
if(data instanceof CVPipeline2d.CVPipeline2dResult) {
|
||||
CVPipeline2d.CVPipeline2dResult result = (CVPipeline2d.CVPipeline2dResult) data;
|
||||
CVPipeline2d.Target2d bestTarget = result.targets.get(0);
|
||||
center.add(bestTarget.rawPoint.center.x);
|
||||
center.add(bestTarget.rawPoint.center.y);
|
||||
calculated.put("pitch", bestTarget.pitch);
|
||||
calculated.put("yaw", bestTarget.yaw);
|
||||
calculated.put("area", bestTarget.area);
|
||||
} else if (data instanceof CVPipeline3d.CVPipeline3dResult) {
|
||||
// TODO: (2.1) 3d stuff in UI
|
||||
} else {
|
||||
center.add(null);
|
||||
center.add(null);
|
||||
calculated.put("pitch", null);
|
||||
calculated.put("yaw", null);
|
||||
calculated.put("area", null);
|
||||
}
|
||||
} else {
|
||||
center.add(null);
|
||||
center.add(null);
|
||||
@@ -167,18 +181,12 @@ public class VisionProcess {
|
||||
calculated.put("yaw", null);
|
||||
calculated.put("area", null);
|
||||
}
|
||||
} else {
|
||||
center.add(null);
|
||||
center.add(null);
|
||||
calculated.put("pitch", null);
|
||||
calculated.put("yaw", null);
|
||||
calculated.put("area", null);
|
||||
point.put("fps", visionRunnable.fps);
|
||||
point.put("calculated", calculated);
|
||||
point.put("rawPoint", center);
|
||||
WebSend.put("point", point);
|
||||
SocketHandler.broadcastMessage(WebSend);
|
||||
}
|
||||
point.put("fps", visionRunnable.fps);
|
||||
point.put("calculated", calculated);
|
||||
point.put("rawPoint", center);
|
||||
WebSend.put("point", point);
|
||||
SocketHandler.broadcastMessage(WebSend);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user