杰理 SDK 文档中心
首页
首页
  • 项目概览与入门

    • 项目概述
    • 快速开始
  • OTA SDK 核心库

    • RCSP 认证库(jl_auth)
    • OTA 流程库(jl_ota)
    • RCSP-OTA 协议库(jl_rcsp_ota)
    • OTAWrapper 高层封装
  • 蓝牙通信与设备管理

    • 蓝牙连接生命周期管理
    • BLE 数据发送与 MTU 管理
    • 自动回连机制
  • 参考 Demo 小程序

    • 应用入口与页面导航
    • 设备连接页(pageConnect)
    • 固件升级页(pageUpdate)
    • 设置与调试页(pageSetting)
    • 自定义 UI 组件
    • 固件文件解析工具(upgradeFileUtil)
    • 日志系统

固件升级页(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.1 SDK 封装,不在本页展开。
  • 升级文件在本地存储中的落盘与持久化由 lib/upgradeFileUtil 负责,本页只描述其调用方式。
  • 进度弹窗 otaProgressView、等待遮罩 waittingView 是独立组件,本页仅说明它们在 pageUpdate 中的 props 绑定关系。

Overview

固件升级页是小程序「OTA 能力」的用户入口,承担三层职责:

  1. 文件管理:通过 wx.chooseMessageFile 从聊天会话导入升级文件(支持单文件重命名、多文件批量导入),以列表形式展示文件名称、大小、导入时间,支持选中、长按删除与转发到聊天。
  2. 状态感知:订阅全局 BluetoothOTAManager 的设备连接事件,实时刷新「设备状态:已连接/未连接」;只有在已连接且已选择文件的条件下才允许发起升级。
  3. 升级驱动:构造 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 状态机"三组概念组织:

字段类型默认值含义
isBleConnectbooleanfalse当前是否有已连接的蓝牙设备
fileArrayany[][]升级文件列表(来自 UpgradeFileUtil)
fileIndexnumber-1当前选中文件下标,-1 表示未选择
fileSizenumber0选中文件大小(保留字段)
showOtabooleanfalse是否展示 OTA 界面(保留字段)
isShowProgressbooleanfalse是否展示 OTA 升级进度弹窗
mValuenumber0升级进度 0-100
mOtaFilestring"otaUpdate.ufw"当前 OTA 文件名
mFailReasonstring"ota Fail"失败原因文案
mOtaResultnumber0升级结果,0 成功 / 1 失败
mStatusnumber0状态机:0 检验中 / 1 升级中 / 2 回连设备 / 3 升级成功 / 4 升级失败
isShowLoadingbooleanfalse是否展示等待遮罩
mLoadingTextstring"加载升级文件"等待遮罩文案

页面级属性 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 是升级的唯一起点,先做两道前置校验,全部通过后才进入升级流程:

  1. 未连接设备(!isBleConnect)→ toast「请先连接的设备」;
  2. 未选择文件(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/otaProgressViewOTA 进度/结果弹窗组件
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.1OTA 配置、回连消息、升级阶段枚举
loge / logv../../lib/log日志输出(error/verbose)
UpgradeFileUtil../../lib/upgradeFileUtil升级文件列表/增删/监听
app.globalData.bluetoothManager全局实例页面持有的蓝牙管理器单例

运行时交互参数(数据字段)

字段可配置点说明
mLoadingText页面 data等待遮罩文案,默认「加载升级文件」
mOtaFile选中文件后自动更新展示在进度弹窗中的固件文件名
OTAConfig.isSupportNewRebootWayonUpdate 中硬编码 true新版重启方式开关,适配固件兼容性
wx.chooseMessageFile.countonAddOTAFile 中硬编码 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 返回的失败原因
用户/系统取消升级onCancelOTAmStatus=4、mFailReason="升级被取消."
升级成功onStopOTAmStatus=3、mOtaResult=0,进度清零
设备重启需回连onNeedReconnectmStatus=2、进度清零,等待 SDK 回连后继续

注意:onError 与 onCancelOTA 都会恢复 TabBar 可用(enable: true),而 onStopOTA 同样恢复——三条路径都保证升级结束后底部导航必然解锁,不会出现升级失败后用户被困在页面的情况。

并发与一致性注意点

  1. OTA 期间禁用 TabBar:onStartOTA 后 100ms 将自定义 TabBar 置为 enable: false。代码注释明确这是"暂时性处理",理想方案是让遮罩覆盖整个小程序。这意味着当前实现只能阻止 TabBar 切换,无法阻止用户通过右上角胶囊菜单或系统手势退出页面——页面退出时 SDK 仍在后台执行升级,属于已知边界。
  2. 单设备假设:onUpdate 只取 getConnectedDevice()[0],不处理多设备同时连接的选择问题;若首个设备并非目标设备,会升级错对象。页面也未展示设备列表,连接管理依赖蓝牙管理页。
  3. 重复点击升级:onUpdate 没有独立的"升级中"防抖标志,仅靠 isShowProgress 弹窗遮挡 + TabBar 禁用间接降低重复触发概率;在弹窗渲染完成前快速连点仍可能重复调用 startOTA。
  4. 文件列表并发修改:页面通过 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 不同步。

扩展点

  1. 蓝牙事件扩展:BluetoothEventCallback 目前只用了三个设备状态事件;该回调类还支持其他蓝牙事件(扫描、RSSI 等),新增能力可直接注册,无需改动页面结构。
  2. 文件存储监听:UpgradeFileUtil.setListener({ onUpgradeFileInfoList }) 是发布-订阅模式,其他页面(如文件管理页)也可复用同一机制,实现跨页文件列表自动同步。
  3. OTA-View / WAIT-View 组件复用:进度弹窗与等待遮罩均为独立组件且 props 化(pShow/pStatus/pOtaFile/pValue/pOtaResult/pFailReason 与 pShow/pText),可在其他涉及 OTA 的页面(如设备详情页)直接复用。
  4. 升级阶段自定义:mStatus 的 0-4 状态映射与 UpgradeType 枚举一一对应,若 SDK 增加新阶段(如"擦除中"),只需在 onProgress 中新增分支并同步 otaProgressView 组件的文案映射。

相关链接

  • pageUpdate.ts(页面逻辑)
  • pageUpdate.wxml(视图模板)
  • pageUpdate.json(页面配置)
  • pageUpdate.less(页面样式)
  • otaProgressView 进度弹窗组件
  • waittingView 等待遮罩组件
  • bluetoothOTAManager(蓝牙/OTA 管理器)
  • jl_ota_2.1.1(OTA SDK)
  • upgradeFileUtil(升级文件管理)
  • custom-tab-bar(自定义底部导航)
Prev
设备连接页(pageConnect)
Next
设置与调试页(pageSetting)