更新记录

1.0.1(2026-06-10)

  • 支持iOS、安卓端

1.0.0(2026-06-10)

  • 初版

平台兼容性

uni-app(5.08)

Vue2 Vue2插件版本 Vue3 Vue3插件版本 Chrome Safari app-vue app-vue插件版本 app-nvue app-nvue插件版本 Android Android插件版本 iOS iOS插件版本 鸿蒙 鸿蒙插件版本
1.0.0 1.0.0 - - 1.0.0 1.0.0 5.0 1.0.1 12 1.0.1 19 1.0.0
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(5.08)

Chrome Safari Android Android插件版本 iOS iOS插件版本 鸿蒙 鸿蒙插件版本 微信小程序
- - 5.0 1.0.1 12 1.0.1 19 1.0.0 -

xwq-ble

xwq-ble 是一个面向 uni-app x 的低功耗蓝牙插件,当前支持 app-androidapp-iosapp-harmony 三端。

适合用来做:

  • BLE 设备扫描
  • BLE 设备连接与断开
  • 服务和特征发现
  • 特征值通知订阅
  • 特征值读取与写入
  • RSSI 读取与 MTU 设置

不适合用来做:

  • 经典蓝牙音频设备发现
  • 蓝牙耳机音频链路控制
  • A2DP、HFP 等经典蓝牙协议场景

平台支持

当前实现状态:

平台 状态
app-harmony 已实现
app-android 已实现
app-ios 已实现

三端当前都支持以下能力:

  • 蓝牙状态检查与状态监听
  • BLE 扫描与停止扫描
  • 设备连接、断开与自动重连
  • 服务发现与特征发现
  • 通知订阅、特征读取、特征写入
  • RSSI 读取与 MTU 设置

平台差异说明:

  • app-android:支持运行时权限申请,设备标识通常为 BLE MAC 地址
  • app-ios:设备标识使用 CBPeripheral.identifier.uuidString,不是物理 MAC 地址;setMtu() 返回的是兼容推导值,不是主动协商 ATT MTU
  • app-harmony:支持权限检查、蓝牙状态监听、扫描、连接、读写、RSSI 和 MTU

三端能力对比

能力 app-android app-ios app-harmony 说明
蓝牙状态检查 isEnabled() 支持 支持 支持 三端都可用于判断当前蓝牙是否可用
蓝牙状态监听 onBtOpenStateListener() 支持 支持 支持 三端都能监听状态变化
蓝牙初始化检查 openBtBluetooth() 支持 支持 支持 主要用于权限和蓝牙状态检查
主动切换蓝牙开关 openBluetooth(on) 部分支持 不支持主动切换 支持 Android 受系统限制;iOS 只能检查或引导用户处理
打开系统设置 openBluetoothSettings() 支持 支持 支持 三端都可用于跳转授权或设置页
扫描 onStartScanBle() / startScanBleDevice() 支持 支持 支持 都支持扫描、名称过滤、停止扫描
连接 connect() 支持 支持 支持 都支持连接、断开、自动重连
服务发现 scanServices() 支持 支持 支持 都支持服务和特征发现
自动 UUID 推导 getUUIDs() 支持 支持 支持 都会推导“可写 + 可通知”组合
通知订阅 onNotifyBleData() 支持 支持 支持 iOS/Android/Harmony 都支持 notify 数据回调
特征读取 onReadData() 支持 支持 支持 三端一致
特征写入 sendData() 支持 支持 支持 都支持 bufferdatahexStrDatafenbao
RSSI readRssi() 支持 支持 支持 三端一致
MTU setMtu() 支持真实请求 部分支持 支持 iOS 返回兼容推导值,不是主动协商 ATT MTU
设备标识 getConnectMac() 通常是 MAC 不是 MAC 设备标识 iOS 返回 CBPeripheral.identifier.uuidString
2M PHY setPhy2MMode() 暂未封装 不支持 不支持 当前插件三端都没有完整可用实现

iOS 权限与限制说明

当前插件已经在 app-ios/info.plist 中补充以下蓝牙权限说明:

  • NSBluetoothAlwaysUsageDescription
  • NSBluetoothPeripheralUsageDescription

适用范围:

  • 前台扫描 BLE 设备
  • 前台连接 BLE 设备
  • 前台订阅通知、读写特征值、读取 RSSI

