From 3a9de3078c6214d2fa23fad01b2164090e0de2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E5=BE=AE=E4=B8=80=E7=AC=91?= <709648985@qq.com> Date: Thu, 19 Mar 2026 14:36:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86100J=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E8=93=9D=E7=89=99=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.vue | 27 ++++++++++++++++--- api/100J/HBY100-J.js | 60 ++++++++++++++++++++++++----------------- pages/100J/HBY100-J.vue | 44 +++++++++++++++++++++--------- utils/BleHelper.js | 14 +++++++--- 4 files changed, 102 insertions(+), 43 deletions(-) diff --git a/App.vue b/App.vue index f9538fe..c5557e6 100644 --- a/App.vue +++ b/App.vue @@ -2,6 +2,10 @@ import bleTool from '@/utils/BleHelper.js'; import upgrade from '@/utils/update.js'; + // 延迟断开蓝牙:选择文件/录音等会触发 onHide,8 秒内返回则不断开 + const BLE_DISCONNECT_DELAY = 8000; + let _bleDisconnectTimer = null; + export default { onLaunch: function() { @@ -74,7 +78,11 @@ }, onShow: function() { console.log('App Show'); - + // 取消延迟断开:用户可能只是选文件/录音后返回,不断开蓝牙 + if (_bleDisconnectTimer) { + clearTimeout(_bleDisconnectTimer); + _bleDisconnectTimer = null; + } //将检查更新换到onshow,因为苹果用户喜欢一直挂着 // #ifdef APP|APP-PLUS @@ -91,11 +99,22 @@ onHide: function() { console.log('App Hide'); // #ifdef APP|APP-PLUS + // 上传中不主动断开:语音上传进行中则不断开蓝牙 let ble = bleTool.getBleTool(); - if (ble) { - console.log("断开所有蓝牙设备"); - ble.disconnectDevice(); + if (ble && ble.isVoiceUploading && ble.isVoiceUploading()) { + console.log('App Hide: 语音上传中,不启动断开定时器'); + return; } + // 延迟断开:选文件/录音会短暂 onHide,8 秒内返回则不断开 + if (_bleDisconnectTimer) clearTimeout(_bleDisconnectTimer); + _bleDisconnectTimer = setTimeout(() => { + _bleDisconnectTimer = null; + let ble2 = bleTool.getBleTool(); + if (ble2) { + console.log("断开所有蓝牙设备"); + ble2.disconnectDevice(); + } + }, BLE_DISCONNECT_DELAY); // #endif }, onError(ex) { diff --git a/api/100J/HBY100-J.js b/api/100J/HBY100-J.js index 023b934..bbfaaeb 100644 --- a/api/100J/HBY100-J.js +++ b/api/100J/HBY100-J.js @@ -320,44 +320,56 @@ class HBY100JProtocol { const ft = (fileType & 0xFF) || 1; const DELAY_AFTER_START = 80; // 开始包后、等设备响应后再发的缓冲(ms) 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); 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 v = new Uint8Array(buf); v[0] = 0xFA; v[1] = 0x05; for (let i = 0; i < dataBytes.length; i++) v[2 + i] = dataBytes[i]; 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)); }; const delay = (ms) => new Promise(r => setTimeout(r, ms)); // 开始包: 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 waitPromise = this.waitForFileResponse(1000); - return send(startData) - .then(() => { if (onProgress) onProgress(3); return waitPromise; }) - .then(() => { if (onProgress) onProgress(5); return delay(DELAY_AFTER_START); }) - .then(() => { - let seq = 0; - const sendNext = (offset) => { - if (offset >= total) { - return delay(DELAY_PACKET).then(() => send([ft, 2])); - } - const chunk = bytes.slice(offset, Math.min(offset + chunkSize, total)); - const chunkData = [ft, 1, seq & 0xFF, (seq >> 8) & 0xFF, ...chunk]; - return send(chunkData).then(() => { - seq++; - if (onProgress) onProgress(Math.min(100, Math.floor((offset + chunk.length) / total * 100))); - return delay(DELAY_PACKET).then(() => sendNext(offset + chunk.length)); - }); - }; - return sendNext(0); - }) - .then(() => { - if (onProgress) onProgress(100); - return { code: 200, msg: '语音文件已通过蓝牙上传' }; - }); + return bleToolPromise.then(ble => { + bleRef = ble; + ble.setVoiceUploading(true); + return send(startData, ' 开始包') + .then(() => { if (onProgress) onProgress(3); return waitPromise; }) + .then(() => { if (onProgress) onProgress(5); return delay(DELAY_AFTER_START); }) + .then(() => { + let seq = 0; + const sendNext = (offset) => { + if (offset >= total) { + return delay(DELAY_PACKET).then(() => send([ft, 2], ' 结束包')); + } + const chunk = bytes.slice(offset, Math.min(offset + chunkSize, total)); + const chunkData = [ft, 1, seq & 0xFF, (seq >> 8) & 0xFF, ...chunk]; + return send(chunkData, ` #${seq} 数据包`).then(() => { + seq++; + if (onProgress) onProgress(Math.min(100, Math.floor((offset + chunk.length) / total * 100))); + return delay(DELAY_PACKET).then(() => sendNext(offset + chunk.length)); + }); + }; + return sendNext(0); + }) + .then(() => { + if (onProgress) onProgress(100); + return { code: 200, msg: '语音文件已通过蓝牙上传' }; + }); + }).finally(() => { + if (bleRef) bleRef.setVoiceUploading(false); + }); } } diff --git a/pages/100J/HBY100-J.vue b/pages/100J/HBY100-J.vue index 7f81f53..6daba74 100644 --- a/pages/100J/HBY100-J.vue +++ b/pages/100J/HBY100-J.vue @@ -607,9 +607,9 @@ console.log(this.activePermissions, 'this.activePermissions'); these.fetchDeviceDetail(data.data.deviceId) } - // 尝试连接蓝牙:需先扫描获取 BLE deviceId,不能直接用 MAC + // 尝试连接蓝牙:需先扫描获取 BLE deviceId,不能直接用 MAC;延迟 500ms 确保蓝牙适配器就绪 if (data.data.deviceMac) { - these.tryConnect100JBle(data.data.deviceMac); + setTimeout(() => { these.tryConnect100JBle(data.data.deviceMac); }, 500); } }); this.createThrottledFunctions(); @@ -991,21 +991,30 @@ deviceRecovry(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) { const that = this; const macNorm = (m) => (m || '').replace(/:/g, '').toUpperCase(); const targetMacNorm = macNorm(deviceMac); 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 匹配 const cached = bleTool.data.LinkedList.find(v => { const m = macNorm(v.macAddress); 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) { console.log('[100J] 使用缓存设备连接', cached.deviceId); - bleTool.LinkBlue(cached.deviceId).then(() => { + bleTool.LinkBlue(cached.deviceId, SVC, WRITE, NOTIFY, 2).then(() => { console.log('100J 蓝牙连接成功(缓存)'); that.bleStateRecovry({ deviceId: cached.deviceId }); }).catch(err => { @@ -1015,18 +1024,29 @@ return; } - // 2. 无缓存:先尝试直连(Android 上 deviceId 可能为 MAC) - console.log('[100J] 尝试直连', deviceMac); - bleTool.LinkBlue(deviceMac).then(() => { - console.log('100J 蓝牙连接成功(直连)'); - that.bleStateRecovry({ deviceId: deviceMac }); - }).catch(err => { - console.log('100J 蓝牙直连失败,开始扫描', err); + // 2. 无缓存:先尝试直连。Android 上 deviceId 多为 MAC 反序(11:22:33:44:55:02->02:55:44:33:22:11) + const tryDirect = (id) => bleTool.LinkBlue(id, SVC, WRITE, NOTIFY, 2).then(() => { + console.log('100J 蓝牙连接成功(直连)', id); + that.bleStateRecovry({ deviceId: id }); + }); + const deviceIdReversed = macToDeviceId(deviceMac); + 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); }); }, connect100JByScan(deviceMac, last6) { 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; const timeout = 15000; const timer = setTimeout(() => { @@ -1050,7 +1070,7 @@ bleTool.StopSearch(); bleTool.removeDeviceFound('HBY100J_SCAN'); console.log('[100J] 扫描到目标设备', match.name, match.deviceId); - bleTool.LinkBlue(match.deviceId).then(() => { + bleTool.LinkBlue(match.deviceId, SVC, WRITE, NOTIFY, 2).then(() => { console.log('100J 蓝牙连接成功(扫描)'); that.bleStateRecovry({ deviceId: match.deviceId }); }).catch(err => { diff --git a/utils/BleHelper.js b/utils/BleHelper.js index 3b9d05c..683782a 100644 --- a/utils/BleHelper.js +++ b/utils/BleHelper.js @@ -61,7 +61,8 @@ class BleHelper { LinkedList: linkedDevices, //已连接的设备列表 platform: systemInfo.uniPlatform, Disconnect: [], //主动断开的设备 - connectingDevices: {} //正在连接的设备 + connectingDevices: {}, //正在连接的设备 + voiceUploading: false //语音上传中(上传中不主动断开蓝牙) } this.cfg = { onDeviceFound: [], //发现新设备的事件 @@ -561,7 +562,7 @@ class BleHelper { return; } if (!this.data.isOpenBlue) { - console.error("蓝牙模块未打开"); + console.log("蓝牙模块未打开,即将初始化"); resolve({ available: false, discovering: false @@ -1624,7 +1625,7 @@ class BleHelper { // console.log("正在连接" + deviceId); uni.createBLEConnection({ deviceId: deviceId, - timeout: 15000, + timeout: 20000, success: (info) => { //释放连接锁 @@ -1808,6 +1809,13 @@ class BleHelper { }); return prom; } + // 语音上传中不主动断开:设置/查询上传状态(App onHide 时检查) + setVoiceUploading(flag) { + this.data.voiceUploading = !!flag; + } + isVoiceUploading() { + return !!this.data.voiceUploading; + } //断开连接 disconnectDevice(deviceId) { if (this.data.platform == 'web') {