- manifest.json配置蓝牙相关用户授权 scope.userLocation(兼容部分手机需要位置授权的情况) scope.bluetooth 详见developers.weixin.qq.com/miniprogram…
developers.weixin.qq.com/community/d… - 判断手机型号
const systemInfo = wx.getSystemInfoSync();
if (/iPhone/.test(systemInfo.model)) {
this.isIos = true;
}
- 用户授权信息判断
getSetting() {
let _that = this
try {
wx.getSetting({
withSubscriptions: true,
success(res) {
//校验蓝牙是否已经授权
if (res.authSetting['scope.bluetooth']) {
_that.initBlueTooth()//初始化蓝牙
} else {
//关闭蓝牙提示相关....
if (_that.$refs["setBlueRef"]) _that.$refs["setBlueRef"].hide();
toast('您拒绝了蓝牙授权')
_that.closeBlueTooth()
}
}
})
} catch (error) {
console.log(error, 'getSetting');
throw error
}
}
- 打开蓝牙适配器 也可以先执行这一步
async initBlueTooth() {
let _that = this;
//可以添加《隐私保护指引》等逻辑
wx.openBluetoothAdapter({
mode: "central",
success: function (res) {
// 获取蓝牙适配器状态
_that.isOpenBluebooth = true
_that.getBluetoothAdapter();
},
fail: function (res) {
//蓝牙未打开
if (res.errCode == 10001) {
uni.hideToast()
if (_that.$refs["setBlueRef"]) return _that.$refs["setBlueRef"].show();//打开蓝牙提示弹窗 后面用onShow加个倒计时监听页面返回执行下面一步
}
_that.isOpenBluebooth = false
_that.getSetting()
},
});
},
跳转系统微信授权管理页
setBlueTooth() {
//ios特殊处理
if (this.isIos) {
wx.openAppAuthorizeSetting({
success: (res) => {
this.isOpenBluebooth = true
// console.log('您拒绝了蓝牙授权', res);
if (this.$refs["setBlueRef"]) this.$refs["setBlueRef"].hide();
},
fail: (res) => {
this.isOpenBluebooth = false
},
});
} else {
//安卓手机打开蓝牙设置页面
wx.openSystemBluetoothSetting({
success: (data) => {
this.isOpenBluebooth = true
if (this.$refs["setBlueRef"]) this.$refs["setBlueRef"].hide();
},
fail: (err) => {
this.isOpenBluebooth = false
},
});
}
}
- 获取本机蓝牙适配器状态
async getBluetoothAdapter() {
let _that = this;
try {
//获取本机蓝牙适配器状态
wx.getBluetoothAdapterState({
success: function (res2) {
console.log('getBluetoothAdapterState', res2);
//跳转到手机系统蓝牙设置页后回到此页面监听蓝牙是否打开
if (!res2.available && _that.isOpenSetting) {
_that.isOpenSetting = false
_that.bleAdapterState = false
toast('蓝牙未打开')
return
}
if (res2.errMsg == "getBluetoothAdapterState:ok") {
//搜寻附近的蓝牙设备
uni.showLoading({
title: "连接设备蓝牙中",
mask: true,
});
_that.startBluetoothDevices();
_that.isOpenSetting = false
_that.bleAdapterState = true
}
},
fail: function (res2) {
if (_that.isOpenSetting)
uni.hideLoading();
},
});
} catch (error) {
throw error
}
}
6.开始搜寻附近的蓝牙外围设备
startBluetoothDevices() {
let _that = this;
try {
wx.startBluetoothDevicesDiscovery({
success: function (res) {
if (res.errCode == 0) {
// 监听寻找到新设备的事件
_that.onBluetoothDevice();
}
console.log('startBluetoothDevicesDiscovery', res);
},
fail: function (res) {
//可能未开启 小程序/微信app 定位相关授权
if (res.errCode < 0 && res.errMsg.includes('location')) {
uni.hideToast()
uni.hideLoading()
toast('请开启微信定位授权')
return
}
/**
或者在前面用处理
wx.authorize({
scope: 'scope.userLocation',
success() {
// 用户同意授权
},
fail() {
// 用户拒绝授权
}
});
*/
console.log(res, 'startBluetoothDevicesDiscovery');
if (res.errCode > 1) toast("连接失败");
},
});
} catch (error) {
console.log(error, "startBluetoothDevicesDiscovery");
throw error
}
},
7.监听寻找到新设备的事件
onBluetoothDevice() {
let _that = this;
try {
wx.onBluetoothDeviceFound((res) => {
res.devices.length &&
res.devices.forEach((device) => {
//https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/wx.onBluetoothDeviceFound.html
//这里要求外围设备发送ManufacturerData 数据段 (advertisData ArrayBuffer数据类型)
if (device.deviceId && device.advertisData && device.localName) {
let deviceId = _that.hex2ab(device.advertisData);
// 确保蓝牙设备中的advertisData字段有数据
if (deviceId) {
console.log(deviceId, _that.sim, device.localName);
//这里与硬件方沟通好根据什么标识判断 进行连接
if (deviceId.endsWith(_that.xx)) {
_that.stopBluetooth()
setTimeout(() => {
_that.createBLEConnectionFn(device.deviceId);
}, 400)
}
});
});
} catch (error) {
uni.hideLoading();
_that.stopBluetooth();
throw error
}
},
- 连接蓝牙
async createBLEConnectionFn() {
let _that = this;
try {
//连接蓝牙前先关闭已连接蓝牙
wx.createBLEConnection({
deviceId: _that.deviceId,
success: function (res) {
//获取连接设备的service服务
_that.getBLEDeviceServicesFn(_that.deviceId);
uni.hideLoading();
uni.showToast({
title: "连接设备蓝牙成功",
icon: "success",
mask: true,
});
_that.connectSuccess = true
//监听蓝牙关闭
wx.onBLEConnectionStateChange(function (res) {
// 该方法回调中可以用于处理连接意外断开等异常情况
if (!res.connected) {
//............断开的业务逻辑
}
});
},
fail: function (res) {
_that.stopBluetooth();
uni.hideLoading();
uni.showToast({
title: "连接蓝牙失败",
icon: "error",
mask: true,
});
},
});
} catch (error) {
throw error
}
},
9.获取连接设备的service服务
getBLEDeviceServicesFn(deviceId) {
let _that = this;
wx.getBLEDeviceServices({
deviceId: _that.deviceId,
success: function (res) {
console.log(res, 'getBLEDeviceServices');
// 遍历服务列表,找到设备支持的主服务 可能是多个主服务 循环取对应的功能的uuid和serviceid
for (let i = 0; i < res.services.length; i++) {
if (
res.services[i].isPrimary
) {
_that.getBLEDevice(res.services[i].uuid);
//获取连接设备的所有特征值
}
}
},
fail: function (res) {
console.log("获取蓝牙失败", res);
},
});
}
- 获取连接设备的所有特征值
getBLEDevice() {
let _that = this;
wx.getBLEDeviceCharacteristics({
deviceId: _that.deviceId,
serviceId: _that.serviceId,
success: function (res) {
if (res.errCode == 0) {
console.log(res, 'getBLEDeviceCharacteristics');
let characteristicId = res.characteristics;
for (var i = 0; i < res.characteristics.length; i++) {
let item = res.characteristics[i].properties
if (item.notify && !_that.notifyUuid) {
//启用蓝牙notify功能uuid
_that.notifyUuid = characteristicId[i]['uuid']
// notifyServiceId: "", //启用蓝牙notify功能serviceId
_that.notifyServiceId = serviceId
}
if (
item.write && !_that.writeUuid
) {
//这里可以开始给设备发送数据了
//启用蓝牙write功能serviceId
_that.writeServiceId = serviceId
// //启用蓝牙write功能uuid
_that.writeUuid = characteristicId[i]["uuid"];
let str = ""
_that.writeBLECharacteristic(str);
}
//确保设备能够写入数据
}
} else {
console.log("获取特征值失败");
}
},
});
},
- 启用低功耗蓝牙设备特征值变化时的 notify 功能
notifyBLECharacteristic() {
let _that = this;
wx.notifyBLECharacteristicValueChange({
deviceId: _that.deviceId,
serviceId: _that.notifyServiceId,
characteristicId: _that.notifyUuid,
state: true,
success: function (res) {
console.log("启用低功耗蓝牙设备特征值变化时的 notify 功能", res);
/*用来监听手机蓝牙设备的数据变化*/
_that.onBLECharacteristic();
_that.watchBleConnectionState()//启动监听连接状态
},
fail: function (res) {
toast('启用notify失败')
console.log(res, '启用notify失败', _that.notifyUuid);
},
});
}
- 监听接受到的数据解析/处理业务逻辑
onBLECharacteristic() {
let _that = this;
try {
wx.onBLECharacteristicValueChange(function (res) {
let flag = _that.hex2ab(res.value);
//........................业务代码 拼接数据等很多逻辑
//_that+=flag
});
} catch (error) {
// console.log(error, 'onBLECharacteristic');
uni.hideLoading()
toast('监听notify失败')
throw error
}
},
离开页面记得释放蓝牙资源
handlerCloeseBle() {
//关闭蓝牙模块。
wx.closeBluetoothAdapter({
success: (res) => {
},
fail: (err) => {
console.log('closeBluetoothAdapter', err);
}
});
}
//...........其他业务代码
工具函数
string2ArrayBuffer(str) {
try {
var bytes = new Array();
var len, c;
len = str.length;
for (var i = 0; i < len; i++) {
c = str.charCodeAt(i);
if (c >= 0x010000 && c <= 0x10ffff) {
bytes.push(((c >> 18) & 0x07) | 0xf0);
bytes.push(((c >> 12) & 0x3f) | 0x80);
bytes.push(((c >> 6) & 0x3f) | 0x80);
bytes.push((c & 0x3f) | 0x80);
} else if (c >= 0x000800 && c <= 0x00ffff) {
bytes.push(((c >> 12) & 0x0f) | 0xe0);
bytes.push(((c >> 6) & 0x3f) | 0x80);
bytes.push((c & 0x3f) | 0x80);
} else if (c >= 0x000080 && c <= 0x0007ff) {
bytes.push(((c >> 6) & 0x1f) | 0xc0);
bytes.push((c & 0x3f) | 0x80);
} else {
bytes.push(c & 0xff);
}
}
var array = new Int8Array(bytes.length);
for (var i in bytes) {
array[i] = bytes[i];
}
return array.buffer;
} catch (error) {
uni.hideLoading()
toast('解析ArrayBuffer失败~')
console.log(error, 'string2ArrayBuffer');
throw error
}
},
//ArrayBuffer 转字符串
str2ab(arrayBuffer) {
try {
let unit8Arr = new Uint8Array(arrayBuffer);
let encodedString = String.fromCharCode.apply(null, unit8Arr);
let decodedString = decodeURIComponent(escape(encodedString)); //没有这一步中文会乱码
return decodedString;
} catch (error) {
console.log(error, 'str2ab');
uni.hideLoading()
toast('解析数据失败~str2ab')
return false;
}
},
hex2ab(str) {
try {
return String.fromCharCode.apply(null, new Uint8Array(str));
} catch (error) {
uni.hideLoading()
toast('解析数据失败~hex2ab')
console.log(error, 'hex2ab');
throw error
}
},
// 16进制(byte格式)转字符串
hexToString(str) {
try {
let res = str.replace(/\\x/g, "%");
let result = decodeURIComponent(res, "utf-8");
return result;
} catch (error) {
uni.hideLoading()
throw error
}
},
//监听蓝牙连接状态
watchBleConnectionState() {
wx.onBLEConnectionStateChange((res) => {
console.log('watchBleConnectionState', res);
if (!res?.connected) {
toast('设备已断开蓝牙连接')
}
})
},
async stopBluetooth() {
let _that = this
wx.stopBluetoothDevicesDiscovery({
success: (result) => {
console.log("停止搜寻附近的蓝牙外围设备成功");
//处理逻辑......
},
fail: (error) => {
console.log("停止搜寻附近的蓝牙外围设备失败", error);
},
});
},
尝试解析拼接数据 视硬件方发送数据而定
transStrToArr(){
//decodeURIComponent(str)
//hexToString(str)
//str.split("/n")
}
写入数据操作writeBLECharacteristic 不要并行写入操作!!! developers.weixin.qq.com/miniprogram… 查看‘注意’
async writeBLECharacteristic(val, index) {
return new Promise((resolve, reject) => {
let _that = this;
let buffer = _that.string2ArrayBuffer(val);
wx.writeBLECharacteristicValue({
deviceId: _that.deviceId,
serviceId: _that.writeServiceId,
characteristicId: _that.writeUuid,
value: buffer,
success: function (res) {
//业务代码..........
//判断是否写入了完整的数据 _that.chunkCount-1===index
resolve(res);
},
fail: function (err) {
toast("写入数据失败");
reject(err);
},
});
});
}
接收数据 要与硬件方沟通好, 数据传输用哪种结构 ,因为数据发过来一般只有20字节(可能不是完整的中文/encodeURIComponent 编码,可能有换行符、特殊字符等), 我们要将他进行拼接处理再解析(decodeURIComponent处理 、处理特殊字符) ,如果有排序去重等建议让硬件方去处理 发送数据 在发送的数据的时候可能硬件也只能收到20字节的数据, 我们也需要去处理(确保每次发送数据不超过20字节大小)
async handlerWrite() {
let position = 0;
let chunks = [];
// 计算字符串的实际字节长度
const getByteLength = (str) => {
let len = 0;
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i);
if (charCode <= 0x007f) {
// ASCII字符占1字节
len += 1;
} else if (charCode <= 0x07ff) {
len += 2; // 部分Unicode字符占2字节
} else {
len += 3; // 中文等字符占3字节
}
}
return len;
// 获取下一个安全的切割点
const getNextChunkEnd = (str, start, maxBytes) => {
let byteCount = 0;
let i = start;
while (i < str.length) {
const charCode = str.charCodeAt(i);
const charByteLength =
charCode <= 0x007f ? 1 : charCode <= 0x07ff ? 2 : 3;
// 如果当前字符会导致超出20字节,就在这里截断
if (byteCount + charByteLength > maxBytes) {
// 如果已经到了字符串末尾,就不再添加
if (i === str.length - 1) {
return i;
}
return i;
}
byteCount += charByteLength;
i++;
}
return i;
};
// 分割字符串,确保不会截断中文字符
while (position < str.length) {
const end = getNextChunkEnd(str, position, 20); // 使用20字节作为上限
const chunk = str.substring(position, end);
chunks.push(chunk);
position = end;
}
// 记录总块数,用于进度跟踪
this.chunkCount = chunks.length;
// 按顺序发送所有数据块
for (let i = 0; i < chunks.length; i++) {
await this.writeBLECharacteristic(chunks[i], i);
}
}
//其他业务代码
},
看社区有人说用wx.setBLEMTU循环设置蓝牙低功耗的最大传输单元 但是有人说不行 但是这api不支持ios!
可以参考blog.csdn.net/weixin_4498…
如有错误疑问 请评论指出