当前 iOS 侧限制:

  • deviceId / getConnectMac() 返回的是 CBPeripheral.identifier.uuidString,不是物理 MAC 地址
  • openBluetooth(on) 不能像 Android 一样主动切换系统蓝牙开关,通常只能检查状态并引导用户手动处理
  • setMtu() 返回的是当前系统可写长度推导出的兼容值,不是应用主动协商出来的 ATT MTU
  • iOS 不提供完整原始广播包,扫描结果里的广播数据会按兼容策略回填,不保证与 Android / Harmony 完全一致

后台能力说明:

  • 当前插件默认面向前台 BLE 链路场景
  • 目前没有默认开启 iOS 蓝牙后台模式配置
  • 如果你的业务需要“退到后台后持续扫描、持续连接或持续接收 BLE 数据”,需要额外补充 iOS 后台蓝牙模式配置,并结合真实业务再做专项验证

Android 权限与限制说明

当前插件已经在 app-android/AndroidManifest.xml 中补充以下蓝牙相关权限声明:

  • android.permission.BLUETOOTH
  • android.permission.BLUETOOTH_ADMIN
  • android.permission.BLUETOOTH_SCAN
  • android.permission.BLUETOOTH_CONNECT
  • android.permission.ACCESS_FINE_LOCATION

运行时权限策略:

  • Android 12 及以上,主要申请 BLUETOOTH_SCANBLUETOOTH_CONNECT
  • Android 11 及以下,扫描 BLE 设备时仍依赖 ACCESS_FINE_LOCATION

当前 Android 侧限制:

  • openBluetooth(on) 在部分系统版本、厂商 ROM 或系统策略下,可能无法由应用直接切换系统蓝牙开关
  • 某些设备即使权限已通过,也可能因为系统蓝牙策略、附近设备限制或厂商兼容性导致扫描/连接失败
  • deviceId / getConnectMac() 通常是 BLE MAC 地址,但最终仍以系统返回结果为准

调试建议:

  • 先确认系统蓝牙已打开,再验证扫描与连接
  • 如果 Android 12 及以上扫描不到设备,优先检查 BLUETOOTH_SCANBLUETOOTH_CONNECT 是否真的授权成功
  • 如果 Android 11 及以下扫描不到设备,优先检查定位权限是否授权

Harmony 权限与限制说明

当前插件会在鸿蒙侧申请以下蓝牙权限:

  • ohos.permission.ACCESS_BLUETOOTH

适用范围:

  • BLE 扫描
  • BLE 连接
  • 服务与特征发现
  • 通知订阅与特征读写
  • RSSI 与 MTU 相关操作

当前 Harmony 侧说明:

  • openBtBluetooth() 主要负责检查权限和当前蓝牙状态
  • openBluetooth(on) 会按系统能力请求打开或关闭蓝牙
  • setPhy2MMode() 当前没有可用的完整实现,暂不建议作为正式业务能力依赖

调试建议:

  • 如果初始化失败,先检查权限是否已授权以及系统蓝牙是否已开启
  • 如果连接后 getUUIDs() 为空,优先打印完整服务发现结果,检查设备是否真的暴露了“可写 + 可通知”特征组合

安装后入口

import { BleLib } from '@/uni_modules/xwq-ble'

创建实例:

const ble = new BleLib()

能力概览

蓝牙状态

  • isEnabled():检查当前蓝牙是否可用
  • openBtBluetooth(callback):检查权限和蓝牙状态
  • openBluetooth(on):请求打开或关闭系统蓝牙
  • onBtOpenStateListener(callback):监听蓝牙状态变化

扫描相关

  • onStartScanBle(scanPara):完整扫描入口
  • startScanBleDevice(scanTime, callback):简化扫描入口
  • stopScanBle():停止扫描
  • onScanDataListener(callback):监听兼容格式的扫描数据

连接相关

  • connect(deviceId, autoConnect, callback):连接设备
  • close():关闭连接并释放资源
  • isConnected():检查是否已连接
  • getConnectMac():获取当前连接设备 ID

服务与特征

  • scanServices(callback):发现服务与特征
  • getUUIDs():获取自动推导出的可写/可通知 UUID 组合
  • setSelectUUID(index):选择当前默认 UUID 组合
  • getServiceUUID():读取当前服务 UUID
  • getNotifyUUID():读取当前通知特征 UUID
  • getWriteUUID():读取当前写特征 UUID

