更新记录
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-android、app-ios、app-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 MTUapp-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() |
支持 | 支持 | 支持 | 都支持 buffer、data、hexStrData、fenbao |
RSSI readRssi() |
支持 | 支持 | 支持 | 三端一致 |
MTU setMtu() |
支持真实请求 | 部分支持 | 支持 | iOS 返回兼容推导值,不是主动协商 ATT MTU |
设备标识 getConnectMac() |
通常是 MAC | 不是 MAC | 设备标识 | iOS 返回 CBPeripheral.identifier.uuidString |
2M PHY setPhy2MMode() |
暂未封装 | 不支持 | 不支持 | 当前插件三端都没有完整可用实现 |
iOS 权限与限制说明
当前插件已经在 app-ios/info.plist 中补充以下蓝牙权限说明:
NSBluetoothAlwaysUsageDescriptionNSBluetoothPeripheralUsageDescription
适用范围:
- 前台扫描 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.BLUETOOTHandroid.permission.BLUETOOTH_ADMINandroid.permission.BLUETOOTH_SCANandroid.permission.BLUETOOTH_CONNECTandroid.permission.ACCESS_FINE_LOCATION
运行时权限策略:
- Android 12 及以上,主要申请
BLUETOOTH_SCAN、BLUETOOTH_CONNECT - Android 11 及以下,扫描 BLE 设备时仍依赖
ACCESS_FINE_LOCATION
当前 Android 侧限制:
openBluetooth(on)在部分系统版本、厂商 ROM 或系统策略下,可能无法由应用直接切换系统蓝牙开关- 某些设备即使权限已通过,也可能因为系统蓝牙策略、附近设备限制或厂商兼容性导致扫描/连接失败
deviceId/getConnectMac()通常是 BLE MAC 地址,但最终仍以系统返回结果为准
调试建议:
- 先确认系统蓝牙已打开,再验证扫描与连接
- 如果 Android 12 及以上扫描不到设备,优先检查
BLUETOOTH_SCAN和BLUETOOTH_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():读取当前服务 UUIDgetNotifyUUID():读取当前通知特征 UUIDgetWriteUUID():读取当前写特征 UUID
数据通信
onNotifyBleData(serviceId, characteristicId, enable, callback):启用或关闭通知onReadData(serviceId, characteristicId, callback):读取特征值sendData(writeData, callback):写入特征值readRssi(callback):读取 RSSIsetMtu(mtu, callback):设置 MTU
快速使用流程
推荐按下面顺序接入:
- 创建
BleLib实例 - 调用
openBtBluetooth()检查权限和蓝牙状态 - 调用
onStartScanBle()或startScanBleDevice()扫描设备 - 选定目标设备后调用
connect() - 连接成功后调用
scanServices() - 通过
getUUIDs()或手动指定服务/特征 UUID - 调用
onNotifyBleData()订阅通知 - 调用
sendData()或onReadData()进行数据通信 - 不再使用时调用
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-android、app-ios、app-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-android、app-ios、app-harmony都可以作为首个验证平台
2. 新建一个 BLE 测试页面
你可以直接参考本页上面的示例代码:
uni-app:新建pages/ble/ble.vueuni-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. 先跑最小按钮流程
第一次接入时,不要一开始就把全部功能堆上去。
建议最小验证顺序:
- 点击“初始化蓝牙”
- 点击“开始扫描”
- 从扫描结果里选一个设备点击“连接”
- 点击“扫描服务”
- 点击“订阅通知”
- 点击“发送测试数据”
如果你是调试设备端协议,也建议每一步都打印日志,不要跳步。
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:为什么初始化蓝牙失败?
先看回调里的 type 和 message。
常见情况:
type=10003:蓝牙权限未授权type=10002:蓝牙未开启type=10000:底层 BLE 操作失败
建议排查顺序:
- 系统蓝牙权限是否已允许
- 系统蓝牙是否已打开
- 是否在当前支持的平台上运行,例如
app-android、app-ios、app-harmony
Q2:为什么点击“开始扫描”后一个设备都没有?
先确认是不是“真的没有扫描结果”,还是“扫描到了但被过滤了”。
建议排查:
- 把
btNameFilter清空 - 不传
filterNames - 把
showEmptyName改成1 - 让目标设备进入可发现或广播状态
- 用另一台手机的 BLE 扫描工具交叉验证目标设备是否真的在发 BLE 广播
Q3:为什么扫不到蓝牙耳机?
因为当前插件扫的是 BLE 广播,不是经典蓝牙设备列表。
很多耳机、音箱、车机:
- 主要走经典蓝牙
- 不会稳定出现在 BLE 扫描结果里
- 即使支持双模,也可能只在特定状态下短暂广播 BLE
所以“扫不到耳机”不一定是插件有问题,更可能是设备本身不属于当前扫描目标。
Q4:为什么扫描时报 BussinessError 401: Invalid parameter?
这通常是底层原生 BLE API 参数不合法。
建议处理方式:
- 先使用 README 里的最小扫描示例,不要自行追加复杂过滤参数
- 不要一开始同时改扫描时间、过滤器、写入逻辑
- 如果你改过插件内部实现,先回到最小参数验证
Q5:为什么连接失败?
常见原因:
- 设备已经停止广播或离得太远
- 设备只支持某种特定连接方式
- 蓝牙权限或系统蓝牙状态异常
- 连接前拿到的设备 ID 已经过期
建议排查:
- 重新扫描,重新选择设备后再连接
- 先只连接一个距离近、已知可用的 BLE 设备
- 检查连接回调中的
type和message
Q6:为什么连接成功后扫服务失败?
如果 connect() 已回调成功,但 scanServices() 失败,常见原因包括:
- 设备刚连上,还没准备好服务发现
- 设备本身服务暴露异常
- 当前连接实际上已经断开
建议处理:
- 在连接成功回调里稍晚一点再调用
scanServices() - 先确认
isConnected()仍然为true - 打印完整回调结果,确认错误文案
Q7:为什么 getUUIDs() 返回空数组?
这不一定是失败,只表示插件没有自动推导出一组“可写 + 可通知”的特征组合。
常见情况:
- 设备只有读特征,没有通知特征
- 写特征和通知特征不在插件自动推导的第一组里
- 设备服务结构比较复杂
建议处理:
- 先看
scanServices()返回的完整服务结构 - 手动找出
write/writeNoResponse为true的特征 - 手动找出
notify/indicate为true的特征 - 使用真实的服务 UUID 和特征 UUID 进行读写和订阅
Q8:为什么订阅通知成功了,但一直收不到数据?
需要区分“订阅成功”和“设备真的有数据推送”。
建议排查:
- 确认订阅的是正确的通知特征 UUID
- 确认设备协议是否要求先写入某条命令,再开始上报数据
- 确认设备不是走
indicate而不是notify - 确认设备端确实已经进入上报状态
Q9:为什么写入失败?
常见原因:
- 用错了
serviceId或characteristicId - 当前写特征其实不支持
write - 数据格式不对,例如十六进制字符串非法
- 单次写入数据过大
建议排查:
- 先用最短的测试数据验证,例如
FF001122 - 确认当前 UUID 来自正确的服务发现结果
- 大包写入时把
fenbao设成true - 必要时切换
writeType
Q10:为什么通知数据和协议预期不一致?
先确认是“插件收到的数据不对”,还是“设备发出来的数据本来就这样”。
建议排查:
- 直接打印
NotifyData.data - 用设备官方 App 或其他 BLE 调试工具交叉验证
- 确认协议文档里的字节序、校验位、帧头帧尾
Q11:为什么读取 RSSI 或设置 MTU 失败?
这些能力依赖当前连接状态和设备支持情况。
建议排查:
- 确认设备当前已连接
- 先完成服务发现后再做增强能力验证
- 某些设备不接受较大的 MTU,请从较小值逐步尝试
Q12:为什么页面切走后再次进入,状态异常?
一般是因为旧连接、旧监听或旧页面状态没有清理。
建议处理:
- 页面卸载时调用
ble.close() - 不要复用已经失效的设备列表或旧 UUID
- 再次进入页面时,重新执行初始化、扫描、连接流程
最小排障顺序
如果你不知道从哪里查起,建议固定按这个顺序排查:
- 看当前平台是不是
app-android、app-ios、app-harmony之一 - 看蓝牙权限是否通过
- 看蓝牙是否开启
- 看扫描是否有结果
- 看连接回调是否成功
- 看服务发现是否成功
- 看
getUUIDs()是否为空 - 看通知订阅是否成功
- 看写入是否成功
- 看设备端是否真的在返回数据
提问时建议提供的信息
如果后续要继续排查,建议至少提供这些信息:
- 当前平台,例如
app-android、app-ios或app-harmony - 使用的是
uni-app还是uni-app x - 页面日志完整输出
- 哪一步失败:初始化、扫描、连接、扫服务、订阅、写入、读 RSSI
- 失败时的
type、message、data - 目标设备类型,例如手环、传感器、开发板、耳机
注意事项
1. 只支持 BLE,不等于支持所有蓝牙设备
当前插件扫描的是低功耗蓝牙广播,很多蓝牙耳机、音箱、车机不会出现在扫描列表里。
2. 空名称设备可能被过滤
如果 showEmptyName = 0,广播名为空的设备会被过滤掉。
3. openBtBluetooth() 更偏向检查,不等于一定主动打开蓝牙
当前三端实现里,它主要用于检查权限和当前蓝牙状态。真正的开关动作使用 openBluetooth(on),但不同平台的系统限制不同:
app-android:部分系统版本或厂商 ROM 可能限制应用直接切换蓝牙开关app-ios:不支持应用主动切换系统蓝牙开关,通常需要用户手动处理app-harmony:按系统能力执行开关请求
4. 大包写入建议分包
很多 BLE 设备默认 MTU 较小,单次写入过大容易失败。大数据建议设置 fenbao: true。
命名兼容说明
插件已经提供新的规范命名,但为了兼容旧代码,下面这些旧拼写仍可继续使用:
fliterNamesscanComplategetSericUUID()getNotityUUID()getwriteUUID()onNotityBleData()NotityDataWNuuidScanRssiBR
新接入代码建议优先使用规范命名。

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 1241
赞赏 6
下载 12209969
赞赏 1918
赞赏
京公网安备:11010802035340号