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

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

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

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

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

设备连接页(pageConnect)

设备连接页是微信小程序 JLOTA 的核心入口页面,负责蓝牙设备的扫描、过滤、连接与断开操作,并通过 BluetoothOTAManager 与底层蓝牙 OTA 通道交互,为后续固件升级提供设备会话基础。

Purpose and Scope

本页文档覆盖 pages/pageConnect/pageConnect 页面的完整实现,包括:

  • 页面生命周期(onLoad / onShow / onUnload)与全局蓝牙管理器 app.globalData.bluetoothManager 的绑定;
  • 蓝牙事件回调 BluetoothEventCallback 的注册与分发;
  • 设备扫描(sanDevice())、RSSI 排序、设备名过滤与下拉刷新机制;
  • 设备点击连接 / 断开交互及用户提示流程;
  • 页面配置(pageConnect.json)与本地存储键 filterDevName。

不属于本页范围的内容:底层蓝牙适配(lib/bluetooth.ts)、OTA 升级流程(lib/bluetoothOTAManager.ts 中的升级逻辑、jl_ota / jl_rcsp SDK)、以及其他页面(如升级页、关于页)。对于 OTA 升级流程,参见仓库中的升级相关页面文档。

Overview

该小程序是一个基于微信小程序 BLE(低功耗蓝牙)能力的耳机 / 音箱 OTA 升级工具。pageConnect 是整个应用的第一个 Tab(连接 Tab),用户进入小程序后首先看到的就是设备列表:

  • 页面通过 wx.getStorageSync('filterDevName') 读取上次保存的设备名过滤条件;
  • 通过 getApp<IAppOption>().globalData.bluetoothManager 获取全局单例 BluetoothOTAManager;
  • 构造一个 BluetoothEventCallback 并把页面的各处理方法(_onScanStart、_OnFoundDevs、_onDevConnectSuccess 等)挂到该回调对象上,再调用 addBluetoothEventCallback 订阅事件;
  • 页面 onLoad 时立即触发一次 sBluetoothManager.sanDevice() 开始扫描。

这种"页面订阅事件 + 全局管理器广播"的设计使得蓝牙扫描、连接等耗时操作与 UI 解耦:BluetoothOTAManager 在后台驱动底层蓝牙适配器,扫描结果和连接状态变化通过回调管理器广播给所有订阅方,页面只负责把事件翻译成 setData 的 UI 更新。

Architecture

flowchart TD
    subgraph sg_Page["页面层 pages/pageConnect"]
        Page["Page 实例<br/>(pageConnect.ts)"]
        Callback["BluetoothEventCallback<br/>实例"]
        View["视图层<br/>(pageConnect.wxml / .less)"]
    end

    subgraph sg_Manager["全局蓝牙管理层 lib"]
        Manager["BluetoothOTAManager<br/>(app.globalData.bluetoothManager)"]
        BLE["蓝牙适配器实例<br/>_bluetoothInstance"]
        CBManager["回调管理器<br/>_BluetoothCallbackManager"]
    end

    subgraph sg_Device["设备"]
        Dev["BLE 设备<br/>(耳机/音箱)"]
    end

    subgraph sg_Storage["本地存储"]
        Storage["wx.setStorageSync<br/>filterDevName"]
    end

    Page -->|"new + 挂载处理方法"| Callback
    Callback -->|"addBluetoothEventCallback"| Manager
    Page -->|"sanDevice / connectDevice /<br/>disconnectDevice / isConnected"| Manager
    Manager -->|"注册/广播"| CBManager
    Manager -->|"startScan / connect /<br/>disconnect / sendData"| BLE
    BLE -->|"BLE 广播与连接"| Dev
    Page -->|"getStorageSync / setStorageSync"| Storage
    CBManager -.->|"onScanStart/onFoundDev/onDevStatus..."| Callback

架构说明:

  • Page 实例(pageConnect.ts):唯一持有 UI 状态(data),并把业务动作委托给全局 BluetoothOTAManager,不直接操作 BLE API。这样页面即使被销毁,底层扫描/连接仍然可以独立进行(onUnload 只是移除回调,不中断底层)。
  • BluetoothEventCallback:一个"方法包"对象,页面把自身的处理函数赋给它(sBluetoothEventCallback.onFoundDev = this._OnFoundDevs),交给管理器统一回调。回调字段在 lib/bluetoothOTAManager.ts 中定义。
  • BluetoothOTAManager:全局单例,持有平台判断(_Platform = "android")、蓝牙适配器实例与 _OTAWrapper,向上提供 sanDevice()、connectDevice()、disconnectDevice()、isConnected()、getConnectedDevice()、sendData() 等统一接口,并负责把底层事件转发给已注册的回调。
  • 本地存储:filterDevName 键保存设备名过滤条件,页面重启后自动恢复,保证用户设置不丢失。