数据通信

  • onNotifyBleData(serviceId, characteristicId, enable, callback):启用或关闭通知
  • onReadData(serviceId, characteristicId, callback):读取特征值
  • sendData(writeData, callback):写入特征值
  • readRssi(callback):读取 RSSI
  • setMtu(mtu, callback):设置 MTU

快速使用流程

推荐按下面顺序接入:

  1. 创建 BleLib 实例
  2. 调用 openBtBluetooth() 检查权限和蓝牙状态
  3. 调用 onStartScanBle()startScanBleDevice() 扫描设备
  4. 选定目标设备后调用 connect()
  5. 连接成功后调用 scanServices()
  6. 通过 getUUIDs() 或手动指定服务/特征 UUID
  7. 调用 onNotifyBleData() 订阅通知
  8. 调用 sendData()onReadData() 进行数据通信
  9. 不再使用时调用 close()

扫描参数

onStartScanBle(scanPara) 常用字段:

字段 说明
btNameFilter 设备名称关键字过滤
filterNames 设备名称白名单过滤,推荐使用
fliterNames 历史拼写,仍兼容
scantime 扫描时长,单位毫秒
showEmptyName 是否允许返回空名称设备
onScanResult 单条扫描结果回调
scanComplete 扫描结束回调,推荐使用
scanComplate 历史拼写,仍兼容

写入参数

sendData(writeData, callback) 支持以下输入方式:

字段 说明
buffer 直接传 ArrayBuffer
data 传数字数组
hexStrData 传十六进制字符串

其他常用字段:

字段 说明
serviceId 服务 UUID
characteristicId 写特征 UUID
writeType 1 表示 WRITE_NO_RESPONSE
fenbao true 时按 20 字节分包发送

统一返回结构

插件异步接口通常通过 MyApiResult 回调:

字段 含义
type 结果类型或业务码
message 结果文案
data 结果附加数据

常见错误码:

错误码 含义
1000 notify 成功
1001 notify 失败或设备已断开
10000 通用 BLE 操作失败
10001 设备已断开
10002 蓝牙未开启
10003 蓝牙权限被拒绝
10004 设备已连接
10005 连接状态或参数无效
10006 服务或特征未找到
10007 写入数据无效

使用示例

1. 扫描设备

const ble = new BleLib()

ble.onStartScanBle({
  scantime: 8000,
  showEmptyName: 0,
  btNameFilter: 'BLE',
  onScanResult: (res) => {
    console.log('scan result', res)
  },
  scanComplete: () => {
    console.log('scan complete')
  }
})

2. 连接设备并发现服务

ble.connect(deviceId, false, (res) => {
  console.log('connect result', res)
  if (res.message == 'ble connected') {
    ble.scanServices((serviceRes) => {
      console.log('services', serviceRes)
    })
  }
})

3. 订阅通知

ble.onNotifyBleData(serviceId, notifyId, true, (res) => {
  console.log('notify result', res)
})

4. 写入数据

ble.sendData({
  serviceId,
  characteristicId: writeId,
  hexStrData: 'A00101',
  fenbao: false
}, (res) => {
  console.log('write result', res)
})

5. 分包写入

ble.sendData({
  serviceId,
  characteristicId: writeId,
  data: [1, 2, 3, 4, 5],
  fenbao: true,
  writeType: 1
}, (res) => {
  console.log('write no response result', res)
})

页面示例代码

下面给出两套页面示例:

  • uni-app 项目示例:传统 Vue 页面写法
  • uni-app x 项目示例:uvue + <script setup> 写法

注意:

  • 两套示例的调用顺序一致
  • 示例代码可以直接用于 app-androidapp-iosapp-harmony
  • 不同平台的设备标识格式、权限弹窗和系统蓝牙开关行为可能存在差异

uni-app 页面示例

适合传统 uni-app 项目页面,例如 pages/ble/ble.vue

<template>
  <view class="page">
    <button @click="initBluetooth">初始化蓝牙</button>
    <button @click="startScan">开始扫描</button>
    <button @click="stopScan">停止扫描</button>
    <button @click="scanServices">扫描服务</button>
    <button @click="subscribeNotify">订阅通知</button>
    <button @click="sendTestData">发送测试数据</button>

    <view v-for="(item, index) in deviceList" :key="item.device.address" class="device-item">
      <text>{{ item.device.name || '未命名设备' }} / {{ item.device.address }}</text>
      <button @click="connectDevice(index)">连接</button>
    </view>

    <view class="log-box">
      <text selectable>{{ logText }}</text>
    </view>
  </view>
