uni-app 【报Bug】uni.onBLEConnectionStateChange CALLBACK.value是否自动分包 每个包20大小 为何出现不触发或丢包现象
uni-app 【报Bug】uni.onBLEConnectionStateChange CALLBACK.value是否自动分包 每个包20大小 为何出现不触发或丢包现象
产品分类
uniapp/App
PC开发环境
- 操作系统:Windows
- 操作系统版本号:23H2
- HBuilderX类型:正式
- HBuilderX版本号:3.99
移动端开发环境
- 手机系统:Android
- 手机系统版本号:Android 14
- 手机厂商:华为
- 手机机型:荣耀v30 pro
项目信息
- 页面类型:vue
- vue版本:vue3
- 打包方式:离线
- 项目创建方式:HBuilderX
示例代码
import {
ab2hex,
hexCharCodeToStr,
hexToArrayBuffer
} from './utils.js'
const BlModule = (function() {
let events = {}
let BLDeviceList = []
// 设备id
let deviceId = null
// 读取id
let read = {
serviceId: null,
uuid: null
}
// 写入id
let write = {
serviceId: null,
uuid: null
}
function _blModule() {
const _this = this
init()
search()
this.on = function(eventName, listener) {
if (!events[eventName]) {
events[eventName] = [];
}
events[eventName].push(listener);
}
// 连接设备
this.connect = connect
//发送消息
this.sendWrite = sendWrite
this.sendRead = sendRead
this.sendHeart = heart
// 获取 设备id
this.getDeviceId = function() {
return deviceId
}
// 获取所有特征编码
this.getCharacteristicIds = function() {
return {
read: read,
write: write,
notify: notify,
indicate: indicate
}
}
}
/**
* 开启蓝牙
*/
function init() {
uni.openBluetoothAdapter({
success() {
uni.getBluetoothAdapterState({
fail(error) {
uni.showToast({
title: '查看手机蓝牙是否打开',
icon: 'none'
});
}
})
},
fail(err) {
uni.showToast({
title: '查看手机蓝牙是否打开',
icon: 'none'
});
}
})
}
/**
* 搜索设备
*/
function search() {
uni.startBluetoothDevicesDiscovery({
success() {
console.log('开始搜索');
// 开启监听回调
uni.onBluetoothDeviceFound(found)
}
})
}
/**
* 发现新设备添加至DlDeviceList中
* @param {Object} res
*/
function found(res) {
const oldArr = JSON.parse(JSON.stringify(BLDeviceList))
BLDeviceList = oldArr.concat(res.devices).filter(item => item.name != "")
// 对搜索到的蓝牙设备去重
for (let i = 0; i < BLDeviceList.length; i++) {
for (let j = i + 1; j < BLDeviceList.length; j++) {
if (BLDeviceList[i].name == BLDeviceList[j].name) {
BLDeviceList.splice(j, 1)
}
}
}
emit('updateBlDevice', BLDeviceList)
}
/**
* 连接设备
* @param {object} data 设备信息
* @param {string} data.deviceId 设备信息
*/
function connect(data) {
deviceId = data.deviceId // 将获取到的设备ID存起来
uni.createBLEConnection({
deviceId: deviceId,
success(res) {
// 停止搜索
stopDiscovery()
setMaxMTU()
uni.showLoading({
title: '连接中...'
})
setTimeout(async () => {
await getServices()
uni.hideLoading()
emit('ConnectSuccess')
}, 1000)
},
fail(err) {
console.error(err)
uni.showToast({
title: '连接失败,请稍后重试',
icon: 'error'
})
}
})
}
/**
* 设置最大值
*/
function setMaxMTU() {
uni.setBLEMTU({
deviceId: deviceId,
mtu: 512,
success(res) {
console.log("设置最大值成功")
}
})
}
/**
* 关闭搜索
*/
function stopDiscovery() {
uni.stopBluetoothDevicesDiscovery({
success(res) {
console.log('停止成功')
},
fail(err) {
console.log('停止失败', err)
}
})
}
/**
* 获取服务
*/
function getServices() {
return new Promise((resolve, reject) => {
let characteristics = []
uni.getBLEDeviceServices({
deviceId: deviceId,
success: async (res) => {
const asyncRes = await Promise.all(res.services.map(async (item) => {
const c = await getCharacteristics(item.uuid)
return c
}));
asyncRes.map(item => {
characteristics = characteristics.concat(item)
})
characteristics.map((c, j) => {
if (c.properties.read && !c.properties.write) {
read = {
serviceId: c.serviceId,
uuid: c.uuid
}
}
if (c.properties.write && !c.properties.read) {
write = {
serviceId: c.serviceId,
uuid: c.uuid
}
}
if (c.properties.notify) {
const st = setTimeout(()=>{
notifyEvn({
serviceId: c.serviceId,
uuid: c.uuid
})
clearTimeout(st)
}, 1000,)
resolve()
}
})
},
fail(err) {
console.log('连接失败');
reject(err)
}
})
})
// 如果是自动链接的话,uni.getBLEDeviceServices方法建议使用setTimeout延迟1秒后再执行
}
/**
* 获取特征值
*/
function getCharacteristics(uuid) {
// 如果是自动链接的话,uni.getBLEDeviceCharacteristics方法建议使用setTimeout延迟1秒后再执行
return new Promise((resolve, reject) => {
uni.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: uuid,
success(res) {
res.characteristics.map(item => {
item.serviceId = uuid
})
const properties = res.characteristics
resolve(properties) // 可以在此判断特征值是否支持读写等操作,特征值其实也需要提前向硬件佬索取的
},
fail(err) {
reject(err)
}
})
})
}
/**
* 开启消息监听
*/
function notifyEvn(notify) {
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
deviceId: deviceId, // 设备id
serviceId: notify.serviceId, // 监听指定的服务
characteristicId: notify.uuid, // 监听对应的特征值
success(res) {
console.log('开启设备监听');
listenValueChange()
},
fail(err) {
console.error('消息监听失败', err)
}
})
}
/**
* 监听消息变化
*/
function listenValueChange() {
uni.onBLECharacteristicValueChange(res => {
let resHex = ab2hex(res.value)
// emit('newMsg', resHex)
})
}
/**
* 发送写入命令
* 接受一个16进制字符串
*/
function sendWrite(val) {
// 向蓝牙设备发送一个0x00的16进制数据
const buffer = hexToArrayBuffer(val);
uni.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: write.serviceId,
characteristicId: write.uuid,
value: buffer,
writeType: 'write',
success(res) {
emit('SendSuccess')
},
fail(err) {
console.error(err)
uni.showToast({
title: '指令发送失败',
icon: 'none'
})
}
})
}
/**
* 发送读取指令
* @param {string} val
*/
function sendRead(val) {
// 向蓝牙设备发送一个0x00的16进制数据
const buffer = hexToArrayBuffer(val);
uni.readBLECharacteristicValue({
deviceId: deviceId,
serviceId: read.serviceId,
characteristicId: read.uuid,
success(res) {
emit('SendSuccess')
},
fail(err) {
console.error(err)
uni.showToast({
title: '指令发送失败',
icon: 'none'
})
}
})
}
/**
* 发送心跳数据
* 接受一个16进制字符串
*/
function heart(val) {
// 向蓝牙设备发送一个0x00的16进制数据
const buffer = hexToArrayBuffer(val);
uni.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: write.serviceId,
characteristicId: write.uuid,
value: buffer,
writeType: 'write',
success(res) {},
fail(err) {
console.error(err)
uni.showToast({
title: '指令发送失败',
icon: 'none'
})
}
})
}
/**
* 事件监听器
*/
function emit(eventName, data) {
if (!events[eventName]) {
return;
}
events[eventName].forEach(listener => listener(data));
}
return _blModule
})()
export default BlModule
操作步骤
根据以上代码连接蓝牙后,发送指令触发消息监听,
预期结果
连接蓝牙后发送指令后,如有消息应当触发消息,并且大于20字节分包后应当返回所有分包
实际结果
连接蓝牙后发送指令,有的时候不触发监听,消息大于20字节后存在丢包现象
bug描述
uni.onBLEConnectionStateChange CALLBACK.value是会自动分包吗? 每个包为20个字节大小?会出现不触发或者大于20个丢包的现象。
在使用 uni.onBLEConnectionStateChange
进行蓝牙连接状态监听时,如果出现不触发或丢包现象,可能是由于以下几个原因导致的:
1. 蓝牙设备通信限制
蓝牙设备在通信时,通常会有每包数据大小的限制。对于 BLE(低功耗蓝牙)设备,每个数据包的大小通常为 20 字节。如果发送的数据超过这个大小,设备或系统可能会自动进行分包处理。如果分包处理不当,可能会导致丢包或数据不完整。
2. 设备或系统问题
某些蓝牙设备或手机系统可能在处理蓝牙数据时存在 bug 或性能瓶颈,导致数据包丢失或 uni.onBLEConnectionStateChange
回调不触发。可以尝试在不同的设备或系统版本上进行测试,确认是否是设备或系统的问题。
3. 代码逻辑问题
检查代码逻辑,确保在连接状态变化时正确地处理了回调函数。确保没有遗漏或错误地处理了蓝牙连接状态的变化。
4. 数据缓冲区溢出
如果数据发送频率过高,可能会导致数据缓冲区溢出,从而导致丢包。可以尝试降低数据发送频率,或在接收到数据后及时处理数据,避免缓冲区溢出。
5. 信号干扰
蓝牙通信受到信号干扰的影响较大,如果周围有其他无线设备或干扰源,可能会导致通信不稳定,从而出现丢包现象。
解决方案
- 分包处理:手动处理数据分包,确保每包数据不超过 20 字节,并在接收端进行数据重组。
- 降低发送频率:降低数据发送频率,避免缓冲区溢出。
- 错误处理:在代码中添加错误处理逻辑,确保在出现异常时能够及时处理。
- 设备兼容性测试:在不同的设备和系统版本上进行测试,确认问题的根源。
- 优化环境:尽量减少蓝牙通信环境中的干扰源,确保通信稳定。
示例代码
以下是一个简单的示例代码,展示如何处理分包数据:
let receivedData = [];
uni.onBLECharacteristicValueChange((res) => {
const value = res.value;
receivedData.push(...value);
// 假设每包数据大小为 20 字节,且最后一包数据不足 20 字节
if (value.length < 20) {
const completeData = new Uint8Array(receivedData);
console.log('Received complete data:', completeData);
receivedData = []; // 清空缓冲区
}
});