微信小程序蓝牙开发全流程:从初始化到数据交互_微信小程序蓝牙开发教程
在物联网快速发展的今天,微信小程序与蓝牙设备的交互已成为智能硬件开发的重要场景。本文将基于官方蓝牙 API,详细讲解小程序连接蓝牙设备的完整流程,涵盖从适配器初始化到数据收发的全链路操作。
一、蓝牙开发基础认知
蓝牙连接是小程序与硬件设备通信的重要方式,主要应用于:
- 智能家居设备控制(智能门锁、灯光)
- 穿戴设备数据同步(手环、健康监测设备)
- 物联网传感器数据采集
核心优势:
- 无需云开发即可实现设备直连
- 低功耗设计适合移动设备
- 微信生态内用户触达便捷
二、蓝牙连接全流程实现
1. 初始化蓝牙适配器
首先需要获取设备的蓝牙权限并初始化适配器:
wx.openBluetoothAdapter({ // 打开蓝牙适配器成功回调 success(res) { console.log(res) console.log(\"初始化成功!\") }, // 打开蓝牙适配器失败回调 fail(res) { console.log(res) console.log(\"初始化失败!\") }, // 无论成功失败都会执行的回调 complete(res) { console.log(res) console.log(\"初始化完成!\") },})
关键说明:
- 首次使用时会弹出权限申请弹窗
- 安卓设备需确保系统蓝牙已开启
- 失败时可能需要引导用户检查蓝牙状态
2. 获取蓝牙适配器状态
初始化后需确认适配器当前状态:
wx.getBluetoothAdapterState({ // 成功获取状态回调 success(res) { console.log(res) console.log(\"得到蓝牙适配器状态成功!\") }, // 失败获取状态回调 fail(res) { console.log(res) console.log(\"得到蓝牙适配器状态失败!\") }, // 状态获取完成回调 complete(res) { console.log(res) console.log(\"得到蓝牙适配器状态完成!\") },})
关键字段:
res.available
:蓝牙是否可用res.discovering
:是否正在搜索设备res.platform
:设备平台(iOS/Android)
3. 搜索附近蓝牙设备
搜索设备是连接的前提,需注意资源消耗问题:
javascript
wx.startBluetoothDevicesDiscovery({ // services参数可选,指定UUID可过滤设备 // services: [\'FEE7\'], success(res) { console.log(res, \'搜索成功\') }, fail(res) { console.log(res, \'搜索失败\') }, complete(res) { console.log(res, \'搜索操作完成\') }})
重要注意事项:
- 安卓 6.0 + 设备需开启定位权限才能搜索
- 搜索会消耗较多电量,找到设备后需及时停止
- 8.0.16 以上版本安卓设备无定位权限会直接报错
4. 获取搜索到的设备列表
实时获取搜索到的设备信息:
javascript
// 工具函数:ArrayBuffer转16进制字符串function ab2hex(buffer) { var hexArr = Array.prototype.map.call( new Uint8Array(buffer), function(bit) { return (\'00\' + bit.toString(16)).slice(-2) } ) return hexArr.join(\'\');}// 获取蓝牙设备列表getBluetoothDevices: function() { wx.getBluetoothDevices({ success: function(res) { console.log(res) // 打印所有设备信息 if (res.devices[0]) { console.log(ab2hex(res.devices[0].advertisData)) // 打印第一个设备的广播数据 } } })}
设备数据解析:
name
:设备名称(可能为空)deviceId
:设备唯一标识RSSI
:信号强度(-120~0dBm,值越大信号越好)advertisData
:广播数据(可解析设备信息)
5. 连接指定蓝牙设备
通过 deviceId 建立与目标设备的连接:
javascript
wx.createBLEConnection({ deviceId: deviceId, // 从搜索结果中获取的设备ID success(res) { console.log(res, \'连接成功\') // 连接成功后立即停止搜索以节省资源 wx.stopBluetoothDevicesDiscovery({ success(res) { console.log(res) } }) }, fail(res) { console.log(res, \'连接失败\') }, complete(res) { console.log(res, \'连接操作完成\') }})
连接管理要点:
- 连接成功后必须调用 stopBluetoothDevicesDiscovery
- 连接失败可能原因:设备距离过远、设备未开启可连接模式
- 每个设备同时只能有一个连接
6. 停止设备搜索
当找到目标设备后务必停止搜索:
wx.stopBluetoothDevicesDiscovery({ success(res) { console.log(res) }, fail(res) { console.log(res, \'停止搜索失败\') }, complete(res) { console.log(res, \'停止搜索完成\') }})
7. 获取设备服务列表
连接后需要获取设备支持的服务:
wx.getBLEDeviceServices({ deviceId, // 已连接的设备ID success(res) { console.log(res) // 从res.services中获取serviceId }, fail(res) { console.log(res, \'获取服务失败\') }, complete(res) { console.log(res, \'获取服务完成\') }})
服务数据结构:
uuid
:服务唯一标识isPrimary
:是否为主服务(主服务是设备核心功能)
8. 获取服务特征值
每个服务包含多个特征值,这是数据交互的关键:
wx.getBLEDeviceCharacteristics({ deviceId, // 已连接设备ID serviceId, // 从服务列表中获取的serviceId success(res) { console.log(\'设备特征值列表:\', res.characteristics) if (res.characteristics.properties) { // 从res.characteristics中获取characteristicId return } }})
特征值属性解析:
read
:是否支持读取write
:是否支持写入notify
:是否支持通知(设备主动推送数据)indicate
:是否支持指示(比通知更可靠的推送)
9. 订阅特征值变化通知
开启设备主动推送数据的功能:
wx.notifyBLECharacteristicValueChange({ state: true, // true为启用notify功能 deviceId, serviceId, characteristicId, // 从特征值列表中获取的characteristicId success(res) { console.log(\'订阅成功\', res.errMsg) }})
订阅注意事项:
- 必须确保特征值支持 notify/indicate
- 订阅后设备主动更新数据才会触发回调
- 安卓部分机型订阅后立即写入可能报错(10008 错误)
10. 监听特征值变化事件
接收设备主动推送的数据:
// 工具函数:ArrayBuffer转16进制字符串function ab2hex(buffer) { let hexArr = Array.prototype.map.call( new Uint8Array(buffer), function(bit) { return (\'00\' + bit.toString(16)).slice(-2) } ) return hexArr.join(\'\');}// 注册监听事件wx.onBLECharacteristicValueChange(function(res) { console.log(`特征值 ${res.characteristicId} 已更新,当前值:${res.value}`) console.log(ab2hex(res.value)) // 打印16进制数据})
数据处理要点:
- 数据以 ArrayBuffer 形式传输
- 需根据设备协议解析数据(如温度、湿度等)
- 建议在页面卸载时取消监听(
wx.offBLECharacteristicValueChange
)
11. 向设备写入数据
与设备交互的最后一步,发送控制指令:
senddata: function() { // 准备写入数据(示例:发送0x00) let buffer = new ArrayBuffer(1) let dataView = new DataView(buffer) dataView.setUint8(0, 0) // 设置第一个字节为0 wx.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: buffer, // 必须为ArrayBuffer类型 success(res) { console.log(\'写入成功\', res.errMsg) } })}
写入操作限制:
- 单次写入建议不超过 20 字节
- 并行多次写入可能失败
- iOS 设备写入过长数据可能无回调
- 安卓订阅后立即写入可能报错
三、实战开发最佳实践
-
设备状态管理:
- 维护全局设备连接状态变量
- 页面卸载时断开连接(
onUnload
中调用wx.closeBLEConnection
)
-
错误处理优化:
- 封装蓝牙操作函数并统一处理错误
- 对常见错误(如 10008)提供用户友好提示
-
性能优化:
- 严格控制搜索时间(建议不超过 10 秒)
- 批量处理特征值读写操作
-
跨平台适配:
- 针对 iOS/Android 做差异化处理
- 安卓设备优先检查定位权限
四、常见问题与解决方案
-
安卓设备无法搜索到设备:
- 检查是否开启定位权限(安卓 6.0 + 强制要求)
- 确认蓝牙适配器状态是否可用
-
连接成功但无法通信:
- 检查 serviceId/characteristicId 是否正确
- 确认特征值是否支持对应操作(read/write/notify)
-
数据传输异常:
- 确保数据格式为 ArrayBuffer
- 按设备协议规范解析数据(如大端 / 小端模式)
通过以上步骤,我们可以完成微信小程序与蓝牙设备的全流程交互。实际开发中需结合具体硬件协议调整数据处理逻辑,并做好异常情况的处理,以提供稳定的用户体验。
下面是写的一些简易的代码,有错误的地方希望大家指出
WXML
<view wx:for=\"{{getbluetoothlist}}\" wx:key=\"index\" class=\"device-card\"> 设备名: {{item.name || \'未知设备\'}} 设备ID: {{item.deviceId}} 信号强度: {{item.RSSI}} 支持连接: {{item.connectable ? \'是\' : \'否\'}} <button class=\"mini-btn\" type=\"primary\" size=\"mini\" bindtap=\'contentdev\' data-devid=\"{{item.deviceId}}\">连接 <view wx:for=\"{{genvalueslist}}\" wx:key=\"index\" class=\"device-card\"> 特征值: {{item.uuid}} 是否可读: {{item.properties.read?\'是\':\'否\'}} 是否可写: {{item.properties.write?\'是\':\'否\'}} 是否支持notify操作: {{item.properties.notify?\'是\':\'否\'}} 是否支持indicate操作: {{item.properties.indicate?\'是\':\'否\'}} 是否支持无回复写操作: {{item.properties.writeNoResponse?\'是\':\'否\'}} 是否支持有回复写操作: {{item.properties.writeDefault?\'是\':\'否\'}}
js
Page({ /** * 页面的初始数据 */ data: { getbluetoothlist: [], //获取到的蓝牙设备列表, genvalueslist: [], //设备特征值列表 deviceId: \'\', //蓝牙设备ID serviceId: \'\', //蓝牙设备服务ID genvalueId: \'\', //特征值ID }, // 打开蓝牙适配器 openbluetooth() { const that = this; console.log(1111) wx.showLoading({ title: \'蓝牙初始化...\', }) wx.openBluetoothAdapter({ //打开蓝牙适配器成功 success(res) { wx.hideLoading() wx.showToast({ title: \'初始化成功\', icon: \'success\', duration: 2000 }) }, //打开蓝牙适配器失败 fail(res) { console.log(res) console.log(\"初始化失败!\") wx.hideLoading() wx.showToast({ title: \'初始化失败\', icon: \'error\', duration: 2000 }) const toptips = that.selectComponent(\'#wux-toptips\') toptips && toptips.warn({ icon: \'cancel\', hidden: false, text: \'请检查蓝牙是否开启\', duration: 3000, }) }, }) }, // 获取蓝牙适配器状态 getbluetoothstatus() { wx.showLoading({ title: \'获取蓝牙适配器状态...\', }) wx.getBluetoothAdapterState({ success(res) { console.log(\"获取蓝牙适配器状态成功!\") wx.hideLoading() wx.showToast({ title: \'获取适配器状态成功\', icon: \'success\', duration: 2000 }) }, fail(res) { console.log(\"得到蓝牙适配器状态失败!\") wx.hideLoading() wx.showToast({ title: \'获取状态失败\', icon: \'error\', duration: 2000 }) } }) }, //开始搜索蓝牙 startsearchbluetooth() { wx.showLoading({ title: \'搜索蓝牙中...\', }) wx.startBluetoothDevicesDiscovery({ // services: [\'FEE7\'], services额可以不填,则搜索的是所有的蓝颜设备,如果填写则搜索广播包有对应 UUID 的主服务的蓝牙设备 success(res) { console.log(res, \'成功\') wx.hideLoading() }, fail(res) { console.log(res, \'失败\') } }) }, //获取搜索到的蓝牙 getbluetooths() { const that = this; // ArrayBuffer转16进制字符串示例 function ab2hex(buffer) { var hexArr = Array.prototype.map.call( new Uint8Array(buffer), function (bit) { return (\'00\' + bit.toString(16)).slice(-2) } ) return hexArr.join(\'\'); } wx.getBluetoothDevices({ success: function (res) { console.log(res) //打印蓝牙设备信息日志 that.setData({ getbluetoothlist: [...res.devices] }) if (that.data.getbluetoothlist) { console.log(22222, that.data.getbluetoothlist) } } }) }, //连接蓝牙设备 contentdev(event) { const that = this that.setData({ deviceId: event.currentTarget.dataset.devid }) wx.createBLEConnection({ deviceId: that.data.deviceId, //(deviceId) success(res) { console.log(res, \'连接成功\') wx.showToast({ title: \'连接成功\', icon: \'success\', duration: 2000 }) //连接成功后最好要释放资源,不然会消耗手机资源 wx.stopBluetoothDevicesDiscovery({ success(res) { console.log(res) } }) }, fail(res) { console.log(res, \'失败\') } }) }, //获取蓝牙设备的服务 getservice() { const that = this wx.getBLEDeviceServices({ deviceId: that.data.deviceId, //deviceId 需要已经通过createBLEConnection与对应设备建立连接 success(res) { console.log(res) //res.services.uuid服务id的获取 res.services.forEach((item) => { if (item.isPrimary) { that.setData({ serviceId: item.uuid }) return } }) }, fail(res) { console.log(res, \'失败\') } }) }, //获取蓝牙设备某服务所有特征值 getserviceeigenvalues() { const that = this wx.getBLEDeviceCharacteristics({ deviceId: that.data.deviceId, serviceId: that.data.serviceId, success(res) { console.log(\'device getBLEDeviceCharacteristics:\', res.characteristics) that.setData({ genvalueslist: [...res.characteristics] }) res.characteristics.forEach((item) => { if (item.properties.notify) { that.setData({ genvalueId: item.uuid }) return } }) } }) }, //启用特征值变化的motify功能 startmonitornotify() { const that = this wx.notifyBLECharacteristicValueChange({ state: true, // 启用 notify 功能 deviceId: that.data.deviceId, serviceId: that.data.serviceId, characteristicId: that.data.genvalueId, success(res) { console.log(\'notifyBLECharacteristicValueChange success\', res.errMsg) } }) }, //监听变化事件 monitoreventchanges() { function ab2hex(buffer) { let hexArr = Array.prototype.map.call( new Uint8Array(buffer), function (bit) { return (\'00\' + bit.toString(16)).slice(-2) } ) return hexArr.join(\'\'); } wx.onBLECharacteristicValueChange(function (res) { console.log(`characteristic ${res.characteristicId} has changed, now is ${res.value}`) console.log(ab2hex(res.value)) }) }, //向蓝牙设备写入二进制数据 writecontent() { const that=this // 向蓝牙设备发送一个0x00的16进制数据 let buffer = new ArrayBuffer(1) // let buffer = new ArrayBuffer(写入内容) let dataView = new DataView(buffer) dataView.setUint8(0, 0) // dataView.setUint8(数组下标, 值) wx.writeBLECharacteristicValue({ deviceId:that.data.deviceId, serviceId:that.data.serviceId, characteristicId:that.data.genvalueId, value: buffer, success(res) { console.log(\'writeBLECharacteristicValue success\', res.errMsg) } }) }, })
wxss
/* pages/bluetooth/bluetooth.wxss */button { margin-top: 30rpx; margin-bottom: 30rpx; width: 90% !important;}.button-sp-area { margin: 0 auto; width: 100%;}.mini-btn { margin-right: 10rpx;}.device-card { background: #fff; border-radius: 12rpx; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); margin: 24rpx 16rpx; padding: 20rpx 24rpx; border: 1rpx solid #f0f0f0;}.device-row { display: flex; align-items: center; margin-bottom: 12rpx;}.device-label { color: #888; width: 160rpx; font-size: 28rpx;}.device-value { color: #222; font-size: 28rpx; word-break: break-all;}.device-row:last-child { margin-bottom: 0;}