</template>

<script>
import { BleLib } from '@/uni_modules/xwq-ble'

export default {
  data() {
    return {
      ble: new BleLib(),
      deviceList: [],
      uuidList: [],
      currentServiceId: '',
      currentNotifyId: '',
      currentWriteId: '',
      logText: ''
    }
  },
  methods: {
    appendLog(message) {
      const line = `${new Date().toLocaleTimeString()} ${message}`
      this.logText = this.logText ? `${this.logText}\n${line}` : line
    },
    initBluetooth() {
      this.ble.openBtBluetooth((res) => {
        this.appendLog(`初始化蓝牙: type=${res.type} message=${res.message}`)
      })
    },
    startScan() {
      this.deviceList = []
      this.ble.onStartScanBle({
        scantime: 8000,
        showEmptyName: 0,
        btNameFilter: '',
        onScanResult: (res) => {
          if (res.type !== 0) {
            this.appendLog(`扫描失败: ${res.message}`)
            return
          }
          const item = res.data
          const index = this.deviceList.findIndex((device) => device.device.address === item.device.address)
          if (index >= 0) {
            this.$set ? this.$set(this.deviceList, index, item) : (this.deviceList[index] = item)
          } else {
            this.deviceList.push(item)
            this.appendLog(`发现设备: ${item.device.name || '未命名设备'}`)
          }
        },
        scanComplete: () => {
          this.appendLog('扫描结束')
        }
      })
    },
    stopScan() {
      this.ble.stopScanBle()
      this.appendLog('手动停止扫描')
    },
    connectDevice(index) {
      const device = this.deviceList[index]
      if (!device) {
        this.appendLog('连接失败: 无效设备索引')
        return
      }
      this.ble.connect(device.device.address, false, (res) => {
        this.appendLog(`连接回调: type=${res.type} message=${res.message}`)
      })
    },
    scanServices() {
      this.ble.scanServices((res) => {
        this.appendLog(`服务扫描: type=${res.type} message=${res.message}`)
        if (res.type !== 0) {
          return
        }
        this.uuidList = this.ble.getUUIDs()
        if (this.uuidList.length > 0) {
          this.ble.setSelectUUID(0)
          this.currentServiceId = this.ble.getServiceUUID()
          this.currentNotifyId = this.ble.getNotifyUUID()
          this.currentWriteId = this.ble.getWriteUUID()
        }
      })
    },
    subscribeNotify() {
      if (!this.currentServiceId || !this.currentNotifyId) {
        this.appendLog('订阅失败: 请先扫描服务')
        return
      }
      this.ble.onNotifyBleData(this.currentServiceId, this.currentNotifyId, true, (res) => {
        this.appendLog(`通知回调: type=${res.type} message=${res.message}`)
        if (res.type === 0 && res.data) {
          this.appendLog(`收到通知数据: ${JSON.stringify(res.data.data)}`)
        }
      })
    },
    sendTestData() {
      if (!this.currentServiceId || !this.currentWriteId) {
        this.appendLog('发送失败: 请先扫描服务')
        return
      }
      this.ble.sendData({
        serviceId: this.currentServiceId,
        characteristicId: this.currentWriteId,
        hexStrData: 'FF001122',
        fenbao: false
      }, (res) => {
        this.appendLog(`发送回调: type=${res.type} message=${res.message}`)
      })
    }
  },
  onUnload() {
    this.ble.close()
  }
}
</script>

<style>
.page {
  padding: 16px;
}

.device-item {
  margin-top: 12px;
}

.log-box {
  margin-top: 16px;
  white-space: pre-wrap;
}
</style>

uni-app x 页面示例

适合 uni-app x 项目页面,例如 pages/ble/ble.uvue

<template>
  <view class="page-root">
    <!-- #ifdef APP -->
    <scroll-view class="page-scroll">
    <!-- #endif -->
      <view class="page-content">
        <button class="action-btn" @click="initBluetooth">初始化蓝牙</button>
        <button class="action-btn" @click="startScan">开始扫描</button>
        <button class="action-btn" @click="stopScan">停止扫描</button>
        <button class="action-btn" @click="scanServices">扫描服务</button>
        <button class="action-btn" @click="subscribeNotify">订阅通知</button>
        <button class="action-btn" @click="sendTestData">发送测试数据</button>

        <view v-for="(item, index) in deviceList" :key="item.device.address" class="device-item">
          <text class="device-text">{{ item.device.name.length > 0 ? item.device.name : '未命名设备' }}</text>
          <text class="device-text">{{ item.device.address }}</text>
          <button class="device-btn" @click="connectDevice(index)">连接</button>
        </view>

        <textarea class="log-box" :value="logText" :disabled="true" />
      </view>
    <!-- #ifdef APP -->
    </scroll-view>
    <!-- #endif -->
  </view>
