mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-20 00:51:41 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user