From 0b98dc3c9f94dbf361c199bd0acefc6ef3191c51 Mon Sep 17 00:00:00 2001 From: Xzibit Date: Fri, 31 Jul 2020 15:43:58 -0400 Subject: [PATCH] Hardware Management, Metrics, PWM/GPIO support. (#12) * [Server] Hardware Management * [Server] Hardware Management * [Server] Hardware Management * [Server] Hardware Management * [Server] Hardware Management * [Server] Hardware Management * Added metricsPublisher * [Server] Hardware Management * [Server] Hardware Management * Fill in HardwareConfig, allow JSON Comments * Use hardware config * [Hardware Management] Use Hardware Config * [Hardware Management] Use softPWM for dimming * [Hardware] Added HardwareConfig Test * [Hardware] Started HardwareManager * Start metrics thread in hardwareManager * [Hardware] Added Hardware Manager Test * [Hardware] Spotless * [Hardware] Added logging, cleaned up HardwareConfig * [Hardware] Added logging to PWM class * [Hardware] Rebase off master, fix merge conflicts * [Hardware] Ignore metrics commands if on pi * [Hardware] Remove GPIO provision after shutdown * [Hardware] Switch over to diozero * [Hardware] Use broadcom pins * [Hardware] Fix PWM port * [Hardware] Use jpi instead of pigpio * [Hardware] Use dizero-core * [Hardware] No need to close LED * [Hardware] Switch to jpigpio * [Hardware] Initalize JPiGPIO in unit tests * [Hardware] Use dutyCycle for LED dimming * [Hardware] Add blink test to HardwareManager * [Hardware] Fix PWM port * [Hardware] Fix HardwareManagerTest * [Hardware] Fix HardwareManagerTest * [Hardware] Use waves for LED blinking * [Hardware] Make blinking part of PWM * [Hardware] Add API methods to hardware Manager * [Hardware] Only start pigpio if on pi * [Hardware] Merge PWM classes into GPIO * [Hardware] Add Hardware stuff to VisionModules * [Hardware] Remove random semicolon Co-authored-by: Banks Troutman --- photon-server/build.gradle | 4 + .../src/main/java/org/photonvision/Main.java | 2 +- .../common/configuration/HardwareConfig.java | 75 +++++++- .../common/hardware/GPIO/CustomGPIO.java | 130 ++++++++++++++ .../common/hardware/GPIO/GPIOBase.java | 73 ++++++++ .../common/hardware/GPIO/PiGPIO.java | 169 ++++++++++++++++++ .../common/hardware/HardwareManager.java | 88 +++++++++ .../common/{util => hardware}/Platform.java | 62 ++++--- .../common/hardware/metrics/CPU.java | 44 +++++ .../common/hardware/metrics/GPU.java | 39 ++++ .../common/hardware/metrics/MetricsBase.java | 66 +++++++ .../hardware/metrics/MetricsPublisher.java | 74 ++++++++ .../common/hardware/metrics/RAM.java | 35 ++++ .../common/scripting/ScriptManager.java | 2 +- .../photonvision/common/util/TestUtils.java | 5 + .../common/util/file/FileUtils.java | 2 +- .../common/util/file/JacksonUtils.java | 2 + .../vision/camera/CameraQuirk.java | 4 +- .../vision/camera/QuirkyCamera.java | 3 +- .../vision/processes/VisionModule.java | 30 ++++ .../hardware/HardwareConfigTest.java | 52 ++++++ .../hardware/HardwareManagerTest.java | 48 +++++ .../photonvision/hardware/HardwareTest.java | 97 ++++++++++ .../resources/hardware/HardwareConfig.json | 23 +++ 24 files changed, 1096 insertions(+), 33 deletions(-) create mode 100644 photon-server/src/main/java/org/photonvision/common/hardware/GPIO/CustomGPIO.java create mode 100644 photon-server/src/main/java/org/photonvision/common/hardware/GPIO/GPIOBase.java create mode 100644 photon-server/src/main/java/org/photonvision/common/hardware/GPIO/PiGPIO.java create mode 100644 photon-server/src/main/java/org/photonvision/common/hardware/HardwareManager.java rename photon-server/src/main/java/org/photonvision/common/{util => hardware}/Platform.java (62%) create mode 100644 photon-server/src/main/java/org/photonvision/common/hardware/metrics/CPU.java create mode 100644 photon-server/src/main/java/org/photonvision/common/hardware/metrics/GPU.java create mode 100644 photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsBase.java create mode 100644 photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java create mode 100644 photon-server/src/main/java/org/photonvision/common/hardware/metrics/RAM.java create mode 100644 photon-server/src/test/java/org/photonvision/hardware/HardwareConfigTest.java create mode 100644 photon-server/src/test/java/org/photonvision/hardware/HardwareManagerTest.java create mode 100644 photon-server/src/test/java/org/photonvision/hardware/HardwareTest.java create mode 100644 photon-server/src/test/resources/hardware/HardwareConfig.json diff --git a/photon-server/build.gradle b/photon-server/build.gradle index d2d7356df..ff94463da 100644 --- a/photon-server/build.gradle +++ b/photon-server/build.gradle @@ -30,6 +30,10 @@ ext { dependencies { implementation "io.javalin:javalin:3.7.0" + compile group: 'eu.xeli', name: 'jpigpio_2.12', version: '0.1.0' + + + implementation "com.fasterxml.jackson.core:jackson-annotations:2.10.0" implementation "com.fasterxml.jackson.core:jackson-core:2.10.0" diff --git a/photon-server/src/main/java/org/photonvision/Main.java b/photon-server/src/main/java/org/photonvision/Main.java index bf036fa97..99b477e07 100644 --- a/photon-server/src/main/java/org/photonvision/Main.java +++ b/photon-server/src/main/java/org/photonvision/Main.java @@ -23,11 +23,11 @@ import java.util.List; import org.photonvision.common.configuration.CameraConfiguration; import org.photonvision.common.configuration.ConfigManager; import org.photonvision.common.dataflow.networktables.NetworkTablesManager; +import org.photonvision.common.hardware.Platform; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.LogLevel; import org.photonvision.common.logging.Logger; import org.photonvision.common.networking.NetworkManager; -import org.photonvision.common.util.Platform; import org.photonvision.server.Server; import org.photonvision.vision.camera.USBCameraSource; import org.photonvision.vision.pipeline.CVPipelineSettings; diff --git a/photon-server/src/main/java/org/photonvision/common/configuration/HardwareConfig.java b/photon-server/src/main/java/org/photonvision/common/configuration/HardwareConfig.java index 67b9cad1f..1fab803e9 100644 --- a/photon-server/src/main/java/org/photonvision/common/configuration/HardwareConfig.java +++ b/photon-server/src/main/java/org/photonvision/common/configuration/HardwareConfig.java @@ -17,7 +17,78 @@ package org.photonvision.common.configuration; -/** Defines a;lskdjfa;dsklf */ +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.Map; + +@SuppressWarnings("unused") public class HardwareConfig { - public int ledPin = 1; // just to stop jackson from yeeting + + public final String deviceName; + public final String deviceLogoPath; + public final String supportURL; + + // LED control + public final ArrayList ledPins; + public final String ledSetCommand; + public final boolean ledsCanDim; + public final ArrayList ledPWMRange; + public final String ledPWMSetRange; + public final String ledDimCommand; + public final String ledBlinkCommand; + + // Metrics + public final String cpuTempCommand; + public final String cpuMemoryCommand; + public final String cpuUtilCommand; + public final String gpuMemoryCommand; + public final String gpuTempCommand; + public final String ramUtilCommand; + + public HardwareConfig() { + deviceName = ""; + deviceLogoPath = ""; + supportURL = ""; + ledPins = new ArrayList<>(); + ledSetCommand = ""; + ledsCanDim = false; + ledPWMRange = new ArrayList<>(); + ledPWMSetRange = ""; + ledDimCommand = ""; + + cpuTempCommand = ""; + cpuMemoryCommand = ""; + cpuUtilCommand = ""; + gpuMemoryCommand = ""; + gpuTempCommand = ""; + ramUtilCommand = ""; + ledBlinkCommand = ""; + } + + @JsonCreator + public HardwareConfig( + @JsonProperty("deviceName") String deviceName, + @JsonProperty("deviceLogoPath") String deviceLogoPath, + @JsonProperty("supportURL") String supportURL, + @JsonProperty("hardware") Map hardware, + @JsonProperty("metrics") Map metrics) { + this.deviceName = deviceName; + this.deviceLogoPath = deviceLogoPath; + this.supportURL = supportURL; + this.ledPins = (ArrayList) hardware.get("leds"); + this.ledSetCommand = (String) hardware.get("ledSetCommand"); + this.ledsCanDim = (Boolean) hardware.get("ledsCanDim"); + this.ledPWMRange = (ArrayList) hardware.get("ledPWMRange"); + this.ledPWMSetRange = (String) hardware.get("ledPWMSetRange"); + this.ledDimCommand = (String) hardware.get("ledDimCommand"); + this.ledBlinkCommand = (String) hardware.get("ledBlinkCommand"); + + this.cpuTempCommand = (String) metrics.get("cpuTemp"); + this.cpuMemoryCommand = (String) metrics.get("cpuMemory"); + this.cpuUtilCommand = (String) metrics.get("cpuUtil"); + this.gpuMemoryCommand = (String) metrics.get("gpuMemory"); + this.gpuTempCommand = (String) metrics.get("gpuUtil"); + this.ramUtilCommand = (String) metrics.get("ramUtil"); + } } diff --git a/photon-server/src/main/java/org/photonvision/common/hardware/GPIO/CustomGPIO.java b/photon-server/src/main/java/org/photonvision/common/hardware/GPIO/CustomGPIO.java new file mode 100644 index 000000000..e47fdf25b --- /dev/null +++ b/photon-server/src/main/java/org/photonvision/common/hardware/GPIO/CustomGPIO.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 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.GPIO; + +import java.util.ArrayList; +import java.util.List; +import org.photonvision.common.configuration.HardwareConfig; +import org.photonvision.common.hardware.Platform; + +public class CustomGPIO extends GPIOBase { + + private boolean currentState; + private List pwmRange = new ArrayList<>(); + private int port; + + public CustomGPIO(int port) { + this.port = port; + } + + @Override + public void togglePin() { + execute( + commands + .get("setState") + .replace("{s}", String.valueOf(!currentState)) + .replace("{p}", String.valueOf(this.port))); + currentState = !currentState; + } + + @Override + public void setLow() { + execute( + commands + .get("setState") + .replace("{s}", String.valueOf(false)) + .replace("{p}", String.valueOf(this.port))); + currentState = false; + } + + @Override + public void setHigh() { + execute( + commands + .get("setState") + .replace("{s}", String.valueOf(true)) + .replace("{p}", String.valueOf(this.port))); + currentState = true; + } + + @Override + public void setState(boolean state) { + execute( + commands + .get("setState") + .replace("{s}", String.valueOf(state)) + .replace("{p}", String.valueOf(this.port))); + currentState = state; + } + + @Override + public boolean shutdown() { + execute(commands.get("shutdown")); + return true; + } + + @Override + public boolean getState() { + return currentState; + } + + @Override + public void setPwmRange(List range) { + execute( + commands + .get("setRange") + .replace("{lower_range}", String.valueOf(range.get(0))) + .replace("{upper_range}", String.valueOf(range.get(1))) + .replace("{p}", String.valueOf(port))); + pwmRange = range; + } + + @Override + public List getPwmRange() { + return pwmRange; + } + + @Override + public void blink(int pulseTimeMillis, int blinks) { + execute( + commands + .get("blink") + .replace("{pulseTime}", String.valueOf(pulseTimeMillis)) + .replace("{blinks}", String.valueOf(blinks)) + .replace("{p}", String.valueOf(this.port))); + } + + @Override + public void dimLED(int dimValue) { + // Check to see if dimValue is within the range + if (dimValue < pwmRange.get(0) || dimValue > pwmRange.get(1)) return; + execute( + commands + .get("dim") + .replace("{p}", String.valueOf(port)) + .replace("{v}", String.valueOf(dimValue))); + } + + public static void setConfig(HardwareConfig config) { + if (Platform.isRaspberryPi()) return; + commands.replace("setState", config.ledSetCommand); + commands.replace("setRange", config.ledPWMSetRange); + commands.replace("dim", config.ledDimCommand); + commands.replace("blink", config.ledBlinkCommand); + } +} diff --git a/photon-server/src/main/java/org/photonvision/common/hardware/GPIO/GPIOBase.java b/photon-server/src/main/java/org/photonvision/common/hardware/GPIO/GPIOBase.java new file mode 100644 index 000000000..28e519380 --- /dev/null +++ b/photon-server/src/main/java/org/photonvision/common/hardware/GPIO/GPIOBase.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 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.GPIO; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import org.photonvision.common.logging.LogGroup; +import org.photonvision.common.logging.Logger; +import org.photonvision.common.util.ShellExec; + +public abstract class GPIOBase { + private static final Logger logger = new Logger(GPIOBase.class, LogGroup.General); + + public static HashMap commands = + new HashMap<>() { + { + put("setState", ""); + put("shutdown", ""); + put("setRange", ""); + put("shutdown", ""); + put("dim", ""); + put("blink", ""); + } + }; + + private static final ShellExec runCommand = new ShellExec(true, true); + + public static String execute(String command) { + try { + runCommand.executeBashCommand(command); + } catch (Exception e) { + logger.error(Arrays.toString(e.getStackTrace())); + return ""; + } + return runCommand.getOutput(); + } + + public abstract void togglePin(); + + public abstract void setLow(); + + public abstract void setHigh(); + + public abstract void setState(boolean state); + + public abstract boolean shutdown(); + + public abstract boolean getState(); + + public abstract void setPwmRange(List range); + + public abstract List getPwmRange(); + + public abstract void blink(int pulseTimeMillis, int blinks); + + public abstract void dimLED(int dimPercentage); +} diff --git a/photon-server/src/main/java/org/photonvision/common/hardware/GPIO/PiGPIO.java b/photon-server/src/main/java/org/photonvision/common/hardware/GPIO/PiGPIO.java new file mode 100644 index 000000000..946b3f00b --- /dev/null +++ b/photon-server/src/main/java/org/photonvision/common/hardware/GPIO/PiGPIO.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2020 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.GPIO; + +import eu.xeli.jpigpio.JPigpio; +import eu.xeli.jpigpio.PigpioException; +import eu.xeli.jpigpio.PigpioSocket; +import eu.xeli.jpigpio.Pulse; +import java.util.ArrayList; +import java.util.List; +import org.photonvision.common.logging.LogGroup; +import org.photonvision.common.logging.Logger; + +public class PiGPIO extends GPIOBase { + private static final Logger logger = new Logger(PiGPIO.class, LogGroup.General); + private final ArrayList pulses = new ArrayList<>(); + private final int pin; + + public static JPigpio getPigpioDaemon() { + return Singleton.INSTANCE; + } + + public PiGPIO(int address, int value, int range) { + this.pin = address; + try { + getPigpioDaemon().setPWMFrequency(this.pin, value); + getPigpioDaemon().setPWMRange(this.pin, range); + } catch (PigpioException e) { + logger.error("Could not set PWM settings on port " + this.pin); + e.printStackTrace(); + } + } + + @Override + public void togglePin() { + try { + getPigpioDaemon().gpioWrite(this.pin, !getPigpioDaemon().gpioRead(this.pin)); + } catch (PigpioException e) { + logger.error("Could not toggle on pin " + this.pin); + e.printStackTrace(); + } + } + + @Override + public void setLow() { + try { + getPigpioDaemon().gpioWrite(this.pin, false); + } catch (PigpioException e) { + logger.error("Could not set pin low on port " + this.pin); + e.printStackTrace(); + } + } + + @Override + public void setHigh() { + try { + getPigpioDaemon().gpioWrite(this.pin, true); + } catch (PigpioException e) { + logger.error("Could not set pin high on port " + this.pin); + e.printStackTrace(); + } + } + + @Override + public void setState(boolean state) { + try { + getPigpioDaemon().gpioWrite(this.pin, state); + } catch (PigpioException e) { + logger.error("Could not set pin state on port " + this.pin); + e.printStackTrace(); + } + } + + @Override + public boolean shutdown() { + try { + getPigpioDaemon().gpioTerminate(); + } catch (PigpioException e) { + logger.error("Could not terminate GPIO instance"); + e.printStackTrace(); + } + return true; + } + + @Override + public boolean getState() { + try { + return getPigpioDaemon().gpioRead(this.pin); + } catch (PigpioException e) { + logger.error("Could not read pin on port " + this.pin); + e.printStackTrace(); + return false; + } + } + + @Override + public void setPwmRange(List range) { + try { + getPigpioDaemon().setPWMRange(this.pin, range.get(0)); + } catch (PigpioException e) { + logger.error("Could not set PWM range on port " + this.pin); + e.printStackTrace(); + } + } + + @Override + public List getPwmRange() { + try { + return List.of(0, getPigpioDaemon().getPWMRange(this.pin)); + } catch (PigpioException e) { + logger.error("Could not get PWM range on port " + this.pin); + e.printStackTrace(); + return null; + } + } + + @Override + public void blink(int pulseTimeMillis, int blinks) { + try { + pulses.clear(); + for (int i = 0; i < blinks; i++) { + pulses.add(new Pulse(1 << this.pin, 0, pulseTimeMillis * 100)); + pulses.add(new Pulse(0, 1 << this.pin, pulseTimeMillis * 100)); + } + getPigpioDaemon().waveAddGeneric(this.pulses); + getPigpioDaemon().waveSendOnce(getPigpioDaemon().waveCreate()); + } catch (PigpioException e) { + e.printStackTrace(); + } + } + + @Override + public void dimLED(int dimPercentage) { + try { + getPigpioDaemon().setPWMDutycycle(this.pin, getPwmRange().get(1) * (dimPercentage / 100)); + } catch (PigpioException e) { + logger.error("Could not dim PWM on port " + this.pin); + e.printStackTrace(); + } + } + + private static class Singleton { + public static JPigpio INSTANCE; + + static { + try { + INSTANCE = new PigpioSocket("localhost", 8888); + } catch (PigpioException e) { + logger.error("Could not connect to pigpio daemon"); + e.printStackTrace(); + } + } + } +} diff --git a/photon-server/src/main/java/org/photonvision/common/hardware/HardwareManager.java b/photon-server/src/main/java/org/photonvision/common/hardware/HardwareManager.java new file mode 100644 index 000000000..a75c1ddb3 --- /dev/null +++ b/photon-server/src/main/java/org/photonvision/common/hardware/HardwareManager.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 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; + +import java.util.HashMap; +import org.photonvision.common.configuration.HardwareConfig; +import org.photonvision.common.hardware.GPIO.CustomGPIO; +import org.photonvision.common.hardware.GPIO.GPIOBase; +import org.photonvision.common.hardware.GPIO.PiGPIO; +import org.photonvision.common.hardware.metrics.MetricsBase; +import org.photonvision.common.hardware.metrics.MetricsPublisher; +import org.photonvision.common.logging.LogGroup; +import org.photonvision.common.logging.Logger; + +public class HardwareManager { + HardwareConfig hardwareConfig; + private static final Logger logger = new Logger(HardwareManager.class, LogGroup.General); + private static final HashMap LEDs = new HashMap<>(); + + public static HardwareManager getInstance() { + return Singleton.INSTANCE; + } + + public void setConfig(HardwareConfig hardwareConfig) { + this.hardwareConfig = hardwareConfig; + CustomGPIO.setConfig(hardwareConfig); + MetricsBase.setConfig(hardwareConfig); + + hardwareConfig.ledPins.forEach( + pin -> { + if (Platform.isRaspberryPi()) { + LEDs.put(pin, new PiGPIO(pin, 0, hardwareConfig.ledPWMRange.get(1))); + } else { + LEDs.put(pin, new CustomGPIO(pin)); + } + }); + + // Start hardware metrics thread + MetricsPublisher.getInstance().startThread(); + } + /** Example: HardwareManager.getInstance().getPWM(port).dimLEDs(int dimValue); */ + public GPIOBase getGPIO(int pin) { + return LEDs.get(pin); + } + + public void blinkLEDs(int pulseTimeMillis, int blinks) { + LEDs.values().forEach(led -> led.blink(pulseTimeMillis, blinks)); + } + + public void setBrightnessPercentage(int percentage) { + LEDs.values().forEach(led -> led.dimLED(percentage)); + } + + public void turnLEDsOn() { + LEDs.values().forEach(GPIOBase::setHigh); + } + + public void turnLEDsOff() { + LEDs.values().forEach(GPIOBase::setLow); + } + + public void toggleLEDs() { + LEDs.values().forEach(GPIOBase::togglePin); + } + + public void shutdown() { + LEDs.values().forEach(GPIOBase::shutdown); + } + + private static class Singleton { + private static final HardwareManager INSTANCE = new HardwareManager(); + } +} diff --git a/photon-server/src/main/java/org/photonvision/common/util/Platform.java b/photon-server/src/main/java/org/photonvision/common/hardware/Platform.java similarity index 62% rename from photon-server/src/main/java/org/photonvision/common/util/Platform.java rename to photon-server/src/main/java/org/photonvision/common/hardware/Platform.java index aed98bfad..76d3baa28 100644 --- a/photon-server/src/main/java/org/photonvision/common/util/Platform.java +++ b/photon-server/src/main/java/org/photonvision/common/hardware/Platform.java @@ -15,27 +15,26 @@ * along with this program. If not, see . */ -package org.photonvision.common.util; +package org.photonvision.common.hardware; import edu.wpi.first.wpiutil.RuntimeDetector; import java.io.IOException; -import org.apache.commons.lang3.SystemUtils; -import org.photonvision.common.logging.LogGroup; -import org.photonvision.common.logging.Logger; +import org.photonvision.common.util.ShellExec; @SuppressWarnings("unused") public enum Platform { + // WPILib Supported (JNI) WINDOWS_32("Windows x32"), WINDOWS_64("Windows x64"), - LINUX_32("Linux x32"), LINUX_64("Linux x64"), - LINUX_RASPBIAN("Linux Raspbian"), // TODO: check that RaspiOS reports the same way - LINUX_AARCH64BIONIC("Linux Aarch64 Bionic"), - UNSUPPORTED( - "Unsupported Platform - OS: " - + SystemUtils.OS_NAME - + ", Architecture: " - + SystemUtils.OS_ARCH); + 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 + + // Completely unsupported + UNSUPPORTED("Unsupported Platform"); public final String value; public final boolean isRoot = checkForRoot(); @@ -44,16 +43,19 @@ public enum Platform { this.value = value; } - private static final Logger logger = new Logger(Platform.class, LogGroup.General); - + private static final String OS_NAME = System.getProperty("os.name"); + private static final String OS_ARCH = System.getProperty("os.arch"); public static final Platform CurrentPlatform = getCurrentPlatform(); - public static boolean isWindows() { - return CurrentPlatform == WINDOWS_64 || CurrentPlatform == WINDOWS_32; + private static String UnknownPlatformString = + String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH); + + public boolean isWindows() { + return this == WINDOWS_64 || this == WINDOWS_32; } - public static boolean isLinux() { - return CurrentPlatform != UNSUPPORTED && !isWindows(); + public boolean isLinux() { + return this == LINUX_64 || this == LINUX_RASPBIAN || this == LINUX_ARM64; } public static boolean isRaspberryPi() { @@ -68,7 +70,7 @@ public enum Platform { try { shell.execute("id", null, true, "-u"); } catch (IOException e) { - logger.error("Failed to perform root check!", e); + e.printStackTrace(); } while (!shell.isOutputCompleted()) { @@ -91,17 +93,25 @@ public enum Platform { if (RuntimeDetector.is64BitIntel()) return WINDOWS_64; } - if (RuntimeDetector.isLinux()) { - if (RuntimeDetector.is32BitIntel()) return LINUX_32; - if (RuntimeDetector.is64BitIntel()) return LINUX_64; - if (RuntimeDetector.isRaspbian()) return LINUX_RASPBIAN; - if (RuntimeDetector.isAarch64Bionic()) return LINUX_AARCH64BIONIC; + if (RuntimeDetector.isMac()) { + if (RuntimeDetector.is32BitIntel()) return UNSUPPORTED; } - return UNSUPPORTED; + if (RuntimeDetector.isLinux()) { + if (RuntimeDetector.is32BitIntel()) return UNSUPPORTED; + if (RuntimeDetector.is64BitIntel()) return LINUX_64; + if (RuntimeDetector.isRaspbian()) return LINUX_RASPBIAN; + } + + System.out.println(UnknownPlatformString); + return Platform.UNSUPPORTED; } public String toString() { - return this.value; + if (this.equals(UNSUPPORTED)) { + return UnknownPlatformString; + } else { + return this.value; + } } } diff --git a/photon-server/src/main/java/org/photonvision/common/hardware/metrics/CPU.java b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/CPU.java new file mode 100644 index 000000000..3e8d97c5f --- /dev/null +++ b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/CPU.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 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 CPU extends MetricsBase { + + private CPU() {} + + public static CPU getInstance() { + return Singleton.INSTANCE; + } + + public double getMemory() { + return execute(cpuMemoryCommand); + } + + // TODO: Command should return in Celsius + public double getTemp() { + return execute(cpuTemperatureCommand) / 1000; + } + + public double getUtilization() { + return execute(cpuUtilizationCommand); + } + + private static class Singleton { + public static final CPU INSTANCE = new CPU(); + } +} diff --git a/photon-server/src/main/java/org/photonvision/common/hardware/metrics/GPU.java b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/GPU.java new file mode 100644 index 000000000..e7188ce51 --- /dev/null +++ b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/GPU.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 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 GPU extends MetricsBase { + + private GPU() {} + + public static GPU getInstance() { + return Singleton.INSTANCE; + } + + public double getMemory() { + return execute(gpuMemoryCommand); + } + + public double getTemp() { + return execute(gpuTemperatureCommand) / 10; + } + + private static class Singleton { + public static final GPU INSTANCE = new GPU(); + } +} diff --git a/photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsBase.java b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsBase.java new file mode 100644 index 000000000..c793a2536 --- /dev/null +++ b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsBase.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 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.Arrays; +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 { + private static final Logger logger = new Logger(MetricsBase.class, LogGroup.General); + // CPU + public static String cpuMemoryCommand = "sudo vcgencmd get_mem arm | grep -Eo '[0-9]+'"; + public static String cpuTemperatureCommand = + "sudo cat /sys/class/thermal/thermal_zone0/temp | grep -x -E '[0-9]+'"; + public static String cpuUtilizationCommand = + "sudo top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk '{print 100 - $1}'"; + + // GPU + public static String gpuMemoryCommand = "sudo vcgencmd get_mem gpu | grep -Eo '[0-9]+'"; + public static String gpuTemperatureCommand = "sudo vcgencmd measure_temp | sed 's/[^0-9]*//g'\n"; + + // RAM + public static String ramUsageCommand = "sudo free | awk -v i=2 -v j=3 'FNR == i {print $j}'"; + + 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; + + gpuMemoryCommand = config.gpuMemoryCommand; + gpuTemperatureCommand = config.gpuTempCommand; + + ramUsageCommand = config.ramUtilCommand; + } + + public static double execute(String command) { + try { + runCommand.executeBashCommand(command); + return Double.parseDouble(runCommand.getOutput()); + } catch (Exception e) { + logger.error(Arrays.toString(e.getStackTrace())); + return Double.NaN; + } + } +} diff --git a/photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java new file mode 100644 index 000000000..b35244b90 --- /dev/null +++ b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2020 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 java.util.Timer; +import java.util.TimerTask; +import org.photonvision.common.dataflow.DataChangeService; +import org.photonvision.common.dataflow.events.OutgoingUIEvent; +import org.photonvision.server.UIUpdateType; + +public class MetricsPublisher { + private final HashMap metrics; + private final Thread metricsThread; + + public static MetricsPublisher getInstance() { + return Singleton.INSTANCE; + } + + private MetricsPublisher() { + var cpu = CPU.getInstance(); + var gpu = GPU.getInstance(); + var ram = RAM.getInstance(); + + metrics = new HashMap<>(); + + this.metricsThread = + new Thread( + () -> { + var timer = new Timer(); + timer.schedule( + new TimerTask() { + public void run() { + metrics.put("cpuTemp", cpu.getTemp()); + metrics.put("cpuUtil", cpu.getUtilization()); + metrics.put("cpuMem", cpu.getMemory()); + metrics.put("gpuTemp", gpu.getTemp()); + metrics.put("gpuMem", gpu.getMemory()); + metrics.put("ramUtil", ram.getUsedRam()); + + DataChangeService.getInstance() + .publishEvent( + new OutgoingUIEvent<>( + UIUpdateType.BROADCAST, "metrics", metrics, null)); + } + }, + 0, + 1000); + }); + } + + public void startThread() { + metricsThread.start(); + } + + private static class Singleton { + public static final MetricsPublisher INSTANCE = new MetricsPublisher(); + } +} diff --git a/photon-server/src/main/java/org/photonvision/common/hardware/metrics/RAM.java b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/RAM.java new file mode 100644 index 000000000..61c0eb35e --- /dev/null +++ b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/RAM.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 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 RAM extends MetricsBase { + private RAM() {} + + public static RAM getInstance() { + return Singleton.INSTANCE; + } + + // TODO: Output in MBs for consistency + public double getUsedRam() { + return execute(ramUsageCommand) / 1000; + } + + private static class Singleton { + public static final RAM INSTANCE = new RAM(); + } +} diff --git a/photon-server/src/main/java/org/photonvision/common/scripting/ScriptManager.java b/photon-server/src/main/java/org/photonvision/common/scripting/ScriptManager.java index 0ac0ab884..270a87800 100644 --- a/photon-server/src/main/java/org/photonvision/common/scripting/ScriptManager.java +++ b/photon-server/src/main/java/org/photonvision/common/scripting/ScriptManager.java @@ -24,9 +24,9 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingDeque; +import org.photonvision.common.hardware.Platform; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; -import org.photonvision.common.util.Platform; import org.photonvision.common.util.TimedTaskManager; import org.photonvision.common.util.file.JacksonUtils; diff --git a/photon-server/src/main/java/org/photonvision/common/util/TestUtils.java b/photon-server/src/main/java/org/photonvision/common/util/TestUtils.java index bd5920758..5882a4eca 100644 --- a/photon-server/src/main/java/org/photonvision/common/util/TestUtils.java +++ b/photon-server/src/main/java/org/photonvision/common/util/TestUtils.java @@ -19,6 +19,7 @@ package org.photonvision.common.util; import edu.wpi.cscore.CameraServerCvJNI; import java.awt.*; +import java.io.File; import java.io.IOException; import java.nio.file.Path; import org.opencv.core.Mat; @@ -165,6 +166,10 @@ public class TestUtils { return getResourcesFolderPath().resolve("calibrationBoardImages"); } + public static File getHardwareConfigJson() { + return getResourcesFolderPath().resolve("hardware").resolve("HardwareConfig.json").toFile(); + } + public static void loadLibraries() { try { CameraServerCvJNI.forceLoad(); diff --git a/photon-server/src/main/java/org/photonvision/common/util/file/FileUtils.java b/photon-server/src/main/java/org/photonvision/common/util/file/FileUtils.java index ee591e189..3f1ef7c66 100644 --- a/photon-server/src/main/java/org/photonvision/common/util/file/FileUtils.java +++ b/photon-server/src/main/java/org/photonvision/common/util/file/FileUtils.java @@ -26,9 +26,9 @@ import java.nio.file.attribute.PosixFilePermission; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import org.photonvision.common.hardware.Platform; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; -import org.photonvision.common.util.Platform; public class FileUtils { diff --git a/photon-server/src/main/java/org/photonvision/common/util/file/JacksonUtils.java b/photon-server/src/main/java/org/photonvision/common/util/file/JacksonUtils.java index cb7289579..96480a538 100644 --- a/photon-server/src/main/java/org/photonvision/common/util/file/JacksonUtils.java +++ b/photon-server/src/main/java/org/photonvision/common/util/file/JacksonUtils.java @@ -17,6 +17,7 @@ package org.photonvision.common.util.file; +import com.fasterxml.jackson.core.json.JsonReadFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.json.JsonMapper; @@ -51,6 +52,7 @@ public class JacksonUtils { BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build(); ObjectMapper objectMapper = JsonMapper.builder() + .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true) .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT) .build(); File jsonFile = new File(path.toString()); diff --git a/photon-server/src/main/java/org/photonvision/vision/camera/CameraQuirk.java b/photon-server/src/main/java/org/photonvision/vision/camera/CameraQuirk.java index 7b7dfff74..541fbc448 100644 --- a/photon-server/src/main/java/org/photonvision/vision/camera/CameraQuirk.java +++ b/photon-server/src/main/java/org/photonvision/vision/camera/CameraQuirk.java @@ -21,5 +21,7 @@ public enum CameraQuirk { /** Camera settable for controllable image gain */ Gain, /** For cameras that need a bit of encouragement for settings to stick */ - DoubleSet + DoubleSet, + /** For cameras that are pi cams */ + PiCam } diff --git a/photon-server/src/main/java/org/photonvision/vision/camera/QuirkyCamera.java b/photon-server/src/main/java/org/photonvision/vision/camera/QuirkyCamera.java index 4eeed5faf..e35e8353a 100644 --- a/photon-server/src/main/java/org/photonvision/vision/camera/QuirkyCamera.java +++ b/photon-server/src/main/java/org/photonvision/vision/camera/QuirkyCamera.java @@ -26,7 +26,8 @@ public class QuirkyCamera { private static final List quirkyCameras = List.of( new QuirkyCamera(0x2000, 0x1415, "PS3Eye", CameraQuirk.Gain), - new QuirkyCamera(0x72E, 0x45D, "LifeCam VX-5500", CameraQuirk.DoubleSet)); + new QuirkyCamera(0x72E, 0x45D, "LifeCam VX-5500", CameraQuirk.DoubleSet), + new QuirkyCamera(-1, -1, "PiCam", CameraQuirk.PiCam)); public static final QuirkyCamera DefaultCamera = new QuirkyCamera(0, 0, "", List.of()); diff --git a/photon-server/src/main/java/org/photonvision/vision/processes/VisionModule.java b/photon-server/src/main/java/org/photonvision/vision/processes/VisionModule.java index 48fcb9ad5..495454efe 100644 --- a/photon-server/src/main/java/org/photonvision/vision/processes/VisionModule.java +++ b/photon-server/src/main/java/org/photonvision/vision/processes/VisionModule.java @@ -31,6 +31,7 @@ import org.photonvision.common.dataflow.events.IncomingWebSocketEvent; import org.photonvision.common.dataflow.events.OutgoingUIEvent; import org.photonvision.common.dataflow.networktables.NTDataPublisher; import org.photonvision.common.dataflow.websocket.UIDataPublisher; +import org.photonvision.common.hardware.HardwareManager; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; import org.photonvision.common.util.SerializationUtils; @@ -192,6 +193,35 @@ public class VisionModule { setPipeline(index); saveAndBroadcastAll(); return; + case "dimLED": + if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { + var dimPercentage = (int) newPropValue; + HardwareManager.getInstance().setBrightnessPercentage(dimPercentage); + } + return; + case "blinkLED": + if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { + var params = (Pair) newPropValue; + HardwareManager.getInstance().blinkLEDs(params.getLeft(), params.getRight()); + } + return; + case "setLED": + if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { + var state = (boolean) newPropValue; + if (state) HardwareManager.getInstance().turnLEDsOn(); + else HardwareManager.getInstance().turnLEDsOff(); + } + return; + case "toggleLED": + if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { + HardwareManager.getInstance().toggleLEDs(); + } + return; + case "shutdownLEDs": + if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { + HardwareManager.getInstance().shutdown(); + } + return; } // special case for camera settables diff --git a/photon-server/src/test/java/org/photonvision/hardware/HardwareConfigTest.java b/photon-server/src/test/java/org/photonvision/hardware/HardwareConfigTest.java new file mode 100644 index 000000000..3859e7f4f --- /dev/null +++ b/photon-server/src/test/java/org/photonvision/hardware/HardwareConfigTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 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.hardware; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.photonvision.common.configuration.HardwareConfig; +import org.photonvision.common.hardware.GPIO.CustomGPIO; +import org.photonvision.common.util.TestUtils; + +public class HardwareConfigTest { + + @Test + public void loadJson() { + try { + System.out.println("Loading Hardware configs..."); + var config = + new ObjectMapper().readValue(TestUtils.getHardwareConfigJson(), HardwareConfig.class); + assertEquals(config.deviceName, "PhotonVision"); + assertEquals(config.deviceLogoPath, "photonvision.png"); + assertEquals(config.supportURL, "https://support.photonvision.com"); + Assertions.assertArrayEquals( + config.ledPins.stream().mapToInt(i -> i).toArray(), new int[] {2, 13}); + Assertions.assertArrayEquals( + config.ledPWMRange.stream().mapToInt(i -> i).toArray(), new int[] {0, 100}); + + CustomGPIO.setConfig(config); + + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/photon-server/src/test/java/org/photonvision/hardware/HardwareManagerTest.java b/photon-server/src/test/java/org/photonvision/hardware/HardwareManagerTest.java new file mode 100644 index 000000000..63d1f0184 --- /dev/null +++ b/photon-server/src/test/java/org/photonvision/hardware/HardwareManagerTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 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.hardware; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.photonvision.common.configuration.HardwareConfig; +import org.photonvision.common.hardware.HardwareManager; +import org.photonvision.common.util.TestUtils; + +public class HardwareManagerTest { + + @Test + public void ManagementTest() throws IOException { + var config = + new ObjectMapper().readValue(TestUtils.getHardwareConfigJson(), HardwareConfig.class); + + HardwareManager.getInstance().setConfig(config); + + var instance = HardwareManager.getInstance(); + + instance.getGPIO(13).setPwmRange(List.of(0, 100)); + Assertions.assertEquals(instance.getGPIO(13).getPwmRange().get(0), 0); + Assertions.assertEquals(instance.getGPIO(13).getPwmRange().get(1), 100); + instance.getGPIO(13).blink(250, 5); + for (int i = 0; i < 101; i++) { + instance.getGPIO(13).dimLED(i); + } + } +} diff --git a/photon-server/src/test/java/org/photonvision/hardware/HardwareTest.java b/photon-server/src/test/java/org/photonvision/hardware/HardwareTest.java new file mode 100644 index 000000000..1b29a6880 --- /dev/null +++ b/photon-server/src/test/java/org/photonvision/hardware/HardwareTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 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.hardware; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.photonvision.common.hardware.GPIO.CustomGPIO; +import org.photonvision.common.hardware.GPIO.GPIOBase; +import org.photonvision.common.hardware.GPIO.PiGPIO; +import org.photonvision.common.hardware.Platform; +import org.photonvision.common.hardware.metrics.CPU; +import org.photonvision.common.hardware.metrics.GPU; +import org.photonvision.common.hardware.metrics.RAM; + +public class HardwareTest { + + @Test + public void testHardware() { + CPU cpu = CPU.getInstance(); + RAM ram = RAM.getInstance(); + GPU gpu = GPU.getInstance(); + + if (!Platform.isRaspberryPi()) return; + + System.out.println("Testing on platform: " + Platform.CurrentPlatform); + + System.out.println("Printing CPU Info:"); + System.out.println("Memory: " + cpu.getMemory() + "MB"); + System.out.println("Temperature: " + cpu.getTemp() + "C"); + System.out.println("Utilization: : " + cpu.getUtilization() + "%"); + + System.out.println("Printing GPU Info:"); + System.out.println("Memory: " + gpu.getMemory() + "MB"); + System.out.println("Temperature: " + gpu.getTemp() + "C"); + + System.out.println("Printing RAM Info: "); + System.out.println("Used RAM: : " + ram.getUsedRam() + "MB"); + } + + @Test + public void testGPIO() { + GPIOBase gpio; + if (Platform.isRaspberryPi()) { + gpio = new PiGPIO(18, 0, 100); + } else { + gpio = new CustomGPIO(18); + } + + gpio.setHigh(); // HIGH + assertTrue(gpio.getState()); + + gpio.setLow(); // LOW + assertFalse(gpio.getState()); + + gpio.togglePin(); // HIGH + assertTrue(gpio.getState()); + + gpio.togglePin(); // LOW + assertFalse(gpio.getState()); + + gpio.setState(true); // HIGH + assertTrue(gpio.getState()); + + gpio.setState(false); // LOW + assertFalse(gpio.getState()); + + var success = gpio.shutdown(); + assertTrue(success); + } + + @Test + public void testBlink() { + if (!Platform.isRaspberryPi()) return; + GPIOBase pwm = new PiGPIO(18, 0, 100); + pwm.blink(125, 3); + var startms = System.currentTimeMillis(); + while (true) { + if (System.currentTimeMillis() - startms > 4500) break; + } + } +} diff --git a/photon-server/src/test/resources/hardware/HardwareConfig.json b/photon-server/src/test/resources/hardware/HardwareConfig.json new file mode 100644 index 000000000..279a9cb2a --- /dev/null +++ b/photon-server/src/test/resources/hardware/HardwareConfig.json @@ -0,0 +1,23 @@ +{ + "deviceName": "PhotonVision", + "deviceLogoPath": "photonvision.png", + "supportURL": "https://support.photonvision.com", + "hardware": { + "leds": [2,13], + "ledSetCommand": "", + "ledsCanDim": true, + "ledPWMRange": [0, 100], + "ledPWMSetRange": "", + "ledDimCommand": "", + "ledBlinkCommand": "" + }, + + "metrics": { + "cpuTemp": "echo 10", + "cpuMemory": "echo 10", + "cpuUtil": "echo 10", + "gpuMemory": "echo 10", + "gpuUtil": "echo 10", + "ramUtil": "echo 10" + } +}