给蓝牙发送数据
- 初始化蓝牙、检查蓝牙是否可用
- 搜索蓝牙
- 获取到搜索到的所有蓝牙信息
- 连接指定蓝牙
- 断开蓝牙
- 蓝牙设备特征值变化时接收 notify(通知)
- 给蓝牙发送数据(二进制数据)
let bluetoothOpen = false; // 手机蓝牙是否打开
let isSearch = false; // 是否正在搜索
let bluetoothArr = []; // 扫描到的蓝牙
let deviceId = null; // 设备id
let serviceId = null; // 服务id
let writeId = null; // 写入服务的特征值id
let readId = null; // 读取服务的特征值id
let indicateId = null; // 指示服务的特征值id
let notifyId = null; // 通知服务的特征值id
// 初始化蓝牙模块 并 获取本机蓝牙适配器状态(是否开启蓝牙)
const getBluetoothState = () => {
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: () => {
console.log('蓝牙初始化成功');
// 获取本机蓝牙适配器状态
uni.getBluetoothAdapterState({
success: function (res) {
console.log('蓝牙是否正在搜索设备:', res.discovering);
console.log('蓝牙适配器是否可用:', res.available);
if (res.available) {
bluetoothOpen = true;
resolve();
} else {
uni.showToast({
title: '请先打开蓝牙',
icon: 'none',
});
bluetoothOpen = false;
reject(new Error('蓝牙适配器不可用'));
}
},
fail: function (err) {
// 请开启蓝牙
uni.showToast({
title: '请先打开蓝牙',
icon: 'none',
});
bluetoothOpen = false;
reject(err);
},
});
},
fail: () => {
uni.showToast({
title: '请先打开蓝牙',
icon: 'none',
});
bluetoothOpen = false;
reject();
},
});
});
};
// 开始搜索蓝牙设备
const startDiscoveryBluetooth = () => {
return new Promise((resolve, reject) => {
if (!bluetoothOpen) {
uni.showToast({
title: '请先打开蓝牙',
icon: 'none',
});
return reject(new Error('蓝牙未打开'));
}
if (isSearch) {
uni.showToast({
title: '已经正在搜索蓝牙设备了',
icon: 'none',
});
return reject(new Error('已在搜索中'));
}
uni.startBluetoothDevicesDiscovery({
success(res) {
console.log('开始搜寻附近的蓝牙外围设备成功', res);
isSearch = true;
uni.showToast({
title: '搜索蓝牙设备完成',
icon: 'none',
});
resolve(res);
},
fail(err) {
console.log('开始搜寻附近的蓝牙外围设备失败', err);
isSearch = false;
reject(err);
},
});
});
};
// 停止搜索蓝牙设备
const stopDiscoveryBluetooth = () => {
return new Promise((resolve, reject) => {
if (!bluetoothOpen) {
uni.showToast({
title: '请先打开蓝牙',
icon: 'none',
});
return reject(new Error('蓝牙未打开'));
}
if (!isSearch) {
uni.showToast({
title: '当前未搜索蓝牙设备',
icon: 'none',
});
return reject(new Error('未在搜索中'));
}
uni.stopBluetoothDevicesDiscovery({
success(res) {
console.log('停止搜寻附近的蓝牙外围设备成功', res);
isSearch = false;
resolve(res);
},
fail(err) {
console.log('停止搜寻附近的蓝牙外围设备失败', err);
reject(err);
},
});
});
};
// 获取搜索到的蓝牙设备信息
const getBluetoothDevicesInfo = () => {
return new Promise((resolve, reject) => {
if (!bluetoothOpen) {
uni.showToast({
title: '请先打开蓝牙',
icon: 'none',
});
return reject(new Error('蓝牙未打开'));
}
uni.getBluetoothDevices({
success(res) {
console.log('获取搜索到的蓝牙设备信息', res.devices);
// 过滤掉name为空或者未知设备的设备
let devices = res.devices.filter(function (obj) {
return obj.name !== '' && obj.name !== '未知设备';
});
console.log('有名称的蓝牙列表', devices);
bluetoothArr = devices;
resolve(bluetoothArr);
},
fail(err) {
console.log('获取搜索到的蓝牙设备信息失败');
reject(err);
},
});
});
};
/* 连接蓝牙
* @param {string} deviceName 设备名称
* @returns {Promise} 返回promise
*/
const connectBluetooth = (deviceName) => {
return new Promise((resolve, reject) => {
if (!bluetoothOpen) {
uni.showToast({
title: '请先打开蓝牙',
icon: 'none',
});
return reject(new Error('蓝牙未打开'));
}
if (deviceId) {
uni.showToast({
title: '请先断开已连接的蓝牙',
icon: 'none',
});
return reject(new Error('已有设备连接'));
}
let isHaveDevice = false; // 是否存在该设备
let lookupDeviceId = null; // 查找到的设备id
for (let index = 0; index < bluetoothArr.length; index++) {
const e = bluetoothArr[index];
if (e.name === deviceName) {
lookupDeviceId = e.deviceId;
isHaveDevice = true;
break;
}
}
if (!isHaveDevice) {
uni.showToast({
title: '未找到该设备',
icon: 'none',
});
return reject(new Error('未找到该设备'));
}
if (lookupDeviceId == deviceId) {
uni.showToast({
title: '已连接该设备',
icon: 'none',
});
return reject(new Error('已连接该设备'));
}
deviceId = lookupDeviceId;
uni.createBLEConnection({
deviceId: deviceId, // 设备id
success() {
console.log('连接蓝牙设备成功', {
蓝牙设备名称: deviceName,
蓝牙设备id: deviceId,
});
if (isSearch) {
stopDiscoveryBluetooth();
}
resolve();
// 获取服务id
getServiceId().catch((err) => {
console.error('获取服务ID失败', err);
});
},
fail(err) {
console.log('蓝牙连接失败');
reject(err);
},
});
});
};
// 断开蓝牙
const disconnectBluetooth = () => {
return new Promise((resolve, reject) => {
if (!bluetoothOpen) {
uni.showToast({
title: '请先打开蓝牙',
icon: 'none',
});
return reject(new Error('蓝牙未打开'));
}
if (!deviceId) {
uni.showToast({
title: '请先连接蓝牙',
icon: 'none',
});
return reject(new Error('未连接蓝牙'));
}
uni.closeBLEConnection({
deviceId: deviceId,
success(res) {
console.log('蓝牙断开成功', res);
deviceId = null;
resolve(res);
},
fail(err) {
console.log('蓝牙断开失败', err);
reject(err);
},
});
});
};
// 获取服务id
const getServiceId = () => {
return new Promise((resolve, reject) => {
uni.getBLEDeviceServices({
deviceId: deviceId,
success(res) {
console.log('获取服务Id成功', res);
/*
res.services是一个数组,数组中的每一项代表一个蓝牙服务
找到你需要的蓝牙服务,将其uuid赋值给serviceId
*/
let found = false;
for (let index = 0; index < res.services.length; index++) {
const service = res.services[index];
if (service.uuid == '改成对接文档上提供的服务id') {
serviceId = service.uuid;
getCharacteristics().then(resolve).catch(reject);
found = true;
break;
}
}
if (!found) {
reject(new Error('未找到指定服务ID'));
}
},
fail(err) {
console.log('获取服务Id失败', err);
reject(err);
},
});
});
};
// 获取蓝牙设备某个服务中所有特征值
const getCharacteristics = () => {
return new Promise((resolve, reject) => {
uni.getBLEDeviceCharacteristics({
deviceId: deviceId, // 蓝牙设备id
serviceId: serviceId, // 蓝牙服务UUID
success(res) {
console.log('服务中所有特征值', res);
/*
特征值的 properties 中包含了 read、write、notify、indicate 四个属性
read: 表示特征值可以读取
write: 表示特征值可以写入
notify: 表示特征值可以通知
indicate: 表示特征值可以指示
*/
res.characteristics.forEach((item) => {
// 通知
if (item.properties.notify === true) {
notifyId = item.uuid;
}
// 指示
if (item.properties.indicate === true) {
indicateId = item.uuid;
}
// 读取
if (item.properties.read === true) {
readId = item.uuid;
}
// 写入
if (item.properties.write === true) {
writeId = item.uuid;
}
});
if (notifyId) {
startNotice().then(resolve).catch(reject);
} else {
resolve();
}
},
fail(err) {
console.log('获取服务中所有特征值失败', err);
reject(err);
},
});
});
};
// 启用低功耗蓝牙设备特征值变化时的notify功能(通知)
const startNotice = () => {
return new Promise((resolve, reject) => {
uni.notifyBLECharacteristicValueChange({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: notifyId,
state: true,
success() {
// 监听低功耗蓝牙设备的特征值变化
uni.onBLECharacteristicValueChange((result) => {
console.log('监听低功耗蓝牙设备的特征值变化', result);
// result.value 是 ArrayBuffer 类型
if (result.value) {
// 1. 转为 16 进制字符串
const hexArr = Array.prototype.map.call(new Uint8Array(result.value), (x) =>
('00' + x.toString(16)).slice(-2)
);
const hexStr = hexArr.join(' ');
console.log('收到蓝牙数据(16进制):', hexStr);
// 2. 尝试转为 UTF-8 字符串
let str = '';
try {
str = String.fromCharCode.apply(null, new Uint8Array(result.value));
console.log('收到蓝牙数据(UTF-8 字符串):', str);
} catch (e) {
console.log('收到蓝牙数据无法转为UTF-8 字符串');
}
// 3. 如果你有设备协议,可以在这里进一步解析成更友好的信息
}
});
resolve();
},
fail(err) {
console.log('启用低功耗蓝牙设备特征值变化时的notify功能失败', err);
reject(err);
},
});
});
};
/**
* 向低功耗蓝牙设备特征值中写入二进制数据(分片+重试)
* @param {ArrayBuffer} ArrayBuffer 二进制数据
* @param {number} [maxRetries=3] 最大重试次数
* @returns {Promise}
*/
function writeBLEValueLoop(ArrayBuffer, maxRetries = 3) {
return new Promise(async (resolve, reject) => {
if (!bluetoothOpen) {
uni.showToast({ title: '请先打开蓝牙', icon: 'none' });
return reject(new Error('蓝牙未打开'));
}
if (!deviceId) {
uni.showToast({ title: '请先连接设备', icon: 'none' });
return reject(new Error('未连接设备'));
}
let bufferList;
try {
bufferList = getSliceBufferList(ArrayBuffer);
} catch (e) {
return reject(e);
}
for (let i = 0; i < bufferList.length; i++) {
const buffer = bufferList[i];
let retries = 0;
while (retries <= maxRetries) {
try {
await writeData(buffer);
console.log(`分片${i + 1}/${bufferList.length}发送成功`);
break;
} catch (error) {
retries++;
console.error(`分片${i + 1}发送失败, 重试${retries}/${maxRetries}`, error);
if (retries > maxRetries) {
return reject(new Error(`分片${i + 1}达到最大重试次数, 发送失败`));
}
await new Promise((res) => setTimeout(res, 500 * retries));
}
}
}
console.log('全部数据发送完成');
resolve();
});
}
/**
* 获取分片数组
* @param {ArrayBuffer} buffer - 要分片的二进制数据缓冲区
* @param {number} [maxChunk=20] - 每包控制大小,默认为 20 字节
* @returns {Array<ArrayBuffer>} - 分片后的缓冲区数组
* @throws {Error} - 当输入缓冲区无效时抛出异常
*/
function getSliceBufferList(buffer, maxChunk = 20) {
if (!(buffer instanceof ArrayBuffer)) {
throw new Error('Invalid input: expected ArrayBuffer');
}
if (typeof maxChunk !== 'number' || maxChunk < 1 || maxChunk > 512) {
throw new Error('maxChunk 必须在 1~512 之间');
}
if (buffer.byteLength === 0) return [];
const result = [];
let start = 0;
while (start < buffer.byteLength) {
const end = Math.min(start + maxChunk, buffer.byteLength);
result.push(buffer.slice(start, end));
start = end;
}
return result;
}
/**
* 写入单个分片
* @param {ArrayBuffer} buffer
* @returns {Promise}
*/
const writeData = (buffer) => {
return new Promise((resolve, reject) => {
if (!deviceId || !serviceId || !writeId) {
return reject(new Error('蓝牙参数未初始化'));
}
uni.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: writeId,
value: buffer,
success(res) {
console.log('写入成功', res);
resolve();
},
fail(err) {
console.log('写入失败', err);
reject(err);
},
});
});
};
export default {
getBluetoothState,
startDiscoveryBluetooth,
stopDiscoveryBluetooth,
getBluetoothDevicesInfo,
connectBluetooth,
disconnectBluetooth,
writeBLEValueLoop,
};