固件升级页(pageUpdate)
pageUpdate 是 JLOTA 微信小程序中负责固件 OTA 升级的核心页面:它管理升级文件的导入/选择/删除/转发,监听蓝牙设备连接状态,并通过 BluetoothOTAManager 与 jl_ota SDK 驱动整个升级流程,实时展示校验、升级、回连、成功/失败等阶段状态。
Purpose and Scope
本页面向开发者完整讲解 pages/pageUpdate 页面的实现:包括页面数据模型、生命周期、升级文件管理、OTA 升级启动与回调处理、UI 状态映射,以及失败模式与并发注意事项。
本页面只覆盖「固件升级页」这一前端页面的职责边界,以下内容属于相关但独立的能力,不在本页展开:
- 蓝牙连接/设备管理由
lib/bluetoothOTAManager.ts与全局app.globalData.bluetoothManager提供,详见蓝牙管理相关页面。 - OTA 协议细节(
OTAConfig、UpgradeType、ReConnectMsg等类型定义与底层传输)由lib/jl_lib/jl_ota_2.1.1SDK 封装,不在本页展开。 - 升级文件在本地存储中的落盘与持久化由
lib/upgradeFileUtil负责,本页只描述其调用方式。 - 进度弹窗
otaProgressView、等待遮罩waittingView是独立组件,本页仅说明它们在 pageUpdate 中的 props 绑定关系。
Overview
固件升级页是小程序「OTA 能力」的用户入口,承担三层职责:
- 文件管理:通过
wx.chooseMessageFile从聊天会话导入升级文件(支持单文件重命名、多文件批量导入),以列表形式展示文件名称、大小、导入时间,支持选中、长按删除与转发到聊天。 - 状态感知:订阅全局
BluetoothOTAManager的设备连接事件,实时刷新「设备状态:已连接/未连接」;只有在已连接且已选择文件的条件下才允许发起升级。 - 升级驱动:构造
OTAConfig(开启isSupportNewRebootWay,注入文件字节流),调用sBluetoothManager.startOTA(),通过六个回调(onStartOTA、onNeedReconnect、onProgress、onStopOTA、onCancelOTA、onError)把底层 SDK 事件翻译成页面数据mStatus(0 检验中 / 1 升级中 / 2 回连设备 / 3 升级成功 / 4 升级失败)与进度mValue(0-100),再由OTA-View子组件渲染。
设计上,页面不直接接触蓝牙协议,只依赖两个稳定接口:BluetoothOTAManager(页面通过 app.globalData.bluetoothManager 获取单例)与 UpgradeFileUtil(文件存储门面)。这种分层让页面逻辑保持简单,且升级状态机完全由 SDK 事件驱动,页面只做"事件 → setData"的映射。
Architecture
flowchart TD
subgraph sg_Page["pageUpdate 页面 (pages/pageUpdate)"]
TS["pageUpdate.ts<br/>(页面逻辑/事件处理)"]
WXML["pageUpdate.wxml<br/>(视图模板)"]
JSON["pageUpdate.json<br/>(组件注册)"]
end
subgraph sg_UI["页面子组件"]
OTAView["otaProgressView<br/>(OTA-View 进度弹窗)"]
WaitView["waittingView<br/>(WAIT-View 等待遮罩)"]
TabBar["custom-tab-bar<br/>(底部导航)"]
end
subgraph sg_Service["全局服务层"]
App["app.globalData<br/>bluetoothManager 单例"]
BTManager["BluetoothOTAManager<br/>(蓝牙事件分发/OTA 入口)"]
OtaLib["jl_ota_2.1.1<br/>(OTA 协议 SDK)"]
FileUtil["UpgradeFileUtil<br/>(升级文件存取)"]
Log["log (logv/loge)"]
end
subgraph sg_Device["设备侧"]
BLE["BLE 设备<br/>(杰理芯片)"]
end
TS --> WXML
TS --> JSON
TS --> OTAView
TS --> WaitView
TS --> TabBar
TS --> App
App --> BTManager
BTManager --> OtaLib
OtaLib -->|"BLE 连接/传输"| BLE
TS --> FileUtil
TS --> Log
WXML -->|"bindtap/catch:tap 事件"| TS
架构要点说明:
- pageUpdate.ts 是唯一的逻辑中枢:所有用户手势(点选文件、长按、点升级)都绑定到它的事件方法,所有 SDK 回调最终也回到它的
setData。 - 蓝牙单例注入:页面在
onLoad时从app.globalData.bluetoothManager取出全局蓝牙管理器sBluetoothManager,并注册BluetoothEventCallback。这样即使页面销毁重建,蓝牙连接状态仍由全局单例持有,不会因页面生命周期而丢失。 - 事件驱动而非轮询:设备断开/连接失败/连接成功都会触发
_checkIsConnected()刷新isBleConnect;OTA 进度由 SDK 回调驱动,页面不做任何定时查询。 - 子组件仅做展示:
OTA-View通过pShow/pStatus/pOtaFile/pValue/pOtaResult/pFailReason等 props 收到状态后自行渲染,页面通过bind:OnConfirm接收用户点击「确定」关闭弹窗的事件。
页面数据模型
页面数据在 data 中集中声明,全部围绕"连接状态、文件列表、OTA 状态机"三组概念组织:
| 字段 | 类型 | 默认值 | 含义 |
|---|---|---|---|
isBleConnect | boolean | false | 当前是否有已连接的蓝牙设备 |
fileArray | any[] | [] | 升级文件列表(来自 UpgradeFileUtil) |
fileIndex | number | -1 | 当前选中文件下标,-1 表示未选择 |
fileSize | number | 0 | 选中文件大小(保留字段) |
showOta | boolean | false | 是否展示 OTA 界面(保留字段) |
isShowProgress | boolean | false | 是否展示 OTA 升级进度弹窗 |
mValue | number | 0 | 升级进度 0-100 |
mOtaFile | string | "otaUpdate.ufw" | 当前 OTA 文件名 |
mFailReason | string | "ota Fail" | 失败原因文案 |
mOtaResult | number | 0 | 升级结果,0 成功 / 1 失败 |
mStatus | number | 0 | 状态机:0 检验中 / 1 升级中 / 2 回连设备 / 3 升级成功 / 4 升级失败 |
isShowLoading | boolean | false | 是否展示等待遮罩 |
mLoadingText | string | "加载升级文件" | 等待遮罩文案 |
页面级属性 upgradeData: Uint8Array 不参与渲染,持有选中文件被完整读入内存的字节流,供 OTA 时注入 OTAConfig.updateFileData。
data: {
isBleConnect: false,
fileArray: new Array(),
fileIndex: -1,
fileSize: 0,
showOta: false,
isShowProgress: false, //展示OTA升级界面
mValue: 0, //进度 0-100
mOtaFile: "otaUpdate.ufw", //OTA文件名
mFailReason: "ota Fail", //失败原因
mOtaResult: 0, //0:成功 1:失败
mStatus: 0, //0:检验中 1:升级中 2:回连设备 3:升级成功 4:升级失败
isShowLoading: false,
mLoadingText: "加载升级文件"
},
upgradeData: new Uint8Array(0),
Source: pageUpdate.ts
生命周期与全局状态接入
onLoad:订阅蓝牙事件、注册文件监听
onLoad 完成三件事:取全局蓝牙管理器、注册设备状态回调、初始化文件列表并挂载 UpgradeFileUtil 的监听器。由于 UpgradeFileUtil 的文件存储可能被其他页面(例如文件管理页)修改,这里通过 setListener 订阅变化,保证页面回到前台时文件列表始终与存储一致。
onLoad() {
sBluetoothManager = app.globalData.bluetoothManager
const bluetoothEventCallback = new BluetoothEventCallback();
/** 设备断开 */
bluetoothEventCallback.onDevStatusDisconnect = (dev: BTBean.BluetoothDevice) => {
this._checkIsConnected()
};
/** 设备连接失败 */
bluetoothEventCallback.onDevStatusFailed = (dev: BTBean.BluetoothDevice) => {
this._checkIsConnected()
};
/** 设备连接成功*/
bluetoothEventCallback.onDevStatusSuccess = (dev: BTBean.BluetoothDevice) => {
this._checkIsConnected()
};
sBluetoothManager.addBluetoothEventCallback(bluetoothEventCallback)
this._onUpgradeFileInfoList(UpgradeFileUtil.getUpgradeFileInfos())
UpgradeFileUtil.setListener({
onUpgradeFileInfoList: (infoList: any[]) => {
this._onUpgradeFileInfoList(infoList)
}
})
},
Source: pageUpdate.ts
设计意图:三个设备事件(断开/失败/成功)最终都收敛到同一个 _checkIsConnected(),因为页面 UI 只关心"现在连没连上"这一个布尔结果,不需要区分具体事件类型——这是典型的状态归一化处理。
onShow:刷新连接状态与 TabBar
onShow() {
this._checkIsConnected()
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({
selected: 1,
})
}
},
Source: pageUpdate.ts
页面每次从后台回到前台都会重新检查连接状态(因为 OTA 期间可能发生回连),并把自定义 TabBar 的选中项设置为第 1 个(升级页)。
状态刷新辅助方法
_checkIsConnected() 通过 sBluetoothManager.getConnectedDevice() 查询当前连接设备列表,非空即视为已连接:
_checkIsConnected() {
var isConnected = false
const connectedDevices = sBluetoothManager.getConnectedDevice()
if (connectedDevices != null && connectedDevices.length > 0) {
isConnected = true
}
this.setData({
isBleConnect: isConnected
})
},
Source: pageUpdate.ts
_onUpgradeFileInfoList 负责把存储中的文件信息加工成 UI 需要的展示格式:列表倒序排列(最新导入在前),并把时间戳格式化为 YYYY/MM/DD HH:mm:ss、文件大小格式化为保留两位小数的 MB 值:
_onUpgradeFileInfoList(infoList: any[]) {
const tempList = infoList.reverse()
tempList.forEach(element => {
element.date = this._formatTime(new Date(element.time))
element.fileSizeStr = (element.fileSize / (1024 * 1024)).toFixed(2)
});
this.setData({ fileArray: tempList })
},
Source: pageUpdate.ts
升级文件管理
导入文件:单文件重命名 vs 多文件批量
onAddOTAFile 调用 wx.chooseMessageFile 拉起聊天文件选择器(最多 10 个)。导入策略按文件数量分支:
- 多个文件:跳过重命名,直接构造
{fileName, fileSrcPath, fileSize}列表调用UpgradeFileUtil.addUpgradeFiles(infos)批量落盘。 - 单个文件:弹出可编辑的
wx.showModal,让用户确认/修改文件名后调用UpgradeFileUtil.addUpgradeFile(fileName, path, size)落盘。 - 空间不足:导入失败且错误码为
1300202时,提示"小程序剩余使用空间不足(上限200MB)"。
} else if (addFileArray.length == 1) {//单个文件重命名
const file = addFileArray[0]
wx.showModal({
title: "请输入文件名",
content: file.name,
editable: true,
success: (res) => {
if (res.confirm == true) {
UpgradeFileUtil.addUpgradeFile(res.content, file.path, file.size).then((res) => {
wx.showToast({
title: '导入成功',
icon: 'success'
})
}).catch((error) => {
if (error == 1300202) {
wx.showToast({
title: '导入失败,小程序剩余使用空间不足(上限200MB)',
icon: 'none'
})
}
})
} else if (res.cancel == true) {
wx.showToast({
title: '取消保存',
icon: 'none'
})
}
}
})
}
Source: pageUpdate.ts
设计意图:单文件场景允许重命名,是因为微信聊天文件默认名可能是随机串,而 OTA 文件名(如 otaUpdate.ufw)对用户辨识升级固件有意义;多文件场景下逐个弹窗会打断操作流,因此直接批量导入。
长按操作:删除与转发
onLongTapFile 通过 wx.showActionSheet 提供两个操作:删除文件、转发文件到聊天。若删除的正是当前选中文件,会同时清空 fileIndex 与 mOtaFile,避免残留无效选中态;转发使用 wx.shareFileMessage,失败时以 toast 展示 errMsg。
onLongTapFile(e: WechatMiniprogram.CustomEvent) {
const index = e.currentTarget.dataset.index
wx.showActionSheet({
itemList: ['删除文件', '转发文件到聊天'],
success: (res) => {
const file = this.data.fileArray[index]
if (res.tapIndex === 0) {
if (this.data.fileIndex == index) {
this.setData({
fileIndex: -1,
mOtaFile: "",
})
}
UpgradeFileUtil.removeUpgradeFile(file)
} else if (res.tapIndex === 1) {
wx.shareFileMessage({
filePath: file.filePath,
fileName: file.fileName,
success: (res) => {
}, fail: (res) => {
wx.showToast({ title: "转发失败,msg:" + res.errMsg })
}
})
}
}
})
},
Source: pageUpdate.ts
选中文件并读入内存
onSelectedFile 是升级前的关键一步:点击列表项先做选中态切换(再次点击同一项则取消选中),随后用 wx.getFileSystemManager() 同步打开文件、一次性把整个文件读入 Uint8Array 并暂存到页面属性 upgradeData。读取成功延迟 200ms 关闭 loading 再提示"加载成功",失败则提示"加载失败";若文件已丢失(打开异常),会提示"文件丢失"并顺手从存储中移除该失效记录。
const selectedFile = this.data.fileArray[e.currentTarget.dataset.index]
this.showLoadingView()
const fs = wx.getFileSystemManager()
try {
const fd = fs.openSync({
filePath: selectedFile.filePath
})
const uint8 = new Uint8Array(selectedFile.fileSize);
fs.read({
fd: fd,
arrayBuffer: uint8.buffer,
length: selectedFile.fileSize,
success: _res => {
this.upgradeData = uint8
logv("------------读取文件成功------------")
setTimeout(() => {
this.dismissLoadingView()
wx.showToast({
title: '加载成功',
icon: 'none'
})
}, 200);
fs.closeSync({ fd: fd })
},
fail: _res => {
this.dismissLoadingView()
wx.showToast({
title: '加载失败',
icon: 'none'
})
fs.closeSync({ fd: fd })
}
})
} catch (error) {
this.dismissLoadingView()
wx.showToast({
title: '文件丢失',
icon: 'none'
})
...
UpgradeFileUtil.removeUpgradeFile(selectedFile)
logv("error", error);
return
}
this.setData({
fileIndex: e.currentTarget.dataset.index,
mOtaFile: selectedFile.fileName,
})
Source: pageUpdate.ts
设计意图:文件在「选中」时就整体读入内存而非在点击升级时才读取,可以提前暴露文件损坏/丢失问题,让用户在进入升级流程前就知道所选文件不可用;代价是内存中始终持有一份完整固件字节流,这对小程序内存预算是一个需要留意的点(见性能章节)。
OTA 升级核心流程
升级入口校验
onUpdate 是升级的唯一起点,先做两道前置校验,全部通过后才进入升级流程:
- 未连接设备(
!isBleConnect)→ toast「请先连接的设备」; - 未选择文件(
fileIndex == -1)→ toast「请先选择升级文件」。
这两道校验与 WXML 中「升级」按钮的展示逻辑(view_4/view_5 分别对应已连接/未连接两种样式)配合,从交互上引导用户按「先连设备 → 再选文件 → 最后升级」的顺序操作。
构造 OTAConfig 并启动
onUpdate: function () {
if (!this.data.isBleConnect) {
wx.showToast({ title: '请先连接的设备', icon: 'none' })
return;
}
if (this.data.fileIndex == -1) {
wx.showToast({ title: '请先选择升级文件', icon: 'none' })
return;
}
this.setData({ isShowProgress: true })
/*--- 开始执行OTA升级 ---*/
const otaConfig: OTAConfig = new OTAConfig()
otaConfig.isSupportNewRebootWay = true
otaConfig.updateFileData = this.upgradeData
logv("upgradeData size: " + this.upgradeData.length);
const connectedDevices = sBluetoothManager.getConnectedDevice()
if (connectedDevices != null && connectedDevices.length > 0) {
const otaDev = connectedDevices[0]
sBluetoothManager.startOTA(otaDev, otaConfig, { ... })
}
},
Source: pageUpdate.ts
关键点:
OTAConfig.isSupportNewRebootWay = true:启用新版重启方式,由 SDK 在升级完成后按新流程控制设备重启与回连,这是 JL 平台不同固件版本兼容性的开关。otaConfig.updateFileData = this.upgradeData:固件字节流直接以内存Uint8Array形式注入,不走临时文件。- 目标设备取
getConnectedDevice()[0]:页面假设同一时刻只对一个已连接设备升级;多设备场景下仅操作列表中的第一个。
升级状态机
升级全程的状态由 mStatus 表达,WXML 通过 OTA-View 组件把该值渲染为不同的界面文案。SDK 回调与状态之间的映射关系如下:
stateDiagram-v2
[*] --> CheckFile: onStartOTA / UPGRADE_TYPE_CHECK_FILE
CheckFile --> Upgrading: onProgress(UPGRADE_TYPE_FIRMWARE)
Upgrading --> Reconnect: onNeedReconnect
Reconnect --> Upgrading: 回连成功后继续升级
Upgrading --> Success: onStopOTA
Upgrading --> Failed: onError
Upgrading --> Failed: onCancelOTA
Success --> [*]
Failed --> [*]
各状态含义(与 data 注释一致):0 检验中、1 升级中、2 回连设备、3 升级成功、4 升级失败。
六大回调详解
startOTA 的第三个参数是一次性传入的回调对象,页面在此把 SDK 事件翻译为 UI 状态。逐个分析:
onStartOTA:升级流程正式启动,保持进度弹窗展示、mStatus = 0(检验中);延迟 100ms 后禁用自定义 TabBar(enable: false),防止用户在升级过程中切换页面导致蓝牙上下文异常——代码注释也说明这是"暂时性处理",更优方案应是让弹窗遮罩覆盖整个小程序。
onNeedReconnect:SDK 通知需要回连设备(固件重启后重新建立 BLE 连接),页面将进度清零、mStatus = 2(回连设备),等待回连成功后 SDK 继续上报进度。
onProgress(type, progress):按升级阶段区分进度展示——UPGRADE_TYPE_CHECK_FILE 时 mStatus = 0(检验中),UPGRADE_TYPE_FIRMWARE 时 mStatus = 1(升级中),两者都实时更新 mValue:
onProgress: (type: UpgradeType, progress: number) => {
if (type == UpgradeType.UPGRADE_TYPE_CHECK_FILE) {
this.setData({ mValue: progress, mStatus: 0 })
}
if (type == UpgradeType.UPGRADE_TYPE_FIRMWARE) {
this.setData({ mValue: progress, mStatus: 1 })
}
},
Source: pageUpdate.ts
onStopOTA:升级成功收尾。恢复 TabBar 可用,进度清零、mOtaResult = 0、mStatus = 3(升级成功),进度弹窗展示成功界面。
onCancelOTA:升级被取消。恢复 TabBar、mOtaResult = 1、mStatus = 4、mFailReason = "升级被取消."。
onError(error, message):升级失败。恢复 TabBar、mOtaResult = 1、mStatus = 4,并把 SDK 返回的 message 直接作为失败原因文案透传给弹窗:
onError: (error: number, message: string) => {
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ enable: true })
}
this.setData({
mValue: 0,
mOtaResult: 1,
mStatus: 4,
mFailReason: message
})
},
Source: pageUpdate.ts
升级时序图
sequenceDiagram
participant U as 用户
participant P as pageUpdate.ts
participant M as BluetoothOTAManager
participant S as jl_ota SDK
participant D as BLE 设备
U->>P: 点击「升级」
P->>P: 校验 isBleConnect 与 fileIndex
P->>M: startOTA(dev, otaConfig, callbacks)
M->>S: 启动 OTA 传输
S->>D: BLE 写特征值/校验固件
S-->>P: onStartOTA
P->>P: setData(isShowProgress=true, mStatus=0)
S-->>P: onProgress(CHECK_FILE, 0-100)
P->>P: setData(mStatus=0, mValue)
S-->>P: onProgress(FIRMWARE, 0-100)
P->>P: setData(mStatus=1, mValue)
S-->>P: onNeedReconnect
P->>P: setData(mStatus=2, mValue=0)
S-->>P: onStopOTA (成功) 或 onError (失败)
P->>P: setData(mStatus=3/4, mOtaResult)
P-->>U: OTA-View 展示结果,OnConfirm 关闭
结果确认
onOtaProgressViewConfirm 是 OTA-View 通过 bind:OnConfirm 冒泡上来的事件,用户点击弹窗确定后关闭进度弹窗:
onOtaProgressViewConfirm: function () {
this.setData({
isShowProgress: false
})
},
Source: pageUpdate.ts
视图模板与组件绑定
WXML 是纯展示层,只做三件事:渲染设备状态、渲染文件列表、挂载两个子组件。文件列表项通过 catch:tap 与 catch:longpress 捕获选中/长按事件(catch 阻止事件冒泡到外层容器,避免误触);选中态由 index != fileIndex 切换 icon_choose_nol/icon_choose_sel 两张图标。
<scroll-view class="file_scroll" scroll-y wx:if="{{fileArray.length>0}}">
<view class="itemView" wx:for="{{fileArray}}" wx:key="id" catch:tap="onSelectedFile" catch:longpress="onLongTapFile" data-item="{{item}}" data-index="{{index}}">
...
<image wx:if="{{index !=fileIndex}}" class="image_r" src="/images/icon_choose_nol.png" mode="aspectFit" />
<image wx:else class="image_r" src="/images/icon_choose_sel.png" mode="aspectFit" />
</view>
</scroll-view>
<OTA-View pShow="{{isShowProgress}}" pStatus="{{mStatus}}" pOtaFile="{{mOtaFile}}" pValue="{{mValue}}" pNumber="{{mNumber}}" pTimes="{{mTimes}}" pOtaResult="{{mOtaResult}}" pFailReason="{{mFailReason}}" bind:OnConfirm="onOtaProgressViewConfirm">
</OTA-View>
<WAIT-View pShow="{{isShowLoading}}" pText="{{mLoadingText}}">
</WAIT-View>
Source: pageUpdate.wxml
pageUpdate.json 负责注册这三个组件与页面背景色:
{
"usingComponents": {
"OTA-View": "/components/otaProgressView/otaProgressView",
"WAIT-View": "/components/waittingView/waittingView",
"tab-bar": "../../custom-tab-bar/index"
},
"backgroundColor": "#F4F7FB"
}
Source: pageUpdate.json
配置项与外部依赖
页面配置(pageUpdate.json)
| 配置键 | 值 | 说明 |
|---|---|---|
usingComponents.OTA-View | /components/otaProgressView/otaProgressView | OTA 进度/结果弹窗组件 |
usingComponents.WAIT-View | /components/waittingView/waittingView | 加载等待遮罩组件 |
usingComponents.tab-bar | ../../custom-tab-bar/index | 自定义底部导航栏 |
backgroundColor | #F4F7FB | 页面背景色 |
外部依赖一览
| 依赖 | 导入位置 | 用途 |
|---|---|---|
IAppOption | ../../../typings/index | 全局 app.globalData 类型定义 |
BTBean.BluetoothDevice | ../../lib/bluetooth | 蓝牙设备数据类型 |
BluetoothEventCallback / BluetoothOTAManager | ../../lib/bluetoothOTAManager | 设备事件回调注册与 OTA 入口 |
OTAConfig / ReConnectMsg / UpgradeType | ../../lib/jl_lib/jl_ota_2.1.1 | OTA 配置、回连消息、升级阶段枚举 |
loge / logv | ../../lib/log | 日志输出(error/verbose) |
UpgradeFileUtil | ../../lib/upgradeFileUtil | 升级文件列表/增删/监听 |
app.globalData.bluetoothManager | 全局实例 | 页面持有的蓝牙管理器单例 |
运行时交互参数(数据字段)
| 字段 | 可配置点 | 说明 |
|---|---|---|
mLoadingText | 页面 data | 等待遮罩文案,默认「加载升级文件」 |
mOtaFile | 选中文件后自动更新 | 展示在进度弹窗中的固件文件名 |
OTAConfig.isSupportNewRebootWay | onUpdate 中硬编码 true | 新版重启方式开关,适配固件兼容性 |
wx.chooseMessageFile.count | onAddOTAFile 中硬编码 10 | 单次最多选择文件数 |
| 存储上限 | UpgradeFileUtil | 错误码 1300202 对应 200MB 空间上限 |
API 参考
页面内定义的方法均为 Page() 配置对象的成员,供 WXML 事件绑定或内部调用。
onLoad()
页面加载生命周期。取全局蓝牙管理器、注册 BluetoothEventCallback(断开/失败/成功 → _checkIsConnected)、初始化文件列表并注册 UpgradeFileUtil 监听。 返回: 无
onShow()
页面显示生命周期。刷新连接状态,并将自定义 TabBar 选中项设为 1。
showLoadingView(): void
设置 isShowLoading = true,展示 WAIT-View 遮罩。
dismissLoadingView(): void
设置 isShowLoading = false,隐藏遮罩。
onAddOTAFile(): void
拉起 wx.chooseMessageFile(count=10, type='file')。多文件批量导入;单文件弹出可编辑 wx.showModal 重命名后导入。 错误处理: 导入失败 1300202 → toast 提示 200MB 空间上限;选择器失败 → 记录日志并提示「数据错误」。
onLongTapFile(e: WechatMiniprogram.CustomEvent): void
长按文件列表项弹出 ActionSheet。tapIndex===0 删除文件(若为选中项则同时清空 fileIndex/mOtaFile);tapIndex===1 通过 wx.shareFileMessage 转发。 参数: e.currentTarget.dataset.index 文件下标 错误处理: 转发失败 → toast 展示 errMsg。
onSelectedFile(e: any): void
点击文件列表项。再次点击已选项取消选中;否则打开文件并整体读入 Uint8Array 存入 upgradeData,同时更新 fileIndex 与 mOtaFile。 异常: 读取失败 → toast「加载失败」;文件丢失(openSync 抛错)→ toast「文件丢失」并从存储移除该文件。
onUpdate(): void
升级入口。校验连接与选中文件后构造 OTAConfig(isSupportNewRebootWay=true、updateFileData=upgradeData),对 getConnectedDevice()[0] 调用 sBluetoothManager.startOTA(dev, config, callbacks)。 回调: onStartOTA / onNeedReconnect / onProgress(type, progress) / onStopOTA / onCancelOTA / onError(error, message)
onOtaProgressViewConfirm(): void
OTA-View 组件 OnConfirm 事件处理,关闭进度弹窗(isShowProgress=false)。
_onUpgradeFileInfoList(infoList: any[]): void
把文件信息列表倒序排列,格式化时间(_formatTime)与大小(MB 两位小数)后写入 fileArray。
_checkIsConnected(): void
查询 getConnectedDevice(),非空则 isBleConnect=true,否则为 false。
_formatTime(date: Date): string
格式化时间为 YYYY/MM/DD HH:mm:ss,借助 _formatNumber 补零。
_formatNumber(n: number): string
将个位数数字补零为两位字符串。
失败模式、边界情况与并发
用户操作层面的防护
页面在进入升级前设置了双保险:未连接设备、未选文件都会以 toast 拦截并直接 return,确保 startOTA 不会被非法状态触发。文件读取阶段对「文件丢失」做了自愈处理——检测到打开失败时自动调用 UpgradeFileUtil.removeUpgradeFile 清理失效记录,避免坏文件一直占用列表与存储空间。
升级失败与取消
| 场景 | 回调 | 页面表现 |
|---|---|---|
| 固件校验失败/传输中断 | onError(error, message) | mStatus=4、mOtaResult=1,弹窗展示 SDK 返回的失败原因 |
| 用户/系统取消升级 | onCancelOTA | mStatus=4、mFailReason="升级被取消." |
| 升级成功 | onStopOTA | mStatus=3、mOtaResult=0,进度清零 |
| 设备重启需回连 | onNeedReconnect | mStatus=2、进度清零,等待 SDK 回连后继续 |
注意:onError 与 onCancelOTA 都会恢复 TabBar 可用(enable: true),而 onStopOTA 同样恢复——三条路径都保证升级结束后底部导航必然解锁,不会出现升级失败后用户被困在页面的情况。
并发与一致性注意点
- OTA 期间禁用 TabBar:
onStartOTA后 100ms 将自定义 TabBar 置为enable: false。代码注释明确这是"暂时性处理",理想方案是让遮罩覆盖整个小程序。这意味着当前实现只能阻止 TabBar 切换,无法阻止用户通过右上角胶囊菜单或系统手势退出页面——页面退出时 SDK 仍在后台执行升级,属于已知边界。 - 单设备假设:
onUpdate只取getConnectedDevice()[0],不处理多设备同时连接的选择问题;若首个设备并非目标设备,会升级错对象。页面也未展示设备列表,连接管理依赖蓝牙管理页。 - 重复点击升级:
onUpdate没有独立的"升级中"防抖标志,仅靠isShowProgress弹窗遮挡 + TabBar 禁用间接降低重复触发概率;在弹窗渲染完成前快速连点仍可能重复调用startOTA。 - 文件列表并发修改:页面通过
UpgradeFileUtil.setListener订阅列表变化,任何页面修改文件后回调都会触发_onUpgradeFileInfoList重建fileArray;由于重建时fileIndex未做下标重映射,若正在展示的选中文件被外部删除,fileIndex可能指向错误条目(选中图标错位),但升级前的onSelectedFile重新校验可部分规避。
典型失败流程图示
flowchart TD
Start([点击升级]) --> Check1{"isBleConnect?"}
Check1 -->|"否"| T1["toast 请先连接的设备"]
Check1 -->|"是"| Check2{"fileIndex == -1?"}
Check2 -->|"是"| T2["toast 请先选择升级文件"]
Check2 -->|"否"| StartOTA["startOTA(dev, config, callbacks)"]
StartOTA --> Wait{"SDK 事件"}
Wait -->|"onStartOTA"| S0["mStatus=0 检验中, TabBar 禁用"]
Wait -->|"onProgress(CHECK_FILE)"| S0
Wait -->|"onProgress(FIRMWARE)"| S1["mStatus=1 升级中"]
Wait -->|"onNeedReconnect"| S2["mStatus=2 回连设备"]
Wait -->|"onStopOTA"| S3["mStatus=3 升级成功"]
Wait -->|"onError"| S4["mStatus=4 升级失败"]
Wait -->|"onCancelOTA"| S4
S3 --> End(["TabBar 恢复, 弹窗展示结果"])
S4 --> End
性能与运维注意
- 大文件内存占用:选中文件时一次性
fs.read整个固件到Uint8Array,之后upgradeData常驻页面实例直至页面销毁。大固件(几十 MB)会显著抬升小程序内存水位,且OTAConfig.updateFileData直接引用该缓冲,升级期间无法释放。若需支持超大固件,应考虑分段读取/边读边发的改造。 - 进度 setData 频率:
onProgress每次回调都setData更新mValue。BLE 传输进度回调频率较高时,频繁 setData 会触发视图层重渲染,可能造成 UI 卡顿;当前实现未做节流,属可优化点。 - 存储上限 200MB:
UpgradeFileUtil导入失败错误码1300202表示小程序本地存储空间不足,页面已对该错误做了用户可读的提示。 - 日志:关键节点(读文件成功、升级数据大小、错误)通过
logv/loge输出,线上问题排查应结合这些日志与 SDK 错误码定位。 - 升级期间导航:TabBar 禁用是唯一防切换手段,建议后续改为全局遮罩 + 页面级
onHide中断处理,避免用户在升级中离开页面造成连接状态与 UI 不同步。
扩展点
- 蓝牙事件扩展:
BluetoothEventCallback目前只用了三个设备状态事件;该回调类还支持其他蓝牙事件(扫描、RSSI 等),新增能力可直接注册,无需改动页面结构。 - 文件存储监听:
UpgradeFileUtil.setListener({ onUpgradeFileInfoList })是发布-订阅模式,其他页面(如文件管理页)也可复用同一机制,实现跨页文件列表自动同步。 - OTA-View / WAIT-View 组件复用:进度弹窗与等待遮罩均为独立组件且 props 化(
pShow/pStatus/pOtaFile/pValue/pOtaResult/pFailReason与pShow/pText),可在其他涉及 OTA 的页面(如设备详情页)直接复用。 - 升级阶段自定义:
mStatus的 0-4 状态映射与UpgradeType枚举一一对应,若 SDK 增加新阶段(如"擦除中"),只需在onProgress中新增分支并同步otaProgressView组件的文案映射。