</template>

<script setup>
import { BleLib } from '@/uni_modules/xwq-ble'
import { BleScanResult, MyApiResult, NotifyData, ScanPara, BleUuidGroup, WriteData } from '@/uni_modules/xwq-ble/utssdk/interface.uts'

const ble = new BleLib()
const deviceList = ref([] as BleScanResult[])
const uuidList = ref([] as BleUuidGroup[])
const currentServiceId = ref('')
const currentNotifyId = ref('')
const currentWriteId = ref('')
const logText = ref('')

function appendLog(message : string) : void {
  const line = `${new Date().toLocaleTimeString()} ${message}`
  logText.value = logText.value.length > 0 ? `${logText.value}\n${line}` : line
}

function initBluetooth() : void {
  ble.openBtBluetooth((res : MyApiResult) => {
    appendLog(`初始化蓝牙: type=${res.type} message=${res.message}`)
  })
}

function startScan() : void {
  deviceList.value = []
  const scanPara : ScanPara = {
    scantime: 8000,
    showEmptyName: 0,
    btNameFilter: '',
    onScanResult: (res : MyApiResult) => {
      if (res.type != 0) {
        appendLog(`扫描失败: ${res.message}`)
        return
      }
      const item = res.data as BleScanResult
      let foundIndex = -1
      let index = 0
      while (index < deviceList.value.length) {
        if (deviceList.value[index].device.address == item.device.address) {
          foundIndex = index
          break
        }
        index += 1
      }
      if (foundIndex >= 0) {
        deviceList.value[foundIndex] = item
      } else {
        deviceList.value.push(item)
        appendLog(`发现设备: ${item.device.name.length > 0 ? item.device.name : '未命名设备'}`)
      }
    },
    scanComplete: () => {
      appendLog('扫描结束')
    }
  }
  ble.onStartScanBle(scanPara)
}

function stopScan() : void {
  ble.stopScanBle()
  appendLog('手动停止扫描')
}

function connectDevice(index : number) : void {
  if (index < 0 || index >= deviceList.value.length) {
    appendLog('连接失败: 无效设备索引')
    return
  }
  const item = deviceList.value[index]
  ble.connect(item.device.address, false, (res : MyApiResult) => {
    appendLog(`连接回调: type=${res.type} message=${res.message}`)
  })
}

function scanServices() : void {
  ble.scanServices((res : MyApiResult) => {
    appendLog(`服务扫描: type=${res.type} message=${res.message}`)
    if (res.type != 0) {
      return
    }
    uuidList.value = ble.getUUIDs()
    if (uuidList.value.length > 0) {
      ble.setSelectUUID(0)
      currentServiceId.value = ble.getServiceUUID()
      currentNotifyId.value = ble.getNotifyUUID()
      currentWriteId.value = ble.getWriteUUID()
    }
  })
}

function subscribeNotify() : void {
  if (currentServiceId.value.length == 0 || currentNotifyId.value.length == 0) {
    appendLog('订阅失败: 请先扫描服务')
    return
  }
  ble.onNotifyBleData(currentServiceId.value, currentNotifyId.value, true, (res : MyApiResult) => {
    appendLog(`通知回调: type=${res.type} message=${res.message}`)
    if (res.type == 0) {
      const data = res.data as NotifyData
      appendLog(`收到通知数据: ${JSON.stringify(data.data)}`)
    }
  })
}

function sendTestData() : void {
  if (currentServiceId.value.length == 0 || currentWriteId.value.length == 0) {
    appendLog('发送失败: 请先扫描服务')
    return
  }
  const writeData : WriteData = {
    serviceId: currentServiceId.value,
    characteristicId: currentWriteId.value,
    hexStrData: 'FF001122',
    fenbao: false
  }
  ble.sendData(writeData, (res : MyApiResult) => {
    appendLog(`发送回调: type=${res.type} message=${res.message}`)
  })
}