页面数据与生命周期

初始数据(data)

data: {
  isScaning: false,        // 是否正在扫描,控制列表头部 loading 图标
  triggered: false,        // 下拉刷新组件的 triggered 状态
  filterDevName: '',       // 设备名过滤条件(来自本地存储)
  connectedDevice: <any>null,  // 当前已连接设备
  scanDevices: <any>[],    // 过滤后展示的设备列表
},
_freshing: false,          // 刷新防重入标志(非 data,页面实例字段)
_foundSrcDevlist: new Array(), // 原始扫描结果缓存(非 data)

Source: pageConnect.ts

设计意图:_freshing 与 _foundSrcDevlist 刻意放在 data 之外,因为它们不需要驱动视图渲染(避免不必要的 setData 开销);isScaning、triggered 等则必须在 data 中,以便 WXML 中的 wx:if / refresher-triggered 属性响应变化。

onLoad:订阅事件并启动扫描

onLoad() {
  const value = wx.getStorageSync('filterDevName')
  if (value) {
    this.setData({ filterDevName: value })
  }
  sBluetoothManager = app.globalData.bluetoothManager
  sBluetoothEventCallback = new BluetoothEventCallback();
  sBluetoothEventCallback.onBluetoothAdapter = this._onBluetoothAdapter
  sBluetoothEventCallback.onLocation = this._onLocation
  sBluetoothEventCallback.onScanStart = this._onScanStart
  sBluetoothEventCallback.onScanFailed = this._onScanFailed
  sBluetoothEventCallback.onScanFinish = this._onScanFinish
  sBluetoothEventCallback.onFoundDev = this._OnFoundDevs
  sBluetoothEventCallback.onDevStatusSuccess = this._onDevConnectSuccess
  sBluetoothEventCallback.onDevStatusDisconnect = this._onDevDisconnect
  sBluetoothEventCallback.onDevStatusFailed = this._onDevConnectFailed
  sBluetoothManager.addBluetoothEventCallback(sBluetoothEventCallback)
  // this._scanDevice()
  sBluetoothManager.sanDevice();
},

Source: pageConnect.ts

注意两点设计细节:

  1. 事件回调字段的赋值顺序:onScanStart → onFoundDev → onDevStatusSuccess 等,与底层扫描/连接事件的自然发生顺序一致,便于阅读时对照事件流。
  2. sBluetoothManager.sanDevice() 带拼写特征:管理器暴露的扫描方法命名为 sanDevice(而非 scanDevice),页面直接调用该名称;BluetoothOTAManager 内部还通过 onSanDeviceStop 通知 OTA 包装层停止扫描,说明扫描动作与 OTA 的 ADV 监听存在联动。

onShow / onUnload

onShow() {
  if (typeof this.getTabBar === 'function' && this.getTabBar()) {
    this.getTabBar().setData({ selected: 0 })
  }
},
onUnload() {
  sBluetoothManager.removeBluetoothEventCallback(sBluetoothEventCallback)
},

Source: pageConnect.ts

  • onShow 把自定义 TabBar(custom-tab-bar/index,pageConnect 为 selected: 0)的高亮项复位为"连接"。
  • onUnload 只移除回调引用而不停止底层扫描/连接,保证页面被关闭时底层 BLE 链路不受影响(例如连接成功后跳转升级页的场景)。

设备扫描与过滤

扫描事件处理

_onScanStart() { this.setData({ isScaning: true }) },
_onScanFailed(_err: BTBean.BluetoothError) { this.setData({ isScaning: false }) },
_onScanFinish() { this.setData({ isScaning: false }) },

lastUpDateTime: 0,
_OnFoundDevs(devices: BTBean.BluetoothDevice[]) {
  const time = new Date().getTime()
  if (time - this.lastUpDateTime < 750) {
    return
  }
  this.lastUpDateTime = time
  let devicesTemp: BTBean.BluetoothDevice[];
  devicesTemp = devices.sort(function (a, b) { return b.RSSI - a.RSSI })
  this._foundSrcDevlist = devicesTemp
  this._filterDevName(this._foundSrcDevlist)
},

Source: pageConnect.ts

