更新记录
1.5.0(2026-07-22)
下载此版本
- 【重要 / 破坏性变更】读写与订阅的数据载体由
ArrayBuffer 改为十六进制字符串(小写、无分隔符):
read(target) 返回 Promise<string>(原 Promise<ArrayBuffer>)
write(target, value, wtype) 的 value 改为十六进制字符串(原 ArrayBuffer)
- 订阅通知回调值改为十六进制字符串
- 原因:经典 uni-app(非 x)桥接层无法可靠透传
ArrayBuffer,会触发 fastjson 序列化崩溃;改用基础类型字符串可靠透传,业务侧可自行 hex↔字节数组互转
- 【破坏性变更】
observe 由 observe(target, cb): Promise<void> 改为 observe(target, options: BleObserveOptions): void:
BleObserveOptions 含 onValue(持续接收通知)、可选 onSuccess(订阅成功)、可选 onError(订阅失败)
- 修复订阅成功后收不到设备通知的问题:原实现返回 Promise,
@UTSJS.keepAlive 在返回 Promise 的方法上不生效,Promise 结算后回调被回收;改为「void 返回 + options 回调对象 + keepAlive」(与 startScan 一致)后可持续接收通知(App 端需 HBuilderX ≥ 4.27)
- 修复 iOS 云打包 Swift 编译错误(UTS→Swift 类型转换):
hexToData 中 Math.floor(...) as number 被编译为非法的 Int as! NSNumber,改用 Number.from(clean.length) / 2
- 扫描超时
setTimeout(..., to) 中 to 源自可选 timeout 被推断为 NSNumber,而延时参数要求 Int,改为 to.toInt()
- 补充 iOS 蓝牙隐私描述配置:缺失
NSBluetoothAlwaysUsageDescription 时,CBCentralManager 初始化访问蓝牙会直接 SIGABRT 闪退(表现为「点击初始化/扫描即闪退」)。需在宿主工程根目录 Info.plist 或 manifest.json → app-plus.distribute.ios.privacyDescription 中声明,且必须重新云打包/重做自定义调试基座后生效
1.4.0(2026-07-20)
下载此版本
- 新增支付宝小程序(mp-alipay)端实现,底层对接支付宝小程序蓝牙 API(
uni.* 映射为 my.*)
- 五端(Android/iOS/HarmonyOS/微信小程序/支付宝小程序)统一
openAdapter/createScanner/createPeripheral 接口
- 内部处理支付宝差异:特征值以十六进制字符串回传时自动转
ArrayBuffer;服务/特征字段命名(serviceId/characteristicId)兼容;初始化失败弹窗引导(可跳转小程序设置页)
平台兼容性
uni-app(5.07)
| Vue2 |
Vue3 |
Vue3插件版本 |
Chrome |
Safari |
app-vue |
app-vue插件版本 |
app-nvue |
app-nvue插件版本 |
Android |
Android插件版本 |
iOS |
iOS插件版本 |
鸿蒙 |
鸿蒙插件版本 |
| - |
√ |
1.5.0 |
× |
× |
√ |
1.5.0 |
√ |
1.5.0 |
5.0 |
1.5.0 |
12 |
1.5.0 |
√ |
1.5.0 |
| 微信小程序 |
微信小程序插件版本 |
支付宝小程序 |
支付宝小程序插件版本 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| √ |
1.5.0 |
√ |
1.5.0 |
× |
× |
× |
× |
× |
× |
× |
× |
× |
× |
uni-app x(5.21)
| Chrome |
Safari |
Android |
Android插件版本 |
iOS |
iOS插件版本 |
鸿蒙 |
鸿蒙插件版本 |
微信小程序 |
微信小程序插件版本 |
| × |
× |
5.0 |
1.4.0 |
12 |
1.4.0 |
√ |
1.4.0 |
√ |
1.4.0 |
bin-bluetooth
跨平台 BLE 低功耗蓝牙插件(Central 中心设备),API 设计参考 kable。
支持 Android / iOS / HarmonyOS(Next) / 微信小程序 / 支付宝小程序,提供扫描、连接、服务发现、读写、订阅通知等能力。
平台底层
- Android:
android.bluetooth.*(BluetoothLeScanner / BluetoothGatt),minSdkVersion 21
- iOS:
CoreBluetooth(CBCentralManager / CBPeripheral),deploymentTarget 12
- HarmonyOS:
@kit.ConnectivityKit(ble / access)
- 微信小程序:微信小程序蓝牙 API(
uni.openBluetoothAdapter / uni.createBLEConnection / uni.readBLECharacteristicValue 等)
- 支付宝小程序:支付宝小程序蓝牙 API(
uni.openBluetoothAdapter / uni.createBLEConnection / uni.readBLECharacteristicValue 等)
权限配置
Android(宿主 manifest.json → app-android.distribute.permissions)
BLUETOOTH / BLUETOOTH_ADMIN(API < 31)
BLUETOOTH_SCAN / BLUETOOTH_CONNECT(API >= 31)
ACCESS_FINE_LOCATION(API 23-30 扫描必需)
插件在 openAdapter() 中会按系统版本动态申请上述权限。
iOS(宿主 manifest.json → app-ios.distribute.privacyDescription,或根目录 Info.plist)
NSBluetoothAlwaysUsageDescription
NSBluetoothPeripheralUsageDescription
⚠️ 必须配置:iOS 13+ 访问蓝牙时如果 Info.plist 缺少 NSBluetoothAlwaysUsageDescription,CBCentralManager 初始化会直接 SIGABRT 闪退(表现为「点击初始化/扫描即闪退」,不是弹窗)。可在 manifest.json → app-plus.distribute.ios.privacyDescription 配置,或在工程根目录新建 Info.plist 声明(二选一即可,两处均配则以 Info.plist 为准)。修改后必须重新云打包或重做自定义调试基座才能生效
HarmonyOS(宿主工程 module.json5 → requestPermissions)
ohos.permission.ACCESS_BLUETOOTH
微信小程序
- 无需在 manifest 中配置权限;首次调用蓝牙相关能力时,微信会弹窗请求用户授权(
scope.bluetooth)
- 部分机型需开启系统定位后才能扫描到设备
支付宝小程序
- 无需在 manifest 中配置权限;首次调用蓝牙相关能力时,支付宝会弹窗请求用户授权
- 部分安卓机型需开启系统定位后才能扫描到设备
API
import {
openAdapter, closeAdapter, createScanner, createPeripheral, characteristicOf,
IScanner, IPeripheral, Advertisement, BleWriteType, BleConnectionState,
BLE_STATE_DISCONNECTED, BLE_STATE_CONNECTING, BLE_STATE_CONNECTED, BLE_STATE_DISCONNECTING
} from '@/uni_modules/bin-bluetooth'
| 方法 |
说明 |
openAdapter(): Promise<boolean> |
初始化适配器并申请权限,返回是否成功 |
closeAdapter(): void |
关闭适配器,停止扫描并释放全局资源 |
createScanner(): IScanner |
创建扫描器 |
createPeripheral(deviceId): IPeripheral |
根据 deviceId 创建外围设备 |
characteristicOf(serviceUuid, characteristicUuid) |
构造特征引用 |
连接状态常量:BLE_STATE_DISCONNECTED(0) / BLE_STATE_CONNECTING(1) / BLE_STATE_CONNECTED(2) / BLE_STATE_DISCONNECTING(3),用于与 getState()、onStateChange() 的返回值比较。
Scanner
startScan(options: BleScanOptions):services 过滤、namePrefix 过滤、onAdvertisement 回调、onError 回调
timeout?: number:扫描超时时间(毫秒),不传默认 15000(15 秒)自动结束扫描;传 0 或负数表示不自动结束
onEnd?: () => void:扫描因超时自动结束时触发(手动 stopScan() 不会触发)
allowDuplicates?: boolean:是否允许重复上报同一设备。不传默认 false(同一设备一次扫描仅上报一次);true 时同一设备的每个广播包都会回调,可持续获取最新 rssi / 广播数据
onAdvertisement 回调的 Advertisement.advertisData: string | null:厂商自定义数据段(Manufacturer Specific Data)的十六进制字符串(小写、无分隔符)。四端语义统一,均取广播数据中 AD Type 0xFF 的厂商数据段(前 2 字节为小端公司标识,其后为厂商自定义内容),与微信小程序 advertisData 一致;广播未携带厂商数据时为 null。之所以用 hex 字符串而非 ArrayBuffer,是因为 ArrayBuffer 作为对象字段经 UTS↔原生回调传递时会丢失(与 discoverServices 说明同理,仅基础类型可靠透传),业务可自行将 hex 转回字节数组解析
stopScan()
Peripheral(kable 风格)
connect() / disconnect()
getState() / onStateChange(cb):状态 disconnected/connecting/connected/disconnecting
discoverServices(onCharacteristic): Promise<void>:发现服务与特征。每发现一个特征回调一次 onCharacteristic(serviceUuid: string, uuid: string, properties: number),全部完成后 Promise resolve()。采用「回调逐条 + 基础类型参数」而非「Promise 返回对象数组」,是因为 iOS(Swift) 端自定义对象/UTSJSONObject 放进数组经 Promise.resolve 返回时字段值会丢失(数组长度保留但字段全 undefined),而基础类型经回调可靠透传(与扫描的 onAdvertisement 同理)
read(target): Promise<string>:读取特征值,返回十六进制字符串(小写、无分隔符)。之所以用 hex 字符串而非 ArrayBuffer,是因为经典 uni-app(非 x)桥接层无法可靠透传 ArrayBuffer(会触发 fastjson 序列化崩溃),业务可自行将 hex 转回字节数组解析
write(target, value: string, wtype): Promise<void>:写入特征值。value 为十六进制字符串(无分隔符,如 "01ff";奇数长度自动前补 0)。wtype:0=带响应(withResponse),1=无响应(withoutResponse)
observe(target, options: BleObserveOptions): void:订阅通知/指示。options 含:
onValue: (value: string) => void:收到通知时持续回调,值为十六进制字符串
onSuccess?: () => void:订阅(开启通知/写 CCCD)成功回调
onError?: (err) => void:订阅失败回调
- 采用「void 返回 + options 回调对象 + keepAlive」而非 Promise,是因为
@UTSJS.keepAlive 在返回 Promise 的方法上不生效,Promise 结算后持续回调会被回收(App 端需 HBuilderX ≥ 4.27)
stopObserve(target): Promise<void>:取消订阅
readRssi() / requestMtu(mtu)
使用示例
await openAdapter()
const scanner = createScanner()
scanner.startScan({
services: [] as string[],
namePrefix: null,
onAdvertisement: (adv: Advertisement) => {
console.log(adv.deviceId, adv.name, adv.rssi)
if (adv.advertisData != null) {
console.log('广播数据(hex):', adv.advertisData)
}
},
onError: null,
timeout: 15000, // 可选,默认 15000(15 秒);传 0 表示不自动结束
allowDuplicates: false, // 可选,默认 false;true 时同一设备重复上报
onEnd: () => { console.log('扫描已自动结束') } // 可选
})
// 连接
const p = createPeripheral(deviceId)
await p.connect()
// 发现特征(逐条回调返回,累积成数组;每条含 serviceUuid,可自行按 serviceUuid 分组)
type CharItem = { serviceUuid : string, uuid : string, properties : number }
const chars = new Array<CharItem>()
await p.discoverServices((serviceUuid : string, uuid : string, properties : number) => {
chars.push({ serviceUuid, uuid, properties } as CharItem)
})
const target = characteristicOf('0000180d-0000-1000-8000-00805f9b34fb', '00002a37-0000-1000-8000-00805f9b34fb')
// 订阅通知:onValue 持续接收十六进制字符串,onSuccess/onError 返回订阅结果
p.observe(target, {
onValue: (valueHex: string) => { /* 心率通知,hex 字符串 */ },
onSuccess: () => { console.log('订阅成功') },
onError: (e) => { console.log('订阅失败:', e.errMsg) }
})
// 写入:value 为十六进制字符串,0=带响应,1=无响应
await p.write(target, '01ff', 0)
// 读取:返回十六进制字符串
const valueHex = await p.read(target)
错误码
| 错误码 |
说明 |
| 9020001 |
蓝牙未开启或适配器不可用 |
| 9020002 |
蓝牙相关权限被拒绝 |
| 9020003 |
扫描失败 |
| 9020004 |
连接失败或连接超时 |
| 9020005 |
设备已断开连接 |
| 9020006 |
服务或特征未找到 |
| 9020007 |
读取特征值失败 |
| 9020008 |
写入特征值失败 |
| 9020009 |
订阅通知失败 |
| 9020010 |
参数错误 |
开发文档
UTS 语法
UTS API插件
UTS for HarmonyOS