mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-02 02:51:40 +00:00
Check offline updates for incompatibility (#2361)
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user