蓝牙通信在微信小程序中的应用越来越广泛,无论是智能家居控制、健康监测设备还是工业传感器,都离不开稳定可靠的蓝牙连接。作为开发者,理解蓝牙通信的基本原理是第一步。
蓝牙设备通常分为主从两种角色。主设备(比如你的手机)负责发起连接和控制通信过程,而从设备(比如蓝牙耳机或传感器)则被动响应请求。这种主从架构决定了我们小程序开发时的基本流程:先搜索设备,再建立连接,最后进行数据交互。
每个蓝牙设备都有唯一的MAC地址,格式类似XX:XX:XX:XX:XX:XX。这个地址相当于设备的身份证,在搜索和连接时都需要用到。更关键的是UUID(通用唯一标识符),这个128位的编码用于标识服务、特征和属性。举个例子,一个心率监测手环可能提供"心率服务"(Service),这个服务下又有"心率测量"(Characteristic),而该特征可能具备读取(read)和通知(notify)两种属性。
在实际开发中,我们最常操作的三个属性是:
理解这些基础概念后,你会发现微信小程序的蓝牙API设计正是围绕这些核心概念展开的。比如获取服务列表对应的是获取设备提供的所有Service,而获取特征值则是获取某个Service下的Characteristic。
在开始编写蓝牙功能前,我们需要做好充分的准备工作。首先是权限配置,这是很多新手容易忽略的关键步骤。在小程序的app.json文件中,必须添加蓝牙相关权限声明:
json复制{
"permission": {
"scope.bluetooth": {
"desc": "需要您的授权才能使用蓝牙功能"
}
}
}
同时,考虑到不同手机型号和系统版本的兼容性问题,建议在代码中加入蓝牙适配器可用性检查:
javascript复制wx.getSystemInfo({
success(res) {
if (!res.bluetoothEnabled) {
wx.showModal({
title: '提示',
content: '请先开启手机蓝牙功能',
showCancel: false
})
}
}
})
设备选择方面,建议准备至少两种不同品牌的蓝牙设备进行测试。我在实际项目中遇到过这样的情况:某款设备在iOS上连接正常,但在部分Android机型上却频繁断开。后来发现是设备厂商对蓝牙协议栈的实现存在差异。因此,兼容性测试清单应该包括:
开发环境配置也有讲究。微信开发者工具虽然提供了蓝牙调试功能,但真机调试才是王道。因为工具中的蓝牙模拟器无法完全还原真实设备的特性,特别是信号强度和抗干扰能力这些关键指标。
设备搜索是蓝牙通信的第一步,也是整个流程中变数最多的环节。微信小程序提供了wx.startBluetoothDevicesDiscovery方法来启动设备搜索,但实际使用中有很多细节需要注意。
首先,搜索参数设置很有讲究。建议这样配置:
javascript复制wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false, // 是否允许重复上报同一设备
interval: 0, // 上报间隔
success(res) {
console.log('开始搜索', res)
},
fail(err) {
console.error('搜索失败', err)
}
})
allowDuplicatesKey设置为false可以减少重复设备的干扰,而interval设为0表示立即上报发现的设备。在实际测试中,我发现Android设备通常会比iOS设备搜索到更多的周边设备,这与系统底层的蓝牙栈实现有关。
设备过滤是提高用户体验的关键。通常我们会根据设备名称或服务UUID来筛选目标设备:
javascript复制wx.onBluetoothDeviceFound((res) => {
const devices = res.devices.filter(device =>
device.name && device.name.includes('目标设备前缀')
)
this.setData({ deviceList: devices })
})
这里有个重要细节:某些低功耗蓝牙设备可能不会广播设备名称,这时就需要通过serviceUUIDs参数来指定目标服务的UUID进行过滤。
搜索超时处理也很重要。建议设置5-10秒的搜索时限,避免无限制搜索消耗用户电量:
javascript复制setTimeout(() => {
wx.stopBluetoothDevicesDiscovery()
console.log('停止搜索')
}, 10000) // 10秒后停止搜索
在实际项目中,我遇到过设备明明在身边却搜索不到的情况。后来发现是因为设备已经连接了其他手机,处于非可发现模式。这时就需要提示用户检查设备状态。
成功搜索到目标设备后,接下来就是建立连接。微信小程序的wx.createBLEConnection方法看似简单,但连接稳定性却是整个蓝牙通信中最具挑战性的部分。
一个健壮的连接流程应该包含以下步骤:
javascript复制wx.createBLEConnection({
deviceId: deviceId,
timeout: 5000, // 5秒超时
success: (res) => {
console.log('连接成功', res)
this.getServices(deviceId)
},
fail: (err) => {
console.error('连接失败', err)
this.retryConnect(deviceId, 3) // 最多重试3次
}
})
连接超时设置非常必要,我建议5秒左右。太短可能导致连接失败,太长则影响用户体验。重试机制也很关键,但要注意不要无限重试,通常3次是比较合理的设置。
连接状态监听是很多开发者容易忽略的部分。蓝牙连接可能会因为各种原因断开(比如设备超出范围),所以必须监听连接状态变化:
javascript复制wx.onBLEConnectionStateChange((res) => {
console.log(`设备 ${res.deviceId} 连接状态变化:`, res.connected)
if (!res.connected) {
this.showReconnectUI() // 显示重新连接UI
}
})
在实际开发中,我发现Android和iOS在连接断开时的表现差异很大。Android通常会立即触发断开事件,而iOS可能会有几秒延迟。因此UI设计上要考虑到这种差异。
MTU(最大传输单元)协商是提升数据传输效率的关键。建立连接后,应该立即协商MTU:
javascript复制wx.setBLEMTU({
deviceId: deviceId,
mtu: 512, // 建议值
success: (res) => {
console.log('MTU设置成功:', res.mtu)
}
})
512字节是比较理想的MTU大小,但实际可用值取决于设备支持情况。在我的测试中,iOS设备通常能支持更大的MTU,而Android设备则因厂商而异。
成功连接设备后,下一步是发现设备提供的服务和特征值。这是蓝牙通信中最技术性的部分,也是实现具体功能的基础。
获取服务列表的API调用相对简单:
javascript复制wx.getBLEDeviceServices({
deviceId: deviceId,
success: (res) => {
console.log('服务列表:', res.services)
this.discoverCharacteristics(deviceId, res.services[0].uuid)
}
})
这里有个重要细节:服务列表的顺序可能与预期不同。有些设备会把标准服务(如电池服务)放在前面,而自定义服务放在后面。因此不能假设服务列表的顺序固定不变。
获取特征值更为关键,因为实际的数据交互都是通过特征值完成的:
javascript复制wx.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: serviceId,
success: (res) => {
const char = res.characteristics.find(c =>
c.properties.notify || c.properties.read || c.properties.write
)
if (char) {
this.setupCharacteristic(deviceId, serviceId, char.uuid)
}
}
})
特征值的properties字段非常重要,它决定了我们能对这个特征值进行什么操作。常见的有:
在我的一个智能灯项目中,就曾因为混淆了write和writeWithoutResponse属性导致控制指令无法正常工作。后来通过仔细检查特征值属性才解决了问题。
数据交互是蓝牙通信的核心功能。微信小程序提供了多种数据交互方式,需要根据具体场景选择合适的方法。
数据读取是最基本的操作:
javascript复制wx.readBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
success: (res) => {
console.log('读取成功')
}
})
wx.onBLECharacteristicValueChange((res) => {
const value = ab2hex(res.value) // ArrayBuffer转16进制
console.log('收到数据:', value)
})
这里的ab2hex是一个工具函数,用于将ArrayBuffer转换为可读的16进制字符串:
javascript复制function ab2hex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(x => x.toString(16).padStart(2, '0'))
.join('')
}
数据写入有两种方式,根据特征值属性决定:
javascript复制// 需要响应的写入
wx.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
value: this.stringToArrayBuffer('Hello'),
success: (res) => {
console.log('写入成功')
}
})
// 不需要响应的写入
wx.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
value: this.stringToArrayBuffer('Hello'),
writeType: 'writeWithoutResponse',
success: (res) => {
console.log('写入成功')
}
})
在实际项目中,我发现writeWithoutResponse的写入速度更快,但不保证数据一定送达。因此关键指令应该使用普通的write方法。
启用通知(notify)是接收设备主动推送数据的最佳方式:
javascript复制wx.notifyBLECharacteristicValueChange({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
state: true, // 启用通知
success: (res) => {
console.log('通知启用成功')
}
})
启用通知后,设备会在数据变化时主动推送,而不需要小程序轮询查询。这大大提高了效率并降低了功耗。
蓝牙开发过程中难免会遇到各种问题,掌握有效的调试方法可以事半功倍。
连接失败是最常见的问题之一。可能的原因包括:
在我的经验中,使用微信开发者工具的"蓝牙调试"功能可以快速定位问题。它能显示详细的蓝牙日志,包括底层协议交互。
数据传输不稳定也是常见痛点。建议采取以下措施:
一个实用的调试技巧是记录完整的蓝牙交互日志:
javascript复制const bleLog = []
function logBle(action, params, result) {
bleLog.push({
timestamp: Date.now(),
action,
params,
result
})
}
// 在所有蓝牙API调用中使用
wx.writeBLECharacteristicValue({
...params,
success: (res) => {
logBle('write', params, res)
},
fail: (err) => {
logBle('write', params, err)
}
})
这样当出现问题时,可以完整复盘蓝牙交互过程,快速定位问题环节。
跨平台兼容性处理也很关键。iOS和Android在蓝牙实现上有不少差异:
建议在代码中做好平台判断:
javascript复制const isiOS = wx.getSystemInfoSync().system.includes('iOS')
function connectDevice(deviceId) {
if (isiOS) {
// iOS特有连接逻辑
} else {
// Android连接逻辑
}
}
良好的用户体验是蓝牙应用成功的关键。以下是一些经过验证的优化建议。
连接流程优化:
数据传输优化:
一个实用的自动重连实现:
javascript复制let reconnectAttempts = 0
const MAX_RECONNECT = 3
function onDisconnected(deviceId) {
if (reconnectAttempts < MAX_RECONNECT) {
reconnectAttempts++
setTimeout(() => {
wx.createBLEConnection({
deviceId,
success: () => {
reconnectAttempts = 0
}
})
}, 1000) // 1秒后重试
}
}
功耗优化也很重要,特别是对需要长时间连接的应用:
内存管理方面,需要注意:
在我的一个健康监测项目中,通过优化连接参数和传输策略,将设备续航时间从8小时提升到了24小时以上。这充分说明了优化的重要性。
蓝牙通信虽然方便,但也带来了一些安全隐患,开发者必须重视。
首先是小程序本身的权限管理。除了基本的蓝牙权限外,在Android上还需要注意:
建议的权限检查流程:
javascript复制function checkPermissions() {
return new Promise((resolve, reject) => {
wx.getSetting({
success(res) {
if (!res.authSetting['scope.bluetooth']) {
wx.authorize({
scope: 'scope.bluetooth',
success: resolve,
fail: reject
})
} else {
resolve()
}
}
})
})
}
数据传输安全方面,建议:
一个简单的数据加密示例:
javascript复制function encryptData(data, key) {
// 简单的XOR加密,实际项目中应使用更安全的算法
const buffer = new ArrayBuffer(data.length)
const view = new Uint8Array(buffer)
for (let i = 0; i < data.length; i++) {
view[i] = data.charCodeAt(i) ^ key.charCodeAt(i % key.length)
}
return buffer
}
设备绑定机制也很重要。可以通过保存设备特征信息来实现:
javascript复制// 保存设备信息
wx.setStorageSync('knownDevice', {
deviceId,
serviceId,
characteristicId
})
// 读取设备信息
const device = wx.getStorageSync('knownDevice')
if (device) {
// 直接连接已知设备
}
在实际项目中,我曾遇到过蓝牙信号被窃听的安全问题。后来通过实现简单的挑战-响应机制解决了这个问题。这提醒我们,即使是小程序蓝牙应用,也需要考虑基本的安全防护。