onUnload(() => {
  ble.close()
})
</script>

<style>
.page-root {
  flex: 1;
}

.page-scroll {
  flex: 1;
}

.page-content {
  display: flex;
  flex-direction: column;
  padding: 24px;
}

.action-btn {
  margin-top: 12px;
}

.device-item {
  display: flex;
  flex-direction: column;
  margin-top: 16px;
  padding: 16px;
  border-radius: 12px;
  background-color: #f5f6f7;
}

.device-text {
  margin-top: 6px;
}

.device-btn {
  margin-top: 10px;
}

.log-box {
  margin-top: 16px;
  min-height: 320px;
}
</style>

最小可运行接入步骤

如果你希望先尽快把插件跑起来,建议按下面步骤接入。

1. 确认项目类型和目标平台

先确认你的项目属于哪一种:

  • uni-app 项目:使用 .vue 页面
  • uni-app x 项目:使用 .uvue 页面

再确认当前准备运行的平台:

  • 推荐优先验证你手边最容易观察 BLE 行为的目标平台
  • app-androidapp-iosapp-harmony 都可以作为首个验证平台

2. 新建一个 BLE 测试页面

你可以直接参考本页上面的示例代码:

  • uni-app:新建 pages/ble/ble.vue
  • uni-app x:新建 pages/ble/ble.uvue

建议先不要把插件接进复杂业务页面,而是先独立做一个测试页,方便排查权限、设备、UUID 和数据链路问题。

3. 在 pages.json 中注册页面

uni-app / uni-app x 通用示例

{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页"
      }
    },
    {
      "path": "pages/ble/ble",
      "style": {
        "navigationBarTitleText": "BLE 调试"
      }
    }
  ]
}

如果你只是临时验证,也可以把 BLE 页面放到首页,方便直接启动测试。

4. 先跑最小按钮流程

第一次接入时,不要一开始就把全部功能堆上去。

建议最小验证顺序:

  1. 点击“初始化蓝牙”
  2. 点击“开始扫描”
  3. 从扫描结果里选一个设备点击“连接”
  4. 点击“扫描服务”
  5. 点击“订阅通知”
  6. 点击“发送测试数据”

如果你是调试设备端协议,也建议每一步都打印日志,不要跳步。

5. 首次验证时最推荐观察的结果

初始化蓝牙

你应该能看到类似日志:

  • 初始化蓝牙: type=0 message=bluetooth ready

如果不是 type=0,先不要继续扫设备。

开始扫描

你应该能看到类似日志:

  • 发现设备: xxx
  • 扫描结束

如果完全没有设备返回,优先排查:

  • 当前设备是否真的在发 BLE 广播
  • 设备是否离得太远
  • 是否把 showEmptyName 设成了 0 导致空名称设备被过滤

连接设备

你应该能看到类似日志:

  • 连接回调: type=0 message=ble connected

扫描服务

你应该能看到类似日志:

  • 服务扫描: type=0 message=scan services success

如果 getUUIDs() 结果为空,不一定说明连接失败,更可能是:

  • 该设备没有同时暴露“可写 + 可通知”这一组特征
  • 需要你手动从服务结果里指定正确 UUID

6. 接入时最小代码组织建议

建议把页面代码分成下面几块:

  • 一个 BleLib 实例
  • 一组状态变量:设备列表、当前 serviceId、notifyId、writeId、日志
  • 一组按钮方法:初始化、扫描、连接、扫服务、订阅、发送
  • 一个统一日志方法,例如 appendLog()

这样后续切到正式业务页时最容易迁移。

7. 常见报错与处理方式

bluetooth permission denied

说明蓝牙权限没有通过。

处理建议:

  • 先确认系统权限弹窗是否已经允许
  • 如果用户点过拒绝,尝试引导到系统设置页重新授权

bluetooth disabled

说明系统蓝牙当前没有打开。

处理建议:

  • 手动打开系统蓝牙
  • 或调用 openBluetooth(true) 再重试

BussinessError 401: Invalid parameter

这类错误通常来自底层原生 BLE API 参数不合法。

处理建议:

  • 先用当前 README 里的示例参数跑通
  • 不要一开始自行改太多扫描或写入参数
  • 如果你改过扫描过滤逻辑,先恢复成最小参数再验证

ble not connected

说明你在没有连上的状态下直接扫服务、读写或订阅通知。

