1. 新增设备信息页OTA升级进度UI与本地持久化传输状态 2. 实现WiFi模式OTA非全屏进度订阅与重启等待逻辑 3. 新增设备列表本地缓存与加载、清空功能 4. 优化蓝牙管理:移除自动弹窗,改为提示用户手动开启蓝牙 5. 新增网络状态监听与设备在线状态刷新逻辑 6. 新增多语言文案与蓝牙未开启提示资源 7. 优化网络请求处理,新增静默错误提示参数
686 lines
29 KiB
C#
686 lines
29 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Threading.Tasks;
|
||
using Kill.Bluetooth;
|
||
using Kill.Core;
|
||
using Kill.Managers;
|
||
using Kill.Network;
|
||
using Kill.UI.Components;
|
||
using Newtonsoft.Json;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
namespace Kill.UI.Pages
|
||
{
|
||
public class FirmwareUpdatePage : MonoBehaviour
|
||
{
|
||
public GameObject latestTip;
|
||
public GameObject getNewVersionTip;
|
||
public Text versionText;
|
||
public Text changeLogText;
|
||
public Button backButton;
|
||
public Button updateButton;
|
||
// WiFi OTA 页内进度条(非全屏,不阻塞操作)
|
||
public GameObject progressPanel;
|
||
public Image progressBar; // Fill Method = Horizontal, Fill Origin = Left
|
||
public Text progressText;
|
||
OTAData otaData;
|
||
// 当前设备持久化的 OTA transferId(WiFi 模式),空表示无进行中的传输
|
||
private const string OTA_TRANSFER_ID_KEY = "otaTransferId";
|
||
private const string OTA_TRANSFER_VER_KEY = "otaTransferVer";
|
||
private const string OTA_TRANSFER_STATUS_KEY = "otaTransferStatus";
|
||
private const string OTA_TRANSFER_DEADLINE_KEY = "otaTransferDeadline"; // 等设备重启的绝对截止时间戳(Unix 秒)
|
||
// 等设备重启的总时长(秒);持久化截止时间,避免退出/重进页面时计时重置
|
||
private const int OTA_WAIT_DEADLINE_SECONDS = 30;
|
||
// status: "pushing"=传输中, "waiting_reboot"=传输完成等待设备重启/重连
|
||
private string currentTransferId;
|
||
private string currentTransferStatus;
|
||
private bool isUpdating; // OTA升级进行中(防连点重入)
|
||
void Start()
|
||
{
|
||
UIManager.Instance.RegisterBackAction(Back);
|
||
backButton.onClick.RemoveAllListeners();
|
||
backButton.onClick.AddListener(Back);
|
||
updateButton.onClick.RemoveAllListeners();
|
||
updateButton.onClick.AddListener(OnUpdateButtonClick);
|
||
updateButton.gameObject.SetActive(false);
|
||
if (progressPanel != null) progressPanel.SetActive(false);
|
||
GetOtaInfo();
|
||
}
|
||
|
||
public async void GetOtaInfo()
|
||
{
|
||
LoadingUI.Show();
|
||
|
||
var response = await NetworkCtrl.Instance.Get<OTAResponse>("/api/v1/ota/latest");
|
||
LoadingUI.Hide();
|
||
ResponseCodeHandler.HandleResponse(response,
|
||
onSuccess: (data) =>
|
||
{
|
||
otaData = data.data;
|
||
Debug.Log($"加载OTA信息成功,版本: {otaData.version}");
|
||
InitOtaUI();
|
||
},
|
||
onError:(code,message)=>
|
||
{
|
||
Debug.LogError($"加载OTA信息失败: {message}");
|
||
}
|
||
);
|
||
}
|
||
public void InitOtaUI()
|
||
{
|
||
string nowVersion = DataManager.Instance.selectedDevice.firmware_version;
|
||
string[] versionParts = otaData.version.Split('.');
|
||
string[] nowVersionParts = nowVersion.Split('.');
|
||
int nowversionNum = 0;
|
||
int versionNum = 0;
|
||
int index=1;
|
||
for(int i=nowVersionParts.Length-1;i>=0;i--)
|
||
{
|
||
nowversionNum += int.Parse(nowVersionParts[i]) * 10*index++;
|
||
}
|
||
index = 1;
|
||
for (int i = versionParts.Length - 1; i >= 0; i--)
|
||
{
|
||
versionNum += int.Parse(versionParts[i]) * 10 * index++;
|
||
}
|
||
Debug.Log($"当前版本: {nowVersion} ({nowversionNum}), 最新版本: {otaData.version} ({versionNum})");
|
||
bool hasNewVersion = versionNum > nowversionNum;
|
||
if(hasNewVersion)
|
||
{
|
||
latestTip.SetActive(false);
|
||
getNewVersionTip.SetActive(true);
|
||
changeLogText.gameObject.SetActive(true);
|
||
versionText=getNewVersionTip.transform.Find("value").GetComponent<Text>();
|
||
versionText.text = otaData.version;
|
||
changeLogText.text = otaData.release_notes;
|
||
updateButton.gameObject.SetActive(true);
|
||
}
|
||
else
|
||
{
|
||
latestTip.SetActive(true);
|
||
getNewVersionTip.SetActive(false);
|
||
updateButton.gameObject.SetActive(false);
|
||
changeLogText.gameObject.SetActive(false);
|
||
versionText = latestTip.transform.Find("value").GetComponent<Text>();
|
||
versionText.text = otaData.version;
|
||
}
|
||
|
||
// 进入页面:若本地有未结束的 transferId,则恢复订阅进度(非全屏,不阻塞)
|
||
TryResumeWifiOta();
|
||
}
|
||
public void Back()
|
||
{
|
||
UIManager.Instance.RegisterBackAction(GetComponentInParent<DeviceInfoPage>().Back);
|
||
Destroy(gameObject);
|
||
Screen.sleepTimeout = SleepTimeout.SystemSetting;
|
||
}
|
||
public async void OnUpdateButtonClick()
|
||
{
|
||
// 防连点:升级流程进行中忽略重复触发,避免并发启动两个OTA
|
||
if (isUpdating) return;
|
||
isUpdating = true;
|
||
Screen.sleepTimeout = SleepTimeout.NeverSleep;
|
||
|
||
// 仅蓝牙连接:不支持直接OTA,提示先配置WiFi
|
||
if (DataManager.Instance.hasBluetooth && !DataManager.Instance.hasWifi)
|
||
{
|
||
ToastUI.Show("100333");
|
||
isUpdating = false;
|
||
return;
|
||
}
|
||
|
||
// WiFi 模式:push OTA 指令,SSE 订阅传输进度(非全屏,不阻塞操作,可关闭页面后下次进入继续订阅)
|
||
if (DataManager.Instance.hasWifi)
|
||
{
|
||
try
|
||
{
|
||
var pushRequest = new OTAPushRequest
|
||
{
|
||
ble_mac = DataManager.Instance.selectedDevice.ble_mac,
|
||
version = otaData.version
|
||
};
|
||
var pushResponse = await NetworkCtrl.Instance.Post<OTAPushResponse>("/api/v1/ota/push", pushRequest);
|
||
// 提取 transferId:优先从 data.transferId,否则在 code=608(已有OTA进行中)时从 message 解析 "transferId=xxx"
|
||
string transferId = null;
|
||
if (pushResponse.Data != null && pushResponse.Data.data != null && !string.IsNullOrEmpty(pushResponse.Data.data.transferId))
|
||
{
|
||
transferId = pushResponse.Data.data.transferId;
|
||
}
|
||
else if (pushResponse.Data != null && pushResponse.Data.code == 608 && !string.IsNullOrEmpty(pushResponse.Data.message))
|
||
{
|
||
var match = System.Text.RegularExpressions.Regex.Match(pushResponse.Data.message, @"transferId\s*=\s*([A-Za-z0-9_\-]+)");
|
||
if (match.Success)
|
||
{
|
||
transferId = match.Groups[1].Value;
|
||
Debug.Log($"WiFi OTA push 返回 code=608(已有OTA进行中),从message提取 transferId: {transferId}");
|
||
}
|
||
}
|
||
if (!string.IsNullOrEmpty(transferId))
|
||
{
|
||
Debug.Log($"WiFi OTA push 成功,transferId: {transferId}");
|
||
// 持久化 transferId,订阅后台进行,不阻塞页面操作
|
||
SaveTransferId(transferId, otaData.version, "pushing");
|
||
StartWifiOtaSubscription(transferId);
|
||
}
|
||
else
|
||
{
|
||
ToastUI.Show("100227");
|
||
Debug.LogError($"WiFi OTA push 失败: {pushResponse.ErrorMessage ?? pushResponse.RawResponse}");
|
||
}
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"WiFi OTA push 异常: {ex.Message}");
|
||
ToastUI.Show("100227");
|
||
}
|
||
finally
|
||
{
|
||
isUpdating = false;
|
||
}
|
||
return;
|
||
}
|
||
|
||
// BLE 模式:下载固件 → 校验 → 蓝牙传输
|
||
ToastUI.Show("100294");
|
||
UpdateLoadingUI.Instance.Show();
|
||
string savePath = Application.persistentDataPath + "/ota.bin";
|
||
var downloadResult = await NetworkCtrl.Instance.DownloadFile(otaData.download_url, savePath, UpdateLoadingUI.Instance.GetDownloadProcess);
|
||
UpdateLoadingUI.Instance.Hide();
|
||
if (!downloadResult.IsSuccess)
|
||
{
|
||
ToastUI.Show("100224");
|
||
isUpdating = false;
|
||
return;
|
||
}
|
||
string md5 = NetworkCtrl.Instance.CalculateMD5(savePath);
|
||
if (md5 != otaData.md5)
|
||
{
|
||
ToastUI.Show("100223");
|
||
Debug.LogError("下载的文件MD5校验失败");
|
||
File.Delete(savePath);
|
||
isUpdating = false;
|
||
return;
|
||
}
|
||
LoadFirmwareFile(savePath);
|
||
if (_selectedFirmwareData == null)
|
||
{
|
||
isUpdating = false;
|
||
return;
|
||
}
|
||
ToastUI.Show("100225");
|
||
|
||
UpdateLoadingUI.Instance.SetPrecent(0);
|
||
UpdateLoadingUI.Instance.Show();
|
||
uint firmwareVersion = ParseFirmwareVersion(otaData.version);
|
||
OTAManager.Instance.OnTransferProgress += OnOTATransferProgress;
|
||
// OTA 传输期间(进度显示)清空返回事件,返回键走两次返回退出App,防止误返回中断升级(成败都会返回主页)
|
||
UIManager.Instance.ClearBackAction();
|
||
StartCoroutine(OTAManager.Instance.PerformFullUpgrade(_selectedFirmwareData, firmwareVersion, (success) =>
|
||
{
|
||
Loom.QueueOnMainThread(() =>
|
||
{
|
||
isUpdating = false;
|
||
if (success)
|
||
{
|
||
DataManager.Instance.selectedDevice.firmware_version = otaData.version;
|
||
ToastUI.Show("100226");
|
||
// OTA 成功后等待 15s 再返回主页(设备升级后需要时间重启)
|
||
StartCoroutine(DelayedGoHome(15f));
|
||
}
|
||
else
|
||
{
|
||
ToastUI.Show("100227");
|
||
UpdateLoadingUI.Instance.Hide();
|
||
UIManager.Instance.OpenMainPage(UIManager.PageName.homePage);
|
||
}
|
||
});
|
||
}));
|
||
}
|
||
|
||
// 选中的固件文件数据
|
||
private byte[] _selectedFirmwareData;
|
||
private string _selectedFirmwarePath;
|
||
|
||
/// <summary>
|
||
/// 延迟指定时间后返回主页
|
||
/// </summary>
|
||
private IEnumerator DelayedGoHome(float delay)
|
||
{
|
||
yield return new WaitForSeconds(delay);
|
||
UpdateLoadingUI.Instance.Hide();
|
||
UIManager.Instance.OpenMainPage(UIManager.PageName.homePage);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 订阅 WiFi OTA 传输进度(SSE),返回是否传输完成(仅供兼容旧调用,BLE 仍走全屏 OTAManager)
|
||
/// </summary>
|
||
private Task<bool> SubscribeOtaProgress(string transferId, Action<int> onProgress)
|
||
{
|
||
var tcs = new TaskCompletionSource<bool>();
|
||
StartCoroutine(SSELegacyCoroutine(transferId, onProgress, success => tcs.TrySetResult(success)));
|
||
return tcs.Task;
|
||
}
|
||
|
||
private IEnumerator SSELegacyCoroutine(string transferId, Action<int> onProgress, Action<bool> onDone)
|
||
{
|
||
bool finished = false;
|
||
yield return SubscribeOtaProgressCoroutine(transferId, onProgress, done => finished = done);
|
||
onDone?.Invoke(finished);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存当前设备的 OTA transferId(与设备mac绑定,切换设备不影响)
|
||
/// </summary>
|
||
private void SaveTransferId(string transferId, string version, string status)
|
||
{
|
||
string mac = DataManager.Instance.selectedDevice?.ble_mac ?? "";
|
||
PlayerPrefs.SetString(GetTransferIdKey(mac), transferId);
|
||
PlayerPrefs.SetString(GetTransferVerKey(mac), version ?? "");
|
||
PlayerPrefs.SetString(GetTransferStatusKey(mac), status ?? "");
|
||
PlayerPrefs.Save();
|
||
currentTransferId = transferId;
|
||
currentTransferStatus = status;
|
||
}
|
||
|
||
private void UpdateTransferStatus(string status)
|
||
{
|
||
string mac = DataManager.Instance.selectedDevice?.ble_mac ?? "";
|
||
PlayerPrefs.SetString(GetTransferStatusKey(mac), status ?? "");
|
||
PlayerPrefs.Save();
|
||
currentTransferStatus = status;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置等待设备重启的绝对截止时间戳(Unix 秒)。退出页面再回来仍按此截止时间判定,不会重置。
|
||
/// </summary>
|
||
private void SetWaitDeadline()
|
||
{
|
||
string mac = DataManager.Instance.selectedDevice?.ble_mac ?? "";
|
||
long deadline = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + OTA_WAIT_DEADLINE_SECONDS;
|
||
PlayerPrefs.SetString(GetTransferDeadlineKey(mac), deadline.ToString());
|
||
PlayerPrefs.Save();
|
||
}
|
||
|
||
private long LoadWaitDeadline()
|
||
{
|
||
string mac = DataManager.Instance.selectedDevice?.ble_mac ?? "";
|
||
string v = PlayerPrefs.GetString(GetTransferDeadlineKey(mac), "");
|
||
if (long.TryParse(v, out long d)) return d;
|
||
return 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取当前设备的 OTA transferId(空表示无)
|
||
/// </summary>
|
||
private string LoadTransferId()
|
||
{
|
||
string mac = DataManager.Instance.selectedDevice?.ble_mac ?? "";
|
||
return PlayerPrefs.GetString(GetTransferIdKey(mac), "");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除当前设备的 OTA transferId
|
||
/// </summary>
|
||
private void ClearTransferId()
|
||
{
|
||
string mac = DataManager.Instance.selectedDevice?.ble_mac ?? "";
|
||
PlayerPrefs.DeleteKey(GetTransferIdKey(mac));
|
||
PlayerPrefs.DeleteKey(GetTransferVerKey(mac));
|
||
PlayerPrefs.DeleteKey(GetTransferStatusKey(mac));
|
||
PlayerPrefs.DeleteKey(GetTransferDeadlineKey(mac));
|
||
PlayerPrefs.Save();
|
||
currentTransferId = null;
|
||
currentTransferStatus = null;
|
||
}
|
||
|
||
private string GetTransferIdKey(string mac) => OTA_TRANSFER_ID_KEY + "_" + mac;
|
||
private string GetTransferVerKey(string mac) => OTA_TRANSFER_VER_KEY + "_" + mac;
|
||
private string GetTransferStatusKey(string mac) => OTA_TRANSFER_STATUS_KEY + "_" + mac;
|
||
private string GetTransferDeadlineKey(string mac) => OTA_TRANSFER_DEADLINE_KEY + "_" + mac;
|
||
|
||
/// <summary>
|
||
/// 启动 WiFi OTA 订阅:仅更新页内小进度条,不阻塞页面,可关闭/重入
|
||
/// </summary>
|
||
private void StartWifiOtaSubscription(string transferId)
|
||
{
|
||
ShowProgressPanel(0);
|
||
updateButton.gameObject.SetActive(false); // 升级中禁用更新按钮
|
||
StartCoroutine(SubscribeAndHandle(transferId));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 订阅协程:完成/失败/连接结束统一处理(不强制回主页)
|
||
/// </summary>
|
||
private IEnumerator SubscribeAndHandle(string transferId)
|
||
{
|
||
bool finished = false;
|
||
// SSE 回调本身在主线程,直接同步赋值即可(不再绕 Loom,否则 yield 之后判断时 finished 仍是旧值)
|
||
yield return SubscribeOtaProgressCoroutine(transferId,
|
||
p => ShowProgressPanel(p),
|
||
done => finished = done);
|
||
// 协程结束:根据是否完成做后续
|
||
if (finished)
|
||
{
|
||
ShowProgressPanel(100);
|
||
// 传输完成 ≠ 设备升级完成:等待设备重启/重连,并以新版本号上线后再清理 transferId、刷新 UI
|
||
UpdateTransferStatus("waiting_reboot");
|
||
// 仅在尚未设置过截止时间时才设置(避免重复订阅重置倒计时)
|
||
if (LoadWaitDeadline() <= 0) SetWaitDeadline();
|
||
StartCoroutine(WaitDeviceRebootAndFinish(transferId));
|
||
}
|
||
else
|
||
{
|
||
// 中途失败/中断
|
||
ToastUI.Show("100227");
|
||
ClearTransferId();
|
||
HideProgressPanel();
|
||
updateButton.gameObject.SetActive(true);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 传输完成后等待设备重启+重连:轮询设备列表直到当前设备以新版本号上线,否则按持久化的绝对截止时间超时退出。
|
||
/// 期间保持 transferId 持久化、保留页内进度条显示「升级中」文案,重新进入页面也能继续等待(截止时间不会重置)。
|
||
/// </summary>
|
||
private IEnumerator WaitDeviceRebootAndFinish(string transferId)
|
||
{
|
||
const float pollInterval = 3f; // 轮询间隔
|
||
long deadline = LoadWaitDeadline();
|
||
// 兜底:若没设置过截止时间,立即设置一个
|
||
if (deadline <= 0)
|
||
{
|
||
SetWaitDeadline();
|
||
deadline = LoadWaitDeadline();
|
||
}
|
||
// 已超过绝对截止时间:直接走超时分支并清掉 transferId,避免每次进入都重复提示
|
||
if (DateTimeOffset.UtcNow.ToUnixTimeSeconds() >= deadline)
|
||
{
|
||
ClearTransferId();
|
||
HideProgressPanel();
|
||
updateButton.gameObject.SetActive(true);
|
||
ToastUI.Show("ota_update_failed");
|
||
yield break;
|
||
}
|
||
ShowProgressPanelWaitingForDevice();
|
||
ToastUI.Show("100226"); // 传输完成提示
|
||
string targetVersion = otaData != null ? otaData.version : "";
|
||
bool rebooted = false;
|
||
while (DateTimeOffset.UtcNow.ToUnixTimeSeconds() < deadline)
|
||
{
|
||
yield return new WaitForSeconds(pollInterval);
|
||
if (gameObject == null) yield break; // 页面已销毁
|
||
// 仅当持久化的还是同一个 transferId 时才继续等待(用户重新点击/切换设备会清掉)
|
||
if (LoadTransferId() != transferId) yield break;
|
||
string nowVersion = null;
|
||
yield return FetchDeviceFirmwareVersion(r => nowVersion = r);
|
||
if (!string.IsNullOrEmpty(nowVersion) && IsVersionGE(nowVersion, targetVersion))
|
||
{
|
||
rebooted = true;
|
||
break;
|
||
}
|
||
// 期间截止时间可能被重置(用户重新点击升级等),以最新值为准
|
||
long currentDeadline = LoadWaitDeadline();
|
||
if (currentDeadline > 0 && currentDeadline != deadline) deadline = currentDeadline;
|
||
}
|
||
if (gameObject == null) yield break;
|
||
if (LoadTransferId() != transferId) yield break;
|
||
if (rebooted)
|
||
{
|
||
// 设备已用新版本上线,更新本地版本并清理状态
|
||
if (DataManager.Instance.selectedDevice != null && !string.IsNullOrEmpty(targetVersion))
|
||
{
|
||
DataManager.Instance.selectedDevice.firmware_version = targetVersion;
|
||
}
|
||
ClearTransferId();
|
||
HideProgressPanel();
|
||
InitOtaUI(); // 重新判断:已是最新 / 又有新版本
|
||
}
|
||
else
|
||
{
|
||
// 超时:升级失败,提示并清理 transferId(避免下次进入页面再触发)
|
||
ToastUI.Show("ota_update_failed");
|
||
ClearTransferId();
|
||
HideProgressPanel();
|
||
updateButton.gameObject.SetActive(true);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 拉取当前设备的最新 firmware_version(仅赋值给回调,返回空表示失败/离线)
|
||
/// </summary>
|
||
private IEnumerator FetchDeviceFirmwareVersion(Action<string> onResult)
|
||
{
|
||
string result = null;
|
||
var task = FetchDeviceFirmwareVersionAsync(v => result = v);
|
||
while (!task.IsCompleted) yield return null;
|
||
onResult?.Invoke(result);
|
||
}
|
||
|
||
private async Task FetchDeviceFirmwareVersionAsync(Action<string> onResult)
|
||
{
|
||
try
|
||
{
|
||
if (DataManager.Instance.userInfo == null || string.IsNullOrEmpty(DataManager.Instance.userInfo.id))
|
||
{
|
||
onResult?.Invoke(null);
|
||
return;
|
||
}
|
||
string url = $"/api/v1/device/user/list?user_id={DataManager.Instance.userInfo.id}";
|
||
var response = await NetworkCtrl.Instance.Get<DeviceListResponse>(url);
|
||
if (response == null || !response.IsSuccess || response.Data == null || response.Data.data == null)
|
||
{
|
||
onResult?.Invoke(null);
|
||
return;
|
||
}
|
||
string mac = DataManager.Instance.selectedDevice?.ble_mac ?? "";
|
||
var list = response.Data.data.owned_devices;
|
||
if (list != null)
|
||
{
|
||
foreach (var d in list)
|
||
{
|
||
if (d != null && d.ble_mac == mac)
|
||
{
|
||
onResult?.Invoke(d.firmware_version);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
var shared = response.Data.data.shared_devices;
|
||
if (shared != null)
|
||
{
|
||
foreach (var d in shared)
|
||
{
|
||
if (d != null && d.ble_mac == mac)
|
||
{
|
||
onResult?.Invoke(d.firmware_version);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
onResult?.Invoke(null);
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"查询设备版本失败: {ex.Message}");
|
||
onResult?.Invoke(null);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 比较版本号:a >= b 返回 true(按 "." 分段整数比较)
|
||
/// </summary>
|
||
private bool IsVersionGE(string a, string b)
|
||
{
|
||
if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) return false;
|
||
string[] ap = a.Split('.');
|
||
string[] bp = b.Split('.');
|
||
int len = Mathf.Max(ap.Length, bp.Length);
|
||
for (int i = 0; i < len; i++)
|
||
{
|
||
int av = 0, bv = 0;
|
||
if (i < ap.Length) int.TryParse(ap[i], out av);
|
||
if (i < bp.Length) int.TryParse(bp[i], out bv);
|
||
if (av > bv) return true;
|
||
if (av < bv) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 进度条切换到「升级中...」状态(fill 保持满,文字提示)
|
||
/// </summary>
|
||
private void ShowProgressPanelWaitingForDevice()
|
||
{
|
||
if (progressPanel != null) progressPanel.SetActive(true);
|
||
if (progressBar != null) progressBar.fillAmount = 1f;
|
||
if (progressText != null && LanguageManager.Instance != null)
|
||
progressText.text = LanguageManager.Instance.GetLanguage("ota_waiting_reboot");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 进入页面时:若本地存在 transferId,则恢复订阅并显示进度条
|
||
/// </summary>
|
||
private void TryResumeWifiOta()
|
||
{
|
||
string mac = DataManager.Instance.selectedDevice?.ble_mac ?? "";
|
||
string savedId = LoadTransferId();
|
||
string savedStatus = PlayerPrefs.GetString(GetTransferStatusKey(mac), "");
|
||
if (string.IsNullOrEmpty(savedId)) return;
|
||
currentTransferId = savedId;
|
||
currentTransferStatus = savedStatus;
|
||
updateButton.gameObject.SetActive(false);
|
||
// 已经传输完成等待设备重启:直接走轮询分支,不重新订阅 SSE
|
||
if (savedStatus == "waiting_reboot")
|
||
{
|
||
ShowProgressPanelWaitingForDevice();
|
||
StartCoroutine(WaitDeviceRebootAndFinish(savedId));
|
||
return;
|
||
}
|
||
ShowProgressPanel(0);
|
||
StartCoroutine(SubscribeAndHandle(savedId));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 仅订阅进度协程,传出是否完成(不弹出后续处理)
|
||
/// </summary>
|
||
private IEnumerator SubscribeOtaProgressCoroutine(string transferId, Action<int> onProgress, Action<bool> onDone)
|
||
{
|
||
bool transferFinished = false;
|
||
string sseUrl = $"/api/v1/ota/transfer/{transferId}/progress";
|
||
var queryParams = new Dictionary<string, string> { { "token", DataManager.Instance.token } };
|
||
yield return HttpRequestManager.Instance.StartSSE(sseUrl, queryParams, null,
|
||
data =>
|
||
{
|
||
try
|
||
{
|
||
var info = JsonConvert.DeserializeObject<OTATransferInfo>(data);
|
||
if (info == null) return;
|
||
onProgress?.Invoke(info.percentage);
|
||
string status = (info.status ?? "").ToLower();
|
||
Debug.Log($"OTA 进度: {status}, {info.percentage}%");
|
||
if (info.percentage >= 100 || status.Contains("completed"))
|
||
{
|
||
transferFinished = true;
|
||
}
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"OTA 进度解析失败: {ex.Message}");
|
||
}
|
||
},
|
||
err =>
|
||
{
|
||
Debug.LogError($"OTA 进度连接错误: {err}");
|
||
});
|
||
onDone?.Invoke(transferFinished);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示页内进度条并更新百分比
|
||
/// </summary>
|
||
private void ShowProgressPanel(int percent)
|
||
{
|
||
if (progressPanel != null) progressPanel.SetActive(true);
|
||
if (progressBar != null) progressBar.fillAmount = Mathf.Clamp01(percent / 100f);
|
||
if (progressText != null) progressText.text = $"{percent}%";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 隐藏页内进度条
|
||
/// </summary>
|
||
private void HideProgressPanel()
|
||
{
|
||
if (progressBar != null) progressBar.fillAmount = 0f;
|
||
if (progressText != null) progressText.text = "0%";
|
||
if (progressPanel != null) progressPanel.SetActive(false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载固件文件
|
||
/// </summary>
|
||
private void LoadFirmwareFile(string filePath)
|
||
{
|
||
try
|
||
{
|
||
|
||
|
||
// 获取文件大小
|
||
FileInfo fileInfo = new FileInfo(filePath);
|
||
long fileSize = fileInfo.Length;
|
||
|
||
|
||
|
||
// 读取文件
|
||
_selectedFirmwareData = File.ReadAllBytes(filePath);
|
||
_selectedFirmwarePath = filePath;
|
||
|
||
string fileName = Path.GetFileName(filePath);
|
||
|
||
}
|
||
catch (System.Exception)
|
||
{
|
||
ToastUI.Show("100224");
|
||
LoadingUI.Hide();
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 解析固件版本号
|
||
/// </summary>
|
||
private uint ParseFirmwareVersion(string version)
|
||
{
|
||
|
||
try
|
||
{
|
||
string[] parts = version.Split('.');
|
||
if (parts.Length >= 3 &&
|
||
uint.TryParse(parts[0], out uint v0) &&
|
||
uint.TryParse(parts[1], out uint v1) &&
|
||
uint.TryParse(parts[2], out uint v2))
|
||
{
|
||
// 版本号格式:主版本.次版本.修订号(可含第4段)
|
||
// 按每段2位十进制压缩,与设备端编码一致(如 00.00.01.05 -> 105)
|
||
uint v3 = 0;
|
||
if (parts.Length >= 4)
|
||
uint.TryParse(parts[3], out v3);
|
||
return v0 * 1000000 + v1 * 10000 + v2 * 100 + v3;
|
||
}
|
||
|
||
}
|
||
catch { }
|
||
|
||
// 默认返回版本号 1
|
||
return 1;
|
||
}
|
||
|
||
/// <summary>
|
||
/// OTA传输进度回调
|
||
/// </summary>
|
||
private void OnOTATransferProgress(int current, int total, int retryCount, int maxRetries)
|
||
{
|
||
UpdateLoadingUI.Instance.SetPrecent((float)current/ total);
|
||
}
|
||
}
|
||
}
|