第一次在小程序里调通蓝牙模块的那个深夜,我盯着手机屏幕上终于跳出来的传感器数据,差点把咖啡打翻在键盘上。这已经是连续第三天的凌晨两点,期间我经历了无数次连接断开、数据乱码、通知不触发的问题。如果你正在经历类似的折磨,这篇文章或许能帮你省下几十个小时的调试时间。
很多开发者拿到蓝牙模块后的第一反应就是直接开始写连接代码,这往往会导致后续一系列问题。就像我当初一样,直到踩了坑才明白:理解蓝牙服务的层级结构比急着写代码更重要。
典型的蓝牙模块通常包含这样的层级关系:
最容易出问题的是特征值的UUID识别。我遇到过三种典型情况:
0000ffe1-0000-1000-8000-00805f9b34fb这样的标准UUID,文档通常会有明确说明FFE1这样的短格式,需要转换为完整格式javascript复制// 短UUID转换示例
function shortToLongUUID(shortUUID) {
return '0000' + shortUUID.toLowerCase() + '-0000-1000-8000-00805f9b34fb';
}
// 使用示例
const serviceUUID = shortToLongUUID('FFE0');
const characteristicUUID = shortToLongUUID('FFE1');
提示:遇到连接成功但获取不到服务的情况,首先检查UUID格式是否正确。我曾因为少了一个横线调试了整整一天。
蓝牙通信中最让人头疼的莫过于数据格式转换。小程序蓝牙API只接受ArrayBuffer格式,但实际业务中我们往往需要处理字符串、JSON、十六进制等多种格式。
案例1:发送简单的控制指令
javascript复制// 错误做法:直接发送字符串
wx.writeBLECharacteristicValue({
value: 'ON' // 这会导致报错
})
// 正确做法:转换为ArrayBuffer
function stringToArrayBuffer(str) {
const buffer = new ArrayBuffer(str.length)
const dataView = new DataView(buffer)
for (let i = 0; i < str.length; i++) {
dataView.setUint8(i, str.charCodeAt(i))
}
return buffer
}
wx.writeBLECharacteristicValue({
value: stringToArrayBuffer('ON')
})
案例2:发送十六进制指令
javascript复制// 发送0xAA 0xBB 0xCC三个字节
function hexToArrayBuffer(hexStr) {
const bytes = hexStr.match(/[0-9a-fA-F]{2}/g)
const buffer = new ArrayBuffer(bytes.length)
const dataView = new DataView(buffer)
bytes.forEach((byte, index) => {
dataView.setUint8(index, parseInt(byte, 16))
})
return buffer
}
wx.writeBLECharacteristicValue({
value: hexToArrayBuffer('AABBCC')
})
接收到的数据也是ArrayBuffer格式,需要根据协议进行解析:
javascript复制wx.onBLECharacteristicValueChange(function(res) {
const value = res.value
// 方式1:转为十六进制字符串
const hex = Array.from(new Uint8Array(value))
.map(b => b.toString(16).padStart(2, '0'))
.join(' ')
// 方式2:转为普通字符串
const str = String.fromCharCode.apply(null, new Uint8Array(value))
console.log('接收数据:', { hex, str })
})
注意:iOS和Android平台对MTU(最大传输单元)的限制可能不同,大块数据需要分片处理。我曾遇到Android能正常收发但iOS失败的情况,最后发现是单次发送数据超限。
Notify机制是小程序蓝牙通信中最强大的特性之一,也是问题高发区。完整的Notify配置流程应该是:
javascript复制// 完整的Notify配置流程
function setupNotify(deviceId, serviceId, characteristicId) {
// 先检查特征属性
wx.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success(res) {
const char = res.characteristics.find(c => c.uuid === characteristicId)
if (!char.properties.notify) {
console.error('该特征不支持Notify')
return
}
// 设置监听
wx.notifyBLECharacteristicValueChange({
deviceId,
serviceId,
characteristicId,
state: true,
success() {
console.log('Notify开启成功')
// iOS特殊处理
if (wx.getSystemInfoSync().platform === 'ios') {
wx.readBLECharacteristicValue({
deviceId,
serviceId,
characteristicId
})
}
}
})
// 注册全局监听
wx.onBLECharacteristicValueChange(function(res) {
console.log('收到Notify数据:', res.value)
})
}
})
}
常见问题排查表:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 完全收不到通知 | 特征不支持Notify | 检查特征属性 |
| iOS收不到但Android正常 | iOS需要先read | 添加read操作 |
| 偶尔收不到数据 | 连接不稳定 | 优化重连机制 |
| 数据不完整 | MTU限制 | 分片发送数据 |
在小程序蓝牙开发中,最让人崩溃的莫过于在Android上运行良好的代码,到iOS上就各种异常。经过多个项目的积累,我总结出这些关键差异点:
Android:
iOS:
javascript复制// 兼容性连接方案
function connectDevice(deviceId) {
return new Promise((resolve, reject) => {
wx.createBLEConnection({
deviceId,
success: resolve,
fail: reject,
// iOS专属配置
...(wx.getSystemInfoSync().platform === 'ios' ? {
timeout: 10000, // 延长超时时间
connectionRetryCount: 3 // 增加重试次数
} : {})
})
})
}
MTU大小:
后台行为:
javascript复制// 分片发送大数据的通用方案
function sendLargeData(deviceId, serviceId, characteristicId, data) {
const chunkSize = wx.getSystemInfoSync().platform === 'android' ? 512 : 20
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize)
wx.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId,
value: chunk,
writeType: 'writeNoResponse' // 提高发送效率
})
}
}
任何蓝牙应用都需要完善的错误处理机制,特别是在移动环境下,连接可能随时中断。我总结了一套"防御性编程"方案:
javascript复制// 状态管理示例
const bluetoothManager = {
state: {
connected: false,
connecting: false,
deviceId: null,
services: []
},
// 带状态检查的连接方法
async connect(deviceId) {
if (this.state.connecting) return
if (this.state.connected && this.state.deviceId === deviceId) return
this.state.connecting = true
try {
await connectDevice(deviceId)
const services = await discoverServices(deviceId)
this.state = {
connected: true,
connecting: false,
deviceId,
services
}
} catch (err) {
this.state.connecting = false
throw err
}
}
}
javascript复制// 自动重连实现
let reconnectAttempts = 0
const MAX_RECONNECT_ATTEMPTS = 3
wx.onBLEConnectionStateChange(function(res) {
if (!res.connected && bluetoothManager.state.connected) {
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
setTimeout(() => {
bluetoothManager.connect(res.deviceId)
reconnectAttempts++
}, 1000)
} else {
console.error('超过最大重连次数')
}
} else {
reconnectAttempts = 0
}
})
建议收集以下关键指标:
javascript复制// 简单的错误收集
const errorStats = {
connectionErrors: 0,
writeErrors: 0,
notifyErrors: 0
}
wx.onError(function(err) {
if (err.errMsg.includes('createBLEConnection')) {
errorStats.connectionErrors++
}
// 其他错误分类...
// 可以上报到监控系统
reportErrorToServer(err, errorStats)
})
在经历了多个蓝牙相关项目后,我发现最稳定的方案往往不是技术最先进的,而是最能适应各种异常情况的。比如在某个工业项目中,我们最终采用"连接-发送-立即断开"的简单模式,反而比维持长连接更可靠。