关键机制:

  • 750ms 节流(throttle):底层扫描回调 onFoundDev 会高频触发(每次收到广播包即回调),直接 setData 会造成频繁渲染与卡顿。页面用 lastUpDateTime 记录上次更新时间,间隔不足 750ms 的直接丢弃,把更新频率限制在约 1.3 次/秒,兼顾实时性与性能。
  • RSSI 降序排序:devices.sort((a, b) => b.RSSI - a.RSSI) 让信号强的设备排在前面——这是 BLE 设备列表的通用最佳实践,因为 RSSI 越高通常意味着距离越近、连接越稳定。
  • 原始列表缓存:_foundSrcDevlist 保存未经过滤的全量结果,供 _filterDevName 在过滤条件变化时(如 onSetFilter 确认后)直接重算,无需重新扫描。

设备名过滤

_filterDevName(devs: BTBean.BluetoothDevice[]) {
  const filterDevName = this.data.filterDevName.toLowerCase()
  const tempList = new Array()
  const connectedDevices = sBluetoothManager.getConnectedDevice();
  if (connectedDevices != null) {
    connectedDevices.forEach(element => {
      if (element.name && element.name.toLowerCase().includes(filterDevName)) {
        tempList.push(element)
      }
    })
  }
  devs.forEach(e => {
    const devName = e.name?.toLowerCase()
    const isConnected = sBluetoothManager.isConnected(e.deviceId)
    if (e.RSSI <= 0 && devName && devName.includes(filterDevName) && !isConnected) {
      tempList.push(e)
    }
  })
  this.setData({ scanDevices: tempList })
},

Source: pageConnect.ts

过滤逻辑有两个值得注意的点:

  1. 已连接设备优先加入列表:先遍历 getConnectedDevice() 返回的已连接设备,只要名字匹配过滤条件就加入;这保证用户断开重连场景下已连接设备始终可见。
  2. 未连接设备的 RSSI 判定:if (e.RSSI <= 0 && ...) —— 扫描结果中 RSSI 大于 0 的条目通常表示信号异常/不可靠的重复广播,直接排除;同时用 sBluetoothManager.isConnected(e.deviceId) 排除已连接的设备,避免重复展示。最终列表 = 已连接设备 ∪ {未连接且 RSSI≤0 且名字匹配过滤词的设备}。

设备选择、连接与断开

onSelectedDevice:点击列表项的分流逻辑

onSelectedDevice: function (e: any) {
  logv(e.currentTarget.dataset.item)
  let device = e.currentTarget.dataset.item;
  let connectedDevices = sBluetoothManager.getConnectedDevice();
  if (connectedDevices != null && connectedDevices.length > 0) {//已连接设备
    if (!sBluetoothManager.isConnected(device)) {
      wx.showToast({ title: '请先断开已连接的设备', icon: 'none' })
      return;
    } else {
      wx.showModal({
        title: '提示',
        content: '是否要断开该设备',
        success(res) {
          if (res.confirm) {
            sBluetoothManager.disconnectDevice(device)
          }
        }
      })
    }
  } else {
    wx.showLoading({ title: '连接中' })
    logv(" 连接中", device);
    sBluetoothManager.connectDevice(device)
  }
},

Source: pageConnect.ts

该方法是整个页面的交互核心,包含一个三态判断:

场景条件行为
已有设备连接、点击的是其他设备connectedDevices.length > 0 && !isConnected(device)Toast 提示"请先断开已连接的设备",直接 return(单连接模型,防止抢占已建立会话)
已有设备连接、点击的是该已连接设备connectedDevices.length > 0 && isConnected(device)wx.showModal 询问是否断开,确认后调 disconnectDevice(device)
当前无连接connectedDevices == null 或空wx.showLoading('连接中') + connectDevice(device)

设计意图:整个系统采用单设备连接模型(底层 BluetoothOTAManager 同一时刻只维护一个目标设备),因此 UI 层必须阻止"连接第二个设备"的操作,避免与底层 RcspImpl 的 mTargetDevice 单目标语义冲突。

连接状态回调

_onDevDisconnect: function (result: BTBean.BluetoothDevice) {
  this.setData({ connectedDevice: null })
},
_onDevConnectFailed: function (result: BTBean.BluetoothDevice) {
  this.setData({ connectedDevice: null })
  wx.hideLoading({
    success: () => {
      wx.showToast({ title: '连接失败', icon: 'none' })
    },
  })
},
_onDevConnectSuccess: function (device: BTBean.BluetoothDevice) {
  this.setData({ connectedDevice: device })
  wx.hideLoading({
    success: () => {
      wx.showToast({ title: '连接成功', icon: 'none' })
    },
  })
},

