Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,4 @@ photon-client/playwright-report/
photon-client/blob-report/
photon-client/playwright/.cache/
photon-client/playwright/.auth/
photon-client/package-lock.json
45 changes: 43 additions & 2 deletions photon-client/src/components/settings/DeviceCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { inject, computed, ref, watch } from "vue";
import { useStateStore } from "@/stores/StateStore";
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import PvSelect from "@/components/common/pv-select.vue";
import PvSwitch from "@/components/common/pv-switch.vue";
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
import MetricsChart from "./MetricsChart.vue";
import { useTheme } from "vuetify";
Expand Down Expand Up @@ -128,9 +129,15 @@ const openExportLogsPrompt = () => {
};

const exportSettings = ref();
const showExportDialog = ref(false);
const excludeImages = ref(false);
const openExportSettingsPrompt = () => {
exportSettings.value.click();
};
const doExportSettings = () => {
showExportDialog.value = false;
exportSettings.value.click();
};

enum ImportType {
AllSettings,
Expand Down Expand Up @@ -356,7 +363,7 @@ watch(metricsHistorySnapshot, () => {
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="openExportSettingsPrompt"
@click="() => (showExportDialog = true)"
>
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
<span class="open-label">Export Settings</span>
Expand Down Expand Up @@ -540,6 +547,40 @@ watch(metricsHistorySnapshot, () => {
</v-card>
</v-dialog>

<!-- Export settings modal -->
<v-dialog
v-model="showExportDialog"
width="600"
@update:modelValue="
() => {
excludeImages = false;
}
"
>
<v-card color="surface" dark>
<v-card-title class="pb-0">Export Settings</v-card-title>
<v-card-text>
Download a ZIP archive of all PhotonVision settings from this device
<div class="pa-5 pb-0">
<pv-switch
v-model="excludeImages"
label="Exclude calibration images"
tooltip="Exclude saved calibration images and snapshots from the export to reduce file size"
/>
<v-btn
color="primary"
class="mt-4"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="doExportSettings"
>
<v-icon start class="open-icon"> mdi-export </v-icon>
<span class="open-label">Export Settings</span>
</v-btn>
</div>
</v-card-text>
</v-card>
</v-dialog>

<v-dialog v-model="offlineUpdateDialog.show" :width="700" dark>
<v-card color="surface" flat>
<v-card-title style="display: flex; justify-content: center"> Offline Update </v-card-title>
Expand Down Expand Up @@ -570,7 +611,7 @@ watch(metricsHistorySnapshot, () => {
<a
ref="exportSettings"
style="color: black; text-decoration: none; display: none"
:href="`http://${address}/api/settings/photonvision_config.zip`"
:href="`http://${address}/api/settings/photonvision_config.zip${excludeImages ? '?excludeImages=true' : ''}`"
download="photonvision-settings.zip"
target="_blank"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,28 @@ public void saveModule(CameraConfiguration config, String uniqueName) {
}

public File getSettingsFolderAsZip() {
return getSettingsFolderAsZip(false);
}

public File getSettingsFolderAsZip(boolean excludeImages) {
File out = Path.of(System.getProperty("java.io.tmpdir"), "photonvision-settings.zip").toFile();
try {
ZipUtil.pack(configDirectoryFile, out);
if (excludeImages) {
ZipUtil.pack(
configDirectoryFile,
out,
name -> {
// Exclude calibration images and saved snapshots
if (name.startsWith("calibImgs/")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good software is DRY - this hardcodes an assumption about the names of the directories, but there's other varibles which define those already.

|| name.startsWith("calibration/")
|| name.startsWith("imgSaves/")) {
return null;
}
return name;
});
} else {
ZipUtil.pack(configDirectoryFile, out);
}
} catch (Exception e) {
e.printStackTrace();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import edu.wpi.first.cscore.CameraServerJNI;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.ProtobufPublisher;
import edu.wpi.first.wpilibj.Alert;
import edu.wpi.first.wpilibj.Alert.AlertType;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.Files;
Expand Down Expand Up @@ -62,6 +65,9 @@ private record NetworkTraffic(double sentBitRate, double recvBitRate) {}
.getProtobufTopic(CameraServerJNI.getHostname(), DeviceMetrics.proto)
.publish();

private final Alert thermalThrottlingAlert =
new Alert("PhotonAlerts", "Thermal throttling detected!", AlertType.kWarning);

private SystemInfo si;
private CentralProcessor cpu;
private OperatingSystem os;
Expand Down Expand Up @@ -140,11 +146,11 @@ protected SystemMonitor() {
}

/**
* Returns a comma-separated list of addtional thermal zone types that should be checked to get
* Returns a comma-separated list of additional thermal zone types that should be checked to get
* the CPU temperature on Unix systems. The temperature will be reported for the first temperature
* zone with a type that mateches an item of this list. If the CPU temperature isn't being
* reported correctly for a coprocessor, override this method to return a string with type
* associated with the thermal zone for that comprocessor.
* zone with a type that matches an item of this list. If the CPU temperature isn't being reported
* correctly for a coprocessor, override this method to return a string with type associated with
* the thermal zone for that coprocessor.
*
* @return String containing a comma-separated list of thermal zone types for reading CPU
* temperature.
Expand All @@ -159,7 +165,7 @@ protected String getThermalZoneTypes() {

/**
* Starts the periodic system monitor that publishes performance metrics. The metrics are
* published every millisUpdateInerval seconds after a millisStartDelay startup delay. Calling
* published every millisUpdateInterval seconds after a millisStartDelay startup delay. Calling
* this method when the monitor is running will stop it and restart it with the new delay and
* update interval.
*
Expand Down Expand Up @@ -210,6 +216,9 @@ private void publishMetrics() {

metricPublisher.set(metrics);

thermalThrottlingAlert.set(this.isThermallyThrottling());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unrelated to settings export, is it intended for this PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so tht where that commit went...

no it was not, sorry

SmartDashboard.updateValues();

if (writeMetricsToLog) {
logMetrics(metrics);
}
Expand All @@ -233,7 +242,7 @@ private void logMetrics(DeviceMetrics metrics) {
String.format("CPU Throttle: %s, ", metrics.cpuThr().isBlank() ? "N/A" : metrics.cpuThr()));
sb.append(
String.format(
"Data sent: %.0f Kbps, Data recieved: %.0f Kbps",
"Data sent: %.0f Kbps, Data received: %.0f Kbps",
metrics.sentBitRate() / 1000, metrics.recvBitRate() / 1000));
logger.debug(sb.toString());
}
Expand Down Expand Up @@ -446,6 +455,16 @@ public String getCpuThrottleReason() {
return "";
}

/**
* Returns true if the device is currently experiencing thermal throttling. Platforms that support
* thermal throttling detection will override this method.
*
* @return true if thermally throttling, false otherwise.
*/
public boolean isThermallyThrottling() {
return false;
}

/**
* Returns the total GPU memory in MiB.
*
Expand Down Expand Up @@ -475,7 +494,7 @@ public String getIpAddress() {
}

/**
* Returns a NetworkTraffic instance containing the average sent and recieved network traffic
* Returns a NetworkTraffic instance containing the average sent and received network traffic
* since the last time this was called.
*
* @return NetworkTraffic instance with data in bits/second. The traffic values will be -1 if the
Expand Down Expand Up @@ -537,7 +556,7 @@ private void testSM() {
() -> {
var nt = getNetworkTraffic();
return String.format(
"Data sent: %.0f Kbps, Data recieved: %.0f Kbps",
"Data sent: %.0f Kbps, Data received: %.0f Kbps",
nt.sentBitRate() / 1000, nt.recvBitRate() / 1000);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,11 @@ public static void onSettingsImportRequest(Context ctx) {
}

public static void onSettingsExportRequest(Context ctx) {
logger.info("Exporting Settings to ZIP Archive");
boolean excludeImages = Boolean.parseBoolean(ctx.queryParam("excludeImages"));
logger.info("Exporting Settings to ZIP Archive (excludeImages=" + excludeImages + ")");

try {
var zip = ConfigManager.getInstance().getSettingsFolderAsZip();
var zip = ConfigManager.getInstance().getSettingsFolderAsZip(excludeImages);
var stream = new FileInputStream(zip);
logger.info("Uploading settings with size " + stream.available());

Expand Down
Loading