处理建议:

  • 确认连接回调已经返回成功
  • 不要在 connect() 刚调用后立刻执行后续动作,尽量等回调成功再继续

扫描到了设备,但 getUUIDs() 为空

说明插件没有自动推导出一组可用的“写 + 通知”特征。

处理建议:

  • 先打印完整服务发现结果
  • 手动检查每个 service 下的 characteristics
  • 找出真正可写和可通知的 UUID 组合再手工指定

8. 调试时建议保留的日志

最少建议保留下面这些日志:

  • 初始化蓝牙结果
  • 扫描开始与扫描结束
  • 每个扫描到的设备地址和名称
  • 连接回调结果
  • 服务扫描结果
  • 订阅通知结果
  • 发送结果
  • 收到的通知数据

9. 从测试页迁移到业务页时的建议

当测试页已经跑通后,再迁移到正式业务页。

迁移时建议保留:

  • 独立的 BLE 初始化流程
  • 独立的日志或调试开关
  • close() 清理逻辑

不建议直接删掉所有调试输出后再联调协议,否则排查问题会慢很多。

FAQ / 排障清单

下面这部分适合在接入失败时按顺序快速排查。

Q1:为什么初始化蓝牙失败?

先看回调里的 typemessage

常见情况:

  • type=10003:蓝牙权限未授权
  • type=10002:蓝牙未开启
  • type=10000:底层 BLE 操作失败

建议排查顺序:

  1. 系统蓝牙权限是否已允许
  2. 系统蓝牙是否已打开
  3. 是否在当前支持的平台上运行,例如 app-androidapp-iosapp-harmony

Q2:为什么点击“开始扫描”后一个设备都没有?

先确认是不是“真的没有扫描结果”,还是“扫描到了但被过滤了”。

建议排查:

  1. btNameFilter 清空
  2. 不传 filterNames
  3. showEmptyName 改成 1
  4. 让目标设备进入可发现或广播状态
  5. 用另一台手机的 BLE 扫描工具交叉验证目标设备是否真的在发 BLE 广播

Q3:为什么扫不到蓝牙耳机?

因为当前插件扫的是 BLE 广播,不是经典蓝牙设备列表。

很多耳机、音箱、车机:

  • 主要走经典蓝牙
  • 不会稳定出现在 BLE 扫描结果里
  • 即使支持双模,也可能只在特定状态下短暂广播 BLE

所以“扫不到耳机”不一定是插件有问题,更可能是设备本身不属于当前扫描目标。

Q4:为什么扫描时报 BussinessError 401: Invalid parameter

这通常是底层原生 BLE API 参数不合法。

建议处理方式:

  1. 先使用 README 里的最小扫描示例,不要自行追加复杂过滤参数
  2. 不要一开始同时改扫描时间、过滤器、写入逻辑
  3. 如果你改过插件内部实现,先回到最小参数验证

Q5:为什么连接失败?

常见原因:

  • 设备已经停止广播或离得太远
  • 设备只支持某种特定连接方式
  • 蓝牙权限或系统蓝牙状态异常
  • 连接前拿到的设备 ID 已经过期

建议排查:

  1. 重新扫描,重新选择设备后再连接
  2. 先只连接一个距离近、已知可用的 BLE 设备
  3. 检查连接回调中的 typemessage

Q6:为什么连接成功后扫服务失败?

如果 connect() 已回调成功,但 scanServices() 失败,常见原因包括:

  • 设备刚连上,还没准备好服务发现
  • 设备本身服务暴露异常
  • 当前连接实际上已经断开

建议处理:

  1. 在连接成功回调里稍晚一点再调用 scanServices()
  2. 先确认 isConnected() 仍然为 true
  3. 打印完整回调结果,确认错误文案

Q7:为什么 getUUIDs() 返回空数组?

这不一定是失败,只表示插件没有自动推导出一组“可写 + 可通知”的特征组合。

常见情况:

  • 设备只有读特征,没有通知特征
  • 写特征和通知特征不在插件自动推导的第一组里
  • 设备服务结构比较复杂

建议处理:

  1. 先看 scanServices() 返回的完整服务结构
  2. 手动找出 write / writeNoResponsetrue 的特征
  3. 手动找出 notify / indicatetrue 的特征
  4. 使用真实的服务 UUID 和特征 UUID 进行读写和订阅

Q8:为什么订阅通知成功了,但一直收不到数据?

需要区分“订阅成功”和“设备真的有数据推送”。

