import { t } from "@/locales"; 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: t('preparing_firmware_update'), downloading: t('downloading_firmware'), installing: t('installing_firmware'), }; // 设备详情中的 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] || t('updating_firmware') ); const resultTitle = computed(() => resultStatus.value === "success" ? t('firmware_update_complete_250') : t('firmware_update_failed') ); const resultContent = computed(() => { if (resultStatus.value === "success") return t('firmware_update_complete'); return resultReason.value || t('update_failed_check_your_device_and_network_then_retry'); }); 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", t('firmware_update_timed_out_please_try_again'), 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: t('target_firmware_version_is_missing'), 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", t('firmware_update_timed_out_please_try_again'), runId); }, UPDATE_TIMEOUT); try { const updateResult = await sendHardwareBoxUpdateAPI({ versionNumber: targetVersion, wifiSsid, wifiPassword, }); if (runId !== updateRunId || !updating.value) return false; if (!updateResult?.taskId) { finishUpdate("failed", t('could_not_create_the_firmware_update_task_please_retry'), runId); return false; } return true; } catch (error) { if (runId === updateRunId) { finishUpdate( "failed", error?.message || t('firmware_update_request_failed_please_retry'), runId ); } return false; } }; const closeResult = () => { resultVisible.value = false; }; return { updating, progress, phase, phaseText, resultVisible, resultStatus, resultTitle, resultContent, startUpdate, closeResult, }; };