Source: pageConnect.ts

  • 成功/失败路径都会 hideLoading,且 toast 放在 hideLoading 的 success 回调里,保证提示顺序正确(先关掉 loading 再弹提示)。
  • connectedDevice 字段驱动 WXML 中的选中图标(icon_choose.png)与 RSSI 显示(已连接设备显示 0)。
  • 断开时把 connectedDevice 置 null,列表回到可连接状态。

下拉刷新机制

onRefresh() {
  if (this._freshing) return;   // 防重入
  this._freshing = true
  if (!this.data.triggered) {
    this.setData({ triggered: true })
  } // 保证刷新状态下 triggered 为 true
  sBluetoothManager.sanDevice();
  setTimeout(() => {
    this.setData({ triggered: false }) // 触发 onRestore,关闭刷新图标
    this._freshing = false
  }, 1500);
},

Source: pageConnect.ts

刷新流程要点:

  • _freshing 标志防止用户在下拉刷新未完成时再次触发;
  • 手动把 triggered 置 true 以显示刷新动画,然后重新调用 sanDevice()(底层 startScan(10000),10 秒超时);
  • 1.5 秒后强制把 triggered 置回 false——此时微信会触发 bindrefresherrestore(onRestore),收起刷新图标。这个固定 1.5 秒的设计保证了交互反馈的确定性,不依赖底层扫描实际结束时间。

核心流程(时序)

sequenceDiagram
    participant U as 用户
    participant P as Page (pageConnect.ts)
    participant M as BluetoothOTAManager
    participant B as 蓝牙适配器 (_bluetoothInstance)
    participant D as BLE 设备

    Note over P,M: onLoad:注册回调 + 启动扫描
    P->>M: addBluetoothEventCallback(callback)
    P->>M: sanDevice()
    M->>B: startScan(10000)
    B-->>M: onFoundDev(devices)
    M-->>P: callback.onFoundDev(devices)
    P->>P: 750ms 节流 + RSSI 排序<br/>+ 名称过滤 → setData

    Note over U,P: 用户点击设备
    U->>P: onSelectedDevice(device)
    alt 已有连接且非本设备
        P-->>U: Toast "请先断开已连接的设备"
    else 点击已连接设备
        P-->>U: Modal "是否断开" → disconnectDevice
    else 无连接
        P-->>U: showLoading "连接中"
        P->>M: connectDevice(device)
        M->>B: connect / 建立 GATT 会话
        B-->>M: onDevStatusSuccess / Failed / Disconnect
        M-->>P: callback.onDevStatusXxx(device)
        P->>P: setData + hideLoading + Toast
    end

Source: pageConnect.ts、bluetoothOTAManager.ts

视图层(WXML)

