diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java b/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java index 7060e6307..b990ff242 100644 --- a/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java +++ b/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java @@ -128,7 +128,22 @@ public class HardwareConfig { this.blacklistedResIndices = blacklistedResIndices; } + /** @return True if the FOV has been preset to a sane value, false otherwise */ public final boolean hasPresetFOV() { return vendorFOV > 0; } + + /** @return True if any command has been configured to a non-default empty, false otherwise */ + public final boolean hasCommandsConfigured() { + return cpuTempCommand != "" + || cpuMemoryCommand != "" + || cpuUtilCommand != "" + || cpuThrottleReasonCmd != "" + || cpuUptimeCommand != "" + || gpuMemoryCommand != "" + || ramUtilCommand != "" + || ledBlinkCommand != "" + || gpuMemUsageCommand != "" + || diskUsageCommand != ""; + } } diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java b/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java index 58d825d41..4230387a9 100644 --- a/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java +++ b/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java @@ -114,7 +114,7 @@ public class PhotonConfiguration { ? "Zerocopy MMAL on " + PicamJNI.getSensorModel().getFriendlyName() : ""); // TODO add support for other types of GPU accel generalSubmap.put("hardwareModel", hardwareConfig.deviceName); - generalSubmap.put("hardwarePlatform", Platform.getCurrentPlatform().toString()); + generalSubmap.put("hardwarePlatform", Platform.getPlatformName()); settingsSubmap.put("general", generalSubmap); map.put("settings", settingsSubmap); diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java b/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java index de8ddd4ae..799e48874 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java @@ -27,7 +27,7 @@ import org.photonvision.common.dataflow.networktables.NTDataChangeListener; import org.photonvision.common.dataflow.networktables.NetworkTablesManager; import org.photonvision.common.hardware.GPIO.CustomGPIO; import org.photonvision.common.hardware.GPIO.pi.PigpioSocket; -import org.photonvision.common.hardware.metrics.MetricsBase; +import org.photonvision.common.hardware.metrics.MetricsManager; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; import org.photonvision.common.util.ShellExec; @@ -41,6 +41,8 @@ public class HardwareManager { private final HardwareConfig hardwareConfig; private final HardwareSettings hardwareSettings; + private final MetricsManager metricsManager; + @SuppressWarnings({"FieldCanBeLocal", "unused"}) private final StatusLED statusLED; @@ -65,8 +67,11 @@ public class HardwareManager { private HardwareManager(HardwareConfig hardwareConfig, HardwareSettings hardwareSettings) { this.hardwareConfig = hardwareConfig; this.hardwareSettings = hardwareSettings; + + this.metricsManager = new MetricsManager(); + this.metricsManager.setConfig(hardwareConfig); + CustomGPIO.setConfig(hardwareConfig); - MetricsBase.setConfig(hardwareConfig); if (Platform.isRaspberryPi()) { pigpioSocket = new PigpioSocket(); @@ -162,4 +167,8 @@ public class HardwareManager { public HardwareConfig getConfig() { return hardwareConfig; } + + public void publishMetrics() { + metricsManager.publishMetrics(); + } } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/PiVersion.java b/photon-core/src/main/java/org/photonvision/common/hardware/PiVersion.java index 6d8ab75fe..dbf23ac7e 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/PiVersion.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/PiVersion.java @@ -17,6 +17,9 @@ package org.photonvision.common.hardware; +import java.io.IOException; +import org.photonvision.common.util.ShellExec; + public enum PiVersion { PI_B("Pi Model B"), COMPUTE_MODULE("Compute Module Rev"), @@ -28,17 +31,41 @@ public enum PiVersion { UNKNOWN("UNKNOWN"); private final String identifier; + private static final ShellExec shell = new ShellExec(true, false); + private static final PiVersion currentPiVersion = calcPiVersion(); PiVersion(String s) { this.identifier = s.toLowerCase(); } public static PiVersion getPiVersion() { + return currentPiVersion; + } + + private static PiVersion calcPiVersion() { if (!Platform.isRaspberryPi()) return PiVersion.UNKNOWN; - String piString = Platform.currentPiVersionStr; + String piString = getPiVersionString(); for (PiVersion p : PiVersion.values()) { if (piString.toLowerCase().contains(p.identifier)) return p; } return UNKNOWN; } + + // Query /proc/device-tree/model. This should return the model of the pi + // Versions here: + // https://github.com/raspberrypi/linux/blob/rpi-5.10.y/arch/arm/boot/dts/bcm2710-rpi-cm3.dts + private static String getPiVersionString() { + if (!Platform.isRaspberryPi()) return ""; + try { + shell.executeBashCommand("cat /proc/device-tree/model"); + } catch (IOException e) { + e.printStackTrace(); + } + if (shell.getExitCode() == 0) { + // We expect it to be in the format "raspberry pi X model X" + return shell.getOutput(); + } + + return ""; + } } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/Platform.java b/photon-core/src/main/java/org/photonvision/common/hardware/Platform.java index e6f9d046b..5644b53e0 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/Platform.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/Platform.java @@ -17,6 +17,7 @@ package org.photonvision.common.hardware; +import com.jogamp.common.os.Platform.OSType; import edu.wpi.first.util.RuntimeDetector; import java.io.BufferedReader; import java.io.IOException; @@ -27,53 +28,80 @@ import org.photonvision.common.util.ShellExec; @SuppressWarnings("unused") public enum Platform { // WPILib Supported (JNI) - WINDOWS_32("Windows x32"), - WINDOWS_64("Windows x64"), - LINUX_64("Linux x64"), - LINUX_RASPBIAN("Linux Raspbian"), // Raspberry Pi 3/4 - LINUX_AARCH64BIONIC("Linux AARCH64 Bionic"), // Jetson Nano, Jetson TX2 - // PhotonVision Supported (Manual install) - LINUX_ARM32("Linux ARM32"), // ODROID XU4, C1+ - LINUX_ARM64("Linux ARM64"), // ODROID C2, N2 + WINDOWS_64("Windows x64", false, OSType.WINDOWS, true), + LINUX_32("Linux x86", false, OSType.LINUX, true), + LINUX_64("Linux x64", false, OSType.LINUX, true), + LINUX_RASPBIAN32( + "Linux Raspbian 32-bit", true, OSType.LINUX, true), // Raspberry Pi 3/4 with a 32-bit image + LINUX_RASPBIAN64( + "Linux Raspbian 64-bit", true, OSType.LINUX, true), // Raspberry Pi 3/4 with a 64-bit image + LINUX_AARCH64("Linux AARCH64", false, OSType.LINUX, true), // Jetson Nano, Jetson TX2 + + // PhotonVision Supported (Manual build/install) + LINUX_ARM32("Linux ARM32", false, OSType.LINUX, true), // ODROID XU4, C1+ + LINUX_ARM64("Linux ARM64", false, OSType.LINUX, true), // ODROID C2, N2 // Completely unsupported - UNSUPPORTED("Unsupported Platform"); + WINDOWS_32("Windows x86", false, OSType.WINDOWS, false), + MACOS("Mac OS", false, OSType.MACOS, false), + UNKNOWN("Unsupported Platform", false, OSType.UNKNOWN, false); + + private enum OSType { + WINDOWS, + LINUX, + MACOS, + UNKNOWN + } private static final ShellExec shell = new ShellExec(true, false); - public final String value; - public static final boolean isRoot = checkForRoot(); + public final String description; + public final boolean isPi; + public final OSType osType; + public final boolean isSupported; - Platform(String value) { - this.value = value; + // Set once at init, shouldn't be needed after. + private static final Platform currentPlatform = getCurrentPlatform(); + private static final boolean isRoot = checkForRoot(); + + Platform(String description, boolean isPi, OSType osType, boolean isSupported) { + this.description = description; + this.isPi = isPi; + this.osType = osType; + this.isSupported = isSupported; } - private static final String OS_NAME = System.getProperty("os.name"); - private static final String OS_ARCH = System.getProperty("os.arch"); - - // These are queried on init and should never change after - public static final Platform currentPlatform = getCurrentPlatform(); - static final String currentPiVersionStr = getPiVersionString(); - public static final PiVersion currentPiVersion = PiVersion.getPiVersion(); - - private static final String UnknownPlatformString = - String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH); - - public static boolean isWindows() { - return currentPlatform == WINDOWS_64 || currentPlatform == WINDOWS_32; - } + ////////////////////////////////////////////////////// + // Public API + // Checks specifically if unix shell and API are supported public static boolean isLinux() { - return currentPlatform == LINUX_64 - || currentPlatform == LINUX_RASPBIAN - || currentPlatform == LINUX_AARCH64BIONIC - || currentPlatform == LINUX_ARM32 - || currentPlatform == LINUX_ARM64; + return currentPlatform.osType == OSType.LINUX; } public static boolean isRaspberryPi() { - return currentPlatform.equals(LINUX_RASPBIAN); + return currentPlatform.isPi; } + public static String getPlatformName() { + if (currentPlatform.equals(UNKNOWN)) { + return UnknownPlatformString; + } else { + return currentPlatform.description; + } + } + + public static boolean isRoot() { + return isRoot; + } + + ////////////////////////////////////////////////////// + + // Debug info related to unknown platforms for debug help + private static final String OS_NAME = System.getProperty("os.name"); + private static final String OS_ARCH = System.getProperty("os.arch"); + private static final String UnknownPlatformString = + String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH); + @SuppressWarnings("StatementWithEmptyBody") private static boolean checkForRoot() { if (isLinux()) { @@ -97,59 +125,90 @@ public enum Platform { return false; } - public static Platform getCurrentPlatform() { + private static Platform getCurrentPlatform() { if (RuntimeDetector.isWindows()) { - if (RuntimeDetector.is32BitIntel()) return WINDOWS_32; - if (RuntimeDetector.is64BitIntel()) return WINDOWS_64; + if (RuntimeDetector.is32BitIntel()) { + return WINDOWS_32; + } else if (RuntimeDetector.is64BitIntel()) { + return WINDOWS_64; + } else { + // please don't try this + return UNKNOWN; + } } if (RuntimeDetector.isMac()) { - if (RuntimeDetector.is32BitIntel()) return UNSUPPORTED; + // TODO - once we have real support, this might have to be more granular + return MACOS; } if (RuntimeDetector.isLinux()) { - if (RuntimeDetector.is32BitIntel()) return UNSUPPORTED; - if (RuntimeDetector.is64BitIntel()) return LINUX_64; - if (isRaspbian()) return LINUX_RASPBIAN; - } - - System.out.println(UnknownPlatformString); - return Platform.UNSUPPORTED; - } - - public String toString() { - if (this.equals(UNSUPPORTED)) { - return UnknownPlatformString; - } else { - return this.value; - } - } - - // Querry /proc/device-tree/model. This should return the model of the pi - // Versions here: - // https://github.com/raspberrypi/linux/blob/rpi-5.10.y/arch/arm/boot/dts/bcm2710-rpi-cm3.dts - private static String getPiVersionString() { - if (!isRaspberryPi()) return ""; - try { - shell.executeBashCommand("cat /proc/device-tree/model"); - } catch (IOException e) { - e.printStackTrace(); - } - if (shell.getExitCode() == 0) { - // We expect it to be in the format "raspberry pi X model X" - return shell.getOutput(); - } - - return ""; - } - - private static boolean isRaspbian() { - try (BufferedReader reader = Files.newBufferedReader(Paths.get("/etc/os-release"))) { - String value = reader.readLine(); - if (value == null) { - return false; + if (isPiSBC()) { + if (RuntimeDetector.isArm32()) { + return LINUX_RASPBIAN32; + } else if (RuntimeDetector.isArm64()) { + return LINUX_RASPBIAN64; + } else { + // Unknown/exotic installation + return UNKNOWN; + } + } else if (isJetsonSBC()) { + if (RuntimeDetector.isArm64()) { + // TODO - do we need to check OS version? + return LINUX_AARCH64; + } else { + // Unknown/exotic installation + return UNKNOWN; + } + } else if (RuntimeDetector.is64BitIntel()) { + return LINUX_64; + } else if (RuntimeDetector.is32BitIntel()) { + return LINUX_32; + } else if (RuntimeDetector.isArm64()) { + // TODO - os detection needed? + return LINUX_AARCH64; + } else { + // Unknown or otherwise unsupported platform + return Platform.UNKNOWN; + } + } + + // If we fall through all the way to here, + return Platform.UNKNOWN; + } + + // Check for various known SBC types + private static boolean isPiSBC() { + return fileHasText("/proc/cpuinfo", "Raspberry Pi"); + } + + private static boolean isJetsonSBC() { + // https://forums.developer.nvidia.com/t/how-to-recognize-jetson-nano-device/146624 + return fileHasText("/proc/device-tree/model", "NVIDIA Jetson"); + } + + // Checks for various names of linux OS + private static boolean isStretch() { + // TODO - this is a total guess + return fileHasText("/etc/os-release", "Stretch"); + } + + private static boolean isBuster() { + // TODO - this is a total guess + return fileHasText("/etc/os-release", "Buster"); + } + + private static boolean fileHasText(String filename, String text) { + try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) { + while (true) { + String value = reader.readLine(); + if (value == null) { + return false; + + } else if (value.contains(text)) { + return true; + } // else, next line } - return value.contains("Raspbian"); } catch (IOException ex) { return false; } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/CPUMetrics.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/CPUMetrics.java deleted file mode 100644 index 36cb023a4..000000000 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/CPUMetrics.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) Photon Vision. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.photonvision.common.hardware.metrics; - -public class CPUMetrics extends MetricsBase { - private String cpuMemSplit = null; - - public String getMemory() { - if (cpuMemoryCommand.isEmpty()) return ""; - if (cpuMemSplit == null) { - cpuMemSplit = execute(cpuMemoryCommand); - } - return cpuMemSplit; - } - - public String getTemp() { - if (cpuTemperatureCommand.isEmpty()) return ""; - try { - return execute(cpuTemperatureCommand); - } catch (Exception e) { - return "N/A"; - } - } - - public String getUtilization() { - return execute(cpuUtilizationCommand); - } - - public String getUptime() { - return execute(cpuUptimeCommand); - } - - public String getThrottleReason() { - return execute(cpuThrottleReasonCmd); - } -} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DiskMetrics.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DiskMetrics.java deleted file mode 100644 index e6b672ef9..000000000 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DiskMetrics.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) Photon Vision. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.photonvision.common.hardware.metrics; - -public class DiskMetrics extends MetricsBase { - public String getUsedDiskPct() { - if (diskUsageCommand.isEmpty()) return ""; - return execute(diskUsageCommand); - } -} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsBase.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsBase.java deleted file mode 100644 index ef19a2b6a..000000000 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsBase.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) Photon Vision. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.photonvision.common.hardware.metrics; - -import java.io.PrintWriter; -import java.io.StringWriter; -import org.photonvision.common.configuration.HardwareConfig; -import org.photonvision.common.hardware.Platform; -import org.photonvision.common.logging.LogGroup; -import org.photonvision.common.logging.Logger; -import org.photonvision.common.util.ShellExec; - -public abstract class MetricsBase { - static final Logger logger = new Logger(MetricsBase.class, LogGroup.General); - // CPU - public static String cpuMemoryCommand = "vcgencmd get_mem arm | grep -Eo '[0-9]+'"; - public static String cpuTemperatureCommand = - "sed 's/.\\{3\\}$/.&/' <<< cat /sys/class/thermal/thermal_zone0/temp"; - public static String cpuUtilizationCommand = - "top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk '{print 100 - $1}'"; - - public static String cpuThrottleReasonCmd = - "if (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x01 )) != 0x00 )); then echo \"LOW VOLTAGE\"; " - + "elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x08 )) != 0x00 )); then echo \"HIGH TEMP\"; " - + "elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x10000 )) != 0x00 )); then echo \"Prev. Low Voltage\"; " - + "elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x80000 )) != 0x00 )); then echo \"Prev. High Temp\"; " - + " else echo \"None\"; fi"; - - public static String cpuUptimeCommand = "uptime -p | cut -c 4-"; - - // GPU - public static String gpuMemoryCommand = "vcgencmd get_mem gpu | grep -Eo '[0-9]+'"; - public static String gpuMemUsageCommand = "vcgencmd get_mem malloc | grep -Eo '[0-9]+'"; - - // RAM - public static String ramUsageCommand = "free --mega | awk -v i=2 -v j=3 'FNR == i {print $j}'"; - - // Disk - public static String diskUsageCommand = "df ./ --output=pcent | tail -n +2"; - - private static ShellExec runCommand = new ShellExec(true, true); - - public static void setConfig(HardwareConfig config) { - if (Platform.isRaspberryPi()) return; - cpuMemoryCommand = config.cpuMemoryCommand; - cpuTemperatureCommand = config.cpuTempCommand; - cpuUtilizationCommand = config.cpuUtilCommand; - cpuThrottleReasonCmd = config.cpuThrottleReasonCmd; - cpuUptimeCommand = config.cpuUptimeCommand; - - gpuMemoryCommand = config.gpuMemoryCommand; - gpuMemUsageCommand = config.gpuMemUsageCommand; - - diskUsageCommand = config.diskUsageCommand; - - ramUsageCommand = config.ramUtilCommand; - } - - public static synchronized String execute(String command) { - try { - runCommand.executeBashCommand(command); - return runCommand.getOutput(); - } catch (Exception e) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); - - logger.error( - "Command: \"" - + command - + "\" returned an error!" - + "\nOutput Received: " - + runCommand.getOutput() - + "\nStandard Error: " - + runCommand.getError() - + "\nCommand completed: " - + runCommand.isOutputCompleted() - + "\nError completed: " - + runCommand.isErrorCompleted() - + "\nExit code: " - + runCommand.getExitCode() - + "\n Exception: " - + e.toString() - + sw.toString()); - return ""; - } - } -} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsManager.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsManager.java new file mode 100644 index 000000000..f0320910e --- /dev/null +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsManager.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.photonvision.common.hardware.metrics; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import org.photonvision.common.configuration.HardwareConfig; +import org.photonvision.common.dataflow.DataChangeService; +import org.photonvision.common.dataflow.events.OutgoingUIEvent; +import org.photonvision.common.hardware.Platform; +import org.photonvision.common.hardware.metrics.cmds.CmdBase; +import org.photonvision.common.hardware.metrics.cmds.FileCmds; +import org.photonvision.common.hardware.metrics.cmds.LinuxCmds; +import org.photonvision.common.hardware.metrics.cmds.PiCmds; +import org.photonvision.common.logging.LogGroup; +import org.photonvision.common.logging.Logger; +import org.photonvision.common.util.ShellExec; + +public class MetricsManager { + final Logger logger = new Logger(MetricsManager.class, LogGroup.General); + + CmdBase cmds; + + private ShellExec runCommand = new ShellExec(true, true); + + public void setConfig(HardwareConfig config) { + if (config.hasCommandsConfigured()) { + cmds = new FileCmds(); + } else if (Platform.isRaspberryPi()) { + cmds = new PiCmds(); // Pi's can use a hardcoded command set + } else if (Platform.isLinux()) { + cmds = new LinuxCmds(); // Linux/Unix platforms assume a nominal command set + } else { + cmds = new CmdBase(); // default - base has no commands + } + + cmds.initCmds(config); + } + + public String safeExecute(String str) { + if (str.isEmpty()) return ""; + try { + return execute(str); + } catch (Exception e) { + return "****"; + } + } + + private String cpuMemSave = null; + + public String getMemory() { + if (cmds.cpuMemoryCommand.isEmpty()) return ""; + if (cpuMemSave == null) { + // save the value and only run it once + cpuMemSave = execute(cmds.cpuMemoryCommand); + } + return cpuMemSave; + } + + public String getTemp() { + return safeExecute(cmds.cpuTemperatureCommand); + } + + public String getUtilization() { + return safeExecute(cmds.cpuUtilizationCommand); + } + + public String getUptime() { + return safeExecute(cmds.cpuUptimeCommand); + } + + public String getThrottleReason() { + return safeExecute(cmds.cpuThrottleReasonCmd); + } + + private String gpuMemSave = null; + + public String getGPUMemorySplit() { + if (gpuMemSave == null) { + // only needs to run once + gpuMemSave = safeExecute(cmds.gpuMemoryCommand); + } + return gpuMemSave; + } + + public String getMallocedMemory() { + return safeExecute(cmds.gpuMemUsageCommand); + } + + public String getUsedDiskPct() { + return safeExecute(cmds.diskUsageCommand); + } + + // TODO: Output in MBs for consistency + public String getUsedRam() { + return safeExecute(cmds.ramUsageCommand); + } + + public void publishMetrics() { + logger.debug("Publishing Metrics..."); + final var metrics = new HashMap(); + + metrics.put("cpuTemp", this.getTemp()); + metrics.put("cpuUtil", this.getUtilization()); + metrics.put("cpuMem", this.getMemory()); + metrics.put("cpuThr", this.getThrottleReason()); + metrics.put("cpuUptime", this.getUptime()); + metrics.put("gpuMem", this.getGPUMemorySplit()); + metrics.put("ramUtil", this.getUsedRam()); + metrics.put("gpuMemUtil", this.getMallocedMemory()); + metrics.put("diskUtilPct", this.getUsedDiskPct()); + + DataChangeService.getInstance().publishEvent(OutgoingUIEvent.wrappedOf("metrics", metrics)); + } + + public synchronized String execute(String command) { + try { + runCommand.executeBashCommand(command); + return runCommand.getOutput(); + } catch (Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + + logger.error( + "Command: \"" + + command + + "\" returned an error!" + + "\nOutput Received: " + + runCommand.getOutput() + + "\nStandard Error: " + + runCommand.getError() + + "\nCommand completed: " + + runCommand.isOutputCompleted() + + "\nError completed: " + + runCommand.isErrorCompleted() + + "\nExit code: " + + runCommand.getExitCode() + + "\n Exception: " + + e.toString() + + sw.toString()); + return ""; + } + } +} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java deleted file mode 100644 index 1d58c3af9..000000000 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) Photon Vision. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.photonvision.common.hardware.metrics; - -import java.util.HashMap; -import org.photonvision.common.dataflow.DataChangeService; -import org.photonvision.common.dataflow.events.OutgoingUIEvent; -import org.photonvision.common.hardware.Platform; -import org.photonvision.common.logging.LogGroup; -import org.photonvision.common.logging.Logger; -import org.photonvision.common.util.TimedTaskManager; - -public class MetricsPublisher { - private static final Logger logger = new Logger(MetricsPublisher.class, LogGroup.General); - private static CPUMetrics cpuMetrics; - private static GPUMetrics gpuMetrics; - private static RAMMetrics ramMetrics; - private static DiskMetrics diskMetrics; - - public static MetricsPublisher getInstance() { - return Singleton.INSTANCE; - } - - private MetricsPublisher() { - cpuMetrics = new CPUMetrics(); - gpuMetrics = new GPUMetrics(); - ramMetrics = new RAMMetrics(); - diskMetrics = new DiskMetrics(); - } - - public void stopTask() { - TimedTaskManager.getInstance().cancelTask("Metrics"); - logger.info("This device does not support running bash commands. Stopped metrics thread."); - } - - public void publish() { - if (!Platform.isRaspberryPi()) { - logger.debug("Ignoring metrics on non-Pi devices"); - return; - } - - logger.debug("Publishing Metrics..."); - final var metrics = new HashMap(); - - metrics.put("cpuTemp", cpuMetrics.getTemp()); - metrics.put("cpuUtil", cpuMetrics.getUtilization()); - metrics.put("cpuMem", cpuMetrics.getMemory()); - metrics.put("cpuThr", cpuMetrics.getThrottleReason()); - metrics.put("cpuUptime", cpuMetrics.getUptime()); - metrics.put("gpuMem", gpuMetrics.getGPUMemorySplit()); - metrics.put("ramUtil", ramMetrics.getUsedRam()); - metrics.put("gpuMemUtil", gpuMetrics.getMallocedMemory()); - metrics.put("diskUtilPct", diskMetrics.getUsedDiskPct()); - - DataChangeService.getInstance().publishEvent(OutgoingUIEvent.wrappedOf("metrics", metrics)); - } - - private static class Singleton { - public static final MetricsPublisher INSTANCE = new MetricsPublisher(); - } -} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/RAMMetrics.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/RAMMetrics.java deleted file mode 100644 index bf0bd2b5a..000000000 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/RAMMetrics.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) Photon Vision. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.photonvision.common.hardware.metrics; - -public class RAMMetrics extends MetricsBase { - // TODO: Output in MBs for consistency - public String getUsedRam() { - if (ramUsageCommand.isEmpty()) return ""; - return execute(ramUsageCommand); - } -} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/GPUMetrics.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/CmdBase.java similarity index 51% rename from photon-core/src/main/java/org/photonvision/common/hardware/metrics/GPUMetrics.java rename to photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/CmdBase.java index 6319280a9..298ca7dd5 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/GPUMetrics.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/CmdBase.java @@ -15,19 +15,26 @@ * along with this program. If not, see . */ -package org.photonvision.common.hardware.metrics; +package org.photonvision.common.hardware.metrics.cmds; -public class GPUMetrics extends MetricsBase { - private String gpuMemSplit = null; +import org.photonvision.common.configuration.HardwareConfig; - public String getGPUMemorySplit() { - if (gpuMemSplit == null) { - gpuMemSplit = execute(gpuMemoryCommand); - } - return gpuMemSplit; - } +public class CmdBase { + // CPU + public String cpuMemoryCommand = ""; + public String cpuTemperatureCommand = ""; + public String cpuUtilizationCommand = ""; + public String cpuThrottleReasonCmd = ""; + public String cpuUptimeCommand = ""; + // GPU + public String gpuMemoryCommand = ""; + public String gpuMemUsageCommand = ""; + // RAM + public String ramUsageCommand = ""; + // Disk + public String diskUsageCommand = ""; - public String getMallocedMemory() { - return execute(gpuMemUsageCommand); + public void initCmds(HardwareConfig config) { + return; // default - do nothing } } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/FileCmds.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/FileCmds.java new file mode 100644 index 000000000..7a2746903 --- /dev/null +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/FileCmds.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.photonvision.common.hardware.metrics.cmds; + +import org.photonvision.common.configuration.HardwareConfig; + +public class FileCmds extends CmdBase { + @Override + public void initCmds(HardwareConfig config) { + cpuMemoryCommand = config.cpuMemoryCommand; + cpuTemperatureCommand = config.cpuTempCommand; + cpuUtilizationCommand = config.cpuUtilCommand; + cpuThrottleReasonCmd = config.cpuThrottleReasonCmd; + cpuUptimeCommand = config.cpuUptimeCommand; + + gpuMemoryCommand = config.gpuMemoryCommand; + gpuMemUsageCommand = config.gpuMemUsageCommand; + + diskUsageCommand = config.diskUsageCommand; + + ramUsageCommand = config.ramUtilCommand; + } +} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/LinuxCmds.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/LinuxCmds.java new file mode 100644 index 000000000..d151b2278 --- /dev/null +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/LinuxCmds.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.photonvision.common.hardware.metrics.cmds; + +import org.photonvision.common.configuration.HardwareConfig; + +public class LinuxCmds extends CmdBase { + public void initCmds(HardwareConfig config) { + // CPU + cpuMemoryCommand = "awk '/MemTotal:/ {print int($2 / 1000);}' /proc/meminfo"; + + // TODO: boards have lots of thermal devices. Hard to pick the CPU + + cpuUtilizationCommand = + "top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk '{print 100 - $1}'"; + + cpuUptimeCommand = "uptime -p | cut -c 4-"; + + // RAM + ramUsageCommand = "awk '/MemFree:/ {print int($2 / 1000);}' /proc/meminfo"; + + // Disk + diskUsageCommand = "df ./ --output=pcent | tail -n +2"; + } +} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/PiCmds.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/PiCmds.java new file mode 100644 index 000000000..6eb005acc --- /dev/null +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/PiCmds.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.photonvision.common.hardware.metrics.cmds; + +import org.photonvision.common.configuration.HardwareConfig; + +public class PiCmds extends LinuxCmds { + /** Applies pi-specific commands, ignoring any input configuration */ + public void initCmds(HardwareConfig config) { + super.initCmds(config); + + // CPU + cpuMemoryCommand = "vcgencmd get_mem arm | grep -Eo '[0-9]+'"; + cpuTemperatureCommand = "sed 's/.\\{3\\}$/.&/' /sys/class/thermal/thermal_zone0/temp"; + cpuThrottleReasonCmd = + "if (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x01 )) != 0x00 )); then echo \"LOW VOLTAGE\"; " + + " elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x08 )) != 0x00 )); then echo \"HIGH TEMP\"; " + + " elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x10000 )) != 0x00 )); then echo \"Prev. Low Voltage\"; " + + " elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x80000 )) != 0x00 )); then echo \"Prev. High Temp\"; " + + " else echo \"None\"; fi"; + + // GPU + gpuMemoryCommand = "vcgencmd get_mem gpu | grep -Eo '[0-9]+'"; + gpuMemUsageCommand = "vcgencmd get_mem malloc | grep -Eo '[0-9]+'"; + } +} diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java index eb90d4dcd..757efaf93 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java @@ -47,7 +47,7 @@ public class NetworkManager { var config = ConfigManager.getInstance().getConfig().getNetworkConfig(); logger.info("Setting " + config.connectionType + " with team team " + config.teamNumber); if (Platform.isLinux()) { - if (!Platform.isRoot) { + if (!Platform.isRoot()) { logger.error("Cannot manage network without root!"); return; } diff --git a/photon-core/src/main/java/org/photonvision/common/scripting/ScriptManager.java b/photon-core/src/main/java/org/photonvision/common/scripting/ScriptManager.java index bd54fb30f..a47684cc5 100644 --- a/photon-core/src/main/java/org/photonvision/common/scripting/ScriptManager.java +++ b/photon-core/src/main/java/org/photonvision/common/scripting/ScriptManager.java @@ -128,7 +128,7 @@ public class ScriptManager { } public static void queueEvent(ScriptEventType eventType) { - if (!Platform.isWindows()) { + if (Platform.isLinux()) { try { queuedEvents.putLast(eventType); logger.info("Queued event: " + eventType.name()); diff --git a/photon-core/src/main/java/org/photonvision/common/util/file/FileUtils.java b/photon-core/src/main/java/org/photonvision/common/util/file/FileUtils.java index 5f7ab344f..23b3056c3 100644 --- a/photon-core/src/main/java/org/photonvision/common/util/file/FileUtils.java +++ b/photon-core/src/main/java/org/photonvision/common/util/file/FileUtils.java @@ -78,7 +78,7 @@ public class FileUtils { } public static void setFilePerms(Path path) throws IOException { - if (!Platform.isWindows()) { + if (Platform.isLinux()) { File thisFile = path.toFile(); Set perms = Files.readAttributes(path, PosixFileAttributes.class).permissions(); @@ -96,7 +96,7 @@ public class FileUtils { } public static void setAllPerms(Path path) { - if (!Platform.isWindows()) { + if (Platform.isLinux()) { String command = String.format("chmod 777 -R %s", path.toString()); try { Process p = Runtime.getRuntime().exec(command); diff --git a/photon-core/src/main/java/org/photonvision/raspi/PicamJNI.java b/photon-core/src/main/java/org/photonvision/raspi/PicamJNI.java index 02ca7dc0c..b987d419b 100644 --- a/photon-core/src/main/java/org/photonvision/raspi/PicamJNI.java +++ b/photon-core/src/main/java/org/photonvision/raspi/PicamJNI.java @@ -87,14 +87,18 @@ public class PicamJNI { } public static boolean isSupported() { + var piVer = PiVersion.getPiVersion(); + + boolean hwSupported = + (piVer == PiVersion.PI_3 + || piVer == PiVersion.COMPUTE_MODULE_3 + || piVer == PiVersion.ZERO_2_W); + return libraryLoaded && enabled && isVCSMSupported() && getSensorModel() != SensorModel.Disconnected - && Platform.isRaspberryPi() - && (Platform.currentPiVersion == PiVersion.PI_3 - || Platform.currentPiVersion == PiVersion.COMPUTE_MODULE_3 - || Platform.currentPiVersion == PiVersion.ZERO_2_W); + && hwSupported; } public static SensorModel getSensorModel() { diff --git a/photon-core/src/test/java/org/photonvision/hardware/HardwareTest.java b/photon-core/src/test/java/org/photonvision/hardware/HardwareTest.java index 61a75c51e..8f6924d75 100644 --- a/photon-core/src/test/java/org/photonvision/hardware/HardwareTest.java +++ b/photon-core/src/test/java/org/photonvision/hardware/HardwareTest.java @@ -24,31 +24,27 @@ import org.photonvision.common.hardware.GPIO.CustomGPIO; import org.photonvision.common.hardware.GPIO.GPIOBase; import org.photonvision.common.hardware.GPIO.pi.PigpioPin; import org.photonvision.common.hardware.Platform; -import org.photonvision.common.hardware.metrics.CPUMetrics; -import org.photonvision.common.hardware.metrics.GPUMetrics; -import org.photonvision.common.hardware.metrics.RAMMetrics; +import org.photonvision.common.hardware.metrics.MetricsManager; public class HardwareTest { @Test public void testHardware() { - CPUMetrics cpuMetrics = new CPUMetrics(); - RAMMetrics ramMetrics = new RAMMetrics(); - GPUMetrics gpuMetrics = new GPUMetrics(); + MetricsManager mm = new MetricsManager(); if (!Platform.isRaspberryPi()) return; - System.out.println("Testing on platform: " + Platform.currentPlatform); + System.out.println("Testing on platform: " + Platform.getPlatformName()); System.out.println("Printing CPU Info:"); - System.out.println("Memory: " + cpuMetrics.getMemory() + "MB"); - System.out.println("Temperature: " + cpuMetrics.getTemp() + "C"); - System.out.println("Utilization: : " + cpuMetrics.getUtilization() + "%"); + System.out.println("Memory: " + mm.getMemory() + "MB"); + System.out.println("Temperature: " + mm.getTemp() + "C"); + System.out.println("Utilization: : " + mm.getUtilization() + "%"); System.out.println("Printing GPU Info:"); - System.out.println("Memory: " + gpuMetrics.getGPUMemorySplit() + "MB"); + System.out.println("Memory: " + mm.getGPUMemorySplit() + "MB"); System.out.println("Printing RAM Info: "); - System.out.println("Used RAM: : " + ramMetrics.getUsedRam() + "MB"); + System.out.println("Used RAM: : " + mm.getUsedRam() + "MB"); } @Test diff --git a/photon-server/src/main/java/org/photonvision/Main.java b/photon-server/src/main/java/org/photonvision/Main.java index 9239422b3..76aa90a71 100644 --- a/photon-server/src/main/java/org/photonvision/Main.java +++ b/photon-server/src/main/java/org/photonvision/Main.java @@ -29,6 +29,7 @@ import org.photonvision.common.configuration.CameraConfiguration; import org.photonvision.common.configuration.ConfigManager; import org.photonvision.common.dataflow.networktables.NetworkTablesManager; import org.photonvision.common.hardware.HardwareManager; +import org.photonvision.common.hardware.PiVersion; import org.photonvision.common.hardware.Platform; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.LogLevel; @@ -321,8 +322,8 @@ public class Main { "Starting PhotonVision version " + PhotonVersion.versionString + " on " - + Platform.currentPlatform.toString() - + (Platform.isRaspberryPi() ? (" (Pi " + Platform.currentPiVersion.name() + ")") : "")); + + Platform.getPlatformName() + + (Platform.isRaspberryPi() ? (" (Pi " + PiVersion.getPiVersion() + ")") : "")); ConfigManager.getInstance().load(); // init config manager ConfigManager.getInstance().requestSave(); diff --git a/photon-server/src/main/java/org/photonvision/server/RequestHandler.java b/photon-server/src/main/java/org/photonvision/server/RequestHandler.java index e52f6d74e..59b2723cf 100644 --- a/photon-server/src/main/java/org/photonvision/server/RequestHandler.java +++ b/photon-server/src/main/java/org/photonvision/server/RequestHandler.java @@ -35,7 +35,6 @@ import org.photonvision.common.configuration.NetworkConfig; import org.photonvision.common.dataflow.networktables.NetworkTablesManager; import org.photonvision.common.hardware.HardwareManager; import org.photonvision.common.hardware.Platform; -import org.photonvision.common.hardware.metrics.MetricsPublisher; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; import org.photonvision.common.networking.NetworkManager; @@ -113,37 +112,29 @@ public class RequestHandler { logger.info("New .jar uploaded successfully."); if (file != null) { - if (Platform.isLinux()) { - try { - Path filePath = - Paths.get(ProgramDirectoryUtilities.getProgramDirectory(), "photonvision.jar"); - File targetFile = new File(filePath.toString()); - var stream = new FileOutputStream(targetFile); + try { + Path filePath = + Paths.get(ProgramDirectoryUtilities.getProgramDirectory(), "photonvision.jar"); + File targetFile = new File(filePath.toString()); + var stream = new FileOutputStream(targetFile); - logger.info( - "Streaming user-provided " + file.getFilename() + " into " + targetFile.toString()); + logger.info( + "Streaming user-provided " + file.getFilename() + " into " + targetFile.toString()); - file.getContent().transferTo(stream); - stream.close(); + file.getContent().transferTo(stream); + stream.close(); - ctx.status(200); - logger.info("New .jar in place, going down for restart..."); - restartProgram(); - - } catch (FileNotFoundException e) { - logger.error( - ".jar of this program could not be found. How the heck this program started in the first place is a mystery."); - ctx.status(500); - } catch (IOException e) { - logger.error("Could not overwrite the .jar for this instance of photonvision."); - ctx.status(500); - } - - } else { - logger.error("Hot .jar replace currently only supported on Linux. Ignoring."); + ctx.status(200); + logger.info("New .jar in place, going down for restart..."); + restartProgram(); + } catch (FileNotFoundException e) { + logger.error( + ".jar of this program could not be found. How the heck this program started in the first place is a mystery."); + ctx.status(500); + } catch (IOException e) { + logger.error("Could not overwrite the .jar for this instance of photonvision."); ctx.status(500); } - } else { logger.error("Couldn't read provided file for new .jar! Ignoring."); ctx.status(500); @@ -274,7 +265,7 @@ public class RequestHandler { } public static void sendMetrics(Context ctx) { - MetricsPublisher.getInstance().publish(); + HardwareManager.getInstance().publishMetrics(); // TimedTaskManager.getInstance().addOneShotTask(() -> RoborioFinder.getInstance().findRios(), // 0); ctx.status(200);