Metrics and lighting implementation (#116)

Implements metrics and lighting control.
This commit is contained in:
Matt
2020-09-15 11:19:36 -07:00
committed by GitHub
parent b73c698e4d
commit 71fc8a7017
23 changed files with 345 additions and 182 deletions

View File

@@ -42,6 +42,7 @@ public class ConfigManager {
private PhotonConfiguration config;
private final File hardwareConfigFile;
private final File hardwareSettingsFile;
private final File networkConfigFile;
private final File camerasFolder;
@@ -82,6 +83,8 @@ public class ConfigManager {
this.configDirectoryFile = new File(configDirectoryFile.toUri());
this.hardwareConfigFile =
new File(Path.of(configDirectoryFile.toString(), "hardwareConfig.json").toUri());
this.hardwareSettingsFile =
new File(Path.of(configDirectoryFile.toString(), "hardwareSettings.json").toUri());
this.networkConfigFile =
new File(Path.of(configDirectoryFile.toString(), "networkSettings.json").toUri());
this.camerasFolder = new File(Path.of(configDirectoryFile.toString(), "cameras").toUri());
@@ -110,6 +113,7 @@ public class ConfigManager {
}
HardwareConfig hardwareConfig;
HardwareSettings hardwareSettings;
NetworkConfig networkConfig;
if (hardwareConfigFile.exists()) {
@@ -129,6 +133,23 @@ public class ConfigManager {
hardwareConfig = new HardwareConfig();
}
if (hardwareSettingsFile.exists()) {
try {
hardwareSettings =
JacksonUtils.deserialize(hardwareSettingsFile.toPath(), HardwareSettings.class);
if (hardwareSettings == null) {
logger.error("Could not deserialize hardware settings! Loading defaults");
hardwareSettings = new HardwareSettings();
}
} catch (IOException e) {
logger.error("Could not deserialize hardware settings! Loading defaults");
hardwareSettings = new HardwareSettings();
}
} else {
logger.info("Hardware settings does not exist! Loading defaults");
hardwareSettings = new HardwareSettings();
}
if (networkConfigFile.exists()) {
try {
networkConfig = JacksonUtils.deserialize(networkConfigFile.toPath(), NetworkConfig.class);
@@ -155,7 +176,9 @@ public class ConfigManager {
HashMap<String, CameraConfiguration> cameraConfigurations = loadCameraConfigs();
this.config = new PhotonConfiguration(hardwareConfig, networkConfig, cameraConfigurations);
this.config =
new PhotonConfiguration(
hardwareConfig, hardwareSettings, networkConfig, cameraConfigurations);
}
public void saveToDisk() {
@@ -167,6 +190,11 @@ public class ConfigManager {
} catch (IOException e) {
logger.error("Could not save network config!", e);
}
try {
JacksonUtils.serialize(hardwareSettingsFile.toPath(), config.getHardwareSettings());
} catch (IOException e) {
logger.error("Could not save hardware config!", e);
}
// save all of our cameras
var cameraConfigMap = config.getCameraConfigurations();

View File

@@ -43,8 +43,8 @@ public class HardwareConfig {
public final String cpuMemoryCommand;
public final String cpuUtilCommand;
public final String gpuMemoryCommand;
public final String gpuTempCommand;
public final String ramUtilCommand;
public final String gpuMemUsageCommand;
// Device stuff
public final String restartHardwareCommand;
@@ -68,9 +68,9 @@ public class HardwareConfig {
cpuMemoryCommand = "";
cpuUtilCommand = "";
gpuMemoryCommand = "";
gpuTempCommand = "";
ramUtilCommand = "";
ledBlinkCommand = "";
gpuMemUsageCommand = "";
restartHardwareCommand = "";
vendorFOV = -1;
@@ -95,8 +95,8 @@ public class HardwareConfig {
String cpuMemoryCommand,
String cpuUtilCommand,
String gpuMemoryCommand,
String gpuTempCommand,
String ramUtilCommand,
String gpuMemUsageCommand,
String restartHardwareCommand,
double vendorFOV,
List<Integer> blacklistedResIndices) {
@@ -116,11 +116,11 @@ public class HardwareConfig {
this.cpuMemoryCommand = cpuMemoryCommand;
this.cpuUtilCommand = cpuUtilCommand;
this.gpuMemoryCommand = gpuMemoryCommand;
this.gpuTempCommand = gpuTempCommand;
this.ramUtilCommand = ramUtilCommand;
this.restartHardwareCommand = restartHardwareCommand;
this.vendorFOV = vendorFOV;
this.blacklistedResIndices = blacklistedResIndices;
this.gpuMemUsageCommand = gpuMemUsageCommand;
}
public final boolean hasPresetFOV() {

View File

@@ -0,0 +1,22 @@
/*
* 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.configuration;
public class HardwareSettings {
public int ledBrightnessPercentage = 100;
}

View File

@@ -23,7 +23,7 @@ import org.photonvision.common.hardware.Platform;
import org.photonvision.common.networking.NetworkMode;
public class NetworkConfig {
public int teamNumber = -1;
public int teamNumber = 0;
public NetworkMode connectionType = NetworkMode.DHCP;
public String staticIp = "";
public String netmask = "";

View File

@@ -30,6 +30,30 @@ import org.photonvision.vision.processes.VisionModuleManager;
// TODO rename this class
public class PhotonConfiguration {
private HardwareConfig hardwareConfig;
private HardwareSettings hardwareSettings;
private NetworkConfig networkConfig;
private HashMap<String, CameraConfiguration> cameraConfigurations;
public PhotonConfiguration(
HardwareConfig hardwareConfig,
HardwareSettings hardwareSettings,
NetworkConfig networkConfig) {
this(hardwareConfig, hardwareSettings, networkConfig, new HashMap<>());
}
public PhotonConfiguration(
HardwareConfig hardwareConfig,
HardwareSettings hardwareSettings,
NetworkConfig networkConfig,
HashMap<String, CameraConfiguration> cameraConfigurations) {
this.hardwareConfig = hardwareConfig;
this.hardwareSettings = hardwareSettings;
this.networkConfig = networkConfig;
this.cameraConfigurations = cameraConfigurations;
}
public HardwareConfig getHardwareConfig() {
return hardwareConfig;
}
@@ -38,6 +62,10 @@ public class PhotonConfiguration {
return networkConfig;
}
public HardwareSettings getHardwareSettings() {
return hardwareSettings;
}
public void setNetworkConfig(NetworkConfig networkConfig) {
this.networkConfig = networkConfig;
}
@@ -60,25 +88,6 @@ public class PhotonConfiguration {
cameraConfigurations.put(name, config);
}
private HardwareConfig hardwareConfig;
private NetworkConfig networkConfig;
private HashMap<String, CameraConfiguration> cameraConfigurations;
public PhotonConfiguration(HardwareConfig hardwareConfig, NetworkConfig networkConfig) {
this(hardwareConfig, networkConfig, new HashMap<>());
}
public PhotonConfiguration(
HardwareConfig hardwareConfig,
NetworkConfig networkConfig,
HashMap<String, CameraConfiguration> cameraConfigurations) {
this.hardwareConfig = hardwareConfig;
this.networkConfig = networkConfig;
this.cameraConfigurations = cameraConfigurations;
}
public Map<String, Object> toHashMap() {
Map<String, Object> map = new HashMap<>();
var settingsSubmap = new HashMap<String, Object>();
@@ -91,7 +100,9 @@ public class PhotonConfiguration {
.map(SerializationUtils::objectToHashMap)
.collect(Collectors.toList()));
settingsSubmap.put("lighting", SerializationUtils.objectToHashMap(hardwareConfig));
var lightingConfig = new UILightingConfig();
// TODO set constants
settingsSubmap.put("lighting", SerializationUtils.objectToHashMap(lightingConfig));
var generalSubmap = new HashMap<String, Object>();
generalSubmap.put("version", PhotonVersion.versionString);
@@ -105,6 +116,11 @@ public class PhotonConfiguration {
return map;
}
public static class UILightingConfig {
public int brightness = 0;
public boolean supported = true;
}
public static class UICameraConfiguration {
@SuppressWarnings("unused")
public double fov, tiltDegrees;

View File

@@ -47,7 +47,7 @@ public class HardwareManager {
@SuppressWarnings("FieldCanBeLocal")
private final NTDataChangeListener ledModeListener;
public final VisionLED visionLED;
public final VisionLED visionLED; // May be null if no LED is specified
public static HardwareManager getInstance() {
if (instance == null) {
@@ -61,28 +61,47 @@ public class HardwareManager {
CustomGPIO.setConfig(hardwareConfig);
MetricsBase.setConfig(hardwareConfig);
statusLED = new StatusLED(hardwareConfig.statusRGBPins);
statusLED =
hardwareConfig.statusRGBPins.size() == 3
? new StatusLED(hardwareConfig.statusRGBPins)
: null;
visionLED =
new VisionLED(
hardwareConfig.ledPins,
hardwareConfig.ledPWMFrequency,
(hardwareConfig.ledPWMRange != null && hardwareConfig.ledPWMRange.size() == 2)
? hardwareConfig.ledPWMRange.get(1)
: 0);
hardwareConfig.ledPins.isEmpty()
? null
: new VisionLED(
hardwareConfig.ledPins,
hardwareConfig.ledPWMFrequency,
(hardwareConfig.ledPWMRange != null && hardwareConfig.ledPWMRange.size() == 2)
? hardwareConfig.ledPWMRange.get(1)
: 0);
ledModeEntry = NetworkTablesManager.getInstance().kRootTable.getEntry("ledMode");
ledModeEntry.setNumber(VisionLEDMode.VLM_DEFAULT.value);
ledModeListener = new NTDataChangeListener(ledModeEntry, visionLED::onLedModeChange);
ledModeListener =
visionLED == null
? null
: new NTDataChangeListener(ledModeEntry, visionLED::onLedModeChange);
Runtime.getRuntime().addShutdownHook(new Thread(this::onJvmExit));
if (visionLED != null)
visionLED.setBrightness(
ConfigManager.getInstance().getConfig().getHardwareSettings().ledBrightnessPercentage);
// Start hardware metrics thread (Disabled until implemented)
// if (Platform.isLinux()) MetricsPublisher.getInstance().startTask();
}
public void setBrightnessPercent(int percent) {
ConfigManager.getInstance().getConfig().getHardwareSettings().ledBrightnessPercentage = percent;
if (visionLED != null) visionLED.setBrightness(percent);
ConfigManager.getInstance().requestSave();
logger.info("Setting led brightness to " + percent + "%");
}
private void onJvmExit() {
logger.info("Shutting down...");
visionLED.setState(false);
logger.info("Shutting down LEDs...");
if (visionLED != null) visionLED.setState(false);
}
public boolean restartDevice() {

View File

@@ -19,17 +19,23 @@ package org.photonvision.common.hardware.metrics;
public class CPUMetrics extends MetricsBase {
public CPUMetrics() {}
private String cpuMemSplit = null;
public String getMemory() {
if (cpuMemoryCommand.isEmpty()) return "";
return execute(cpuMemoryCommand);
if (cpuMemSplit == null) {
cpuMemSplit = execute(cpuMemoryCommand);
}
return cpuMemSplit;
}
// TODO: Command should return in Celsius
public String getTemp() {
if (cpuTemperatureCommand.isEmpty()) return "";
return execute(cpuTemperatureCommand);
try {
return execute(cpuTemperatureCommand);
} catch (Exception e) {
return "N/A";
}
}
public String getUtilization() {

View File

@@ -18,11 +18,16 @@
package org.photonvision.common.hardware.metrics;
public class GPUMetrics extends MetricsBase {
public String getMemory() {
return execute(gpuMemoryCommand);
private String gpuMemSplit = null;
public String getGPUMemorySplit() {
if (gpuMemSplit == null) {
gpuMemSplit = execute(gpuMemoryCommand);
}
return gpuMemSplit;
}
public String getTemp() {
return execute(gpuTemperatureCommand);
public String getMallocedMemory() {
return execute(gpuMemUsageCommand);
}
}

View File

@@ -26,18 +26,18 @@ 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 cpuMemoryCommand = "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]+'";
"sed 's/.\\{3\\}$/.&/' <<< cat /sys/class/thermal/thermal_zone0/temp";
public static String cpuUtilizationCommand =
"sudo top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk '{print 100 - $1}'";
"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";
public static String gpuMemoryCommand = "vcgencmd get_mem gpu | grep -Eo '[0-9]+'";
public static String gpuMemUsageCommand = "vcgencmd get_mem malloc | grep -Eo '[0-9]+'";
// RAM
public static String ramUsageCommand = "sudo free | awk -v i=2 -v j=3 'FNR == i {print $j}'";
public static String ramUsageCommand = "free --mega | awk -v i=2 -v j=3 'FNR == i {print $j}'";
private static ShellExec runCommand = new ShellExec(true, true);
@@ -48,7 +48,7 @@ public abstract class MetricsBase {
cpuUtilizationCommand = config.cpuUtilCommand;
gpuMemoryCommand = config.gpuMemoryCommand;
gpuTemperatureCommand = config.gpuTempCommand;
gpuMemUsageCommand = config.gpuMemUsageCommand;
ramUsageCommand = config.ramUtilCommand;
}

View File

@@ -17,15 +17,15 @@
package org.photonvision.common.hardware.metrics;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.HashMap;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.TimedTaskManager;
import org.photonvision.server.SocketHandler;
public class MetricsPublisher {
private final HashMap<String, String> metrics;
private static final Logger logger = new Logger(MetricsPublisher.class, LogGroup.General);
private static CPUMetrics cpuMetrics;
private static GPUMetrics gpuMetrics;
@@ -39,26 +39,6 @@ public class MetricsPublisher {
cpuMetrics = new CPUMetrics();
gpuMetrics = new GPUMetrics();
ramMetrics = new RAMMetrics();
metrics = new HashMap<>();
}
public void startTask() {
TimedTaskManager.getInstance()
.addTask(
"Metrics",
() -> {
metrics.put("cpuTemp", cpuMetrics.getTemp());
metrics.put("cpuUtil", cpuMetrics.getUtilization());
metrics.put("cpuMem", cpuMetrics.getMemory());
metrics.put("gpuTemp", gpuMetrics.getTemp());
metrics.put("gpuMem", gpuMetrics.getMemory());
metrics.put("ramUtil", ramMetrics.getUsedRam());
DataChangeService.getInstance()
.publishEvent(new OutgoingUIEvent<>("metrics", metrics));
},
1000);
}
public void stopTask() {
@@ -66,6 +46,32 @@ public class MetricsPublisher {
logger.info("This device does not support running bash commands. Stopped metrics thread.");
}
public void publish() {
if (!Platform.isRaspberryPi()) {
logger.debug("Ignoring metrics on non-Pi devices");
return;
}
logger.debug("Publishing Metrics...");
final var metrics = new HashMap<String, String>();
metrics.put("cpuTemp", cpuMetrics.getTemp());
metrics.put("cpuUtil", cpuMetrics.getUtilization());
metrics.put("cpuMem", cpuMetrics.getMemory());
metrics.put("gpuMem", gpuMetrics.getGPUMemorySplit());
metrics.put("ramUtil", ramMetrics.getUsedRam());
metrics.put("gpuMemUtil", gpuMetrics.getMallocedMemory());
var retMap = new HashMap<String, Object>();
retMap.put("metrics", metrics);
try {
SocketHandler.getInstance().broadcastMessage(retMap, null);
} catch (JsonProcessingException e) {
logger.error("Exception while sending metrics!", e);
}
}
private static class Singleton {
public static final MetricsPublisher INSTANCE = new MetricsPublisher();
}

View File

@@ -28,6 +28,7 @@ public final class SerializationUtils {
var ret = new HashMap<String, Object>();
for (var field : src.getClass().getFields()) {
try {
field.setAccessible(true);
if (!field
.getType()
.isEnum()) { // if the field is not an enum, get it based on the current pipeline