页面结构(pageConnect.wxml)分三块:

  1. 过滤条件行(#filter-contain):点击触发 onSetFilter,显示当前 filterDevName(空则显示占位文本"内容"),右侧箭头图标。
  2. 扫描状态行(#scan-status):标题"设备列表" + isScaning 时显示 loading.png 旋转图标。
  3. 设备列表(scroll-view):启用 refresher-enabled 下拉刷新(自定义刷新样式,refresher-default-style="none",背景 #F4F7FB),绑定 bindrefresherrefresh="onRefresh";列表项 wx:for 渲染 scanDevices,展示设备名、rssi:{{(item.deviceId===connectedDevice.deviceId)? 0 : item.RSSI}} 与 deviceId;已连接设备项右侧显示 icon_choose.png 选中图标,点击通过 catchtap="onSelectedDevice" 捕获(catch 阻止事件冒泡到过滤行容器)。

WXML 中的三态表达式 (item.deviceId===connectedDevice.deviceId)? 0 : item.RSSI 与 TS 中的 connectedDevice: <any>null 相呼应:当 connectedDevice 为 null 时表达式求值安全(null 恒不等于 deviceId),已连接设备的 RSSI 显示为 0。

页面配置

pageConnect.json 的配置项:

配置项类型值说明
usingComponents.tab-barstring"../../custom-tab-bar/index"引入自定义 TabBar 组件,onShow 中通过 getTabBar() 控制选中态
backgroundColorstring"#F4F7FB"页面背景色,与刷新区域背景一致,保证下拉时视觉连贯

自定义 TabBar 中 pageConnect 的注册(custom-tab-bar/index.js):"pagePath": "pages/pageConnect/pageConnect"、"text": "连接",位于 Tab 列表第 0 位,与 onShow 中 selected: 0 对应。

本地存储键:

键名类型写入位置读取位置说明
filterDevNamestringonSetFilter 确认后onLoad设备名过滤条件持久化,重启小程序后自动恢复

使用示例

示例 1:完整接入流程(页面视角)

以下代码展示了页面从订阅事件到完成一次设备连接的标准用法,摘取自 pageConnect.ts 的 onLoad 与 onSelectedDevice:

// 1. 获取全局蓝牙管理器(在 app.ts 中创建并存入 globalData)
sBluetoothManager = app.globalData.bluetoothManager

// 2. 构造回调对象并把页面方法挂载上去
sBluetoothEventCallback = new BluetoothEventCallback();
sBluetoothEventCallback.onScanStart = this._onScanStart
sBluetoothEventCallback.onFoundDev = this._OnFoundDevs
sBluetoothEventCallback.onDevStatusSuccess = this._onDevConnectSuccess
sBluetoothEventCallback.onDevStatusFailed = this._onDevConnectFailed
sBluetoothManager.addBluetoothEventCallback(sBluetoothEventCallback)

// 3. 启动扫描
sBluetoothManager.sanDevice();

// 4. 用户点击设备后连接
sBluetoothManager.connectDevice(device)

Source: pageConnect.ts

示例 2:设备名过滤(含持久化)

onSetFilter() {
  wx.showModal({
    title: "设备过滤条件",
    editable: true,
    content: this.data.filterDevName,
    success: (res) => {
      if (res.confirm) {
        this.setData({ filterDevName: res.content })
        this._filterDevName(this._foundSrcDevlist)   // 用缓存的原始列表重算
        wx.setStorageSync('filterDevName', res.content) // 持久化
      }
    }
  })
},

Source: pageConnect.ts

该示例展示了一个典型的"编辑 → 应用 → 持久化"闭环:使用 wx.showModal 的 editable: true 作为轻量输入框(无需自定义弹窗组件),确认后立即用缓存的 _foundSrcDevlist 重算列表,并写入本地存储以便下次启动恢复。

示例 3:管理器侧接口调用(底层视角)

BluetoothOTAManager 对页面暴露的封装方法,页面调用的 connectDevice(device) / disconnectDevice(device) 实际会先拷贝设备对象再交给底层适配器:

connectDevice: (device: BluetoothDevice) => {
    const tempDev = new BTBean.BluetoothDevice()
    Object.assign(tempDev, device)
    this.connectDevice(tempDev)
},
disconnectDevice: (device: BluetoothDevice) => {
    const tempDev = new BTBean.BluetoothDevice()
    Object.assign(tempDev, device)
    this.disconnectDevice(tempDev)
},

Source: bluetoothOTAManager.ts

设计意图:Object.assign 拷贝是为了避免页面传入的 dataset.item 对象(WXML 序列化产物)直接进入底层连接流程,防止引用污染与后续异步回调期间对象被外部修改的问题。

API 参考(页面公开方法)

页面对象(Page({...}))暴露的方法与数据,供事件回调与 WXML 绑定使用:

数据字段

字段类型说明
data.isScaningboolean是否正在扫描,控制头部 loading 图标
data.triggeredboolean下拉刷新组件的 triggered 状态
data.filterDevNamestring设备名过滤条件
data.connectedDeviceBTBean.BluetoothDevice | null当前已连接设备
data.scanDevicesBTBean.BluetoothDevice[]过滤后展示的设备列表
_freshingboolean刷新防重入标志(非渲染数据)
_foundSrcDevlistBTBean.BluetoothDevice[]原始扫描结果缓存(非渲染数据)
lastUpDateTimenumber扫描回调节流时间戳

生命周期与事件方法

方法触发时机行为
onLoad()页面加载读取 filterDevName、获取全局管理器、注册回调、启动扫描
onShow()页面显示设置 TabBar selected: 0
onUnload()页面卸载移除事件回调
onSelectedDevice(e)点击设备列表项(WXML catchtap)三态分流:提示/断开/连接
onRefresh()下拉刷新(bindrefresherrefresh)防重入 + 重新扫描 + 1.5s 复位
onSetFilter()点击过滤行showModal(editable) 编辑过滤词并持久化
_OnFoundDevs(devices)管理器广播扫描结果750ms 节流、RSSI 排序、过滤、setData
_filterDevName(devs)过滤词变化 / 扫描结果到达合并已连接设备与未连接设备生成列表
_onDevConnectSuccess(device)连接成功更新 connectedDevice、关闭 loading、Toast
_onDevConnectFailed(result)连接失败清空 connectedDevice、关闭 loading、Toast"连接失败"
_onDevDisconnect(result)设备断开清空 connectedDevice
_onScanStart()/_onScanFailed()/_onScanFinish()扫描状态变化更新 isScaning
_onBluetoothAdapter/_onLocation适配器/定位权限回调空实现(占位,预留权限提示扩展)

失败模式、边界情况与并发

单连接模型的并发防护

  • 多设备连接冲突:onSelectedDevice 在已有连接时,对非当前设备一律 Toast 拦截;底层 BluetoothOTAManager 同样只维护一个目标设备(_bluetoothInstance 单会话),双层防护避免 GATT 会话抢占。
  • 刷新重入:onRefresh 用 _freshing 标志防止快速连续下拉触发多次 sanDevice();_OnFoundDevs 的 750ms 节流也防止高频 setData 造成渲染卡顿。

事件回调生命周期边界

  • 页面销毁后的事件泄漏:onUnload 调用 removeBluetoothEventCallback 移除回调;若遗漏,BluetoothOTAManager 会持续向已销毁页面的方法广播,导致 setData 在无效页面实例上执行(微信框架会告警)。因此注册/移除必须成对出现。
  • connectedDevice 为 null 时的 WXML 安全:(item.deviceId===connectedDevice.deviceId) 在 connectedDevice 为 null 时求值为 false,不会抛错,因此首次进入页面(尚未连接任何设备)列表可正常渲染。

连接失败处理

  • 连接失败与断开都清空 connectedDevice,但两者 UI 反馈不同:失败弹 Toast"连接失败",断开是静默的(由 wx.showModal 确认流程承载反馈)。
  • 底层 BluetoothOTAManager 的 connectDevice 为异步流程,页面通过 wx.showLoading('连接中') 占位,成功/失败回调中 hideLoading 保证提示不被阻塞。

扫描失败边界

  • _onScanFailed 只把 isScaning 置 false,不做额外提示——用户可通过下拉刷新重试;若蓝牙未开启,页面留白由 _onBluetoothAdapter(当前空实现)承接,属于预留的权限引导扩展点。

性能与运维要点

  • 节流策略:扫描回调 750ms 节流 + 排序 + 过滤一次完成,每次 setData 仅更新 scanDevices 一个字段,最小化视图 diff 开销。
  • 刷新固定 1.5s:onRefresh 用 setTimeout 强制复位 triggered,避免刷新图标卡死;代价是若底层扫描在 1.5s 内未结束,列表可能尚未更新完毕,属于"交互确定性优先"的取舍。
  • 本地存储读取时机:filterDevName 在 onLoad 同步读取(wx.getStorageSync),避免异步回调中页面尚未初始化导致竞态。
  • 底层扫描超时:sanDevice() 内部调用 startScan(10000)(10 秒超时),超时后触发 onScanFinish 并联动 _OTAWrapper.onSanDeviceStop() 停止 OTA 的 ADV 监听,保证扫描与 OTA 广播监听互斥。

扩展点

  • 权限引导:_onBluetoothAdapter、_onLocation 目前为空实现,可在此扩展"蓝牙未开启 / 定位权限拒绝"的引导 UI(如 wx.openSetting)。
  • 过滤策略:_filterDevName 是独立方法,可替换为按 VID/PID、信号强度区间等更复杂的过滤规则,无需改动扫描与渲染逻辑。
  • 连接后跳转:_onDevConnectSuccess 是连接成功后的统一出口,可在此加入跳转升级页、读取设备信息等后续动作(当前仅更新 UI)。

Related Links

  • pageConnect.ts(页面逻辑)
  • pageConnect.wxml(视图结构)
  • pageConnect.json(页面配置)
  • pageConnect.less(页面样式)
  • bluetoothOTAManager.ts(全局蓝牙/OTA 管理器)
  • custom-tab-bar/index.js(自定义 TabBar,连接为第 0 个 Tab)
  • 相关主题:OTA 升级流程(lib/bluetoothOTAManager.ts 升级逻辑、lib/jl_lib/jl_ota_2.1.1.js)、底层 BLE 适配(lib/bluetooth.ts)
Prev
应用入口与页面导航
Next
固件升级页(pageUpdate)