killapp/Assets/Scripts/UI/Pages/DeviceInfoPage/FirmwareUpdatePage.cs
“虞渠成” 8568724c3f feat: 新增WiFi OTA升级功能与相关优化
1. 新增多语言文案用于OTA升级提示
2. 优化更新进度显示为整数百分比
3. 调整设备轮询间隔为5秒
4. 新增SSE流式请求处理与OTA数据模型
5. 实现WiFi环境下基于SSE的OTA升级流程
6. 增加蓝牙环境下OTA升级的前置校验与提示
2026-08-25 14:12:45 +08:00

357 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
OTAData otaData;
void Start()
{
UIManager.Instance.RegisterBackAction(Back);
backButton.onClick.RemoveAllListeners();
backButton.onClick.AddListener(Back);
updateButton.onClick.RemoveAllListeners();
updateButton.onClick.AddListener(OnUpdateButtonClick);
updateButton.gameObject.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;
}
}
public void Back()
{
UIManager.Instance.RegisterBackAction(GetComponentInParent<DeviceInfoPage>().Back);
Destroy(gameObject);
Screen.sleepTimeout = SleepTimeout.SystemSetting;
}
private bool isUpdating; // OTA升级进行中(防连点重入)
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)
{
ToastUI.Show("100294");
UpdateLoadingUI.Instance.Show();
UpdateLoadingUI.Instance.SetPrecent(0);
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);
if (pushResponse.IsSuccess && pushResponse.Data != null && pushResponse.Data.data != null && !string.IsNullOrEmpty(pushResponse.Data.data.transferId))
{
string transferId = pushResponse.Data.data.transferId;
Debug.Log($"WiFi OTA push 成功transferId: {transferId}");
// SSE 订阅传输进度,传输完成返回 true
bool success = await SubscribeOtaProgress(transferId, p => UpdateLoadingUI.Instance.SetPrecent(p / 100f));
if (success)
{
ToastUI.Show("100226");
DataManager.Instance.selectedDevice.firmware_version = otaData.version;
// 传输完成后等待 10s 再返回主页(设备升级后需要时间重启)
StartCoroutine(DelayedGoHome(10f));
}
else
{
ToastUI.Show("100227");
UIManager.Instance.OpenMainPage(UIManager.PageName.homePage);
}
}
else
{
ToastUI.Show("100227");
Debug.LogError($"WiFi OTA push 失败: {pushResponse.ErrorMessage ?? pushResponse.RawResponse}");
UpdateLoadingUI.Instance.Hide();
UIManager.Instance.OpenMainPage(UIManager.PageName.homePage);
}
}
catch (System.Exception ex)
{
Debug.LogError($"WiFi OTA push 异常: {ex.Message}");
UpdateLoadingUI.Instance.Hide();
ToastUI.Show("100227");
UIManager.Instance.OpenMainPage(UIManager.PageName.homePage);
}
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 传输期间清空返回事件,防止用户触发返回中断升级;传输结束恢复
var otaBackAction = UIManager.Instance.GetBackAction();
UIManager.Instance.ClearBackAction();
StartCoroutine(OTAManager.Instance.PerformFullUpgrade(_selectedFirmwareData, firmwareVersion, (success) =>
{
Loom.QueueOnMainThread(() =>
{
isUpdating = false;
// 恢复返回事件
if (otaBackAction != null)
UIManager.Instance.RegisterBackAction(otaBackAction);
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返回是否传输完成
/// </summary>
private Task<bool> SubscribeOtaProgress(string transferId, Action<int> onProgress)
{
var tcs = new TaskCompletionSource<bool>();
StartCoroutine(SSEProgressCoroutine(transferId, onProgress, success => tcs.TrySetResult(success)));
return tcs.Task;
}
/// <summary>
/// SSE 进度协程持续接收进度事件传输完成status/percentage或连接结束后结束
/// </summary>
private IEnumerator SSEProgressCoroutine(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();
if (info.percentage >= 100 || status.Contains("completed") || status.Contains("success") || status.Contains("done"))
{
transferFinished = true;
}
}
catch (System.Exception ex)
{
Debug.LogError($"OTA 进度解析失败: {ex.Message}");
}
},
err =>
{
Debug.LogError($"OTA 进度连接错误: {err}");
});
// SSE 连接结束(服务端关闭或出错),以最后收到的进度状态判定是否完成
onDone?.Invoke(transferFinished);
}
/// <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);
}
}
}