因为开发遇到了项目需要做应用与蓝牙设备连接并下发指令获取和写入数据的操作,之前没有过这种经验,所以记录一下关于小程序与蓝牙设备通信的方法和步骤。首先先介绍一下相关的蓝牙核心概念,再实现低功耗(BLE)连接和使用的全过程。
一、基础概念了解
Uniapp蓝牙开发主要针对低功耗蓝牙(BLE)(手机 / 平板常用,手环、传感器、蓝牙音箱基本都是 BLE)。先来了解几个概念:
1、BLE
- 特点:耗电极低、连接快、适合小数据传输
- 对比:传统蓝牙(经典蓝牙)耗电高,用于耳机 / 键盘等,UniApp 优先支持 BLE
2、设备(Device)
- 就是你要连接的硬件(如蓝牙手环、温湿度传感器)
- 关键标识:deviceId(设备唯一 ID)
3、服务(Service)
- 理解:设备的 "功能分组"(比如一个蓝牙手环有 "心率服务""电量服务")
- 关键标识:serviceUUID(每个服务的唯一 ID,硬件厂商提供)
4、特征值(Characteristic)
- 理解:服务里的 "数据通道"(比如 "心率服务" 下有 "心率数据特征值")
- 关键标识:characteristicUUID(硬件厂商提供)
- 核心权限:
- Notify:设备主动发数据给手机(如实时心率)
- Write:手机发数据给设备(如控制设备开关)
- Read:手机主动读取设备数据(如查电量)
5、ArrayBuffer(二进制数据)
- 蓝牙传输的原始数据格式(不能直接传字符串 / 数字)
- 开发时需做转换:字符串 ↔ 十六进制 ↔ ArrayBuffer
二、开发前准备
1、环境与设备
- 开发工具:HBuilderX(我一般用其他AI编辑器,然后在HBuilderX打开运行小程序)
- 测试设备:安卓 /iOS 手机(必须真机,模拟器不支持蓝牙)
- 蓝牙硬件:支持 BLE 的设备
2、微信小程序端
- 微信公众平台 → 开发 → 开发管理 → 接口设置 → 开启 蓝牙接口
- 小程序
app.json配置
json
"permission": {
"scope.bluetooth": { "desc": "用于连接蓝牙设备" },
"scope.userLocation": { "desc": "用于扫描附近蓝牙设备" }
}
3、必备信息(向硬件厂商要)
- 设备的 serviceUUID(服务 ID)
- 用于发数据的 writeCharacteristicUUID(写特征值 ID)
- 用于收数据的 notifyCharacteristicUUID(通知特征值 ID)
三、蓝牙连接完整流程
总体的主流程是
- 申请权限
- 初始化蓝牙
- 搜索设备
- 连接设备
- 获取服务
- 启用通知
- 收发数据
- 断开连接
在使用蓝牙设备之前,需要先进行权限校验,看看手机是否已经开启蓝牙和定位
javascript
onShow() {
requestPermission()
}
requestPermission() {
const permissions = ['scope.bluetooth', 'scope.userLocation'];
permissions.forEach(scope => {
uni.authorize({
scope,
success: () => console.log(`${scope} 权限申请成功`),
fail: () => {
uni.showModal({
title: '权限不足',
content: '请在设置中开启蓝牙和定位权限',
showCancel: false
});
}
});
});
}
如果权限已经具备再进行以下流程。
1、初始化蓝牙适配器
打开手机蓝牙模块,检测蓝牙是否开启
javascript
// 建议在页面onLoad或onShow中调用
initBluetooth() {
uni.showLoading({
title: '初始化蓝牙中...'
});
// 调用API初始化蓝牙
uni.openBluetoothAdapter({
success: (res) => {
console.log('蓝牙初始化成功', res); // 初始化成功后,开始搜索设备
this.startSearch();
},
fail: (err) => {
console.error('蓝牙初始化失败', err); // 常见错误:10001=手机蓝牙未开启
if (err.errCode === 10001) {
uni.showModal({
title: '提示',
content: '请先开启手机蓝牙',
showCancel: false });
}
},
complete: () => {
uni.hideLoading();
}
});
}
2、搜索附近蓝牙设备(找到你的硬件)
扫描周围 BLE 设备,获取 deviceId
javascript
// 页面数据
data() {
return {
deviceList: [], // 存储搜索到的设备
isSearching: false // 是否正在搜索
};
}
// 开始搜索
startSearch() {
this.isSearching = true;
this.deviceList = []; // 清空旧设备
// 开启搜索
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false, // 不重复上报同一设备
success: () => {
console.log('开始搜索蓝牙设备...');
// 监听发现新设备
uni.onBluetoothDeviceFound((res) => {
const device = res.devices[0];
// 过滤无效设备(name不为空)
if (device.name && !this.deviceList.find(item => item.deviceId === device.deviceId)) {
this.deviceList.push(device);
console.log('发现设备:', device.name, device.deviceId);
}
});
}
});
// 5秒后自动停止搜索(省电、避免卡顿)
setTimeout(() => {
this.stopSearch();
}, 5000);
}
// 停止搜索
stopSearch() {
if (this.isSearching) {
uni.stopBluetoothDevicesDiscovery({
success: () => {
console.log('停止搜索');
this.isSearching = false;
// 移除设备监听
uni.offBluetoothDeviceFound();
}
});
}
}
3、连接指定设备(选中目标硬件)
通过连接指定设备,获取设备的deviceId建立连接
javascript
// 连接设备(点击设备列表时调用)
connectDevice(device) {
uni.showLoading({ title: '正在连接...' });
this.stopSearch(); // 连接前停止搜索
// 建立BLE连接
uni.createBLEConnection({
deviceId: device.deviceId,
timeout: 10000, // 超时时间10秒
success: (res) => {
console.log('连接成功', res);
this.connectedDeviceId = device.deviceId;
uni.showToast({ title: '连接成功' });
// 连接成功后,获取设备服务
this.getServices();
// 监听连接断开(如设备关机、距离过远)
this.onDisconnect();
},
fail: (err) => {
console.error('连接失败', err);
uni.showToast({ title: '连接失败', icon: 'error' });
},
complete: () => {
uni.hideLoading();
}
});
}
// 监听断开
onDisconnect() {
uni.onBLEConnectionStateChange((res) => {
if (!res.connected) {
uni.showToast({ title: '设备已断开', icon: 'none' });
this.connectedDeviceId = '';
}
});
}
5、获取服务和特征值
拿到设备的 serviceUUID 和 characteristicUUID,是最核心的一步,是蓝牙通信必须要用的信息。这里只是给示例,具体获取服务和特征值需根据真实出厂设备的数据结构获取。
javascript
// 1. 获取设备所有服务
getServices() {
uni.getBLEDeviceServices({
deviceId: this.connectedDeviceId,
success: (res) => {
console.log('设备服务列表', res.services);
// 遍历服务,找到目标serviceUUID(替换为你的)
const targetService = res.services.find(item => item.uuid === 'FFF0');
if (targetService) {
this.serviceUUID = targetService.uuid;
// 2. 获取该服务下的所有特征值
this.getCharacteristics();
} else {
uni.showToast({ title: '未找到目标服务', icon: 'error' });
}
}
});
}
// 获取特征值
getCharacteristics() {
uni.getBLEDeviceCharacteristics({
deviceId: this.connectedDeviceId,
serviceId: this.serviceUUID,
success: (res) => {
console.log('特征值列表', res.characteristics);
// 找到写特征值(FFF1)和通知特征值(FFF2)(替换为你的)
this.writeUUID = res.characteristics.find(item => item.uuid === 'FFF1').uuid;
this.notifyUUID = res.characteristics.find(item => item.uuid === 'FFF2').uuid;
// 启用通知(必须!否则收不到设备数据)
this.enableNotify();
}
});
}
5、启用通知
启用通知让设备主动发数据给手机,这是收设备数据的关键
javascript
enableNotify() {
uni.notifyBLECharacteristicValueChange({
deviceId: this.connectedDeviceId,
serviceId: this.serviceUUID,
characteristicId: this.notifyUUID,
state: true, // 开启通知
success: () => {
console.log('通知启用成功,开始监听数据...');
// 监听设备发来的数据
this.onReceiveData();
},
fail: () => {
uni.showToast({ title: '启用通知失败', icon: 'error' });
}
});
}
// 接收设备数据
onReceiveData() {
uni.onBLECharacteristicValueChange((res) => {
// res.value是ArrayBuffer,转为十六进制字符串
const hex = this.arrayBufferToHex(res.value);
console.log('收到设备数据(十六进制):', hex);
// 后续可解析数据(如转字符串、数字)
});
}
6、发送数据给设备(控制设备)
当手机需要获取设备的信息时(比如设备版本号、型号、电量等),需要下发指令给设备
javascript
// 发送数据(十六进制字符串)
sendData(hexStr) {
if (!this.connectedDeviceId) {
uni.showToast({ title: '请先连接设备', icon: 'none' });
return;
}
// 十六进制转ArrayBuffer
const buffer = this.hexToArrayBuffer(hexStr);
uni.writeBLECharacteristicValue({
deviceId: this.connectedDeviceId,
serviceId: this.serviceUUID,
characteristicId: this.writeUUID,
value: buffer,
success: () => {
console.log('数据发送成功:', hexStr);
},
fail: () => {
console.error('数据发送失败');
}
});
}
数据格式转换方法
javascript
// ArrayBuffer转十六进制
arrayBufferToHex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
bit => ('00' + bit.toString(16)).slice(-2)
);
return hexArr.join('');
},
// 十六进制转ArrayBuffer
hexToArrayBuffer(hexStr) {
const len = hexStr.length;
const buffer = new ArrayBuffer(len / 2);
const dataView = new DataView(buffer);
for (let i = 0; i < len; i += 2) {
dataView.setUint8(i / 2, parseInt(hexStr.substr(i, 2), 16));
}
return buffer;
}
如果在这个过程中搜索不到设备,要确认手机蓝牙/定位是否开启,在连接过程中deviceId是否正确,能连接但收不到数据看一下下发的指令是否正确,数据格式是否正确。 另外,在给设备下发指令时,也要区分指令是小端排序还是大端排序,严格遵循设备的开发使用说明。