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

@@ -38,9 +38,9 @@
</v-list-item-content>
</v-list-item>
<v-list-item
ref="camerasTabOpener"
link
to="cameras"
ref="camerasTabOpener"
@click="switchToDriverMode()"
>
<v-list-item-icon>
@@ -53,6 +53,7 @@
<v-list-item
link
to="settings"
@click="switchToSettingsTab()"
>
<v-list-item-icon>
<v-icon>mdi-settings</v-icon>
@@ -118,7 +119,7 @@
>
<v-layout>
<v-flex>
<router-view v-on:switch-to-cameras="switchToDriverMode" />
<router-view @switch-to-cameras="switchToDriverMode" />
</v-flex>
</v-layout>
</v-container>
@@ -246,6 +247,9 @@ import Logs from "./views/LogsView"
this.handleInputWithIndex('currentPipeline', this.previouslySelectedIndex || 0);
}
this.previouslySelectedIndex = null;
},
switchToSettingsTab() {
this.axios.post('http://' + this.$address + '/api/sendMetrics', {})
}
}
};

View File

@@ -127,6 +127,14 @@ export default new Vuex.Store({
patternHeight: 7,
boardType: 0, // Chessboard, dotboard
},
metrics: {
cpuTemp: "N/A",
cpuUtil: "N/A",
cpuMem: "N/A",
gpuMem: "N/A",
ramUtil: "N/A",
gpuMemUtil: "N/A",
}
},
mutations: {
compactMode: set('compactMode'),
@@ -135,6 +143,7 @@ export default new Vuex.Store({
selectedOutputs: set('selectedOutputs'),
settings: set('settings'),
calibrationData: set('calibrationData'),
metrics: set('metrics'),
logString: (state, newStr) => {
const str = state.logMessages;
str.push(newStr)
@@ -182,8 +191,12 @@ export default new Vuex.Store({
Vue.set(state, 'pipelineResults', payload[key])
}
}
},
mutateEnabledLEDPercentage(state, payload) {
const settings = state.settings;
settings.lighting.brightness = payload;
Vue.set(state, "settings", settings);
},
mutateCalibrationState: (state, payload) => {

View File

@@ -8,32 +8,19 @@
cols="12"
style="max-width: 1400px"
>
<v-form
ref="form"
v-model="valid"
<v-card
v-for="item in tabList"
:key="item.name"
dark
class="mb-3 pr-6 pb-3"
style="background-color: #006492;"
>
<v-card
v-for="item in tabList"
:key="item.name"
dark
class="mb-3 pr-6 pb-3"
style="background-color: #006492;"
>
<v-card-title>{{ item.name }}</v-card-title>
<component
:is="item"
class="ml-5"
/>
</v-card>
<v-btn
color="accent"
style="color: black; width: 100%;"
:disabled="!valid"
@click="sendGeneralSettings()"
>
Save
</v-btn>
</v-form>
<v-card-title>{{ item.name }}</v-card-title>
<component
:is="item"
class="ml-5"
/>
</v-card>
</v-col>
</v-row>
<v-snackbar
@@ -61,7 +48,6 @@
data() {
return {
selectedTab: 0,
valid: true, // Are all settings valid
snack: false,
snackbar: {
color: "accent",
@@ -86,28 +72,6 @@
}
}
},
methods: {
sendGeneralSettings() {
this.axios.post("http://" + this.$address + "/api/settings/general", this.settings).then(
function (response) {
if (response.status === 200) {
this.snackbar = {
color: "success",
text: "Settings updated successfully"
};
this.snack = true;
}
},
function (error) {
this.snackbar = {
color: "error",
text: (error.response || {data: "Couldn't save settings"}).data
};
this.snack = true;
}
)
},
}
}
</script>

View File

@@ -1,14 +1,21 @@
<template>
<div>
<span>Version: {{ settings.version }}</span>
&mdash;
<div v-if="settings.hardwareModel !== ''">
<span>Hardware model: {{ settings.hardwareModel }}</span>
&mdash;
<v-row class="pa-4">
<span>{{ infoTabs.join(' — ') }}</span>
</v-row>
<div v-if="metrics.cpuUtil !== 'N/A'">
<v-row class="pa-4">
<span>CPU Usage: {{ metrics.cpuUtil.replace(" ", "") }}%</span>
&nbsp;&ndash;&nbsp;
<span>CPU Temp: {{ parseInt(metrics.cpuTemp) }}&deg;&nbsp;C</span>
&nbsp;&ndash;&nbsp;
<span>CPU Memory Usage: {{ metrics.ramUtil.replace(" ", "") }}MB of {{ metrics.cpuMem }}MB</span>
&ndash;
<span>GPU Memory Usage: {{ metrics.gpuMemUtil }}MB of {{ metrics.gpuMem }}MB</span>
</v-row>
</div>
<span>Platform: {{ settings.hardwarePlatform }}</span>
&mdash;
<span>GPU Acceleration: {{ settings.gpuAcceleration ? "Enabled" : "Unsupported" }}{{ settings.gpuAcceleration ? " (" + settings.gpuAcceleration + " mode)" : "" }}</span>
<v-row>
<v-col
cols="12"
@@ -21,7 +28,8 @@
>
<v-icon left>
mdi-download
</v-icon> Export Settings
</v-icon>
Export Settings
</v-btn>
</v-col>
<v-col
@@ -35,7 +43,8 @@
>
<v-icon left>
mdi-upload
</v-icon> Import Settings
</v-icon>
Import Settings
</v-btn>
</v-col>
<v-col
@@ -48,7 +57,8 @@
>
<v-icon left>
mdi-restart
</v-icon> Restart Photon
</v-icon>
Restart Photon
</v-btn>
</v-col>
<v-col
@@ -61,7 +71,8 @@
>
<v-icon left>
mdi-restart
</v-icon> Restart Device
</v-icon>
Restart Device
</v-btn>
</v-col>
</v-row>
@@ -108,6 +119,21 @@ export default {
computed: {
settings() {
return this.$store.state.settings.general;
},
infoTabs() {
let ret = [];
let idx = 0;
ret[idx++] = `Version: ${this.settings.version}`;
if (this.settings.hardwareModel !== '') {
ret[idx++] = `Hardware model: ${this.settings.hardwareModel}`;
}
ret[idx++] = `Platform: ${this.settings.hardwarePlatform}`;
ret[idx++] = `GPU Acceleration: ${this.settings.gpuAcceleration ? "Enabled" : "Unsupported"}${this.settings.gpuAcceleration ? " (" + this.settings.gpuAcceleration + " mode)" : ""}`
return ret;
},
metrics() {
return this.$store.state.metrics;
}
},
methods: {

View File

@@ -1,14 +1,13 @@
<template>
<div>
<CVslider
v-model="settings.brightness"
v-model="enabledLEDPercentage"
class="pt-2"
slider-cols="12"
name="Brightness"
min="0"
max="100"
@input="handleData('accuracy')"
@rollback="e => rollback('accuracy', e)"
@input="handleData('enabledLEDPercentage')"
/>
</div>
</template>
@@ -22,6 +21,14 @@
CVslider,
},
computed: {
enabledLEDPercentage: {
get() {
return this.settings.brightness
},
set(value) {
this.$store.commit("mutateEnabledLEDPercentage", value)
}
},
isDHCP() {
return this.settings.connectionType === 0;
},

View File

@@ -1,36 +1,50 @@
<template>
<div>
<CVnumberinput
v-model="settings.teamNumber"
name="Team Number"
:rules="[v => (v > 0) || 'Team number must be greater than zero', v => (v < 10000) || 'Team number must have fewer than five digits']"
class="mb-4"
/>
<CVSwitch
v-model="settings.runNTServer"
name="Run NetworkTables Server"
tooltip="If enabled, this device will create a NT server. This is useful for home debugging, but should be disabled on-robot."
/>
<template v-if="$store.state.settings.networkSettings.supported">
<CVradio
v-model="settings.connectionType"
:list="['DHCP','Static']"
<v-form
ref="form"
v-model="valid"
>
<CVnumberinput
v-model="settings.teamNumber"
:disabled="settings.runNTServer"
name="Team Number"
:rules="[v => (v > 0) || 'Team number must be greater than zero', v => (v < 10000) || 'Team number must have fewer than five digits']"
class="mb-4"
/>
<template v-if="!isDHCP">
<CVinput
v-model="settings.staticIp"
:input-cols="inputCols"
:rules="[v => isIPv4(v) || 'Invalid IPv4 address']"
name="IP"
<CVSwitch
v-model="settings.runNTServer"
name="Run NetworkTables Server"
tooltip="If enabled, this device will create a NT server. This is useful for home debugging, but should be disabled on-robot."
/>
<template v-if="$store.state.settings.networkSettings.supported">
<CVradio
v-model="settings.connectionType"
:list="['DHCP','Static']"
/>
<template v-if="!isDHCP">
<CVinput
v-model="settings.staticIp"
:input-cols="inputCols"
:rules="[v => isIPv4(v) || 'Invalid IPv4 address']"
name="IP"
/>
</template>
</template>
</template>
<CVinput
v-model="settings.hostname"
:input-cols="inputCols"
:rules="[v => isHostname(v) || 'Invalid hostname']"
name="Hostname"
/>
<CVinput
v-model="settings.hostname"
:input-cols="inputCols"
:rules="[v => isHostname(v) || 'Invalid hostname']"
name="Hostname"
/>
</v-form>
<v-btn
color="accent"
style="color: black; width: 100%;"
:disabled="!valid"
@click="sendGeneralSettings()"
>
Save
</v-btn>
</div>
</template>
@@ -61,7 +75,8 @@
text: ""
},
snack: false,
isLoading: false
isLoading: false,
valid: true, // Are all settings valid
}
},
computed: {
@@ -99,7 +114,27 @@
}
}
return true;
}
},
sendGeneralSettings() {
this.axios.post("http://" + this.$address + "/api/settings/general", this.settings).then(
function (response) {
if (response.status === 200) {
this.snackbar = {
color: "success",
text: "Settings updated successfully"
};
this.snack = true;
}
},
function (error) {
this.snackbar = {
color: "error",
text: (error.response || {data: "Couldn't save settings"}).data
};
this.snack = true;
}
)
},
},
}
</script>

