Check offline updates for incompatibility (#2361)

This commit is contained in:
Sam Freund
2026-02-18 01:24:46 -06:00
committed by GitHub
parent c91df33b63
commit 5474e28826
8 changed files with 132 additions and 6 deletions

View File

@@ -30,11 +30,67 @@ const offlineUpdate = ref();
const openOfflineUpdatePrompt = () => {
offlineUpdate.value.click();
};
const handleOfflineUpdate = async () => {
const offlineUpdateRegex = new RegExp("photonvision-((?:dev-)?v[\\w.-]+)-((?:linux|win|mac)\\w+)\\.jar");
const majorVersionRegex = new RegExp("(?:dev-)?(\\d+)\\.\\d+\\.\\d+");
const offlineUpdateDialog = ref({ show: false, confirmString: "" });
const handleOfflineUpdateRequest = async () => {
const files = offlineUpdate.value.files;
if (files.length === 0) return;
const match = files[0].name.match(offlineUpdateRegex);
if (!match) {
useStateStore().showSnackbarMessage({
message: "Selected file does not match expected naming convention.",
color: "error"
});
return;
}
const version = match[1] as string;
const arch = match[2] as string;
const currentVersion = useSettingsStore().general.imageVersion;
const currentArch = useSettingsStore().general.wpilibArch;
const versionMajor = version.match(majorVersionRegex)?.[1];
const currentVersionMajor = currentVersion?.match(majorVersionRegex)?.[1];
const versionMatch = currentVersion ? versionMajor === currentVersionMajor : false;
const dev = version.includes("dev");
if (currentArch && arch !== currentArch) {
useStateStore().showSnackbarMessage({
message: `Selected file architecture (${arch}) does not match device architecture (${currentArch}).`,
color: "error"
});
return;
} else if (versionMatch && !dev) {
handleOfflineUpdate(files[0]);
} else if (!versionMatch && !dev) {
offlineUpdateDialog.value = {
show: true,
confirmString: `You are attempting to update from PhotonVision ${currentVersion} on image ${useSettingsStore().general.imageVersion} to ${version} from a different FRC year. These versions may be incompatible. Are you sure you want to proceed?`
};
} else if (versionMatch && dev) {
offlineUpdateDialog.value = {
show: true,
confirmString:
"You are attempting to update to a dev version. This could result in instability. Are you sure you want to proceed?"
};
} else if (!versionMatch && dev) {
offlineUpdateDialog.value = {
show: true,
confirmString: `You are attempting to update to a dev version, from PhotonVision ${currentVersion} on image ${useSettingsStore().general.imageVersion} to ${version} from a different FRC year. These versions may be incompatible, and you may experience instability. Are you sure you want to proceed?`
};
}
};
const handleOfflineUpdate = async (file: File) => {
const formData = new FormData();
formData.append("jarData", files[0]);
formData.append("jarData", file);
useStateStore().showSnackbarMessage({
message: "New Software Upload in Progress...",
color: "secondary",
@@ -134,6 +190,7 @@ interface MetricItem {
const generalMetrics = computed<MetricItem[]>(() => {
const stats = [
{ header: "Version", value: useSettingsStore().general.version || "Unknown" },
{ header: "Image Version", value: useSettingsStore().general.imageVersion || "Unknown" },
{ header: "Hardware Model", value: useSettingsStore().general.hardwareModel || "Unknown" },
{ header: "Platform", value: useSettingsStore().general.hardwarePlatform || "Unknown" },
{ header: "GPU Acceleration", value: useSettingsStore().general.gpuAcceleration || "None detected" }
@@ -333,7 +390,7 @@ watch(metricsHistorySnapshot, () => {
type="file"
accept=".jar"
style="display: none"
@change="handleOfflineUpdate"
@change="handleOfflineUpdateRequest"
/>
</v-col>
</v-row>
@@ -483,6 +540,33 @@ watch(metricsHistorySnapshot, () => {
</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>
<v-card-text class="pt-0 pb-10px">
<span> {{ offlineUpdateDialog.confirmString }} </span>
</v-card-text>
<v-card-text class="pt-10px">
<v-row class="align-center text-white">
<v-col cols="12">
<v-btn
color="buttonActive"
width="100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="
offlineUpdateDialog.show = false;
handleOfflineUpdate(offlineUpdate.value.files[0]);
"
>
<v-icon start class="open-icon" size="large"> mdi-upload </v-icon>
<span class="open-label"> Confirm Update </span>
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
<a
ref="exportSettings"
style="color: black; text-decoration: none; display: none"

View File

@@ -182,7 +182,7 @@ export const useStateStore = defineStore("state", {
message: data.message,
color: data.color,
progressBarColor: data.progressBarColor || "",
timeout: data.timeout || 2000
timeout: data.timeout || 5000
};
}
}

View File

@@ -68,9 +68,11 @@ export const useSettingsStore = defineStore("settings", {
state: (): GeneralSettingsStore => ({
general: {
version: undefined,
imageVersion: undefined,
gpuAcceleration: undefined,
hardwareModel: undefined,
hardwarePlatform: undefined,
wpilibArch: undefined,
mrCalWorking: true,
availableModels: [],
supportedBackends: [],
@@ -155,8 +157,10 @@ export const useSettingsStore = defineStore("settings", {
updateGeneralSettingsFromWebsocket(data: WebsocketSettingsUpdate) {
this.general = {
version: data.general.version || undefined,
imageVersion: data.general.imageVersion || undefined,
hardwareModel: data.general.hardwareModel || undefined,
hardwarePlatform: data.general.hardwarePlatform || undefined,
wpilibArch: data.general.wpilibArch || undefined,
gpuAcceleration: data.general.gpuAcceleration || undefined,
mrCalWorking: data.general.mrCalWorking,
availableModels: data.general.availableModels || undefined,

View File

@@ -5,9 +5,11 @@ import { reactive } from "vue";
export interface GeneralSettings {
version?: string;
imageVersion?: string;
gpuAcceleration?: string;
hardwareModel?: string;
hardwarePlatform?: string;
wpilibArch?: string;
mrCalWorking: boolean;
availableModels: ObjectDetectionModelProperties[];
supportedBackends: string[];

View File

@@ -23,32 +23,38 @@ import org.photonvision.common.configuration.NeuralNetworkModelsSettings;
public class UIGeneralSettings {
public UIGeneralSettings(
String version,
String imageVersion,
String gpuAcceleration,
boolean mrCalWorking,
NeuralNetworkModelsSettings.ModelProperties[] availableModels,
List<String> supportedBackends,
String hardwareModel,
String hardwarePlatform,
String wpilibArch,
boolean conflictingHostname,
String conflictingCameras) {
this.version = version;
this.imageVersion = imageVersion;
this.gpuAcceleration = gpuAcceleration;
this.mrCalWorking = mrCalWorking;
this.availableModels = availableModels;
this.supportedBackends = supportedBackends;
this.hardwareModel = hardwareModel;
this.hardwarePlatform = hardwarePlatform;
this.wpilibArch = wpilibArch;
this.conflictingHostname = conflictingHostname;
this.conflictingCameras = conflictingCameras;
}
public String version;
public String imageVersion;
public String gpuAcceleration;
public boolean mrCalWorking;
public NeuralNetworkModelsSettings.ModelProperties[] availableModels;
public List<String> supportedBackends;
public String hardwareModel;
public String hardwarePlatform;
public String wpilibArch;
public boolean conflictingHostname;
public String conflictingCameras;
}

View File

@@ -24,6 +24,7 @@ import org.photonvision.common.LoadJNI.JNITypes;
import org.photonvision.common.configuration.NeuralNetworkModelManager;
import org.photonvision.common.configuration.PhotonConfiguration;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.hardware.OsImageData;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.networking.NetworkManager;
import org.photonvision.common.networking.NetworkUtils;
@@ -52,6 +53,9 @@ public class UIPhotonConfiguration {
!c.getHardwareConfig().ledPins.isEmpty()),
new UIGeneralSettings(
PhotonVersion.versionString,
OsImageData.IMAGE_METADATA.isPresent()
? OsImageData.IMAGE_METADATA.get().commitTag()
: "",
// TODO add support for other types of GPU accel
LoadJNI.hasLoaded(JNITypes.LIBCAMERA) ? "Zerocopy Libcamera Working" : "",
LoadJNI.hasLoaded(JNITypes.MRCAL),
@@ -61,6 +65,7 @@ public class UIPhotonConfiguration {
? Platform.getHardwareModel()
: c.getHardwareConfig().deviceName,
Platform.getPlatformName(),
Platform.getNativePlatform(),
NetworkTablesManager.getInstance().conflictingHostname,
NetworkTablesManager.getInstance().conflictingCameras),
c.getApriltagFieldLayout()),

View File

@@ -38,8 +38,11 @@ public class OsImageData {
private static Path imageVersionFile = Path.of("/opt/photonvision/image-version");
private static Path imageMetadataFile = Path.of("/opt/photonvision/image-version.json");
/** The OS image version string, if available. This is legacy, use {@link ImageMetadata}. */
public static final Optional<String> IMAGE_VERSION = getImageVersion();
/**
* The OS image version string, if available. This is legacy, use {@link ImageMetadata}.
* Deprecated for removal in 2027.
*/
@Deprecated public static final Optional<String> IMAGE_VERSION = getImageVersion();
private static Optional<String> getImageVersion() {
if (!imageVersionFile.toFile().exists()) {

View File

@@ -17,6 +17,7 @@
package org.photonvision.common.hardware;
import edu.wpi.first.util.CombinedRuntimeLoader;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
@@ -139,6 +140,27 @@ public enum Platform {
}
}
/**
* This function serves to map between formats used in the CombinedRuntimeLoader and the platform
* names used in the wpilib-tools-plugin. This is typically used for native libraries.
*
* @return String representing the platform in the format used by wpilib-tools-plugin, or an empty
* string if the platform is not recognized.
*/
public static String getNativePlatform() {
String platPath = CombinedRuntimeLoader.getPlatformPath();
if (platPath == "/linux/x86-64/") {
return "linuxx64";
} else if (platPath == "/windows/x86-64/") {
return "winx64";
} else if (platPath == "/linux/arm64/") {
return "linuxarm64";
} else {
return "";
}
}
public static String getHardwareModel() {
return currentPlatform.hardwareModel;
}