建议排查:

  1. 确认订阅的是正确的通知特征 UUID
  2. 确认设备协议是否要求先写入某条命令,再开始上报数据
  3. 确认设备不是走 indicate 而不是 notify
  4. 确认设备端确实已经进入上报状态

Q9:为什么写入失败?

常见原因:

  • 用错了 serviceIdcharacteristicId
  • 当前写特征其实不支持 write
  • 数据格式不对,例如十六进制字符串非法
  • 单次写入数据过大

建议排查:

  1. 先用最短的测试数据验证,例如 FF001122
  2. 确认当前 UUID 来自正确的服务发现结果
  3. 大包写入时把 fenbao 设成 true
  4. 必要时切换 writeType

Q10:为什么通知数据和协议预期不一致?

先确认是“插件收到的数据不对”,还是“设备发出来的数据本来就这样”。

建议排查:

  1. 直接打印 NotifyData.data
  2. 用设备官方 App 或其他 BLE 调试工具交叉验证
  3. 确认协议文档里的字节序、校验位、帧头帧尾

Q11:为什么读取 RSSI 或设置 MTU 失败?

这些能力依赖当前连接状态和设备支持情况。

建议排查:

  1. 确认设备当前已连接
  2. 先完成服务发现后再做增强能力验证
  3. 某些设备不接受较大的 MTU,请从较小值逐步尝试

Q12:为什么页面切走后再次进入,状态异常?

一般是因为旧连接、旧监听或旧页面状态没有清理。

建议处理:

  1. 页面卸载时调用 ble.close()
  2. 不要复用已经失效的设备列表或旧 UUID
  3. 再次进入页面时,重新执行初始化、扫描、连接流程

最小排障顺序

如果你不知道从哪里查起,建议固定按这个顺序排查:

  1. 看当前平台是不是 app-androidapp-iosapp-harmony 之一
  2. 看蓝牙权限是否通过
  3. 看蓝牙是否开启
  4. 看扫描是否有结果
  5. 看连接回调是否成功
  6. 看服务发现是否成功
  7. getUUIDs() 是否为空
  8. 看通知订阅是否成功
  9. 看写入是否成功
  10. 看设备端是否真的在返回数据

提问时建议提供的信息

如果后续要继续排查,建议至少提供这些信息:

  • 当前平台,例如 app-androidapp-iosapp-harmony
  • 使用的是 uni-app 还是 uni-app x
  • 页面日志完整输出
  • 哪一步失败:初始化、扫描、连接、扫服务、订阅、写入、读 RSSI
  • 失败时的 typemessagedata
  • 目标设备类型,例如手环、传感器、开发板、耳机

注意事项

1. 只支持 BLE,不等于支持所有蓝牙设备

当前插件扫描的是低功耗蓝牙广播,很多蓝牙耳机、音箱、车机不会出现在扫描列表里。

2. 空名称设备可能被过滤

如果 showEmptyName = 0,广播名为空的设备会被过滤掉。

3. openBtBluetooth() 更偏向检查,不等于一定主动打开蓝牙

当前三端实现里,它主要用于检查权限和当前蓝牙状态。真正的开关动作使用 openBluetooth(on),但不同平台的系统限制不同:

  • app-android:部分系统版本或厂商 ROM 可能限制应用直接切换蓝牙开关
  • app-ios:不支持应用主动切换系统蓝牙开关,通常需要用户手动处理
  • app-harmony:按系统能力执行开关请求

4. 大包写入建议分包

很多 BLE 设备默认 MTU 较小,单次写入过大容易失败。大数据建议设置 fenbao: true

命名兼容说明

插件已经提供新的规范命名,但为了兼容旧代码,下面这些旧拼写仍可继续使用:

  • fliterNames
  • scanComplate
  • getSericUUID()
  • getNotityUUID()
  • getwriteUUID()
  • onNotityBleData()
  • NotityData
  • WNuuid
  • ScanRssiBR

新接入代码建议优先使用规范命名。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

插件在 Android 平台会声明和申请蓝牙扫描、连接及旧版定位权限;在 iOS 平台会声明蓝牙使用说明;在鸿蒙平台会申请 ohos.permission.ACCESS_BLUETOOTH,用于 BLE 设备扫描、连接、服务发现、通知订阅和数据读写。

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件仅在设备本地执行低功耗蓝牙扫描、连接、通知订阅、读写等操作,不内置远程数据上传逻辑。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。