背景
作为一个程序员,基本上公司的需求就是你的技能。最近公司让我做一个与设备进行蓝牙通信的微信小程序。起初我一脸懵,但经过摸索,还是成功打通了 BLE 通信的完整流程。
适合第一次接触 BLE 的小程序开发者,本文将完整讲解连接、读写、监听的基础用法。
一、目标拆解
我们的目标是:
🔧 通过微信小程序连接蓝牙设备,并实现读写数据通信。
实现步骤大致如下:
- 初始化蓝牙模块
- 监听蓝牙模块状态
- 获取本机蓝牙状态
- 开启扫描设备
- 监听发现设备
- 停止扫描
- 连接设备
- 获取服务列表
- 获取特征值并监听通知
- 读写数据
- 断开连接&关闭蓝牙
二、准备工作
环境要求
- 微信开发者工具(建议新版本)
- 安卓或支持 BLE 的 iOS 手机(真机调试必须)
- 一台支持蓝牙 BLE 的设备(或模拟器)
权限配置(app.json
)
json
"permission": {
"scope.userLocation": {
"desc": "用于蓝牙设备搜索"
}
},
"requiredBackgroundModes": ["bluetooth"]
很重要,如果你没有配置,你会发现上线后,你会搜索不到设备或者直接使用不了蓝牙。
蓝牙权限
在真机中需要开启蓝牙和定位,安卓手机必须开启定位权限才能搜索到设备。
1、初始化蓝牙模块
目标:激活微信小程序蓝牙模块。
注意:安卓首次需授权蓝牙权限,IOS如果为开启蓝牙,将直接报错。
javascript
wx.openBluetoothAdapter({
success() {
console.log("蓝牙初始化成功");
// 可继续 startBluetoothDevicesDiscovery
},
fail(err) {
console.error("蓝牙初始化失败", err);
}
});
2、监听蓝牙模块状态
目的:检查蓝牙是否被用户关闭,或临时断开
javascript
wx.onBluetoothAdapterStateChange((res) => {
console.log("蓝牙状态变化:", res);
if (!res.available) {
wx.showToast({ title: '蓝牙不可用', icon: 'none' });
}
});
3、获取本机蓝牙状态
目的:初始化时判断蓝牙是否可用。避免用户未开启蓝牙就触发后续流程。
javascript
wx.getBluetoothAdapterState({
success(res) {
if (!res.available) {
wx.showToast({ title: '请开启蓝牙', icon: 'none' });
}
}
});
4、开始扫描设备
目的:搜索附近蓝牙设备,通常需设置allowDuplicatesKey: false,避免重复。
javascript
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success() {
console.log("开始搜索设备");
}
});
5、监听发现设备
目的:拿到设备列表,根据设备名或广播字段筛选出目标设备。
ini
wx.onBluetoothDeviceFound(function (res) {
const devices = res.devices;
devices.forEach(device => {
console.log('发现设备:', device);
if (device.name.includes('MyDevice')) {
// 记录下来供点击连接
}
});
});
6、停止扫描
目的:找到设备后停止扫描,可以节省资源,防止冲突。
ini
wx.stopBluetoothDevicesDiscovery();
7、连接蓝牙设备
目的:建立BLE连接,注意处理连接失败重试。
javascript
wx.createBLEConnection({
deviceId: deviceId,
success() {
console.log("连接成功");
},
fail(err) {
console.log("连接失败", err);
}
});
8、获取服务列表
目的:获取设备的服务(service),筛选出主服务(一般不是isPrimary:false的跳过)
javascript
wx.getBLEDeviceServices({
deviceId,
success(res) {
const primaryService = res.services.find(s => s.isPrimary);
console.log('主服务', primaryService);
}
});
9、获取特征值并监听通知
目的:获取通知特征值,用于接收设备传回的数据。
ini
wx.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success(res) {
const notifyChar = res.characteristics.find(item => item.properties.notify);
wx.notifyBLECharacteristicValueChange({
deviceId,
serviceId,
characteristicId: notifyChar.uuid,
state: true
});
}
});
10、读写数据
目的:收发数据,完成通信。
javascript
// 写入数据
wx.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId,
value: new Uint8Array([0x01, 0xA0]).buffer
});
// 监听数据返回
wx.onBLECharacteristicValueChange(function (res) {
const buffer = res.value;
const data = new Uint8Array(buffer);
console.log('收到数据:', data);
});
11、断开连接&关闭蓝牙
javascript
wx.closeBLEConnection({
deviceId,
success() {
console.log("🔌 连接断开");
}
});
wx.closeBluetoothAdapter();
三、踩坑小结(新手必看)
问题 | 解决方式 |
---|---|
安卓搜索不到设备 | 一定要打开位置权限 |
ios设备搜索不到服务 | ios设备服务较少,需特定服务 |
发现设备列表 | 设备一定要去重,不然会出现几百条数据 |
设备MTU | IOS一般都是512,但安卓一般的都是23,并且不支持修改,所以你发送的数据包字节数大于20的时候,是需要分包发送的,ios则是509 |
数据格式 | 每个设备的接受的数据格式是不一样的,这个你需要和嵌入式那边去定好协议 |
监听不到设备数据 | 必须先 notifyBLECharacteristicValueChange 成功后设备才上报数据 |
写入数据错误 | 数据类型必须是ArrayBuffer |
四、总结
BLE开发并不复杂,难的是:不知从何下手,以及细节调试一堆。 这篇文章希望给初学者一个完整的思路,如果你也在学习BLE项目,欢迎留言交流。
下一篇:实现蓝牙列表点击连接,扫码连接,以及自动回连。
五 代码实例(可直接用)
js
// 文件结构:
// ├── pages
// │ └── ble-demo
// │ └── index.js / index.json / index.wxml / index.wxss
// └── utils
// └── ble.js
// ========== utils/ble.js ==========
const ble = {
deviceId: null,
serviceId: null,
writeCharId: null,
notifyCharId: null,
/**
* 打开蓝牙适配器
*/
openAdapter() {
return new Promise((resolve, reject) => {
wx.openBluetoothAdapter({
success: resolve,
fail: reject
});
});
},
/**
* 开始搜索蓝牙设备
*/
startDiscovery() {
return new Promise((resolve, reject) => {
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: resolve,
fail: reject
});
});
},
/**
* 监听设备发现事件,并通过回调返回设备信息
* @param {*} callback
*/
onDeviceFound(callback) {
wx.onBluetoothDeviceFound(callback);
},
/**
* 停止搜索设备
*/
stopDiscovery() {
return wx.stopBluetoothDevicesDiscovery();
},
/**
* 创建蓝牙连接
* @param {*} deviceId
*/
createConnection(deviceId) {
this.deviceId = deviceId;
return new Promise((resolve, reject) => {
wx.createBLEConnection({
deviceId,
success: resolve,
fail: reject
});
});
},
/**
* 获取设备主服务
*/
getPrimaryService() {
return new Promise((resolve, reject) => {
wx.getBLEDeviceServices({
deviceId: this.deviceId,
success(res) {
const primary = res.services.find(s => s.isPrimary);
ble.serviceId = primary.uuid;
resolve(primary);
},
fail: reject
});
});
},
/**
* 获取设备特征值
*/
getCharacteristics() {
const that=this;
return new Promise((resolve, reject) => {
wx.getBLEDeviceCharacteristics({
deviceId: this.deviceId,
serviceId: this.serviceId,
success(res) {
for (let char of res.characteristics) {
if (char.properties.write) ble.writeCharId = char.uuid;
if (char.properties.notify) ble.notifyCharId = char.uuid;
if (char.properties.notify || char.properties.indicate) {
that.enableNotify(char.uuid); // 启用特征值变化监听
}
}
resolve(res.characteristics);
},
fail: reject
});
});
},
/**
* 开启特征值通知(监听蓝牙数据)
* @param {*} characteristicId
*/
enableNotify(callback) {
wx.notifyBLECharacteristicValueChange({
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.notifyCharId,
state: true,
success: () => {
},
fail:()=>{
}
});
wx.onBLECharacteristicValueChange(callback)
// wx.onBLECharacteristicValueChange((characteristic) => {
// console.log(characteristic)
// const value = characteristic.value;
// const data = new Uint8Array(value);
// console.log(this.ab2hex(data))
// })
},
// ArrayBuffer转16进度字符串示例
ab2hex(buffer) {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
},
/**
* 向设备写入数据(valueArray 是一个字节数组)
* @param {*} valueArray
*/
write(valueArray) {
let buffer = new Uint8Array(valueArray).buffer;
return new Promise((resolve, reject) => {
wx.writeBLECharacteristicValue({
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.writeCharId,
value: buffer,
success: resolve,
fail: reject
});
});
},
/**
* 获取设备MTU大小
* @param {*} mtu
*/
requestMTU() {
return new Promise((resolve, reject) => {
wx.getBLEMTU({
deviceId: this.deviceId,
success: (res) => {
let mtu = res.mtu;
let maxWriteSize = Math.min(mtu - 3, 509); // **最大单次可写入的数据**
console.log(`✅ 设备 MTU: ${mtu}, 单次最大可写: ${maxWriteSize}`);
resolve({
mtu,
maxWriteSize
})
},
fail: (err) => {
reject(err)
}
});
});
},
/**
* 断开蓝牙连接
*/
closeConnection() {
return new Promise((resolve, reject) => {
wx.closeBLEConnection({
deviceId: this.deviceId,
success: resolve,
fail: reject
});
});
},
/**
* 工具方法:将十六进制字符串转换为字节数组
* @param {*} hexStr
*/
hexStringToBytes(hexStr) {
if (!hexStr || typeof hexStr !== 'string') return [];
return hexStr.match(/.{1,2}/g).map(byte => parseInt(byte, 16));
},
};
module.exports = ble;
// ========== pages/ble-demo/index/index.js ==========
const ble = require('../../../utils/ble');
Page({
/**
* 页面的初始数据
*/
data: {
log: '',
foundDevice: null
},
log(msg) {
this.setData({
log: this.data.log + `\n` + msg
});
},
/**
* 初始化 BLE 并扫描设备
*/
async startBLE() {
try {
this.log('开始初始化蓝牙');
await ble.openAdapter();
this.log('蓝牙模块已打开');
await ble.startDiscovery();
this.log('正在扫描设备...');
this.discoveredIds = new Set();
ble.onDeviceFound(res => {
/**
* 增加筛选条件,不然会出现很多设备
* 可以是名字,也可以是UUID
* 还可以是值搜索有广播的数据
* 添加去重
*/
const devices = res.devices || [res.device];
devices.forEach(device => {
if (!device.name || !device.name.includes('TPS-22C1')) return;
if (this.discoveredIds.has(device.deviceId)) return; // 跳过重复设备
this.discoveredIds.add(device.deviceId); // 添加到已发现集合
this.setData({ foundDevice: device });
ble.stopDiscovery();
this.log(`发现设备: ${device.name} 设备id:(${device.deviceId})`);
});
});
} catch (e) {
this.log('初始化失败: ' + e.errMsg);
}
},
/**
* 与设备建立连接
*/
async connectBLE() {
try {
const {
foundDevice
} = this.data;
if (!foundDevice) return;
this.log('正在连接设备...');
await ble.createConnection(foundDevice.deviceId);
await ble.getPrimaryService();
await ble.getCharacteristics();
// 协商 MTU 提高数据传输效率
const mtuInfo = await ble.requestMTU();
this.log(`MTU: ${mtuInfo.mtu}, 单次最大可写: ${mtuInfo.maxWriteSize}`);
ble.enableNotify(res => {
console.log(res)
const value = res.value;
const data = new Uint8Array(value);
this.log('监听设备返回数据: ' + ble.ab2hex(data));
console.log(ble.ab2hex(data))
});
this.log('设备连接成功');
} catch (e) {
this.log('连接失败: ' + e.errMsg);
}
},
/**
* 发送测试数据(0x01, 0xA0)
*/
sendTestData() {
const hex = '5AA5060000';
const data = ble.hexStringToBytes(hex);
ble.write(data).then(() => {
this.log('指令已发送: 5AA5060000');
}).catch(err => {
this.log('指令发送失败: ' + err.errMsg);
});
},
/**
* 断开设备连接
*/
disconnectBLE() {
ble.closeConnection().then(() => {
this.log('已断开连接');
}).catch(err => {
this.log('断开失败: ' + err.errMsg);
});
},
})
// ========== pages/ble-demo/index/index.wxml ==========
<view class="container">
<button bindtap="startBLE">初始化并扫描</button>
<button bindtap="connectBLE">连接设备</button>
<button bindtap="sendTestData">发送测试数据</button>
<button bindtap="disconnectBLE">断开连接</button>
<view class="log-area">{{log}}</view>
</view>
// ========== pages/ble-demo/index/index.wxss ==========
.container {
padding: 20rpx;
width: 96%;
}
.log-area {
margin-top: 30rpx;
white-space: pre-wrap;
font-size: 28rpx;
background: #333333;
padding: 20rpx;
border-radius: 12rpx;
max-height: 600rpx;
overflow-y: scroll;
color: #ffffff;
}