View File

@@ -24,6 +24,7 @@ import org.apache.commons.cli.*;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.hardware.HardwareManager;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.LogLevel;
@@ -133,14 +134,13 @@ public class Main {
logger.error("Failed to parse command-line options!", e);
}
logger.info("Running in " + (isRelease ? "release" : "development") + " mode!");
var logLevel = (isRelease || printDebugLogs) ? LogLevel.INFO : LogLevel.DEBUG;
var logLevel = LogLevel.DEBUG;
Logger.setLevel(LogGroup.Camera, logLevel);
Logger.setLevel(LogGroup.WebServer, logLevel);
Logger.setLevel(LogGroup.VisionModule, logLevel);
Logger.setLevel(LogGroup.Data, logLevel);
Logger.setLevel(LogGroup.General, logLevel);
logger.info("Logging initialized in " + (isRelease ? "Release" : "Debug") + " mode.");
logger.info("Logging initialized in debug mode.");
logger.info(
"Starting PhotonVision version "
@@ -159,17 +159,14 @@ public class Main {
ConfigManager.getInstance().load(); // init config manager
ConfigManager.getInstance().requestSave();
// Force load the hardware manager
HardwareManager.getInstance();
NetworkManager.getInstance().initialize(false);
NetworkTablesManager.getInstance()
.setConfig(ConfigManager.getInstance().getConfig().getNetworkConfig());
// HashMap<VisionSource, List<CVPipelineSettings>> allSources = gatherSources();
// logger.info("Adding " + allSources.size() + " configs to VMM.");
// VisionModuleManager.getInstance().addSources(allSources);
// ConfigManager.getInstance().addCameraConfigurations(allSources);
if (!isTestMode) {
VisionSourceManager.getInstance()
.registerLoadedConfigs(

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

View File

@@ -33,6 +33,7 @@ import org.photonvision.common.configuration.NetworkConfig;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.hardware.HardwareManager;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.hardware.metrics.MetricsPublisher;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.networking.NetworkManager;
@@ -180,6 +181,11 @@ public class RequestHandler {
ctx.status(200);
}
public static void sendMetrics(Context ctx) {
MetricsPublisher.getInstance().publish();
ctx.status(200);
}
public static class UITargetData {
public int index;
public TargetModel targetModel;

View File

@@ -80,6 +80,7 @@ public class Server {
app.post("/api/restartDevice", RequestHandler::restartDevice);
app.post("api/restartProgram", RequestHandler::restartProgram);
app.post("api/vision/pnpModel", RequestHandler::uploadPnpModel);
app.post("api/sendMetrics", RequestHandler::sendMetrics);
app.start(port);
}

View File

@@ -35,6 +35,7 @@ import org.msgpack.jackson.dataformat.MessagePackFactory;
import org.photonvision.common.dataflow.DataChangeDestination;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.IncomingWebSocketEvent;
import org.photonvision.common.hardware.HardwareManager;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.vision.pipeline.PipelineType;
@@ -182,6 +183,12 @@ public class SocketHandler {
dcService.publishEvent(newPipelineEvent);
break;
}
case SMT_CHANGEBRIGHTNESS:
{
HardwareManager.getInstance()
.setBrightnessPercent(Integer.parseInt(entryValue.toString()));
break;
}
case SMT_DUPLICATEPIPELINE:
{
var pipeIndex = (Integer) entryValue;

View File

@@ -34,6 +34,7 @@ public enum SocketMessageType {
SMT_STARTPNPCALIBRATION("startPnpCalibration"),
SMT_TAKECALIBRATIONSNAPSHOT("takeCalibrationSnapshot"),
SMT_DUPLICATEPIPELINE("duplicatePipeline"),
SMT_CHANGEBRIGHTNESS("enabledLEDPercentage"),
SMT_ROBOTOFFSETPOINT("robotOffsetPoint");
public final String entryKey;

View File

@@ -46,8 +46,7 @@ public class HardwareTest {
System.out.println("Utilization: : " + cpuMetrics.getUtilization() + "%");
System.out.println("Printing GPU Info:");
System.out.println("Memory: " + gpuMetrics.getMemory() + "MB");
System.out.println("Temperature: " + gpuMetrics.getTemp() + "C");
System.out.println("Memory: " + gpuMetrics.getGPUMemorySplit() + "MB");
System.out.println("Printing RAM Info: ");
System.out.println("Used RAM: : " + ramMetrics.getUsedRam() + "MB");