1
0
forked from dyf/APP

处理100J设备蓝牙问题

This commit is contained in:
微微一笑
2026-03-19 14:36:17 +08:00
parent ac59e28281
commit 3a9de3078c
4 changed files with 102 additions and 43 deletions

27
App.vue
View File

@ -2,6 +2,10 @@
import bleTool from '@/utils/BleHelper.js'; import bleTool from '@/utils/BleHelper.js';
import upgrade from '@/utils/update.js'; import upgrade from '@/utils/update.js';
// 延迟断开蓝牙:选择文件/录音等会触发 onHide8 秒内返回则不断开
const BLE_DISCONNECT_DELAY = 8000;
let _bleDisconnectTimer = null;
export default { export default {
onLaunch: function() { onLaunch: function() {
@ -74,7 +78,11 @@
}, },
onShow: function() { onShow: function() {
console.log('App Show'); console.log('App Show');
// 取消延迟断开:用户可能只是选文件/录音后返回,不断开蓝牙
if (_bleDisconnectTimer) {
clearTimeout(_bleDisconnectTimer);
_bleDisconnectTimer = null;
}
//将检查更新换到onshow,因为苹果用户喜欢一直挂着 //将检查更新换到onshow,因为苹果用户喜欢一直挂着
// #ifdef APP|APP-PLUS // #ifdef APP|APP-PLUS
@ -91,11 +99,22 @@
onHide: function() { onHide: function() {
console.log('App Hide'); console.log('App Hide');
// #ifdef APP|APP-PLUS // #ifdef APP|APP-PLUS
// 上传中不主动断开:语音上传进行中则不断开蓝牙
let ble = bleTool.getBleTool(); let ble = bleTool.getBleTool();
if (ble) { if (ble && ble.isVoiceUploading && ble.isVoiceUploading()) {
console.log("断开所有蓝牙设备"); console.log('App Hide: 语音上传中,不启动断开定时器');
ble.disconnectDevice(); return;
} }
// 延迟断开:选文件/录音会短暂 onHide8 秒内返回则不断开
if (_bleDisconnectTimer) clearTimeout(_bleDisconnectTimer);
_bleDisconnectTimer = setTimeout(() => {
_bleDisconnectTimer = null;
let ble2 = bleTool.getBleTool();
if (ble2) {
console.log("断开所有蓝牙设备");
ble2.disconnectDevice();
}
}, BLE_DISCONNECT_DELAY);
// #endif // #endif
}, },
onError(ex) { onError(ex) {

View File

@ -320,44 +320,56 @@ class HBY100JProtocol {
const ft = (fileType & 0xFF) || 1; const ft = (fileType & 0xFF) || 1;
const DELAY_AFTER_START = 80; // 开始包后、等设备响应后再发的缓冲(ms) const DELAY_AFTER_START = 80; // 开始包后、等设备响应后再发的缓冲(ms)
const DELAY_PACKET = 80; // 数据包间延时(ms)参考6155 const DELAY_PACKET = 80; // 数据包间延时(ms)参考6155
const toHex = (arr) => Array.from(arr).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ');
console.log('[100J-蓝牙] 语音下发总大小:', total, '字节, fileType=', ft);
if (onProgress) onProgress(1); if (onProgress) onProgress(1);
const bleToolPromise = import('@/utils/BleHelper.js').then(m => m.default.getBleTool()); const bleToolPromise = import('@/utils/BleHelper.js').then(m => m.default.getBleTool());
const send = (dataBytes) => { let bleRef = null;
const send = (dataBytes, label = '') => {
const buf = new ArrayBuffer(dataBytes.length + 3); const buf = new ArrayBuffer(dataBytes.length + 3);
const v = new Uint8Array(buf); const v = new Uint8Array(buf);
v[0] = 0xFA; v[0] = 0xFA;
v[1] = 0x05; v[1] = 0x05;
for (let i = 0; i < dataBytes.length; i++) v[2 + i] = dataBytes[i]; for (let i = 0; i < dataBytes.length; i++) v[2 + i] = dataBytes[i];
v[v.length - 1] = 0xFF; v[v.length - 1] = 0xFF;
const hex = toHex(v);
const preview = v.length <= 32 ? hex : hex.slice(0, 96) + '...';
console.log(`[100J-蓝牙] 下发${label}${v.length}字节:`, preview);
return bleToolPromise.then(ble => ble.sendData(this.bleDeviceId, buf, this.SERVICE_UUID, this.WRITE_UUID)); return bleToolPromise.then(ble => ble.sendData(this.bleDeviceId, buf, this.SERVICE_UUID, this.WRITE_UUID));
}; };
const delay = (ms) => new Promise(r => setTimeout(r, ms)); const delay = (ms) => new Promise(r => setTimeout(r, ms));
// 开始包: FA 05 [fileType] [phase=0] [size 4B LE] FF // 开始包: FA 05 [fileType] [phase=0] [size 4B LE] FF
const startData = [ft, 0, total & 0xFF, (total >> 8) & 0xFF, (total >> 16) & 0xFF, (total >> 24) & 0xFF]; const startData = [ft, 0, total & 0xFF, (total >> 8) & 0xFF, (total >> 16) & 0xFF, (total >> 24) & 0xFF];
const waitPromise = this.waitForFileResponse(1000); const waitPromise = this.waitForFileResponse(1000);
return send(startData) return bleToolPromise.then(ble => {
.then(() => { if (onProgress) onProgress(3); return waitPromise; }) bleRef = ble;
.then(() => { if (onProgress) onProgress(5); return delay(DELAY_AFTER_START); }) ble.setVoiceUploading(true);
.then(() => { return send(startData, ' 开始包')
let seq = 0; .then(() => { if (onProgress) onProgress(3); return waitPromise; })
const sendNext = (offset) => { .then(() => { if (onProgress) onProgress(5); return delay(DELAY_AFTER_START); })
if (offset >= total) { .then(() => {
return delay(DELAY_PACKET).then(() => send([ft, 2])); let seq = 0;
} const sendNext = (offset) => {
const chunk = bytes.slice(offset, Math.min(offset + chunkSize, total)); if (offset >= total) {
const chunkData = [ft, 1, seq & 0xFF, (seq >> 8) & 0xFF, ...chunk]; return delay(DELAY_PACKET).then(() => send([ft, 2], ' 结束包'));
return send(chunkData).then(() => { }
seq++; const chunk = bytes.slice(offset, Math.min(offset + chunkSize, total));
if (onProgress) onProgress(Math.min(100, Math.floor((offset + chunk.length) / total * 100))); const chunkData = [ft, 1, seq & 0xFF, (seq >> 8) & 0xFF, ...chunk];
return delay(DELAY_PACKET).then(() => sendNext(offset + chunk.length)); return send(chunkData, ` #${seq} 数据包`).then(() => {
}); seq++;
}; if (onProgress) onProgress(Math.min(100, Math.floor((offset + chunk.length) / total * 100)));
return sendNext(0); return delay(DELAY_PACKET).then(() => sendNext(offset + chunk.length));
}) });
.then(() => { };
if (onProgress) onProgress(100); return sendNext(0);
return { code: 200, msg: '语音文件已通过蓝牙上传' }; })
}); .then(() => {
if (onProgress) onProgress(100);
return { code: 200, msg: '语音文件已通过蓝牙上传' };
});
}).finally(() => {
if (bleRef) bleRef.setVoiceUploading(false);
});
} }
} }

View File

@ -607,9 +607,9 @@
console.log(this.activePermissions, 'this.activePermissions'); console.log(this.activePermissions, 'this.activePermissions');
these.fetchDeviceDetail(data.data.deviceId) these.fetchDeviceDetail(data.data.deviceId)
} }
// 尝试连接蓝牙:需先扫描获取 BLE deviceId不能直接用 MAC // 尝试连接蓝牙:需先扫描获取 BLE deviceId不能直接用 MAC;延迟 500ms 确保蓝牙适配器就绪
if (data.data.deviceMac) { if (data.data.deviceMac) {
these.tryConnect100JBle(data.data.deviceMac); setTimeout(() => { these.tryConnect100JBle(data.data.deviceMac); }, 500);
} }
}); });
this.createThrottledFunctions(); this.createThrottledFunctions();
@ -991,21 +991,30 @@
deviceRecovry(res) {}, deviceRecovry(res) {},
deviceDispose(res) {}, deviceDispose(res) {},
// 100J 蓝牙连接:先查缓存/尝试直连,失败则扫描createBLEConnection 需要扫描返回的 deviceId // 100J 蓝牙连接:先查缓存/尝试直连,失败则扫描
// 注意Android 的 createBLEConnection 需要系统返回的 deviceId服务端 MAC(11:22:33:44:55:02) 与 deviceId(02:55:44:33:22:11) 字节序相反
tryConnect100JBle(deviceMac) { tryConnect100JBle(deviceMac) {
const that = this; const that = this;
const macNorm = (m) => (m || '').replace(/:/g, '').toUpperCase(); const macNorm = (m) => (m || '').replace(/:/g, '').toUpperCase();
const targetMacNorm = macNorm(deviceMac); const targetMacNorm = macNorm(deviceMac);
const last6 = targetMacNorm.slice(-6); const last6 = targetMacNorm.slice(-6);
// Android BLE deviceId 多为 MAC 字节反序,如 11:22:33:44:55:02 -> 02:55:44:33:22:11
const macToDeviceId = (mac) => {
const parts = (mac || '').split(':').filter(Boolean);
return parts.length === 6 ? parts.reverse().join(':') : mac;
};
// 1. 查缓存:之前连过且 mac 匹配 // 1. 查缓存:之前连过且 mac 匹配
const cached = bleTool.data.LinkedList.find(v => { const cached = bleTool.data.LinkedList.find(v => {
const m = macNorm(v.macAddress); const m = macNorm(v.macAddress);
return m === targetMacNorm || m.slice(-6) === last6; return m === targetMacNorm || m.slice(-6) === last6;
}); });
const SVC = '0000AE30-0000-1000-8000-00805F9B34FB';
const WRITE = '0000AE03-0000-1000-8000-00805F9B34FB';
const NOTIFY = '0000AE02-0000-1000-8000-00805F9B34FB';
if (cached && cached.deviceId) { if (cached && cached.deviceId) {
console.log('[100J] 使用缓存设备连接', cached.deviceId); console.log('[100J] 使用缓存设备连接', cached.deviceId);
bleTool.LinkBlue(cached.deviceId).then(() => { bleTool.LinkBlue(cached.deviceId, SVC, WRITE, NOTIFY, 2).then(() => {
console.log('100J 蓝牙连接成功(缓存)'); console.log('100J 蓝牙连接成功(缓存)');
that.bleStateRecovry({ deviceId: cached.deviceId }); that.bleStateRecovry({ deviceId: cached.deviceId });
}).catch(err => { }).catch(err => {
@ -1015,18 +1024,29 @@
return; return;
} }
// 2. 无缓存:先尝试直连Android 上 deviceId 可能为 MAC // 2. 无缓存:先尝试直连Android 上 deviceId 为 MAC 反序(11:22:33:44:55:02->02:55:44:33:22:11)
console.log('[100J] 尝试直连', deviceMac); const tryDirect = (id) => bleTool.LinkBlue(id, SVC, WRITE, NOTIFY, 2).then(() => {
bleTool.LinkBlue(deviceMac).then(() => { console.log('100J 蓝牙连接成功(直连)', id);
console.log('100J 蓝牙连接成功(直连)'); that.bleStateRecovry({ deviceId: id });
that.bleStateRecovry({ deviceId: deviceMac }); });
}).catch(err => { const deviceIdReversed = macToDeviceId(deviceMac);
console.log('100J 蓝牙直连失败,开始扫描', err); console.log('[100J] 尝试直连', deviceIdReversed, '(MAC反序)');
tryDirect(deviceIdReversed).catch(() => {
if (deviceIdReversed !== deviceMac) {
console.log('[100J] 反序直连失败,尝试原 MAC', deviceMac);
return tryDirect(deviceMac);
}
return Promise.reject();
}).catch(() => {
console.log('[100J] 蓝牙直连失败,开始扫描');
that.connect100JByScan(deviceMac, last6); that.connect100JByScan(deviceMac, last6);
}); });
}, },
connect100JByScan(deviceMac, last6) { connect100JByScan(deviceMac, last6) {
const that = this; const that = this;
const SVC = '0000AE30-0000-1000-8000-00805F9B34FB';
const WRITE = '0000AE03-0000-1000-8000-00805F9B34FB';
const NOTIFY = '0000AE02-0000-1000-8000-00805F9B34FB';
let resolved = false; let resolved = false;
const timeout = 15000; const timeout = 15000;
const timer = setTimeout(() => { const timer = setTimeout(() => {
@ -1050,7 +1070,7 @@
bleTool.StopSearch(); bleTool.StopSearch();
bleTool.removeDeviceFound('HBY100J_SCAN'); bleTool.removeDeviceFound('HBY100J_SCAN');
console.log('[100J] 扫描到目标设备', match.name, match.deviceId); console.log('[100J] 扫描到目标设备', match.name, match.deviceId);
bleTool.LinkBlue(match.deviceId).then(() => { bleTool.LinkBlue(match.deviceId, SVC, WRITE, NOTIFY, 2).then(() => {
console.log('100J 蓝牙连接成功(扫描)'); console.log('100J 蓝牙连接成功(扫描)');
that.bleStateRecovry({ deviceId: match.deviceId }); that.bleStateRecovry({ deviceId: match.deviceId });
}).catch(err => { }).catch(err => {

View File

@ -61,7 +61,8 @@ class BleHelper {
LinkedList: linkedDevices, //已连接的设备列表 LinkedList: linkedDevices, //已连接的设备列表
platform: systemInfo.uniPlatform, platform: systemInfo.uniPlatform,
Disconnect: [], //主动断开的设备 Disconnect: [], //主动断开的设备
connectingDevices: {} //正在连接的设备 connectingDevices: {}, //正在连接的设备
voiceUploading: false //语音上传中(上传中不主动断开蓝牙)
} }
this.cfg = { this.cfg = {
onDeviceFound: [], //发现新设备的事件 onDeviceFound: [], //发现新设备的事件
@ -561,7 +562,7 @@ class BleHelper {
return; return;
} }
if (!this.data.isOpenBlue) { if (!this.data.isOpenBlue) {
console.error("蓝牙模块未打开"); console.log("蓝牙模块未打开,即将初始化");
resolve({ resolve({
available: false, available: false,
discovering: false discovering: false
@ -1624,7 +1625,7 @@ class BleHelper {
// console.log("正在连接" + deviceId); // console.log("正在连接" + deviceId);
uni.createBLEConnection({ uni.createBLEConnection({
deviceId: deviceId, deviceId: deviceId,
timeout: 15000, timeout: 20000,
success: (info) => { success: (info) => {
//释放连接锁 //释放连接锁
@ -1808,6 +1809,13 @@ class BleHelper {
}); });
return prom; return prom;
} }
// 语音上传中不主动断开:设置/查询上传状态App onHide 时检查)
setVoiceUploading(flag) {
this.data.voiceUploading = !!flag;
}
isVoiceUploading() {
return !!this.data.voiceUploading;
}
//断开连接 //断开连接
disconnectDevice(deviceId) { disconnectDevice(deviceId) {
if (this.data.platform == 'web') { if (this.data.platform == 'web') {