微信小程序蓝牙开发全流程:从初始化到数据交互
在物联网快速发展的今天,微信小程序与蓝牙设备的交互已成为智能硬件开发的重要场景。本文将基于官方蓝牙 API,详细讲解小程序连接蓝牙设备的完整流程,涵盖从适配器初始化到数据收发的全链路操作。
·
在物联网快速发展的今天,微信小程序与蓝牙设备的交互已成为智能硬件开发的重要场景。本文将基于官方蓝牙 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 class="page-body">
<view class="btn-area" id="buttonContainer">
<wux-toptips class="wux-light--bg" id="wux-toptips" />
<button type="primary" bindtap='openbluetooth'>打开蓝牙适配器</button>
<button type="primary" bindtap='getbluetoothstatus'>获取蓝牙适配器状态</button>
<button type="primary" bindtap='startsearchbluetooth'>开始搜索附近的蓝牙设备</button>
<button type="primary" bindtap='getbluetooths'>获取搜到的蓝牙设备</button>
<view wx:for="{{getbluetoothlist}}" wx:key="index" class="device-card">
<view class="device-row">
<text class="device-label">设备名:</text>
<text class="device-value">{{item.name || '未知设备'}}</text>
</view>
<view class="device-row">
<text class="device-label">设备ID:</text>
<text class="device-value">{{item.deviceId}}</text>
</view>
<view class="device-row">
<text class="device-label">信号强度:</text>
<text class="device-value">{{item.RSSI}}</text>
</view>
<view class="device-row">
<text class="device-label">支持连接:</text>
<text class="device-value">{{item.connectable ? '是' : '否'}}</text>
</view>
<button class="mini-btn" type="primary" size="mini" bindtap='contentdev' data-devid="{{item.deviceId}}">连接</button>
</view>
<button type="primary" bindtap='getservice'>获取已连接蓝牙的服务</button>
<button type="primary" bindtap='getserviceeigenvalues'>获取蓝牙设备某服务的所有特征值</button>
<view wx:for="{{genvalueslist}}" wx:key="index" class="device-card">
<view class="device-row">
<text class="device-label">特征值:</text>
<text class="device-value">{{item.uuid}}</text>
</view>
<view class="device-row">
<text class="device-label">是否可读:</text>
<text class="device-value">{{item.properties.read?'是':'否'}}</text>
</view>
<view class="device-row">
<text class="device-label">是否可写:</text>
<text class="device-value">{{item.properties.write?'是':'否'}}</text>
</view>
<view class="device-row">
<text class="device-label">是否支持notify操作:</text>
<text class="device-value">{{item.properties.notify?'是':'否'}}</text>
</view>
<view class="device-row">
<text class="device-label">是否支持indicate操作:</text>
<text class="device-value">{{item.properties.indicate?'是':'否'}}</text>
</view>
<view class="device-row">
<text class="device-label">是否支持无回复写操作:</text>
<text class="device-value">{{item.properties.writeNoResponse?'是':'否'}}</text>
</view>
<view class="device-row">
<text class="device-label">是否支持有回复写操作:</text>
<text class="device-value">{{item.properties.writeDefault?'是':'否'}}</text>
</view>
</view>
<button type="primary" bindtap='startmonitornotify'>启用特征值变化时的 notify 功能</button>
<button type="primary" bindtao='monitoreventchanges'>监听低功耗蓝牙设备的特征值变化事件</button>
<button type="primary" bindtap='writecontent'>向低功耗蓝牙设备特征值中写入二进制数据</button>
</view>
</view>
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;
}
更多推荐
所有评论(0)