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 <btrout.dhrs@gmail.com>
This commit is contained in:
Xzibit
2020-07-31 15:43:58 -04:00
committed by GitHub
parent 65964726a5
commit 0b98dc3c9f
24 changed files with 1096 additions and 33 deletions

View File

@@ -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;

View File

@@ -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<Integer> ledPins;
public final String ledSetCommand;
public final boolean ledsCanDim;
public final ArrayList<Integer> 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<String, ?> hardware,
@JsonProperty("metrics") Map<String, ?> metrics) {
this.deviceName = deviceName;
this.deviceLogoPath = deviceLogoPath;
this.supportURL = supportURL;
this.ledPins = (ArrayList<Integer>) hardware.get("leds");
this.ledSetCommand = (String) hardware.get("ledSetCommand");
this.ledsCanDim = (Boolean) hardware.get("ledsCanDim");
this.ledPWMRange = (ArrayList<Integer>) 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");
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Integer> 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<Integer> 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<Integer> 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);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, String> 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<Integer> range);
public abstract List<Integer> getPwmRange();
public abstract void blink(int pulseTimeMillis, int blinks);
public abstract void dimLED(int dimPercentage);
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Pulse> 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<Integer> 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<Integer> 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();
}
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Integer, GPIOBase> 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();
}
}

View File

@@ -15,27 +15,26 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, Double> 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();
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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());

View File

@@ -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
}

View File

@@ -26,7 +26,8 @@ public class QuirkyCamera {
private static final List<QuirkyCamera> 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());

View File

@@ -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<Integer, Integer>) 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