242 lines
6.8 KiB
JavaScript
242 lines
6.8 KiB
JavaScript
import { computed, ref } from "vue";
|
|
import { sendHardwareBoxUpdateAPI } from "@/apis";
|
|
import useStore from "@/store";
|
|
import { storeToRefs } from "pinia";
|
|
|
|
const OTA_PROGRESS_EVENT = "/addons/shoot/otaProgress";
|
|
const OTA_RESULT_EVENT = "/addons/shoot/otaResult";
|
|
const UPDATE_TIMEOUT = 10 * 60 * 1000;
|
|
|
|
const phaseTextMap = {
|
|
started: "正在准备固件更新",
|
|
downloading: "正在下载固件",
|
|
installing: "正在安装固件",
|
|
};
|
|
|
|
// 设备详情中的 latestVersion 是升级目标,version 是当前固件版本。
|
|
export const getOtaInfoFromDetail = (detail) => ({
|
|
versionNumber: String(detail?.latestVersion ?? "").trim(),
|
|
currentVersion: String(detail?.version ?? "").trim(),
|
|
versionInfo: String(detail?.versionInfo ?? "").trim(),
|
|
needUpdate: detail?.needUpdate === true || Number(detail?.needUpdate) === 1,
|
|
forceUpdate: detail?.forceUpdate === true || Number(detail?.forceUpdate) === 1,
|
|
});
|
|
|
|
// OTA 状态由首页、WiFi 设置页和我的设备页共享,页面切换后仍可继续接收进度。
|
|
const updating = ref(false);
|
|
const progress = ref(0);
|
|
const phase = ref("started");
|
|
const resultVisible = ref(false);
|
|
const resultStatus = ref("");
|
|
const resultReason = ref("");
|
|
|
|
let deviceRef = null;
|
|
let updateRunId = 0;
|
|
let targetVersion = "";
|
|
let lastFinishedVersion = "";
|
|
let timeoutTimer = null;
|
|
let successCallback = null;
|
|
let socketListening = false;
|
|
|
|
const phaseText = computed(
|
|
() => phaseTextMap[phase.value] || "正在进行固件更新"
|
|
);
|
|
const resultTitle = computed(() =>
|
|
resultStatus.value === "success" ? "固件更新完成" : "固件更新失败"
|
|
);
|
|
const resultContent = computed(() => {
|
|
if (resultStatus.value === "success") return "固件更新已完成";
|
|
return resultReason.value || "更新失败,请检查设备及网络后重试";
|
|
});
|
|
|
|
const clearTimers = () => {
|
|
clearTimeout(timeoutTimer);
|
|
timeoutTimer = null;
|
|
};
|
|
|
|
const resetActiveUpdate = () => {
|
|
clearTimers();
|
|
successCallback = null;
|
|
};
|
|
|
|
const finishUpdate = (status, reason = "", runId = updateRunId) => {
|
|
if (runId !== updateRunId || !updating.value) return;
|
|
const onSuccess = successCallback;
|
|
resetActiveUpdate();
|
|
updating.value = false;
|
|
resultStatus.value = status;
|
|
resultReason.value = reason;
|
|
lastFinishedVersion = targetVersion;
|
|
if (status === "success") {
|
|
progress.value = 100;
|
|
phase.value = "installing";
|
|
onSuccess?.();
|
|
}
|
|
resultVisible.value = true;
|
|
};
|
|
|
|
const isCurrentDeviceMessage = (data) => {
|
|
const messageDeviceId = String(data?.deviceId || "");
|
|
const currentDeviceId = String(deviceRef?.value?.deviceId || "");
|
|
return !messageDeviceId || !currentDeviceId || messageDeviceId === currentDeviceId;
|
|
};
|
|
|
|
const isCurrentVersionMessage = (data) => {
|
|
const messageVersion = String(data?.versionNumber || "");
|
|
return !messageVersion || !targetVersion || messageVersion === targetVersion;
|
|
};
|
|
|
|
// 页面切换导致弹窗关闭后,使用下一条进度消息恢复当前 OTA 会话。
|
|
const recoverUpdateFromMessage = (data) => {
|
|
updateRunId += 1;
|
|
const runId = updateRunId;
|
|
targetVersion = String(data?.versionNumber || "");
|
|
lastFinishedVersion = "";
|
|
successCallback = null;
|
|
resultVisible.value = false;
|
|
resultStatus.value = "";
|
|
resultReason.value = "";
|
|
progress.value = 0;
|
|
phase.value = "started";
|
|
updating.value = true;
|
|
clearTimers();
|
|
timeoutTimer = setTimeout(() => {
|
|
finishUpdate("failed", "固件更新超时,请稍后重试", runId);
|
|
}, UPDATE_TIMEOUT);
|
|
};
|
|
|
|
function handleSocketMessage(message) {
|
|
if (Number(message?.code ?? 0) !== 0) return;
|
|
if (![OTA_PROGRESS_EVENT, OTA_RESULT_EVENT].includes(message?.event)) return;
|
|
if (!isCurrentDeviceMessage(message.data)) return;
|
|
|
|
const messageVersion = String(message.data?.versionNumber || "");
|
|
|
|
if (message.event === OTA_PROGRESS_EVENT) {
|
|
if (!updating.value) {
|
|
if (
|
|
lastFinishedVersion &&
|
|
(!messageVersion || messageVersion === lastFinishedVersion)
|
|
) {
|
|
return;
|
|
}
|
|
recoverUpdateFromMessage(message.data);
|
|
} else if (!isCurrentVersionMessage(message.data)) {
|
|
return;
|
|
}
|
|
if (!targetVersion && messageVersion) targetVersion = messageVersion;
|
|
|
|
const nextProgress = Math.min(
|
|
100,
|
|
Math.max(0, Number(message.data?.progress) || 0)
|
|
);
|
|
progress.value = Math.max(progress.value, nextProgress);
|
|
if (phaseTextMap[message.data?.phase]) {
|
|
phase.value = message.data.phase;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!updating.value) {
|
|
if (
|
|
lastFinishedVersion &&
|
|
(!messageVersion || messageVersion === lastFinishedVersion)
|
|
) {
|
|
return;
|
|
}
|
|
recoverUpdateFromMessage(message.data);
|
|
} else if (!isCurrentVersionMessage(message.data)) {
|
|
return;
|
|
}
|
|
if (!targetVersion && messageVersion) targetVersion = messageVersion;
|
|
|
|
if (message.data?.status === "success") {
|
|
finishUpdate("success");
|
|
} else if (message.data?.status === "failed") {
|
|
finishUpdate("failed", message.data?.reason || "");
|
|
}
|
|
}
|
|
|
|
const startSocketListening = () => {
|
|
if (socketListening) return;
|
|
uni.$on("socket-inbox", handleSocketMessage);
|
|
socketListening = true;
|
|
};
|
|
|
|
export const useOtaUpdate = () => {
|
|
const store = useStore();
|
|
const { device } = storeToRefs(store);
|
|
deviceRef = device;
|
|
startSocketListening();
|
|
|
|
const startUpdate = async ({
|
|
versionNumber,
|
|
wifiSsid = "",
|
|
wifiPassword = "",
|
|
onSuccess,
|
|
}) => {
|
|
if (updating.value) return false;
|
|
const nextVersion = String(versionNumber || "").trim();
|
|
if (!nextVersion) {
|
|
uni.showToast({ title: "缺少固件目标版本", icon: "none" });
|
|
return false;
|
|
}
|
|
|
|
updateRunId += 1;
|
|
const runId = updateRunId;
|
|
targetVersion = nextVersion;
|
|
lastFinishedVersion = "";
|
|
successCallback = onSuccess || null;
|
|
resultVisible.value = false;
|
|
resultStatus.value = "";
|
|
resultReason.value = "";
|
|
progress.value = 0;
|
|
phase.value = "started";
|
|
updating.value = true;
|
|
clearTimers();
|
|
timeoutTimer = setTimeout(() => {
|
|
finishUpdate("failed", "固件更新超时,请稍后重试", runId);
|
|
}, UPDATE_TIMEOUT);
|
|
|
|
try {
|
|
const updateResult = await sendHardwareBoxUpdateAPI({
|
|
versionNumber: targetVersion,
|
|
wifiSsid,
|
|
wifiPassword,
|
|
});
|
|
if (runId !== updateRunId || !updating.value) return false;
|
|
if (!updateResult?.taskId) {
|
|
finishUpdate("failed", "固件更新任务创建失败,请稍后重试", runId);
|
|
return false;
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
if (runId === updateRunId) {
|
|
finishUpdate(
|
|
"failed",
|
|
error?.message || "固件更新请求失败,请稍后重试",
|
|
runId
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const closeResult = () => {
|
|
resultVisible.value = false;
|
|
};
|
|
|
|
return {
|
|
updating,
|
|
progress,
|
|
phase,
|
|
phaseText,
|
|
resultVisible,
|
|
resultStatus,
|
|
resultTitle,
|
|
resultContent,
|
|
startUpdate,
|
|
closeResult,
|
|
};
|
|
};
|