diff --git a/chameleon-server/src/main/java/com/chameleonvision/Main.java b/chameleon-server/src/main/java/com/chameleonvision/Main.java index 7223da8df..4cefbc29e 100644 --- a/chameleon-server/src/main/java/com/chameleonvision/Main.java +++ b/chameleon-server/src/main/java/com/chameleonvision/Main.java @@ -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; diff --git a/chameleon-server/src/main/java/com/chameleonvision/config/CameraConfig.java b/chameleon-server/src/main/java/com/chameleonvision/config/CameraConfig.java index fd0262cdf..2bbcb68ec 100644 --- a/chameleon-server/src/main/java/com/chameleonvision/config/CameraConfig.java +++ b/chameleon-server/src/main/java/com/chameleonvision/config/CameraConfig.java @@ -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); } } diff --git a/chameleon-server/src/main/java/com/chameleonvision/config/ConfigManager.java b/chameleon-server/src/main/java/com/chameleonvision/config/ConfigManager.java index 3003b6d02..85a00b8fb 100644 --- a/chameleon-server/src/main/java/com/chameleonvision/config/ConfigManager.java +++ b/chameleon-server/src/main/java/com/chameleonvision/config/ConfigManager.java @@ -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 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!"); } diff --git a/chameleon-server/src/main/java/com/chameleonvision/config/PipelineConfig.java b/chameleon-server/src/main/java/com/chameleonvision/config/PipelineConfig.java index 61376ed0e..03c375160 100644 --- a/chameleon-server/src/main/java/com/chameleonvision/config/PipelineConfig.java +++ b/chameleon-server/src/main/java/com/chameleonvision/config/PipelineConfig.java @@ -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(); } diff --git a/chameleon-server/src/main/java/com/chameleonvision/util/FileHelper.java b/chameleon-server/src/main/java/com/chameleonvision/util/FileHelper.java new file mode 100644 index 000000000..88ea67921 --- /dev/null +++ b/chameleon-server/src/main/java/com/chameleonvision/util/FileHelper.java @@ -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 allReadWriteExecutePerms = new HashSet<>(Arrays.asList(PosixFilePermission.values())); + + public static void setFilePerms(Path path) throws IOException { + if (!Platform.CurrentPlatform.isWindows()) { + Set perms = Files.readAttributes(path, PosixFileAttributes.class).permissions(); + if (!perms.equals(allReadWriteExecutePerms)) { + Files.setPosixFilePermissions(path, perms); + } + } + } +} diff --git a/chameleon-server/src/main/java/com/chameleonvision/util/Platform.java b/chameleon-server/src/main/java/com/chameleonvision/util/Platform.java index 4bc4e6712..d0c78e27b 100644 --- a/chameleon-server/src/main/java/com/chameleonvision/util/Platform.java +++ b/chameleon-server/src/main/java/com/chameleonvision/util/Platform.java @@ -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; } diff --git a/chameleon-server/src/main/java/com/chameleonvision/util/ShellExec.java b/chameleon-server/src/main/java/com/chameleonvision/util/ShellExec.java index f0fa0f1cd..60856cf79 100644 --- a/chameleon-server/src/main/java/com/chameleonvision/util/ShellExec.java +++ b/chameleon-server/src/main/java/com/chameleonvision/util/ShellExec.java @@ -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") diff --git a/chameleon-server/src/main/java/com/chameleonvision/vision/VisionProcess.java b/chameleon-server/src/main/java/com/chameleonvision/vision/VisionProcess.java index c99e5af0b..af1c163f5 100644 --- a/chameleon-server/src/main/java/com/chameleonvision/vision/VisionProcess.java +++ b/chameleon-server/src/main/java/com/chameleonvision/vision/VisionProcess.java @@ -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 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 WebSend = new HashMap<>(); - HashMap point = new HashMap<>(); - HashMap calculated = new HashMap<>(); - List 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 WebSend = new HashMap<>(); + HashMap point = new HashMap<>(); + HashMap calculated = new HashMap<>(); + List 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); } }