Cordova蓝牙BLE:相似函数在AngularJS $interval中表现异常
问题分析与解决方案
从你的代码和现象来看,问题的核心出在BLE长数据的分段读取处理上,和$interval本身无关,下面一步步拆解原因并给出修复方案:
为什么会出现这个差异?
BLE协议单包最大传输字节数通常是20字节(不同设备可能略有浮动):
isRunning返回的JSON数据很小(比如{"status":"ok"}),刚好能塞进一个BLE数据包,所以外设的onReadRequest只会被调用一次,Cordova端的$cordovaBluetoothLE.read也只会resolve一次Promise,你的then回调执行一次,$interval能正常按5秒间隔触发。- 当
getTemp返回的数据变大(比如{"status":"ok", "temp":23}或者大数值),数据长度超过单包限制,外设的onReadRequest会被多次调用(每次offset参数递增),而你当前的外设代码没处理offset,每次都返回完整数据;同时ng-cordova-bluetoothle的read方法在这种场景下,会每收到一段数据就resolve一次Promise,导致你的getTemp()的then回调被连续触发,看起来像是函数被无间隔调用。
修复步骤
1. 先修复外设端的分段读取逻辑
修改Node.js+bleno的getTemp特征代码,正确处理offset参数,让分段请求返回对应的数据段:
// GET TEMP CHARACTERISTIC new bleno.Characteristic({ value:null, uuid: settings.get_temp_characteristic_id, properties: ['read'], onReadRequest: function(offset, callback){ getTemp() .then( (res) => { var buffer = Buffer.from( JSON.stringify(res) ); // 处理分段读取:如果offset超过数据长度,返回错误 if (offset > buffer.length) { callback(this.RESULT_INVALID_OFFSET, null); return; } // 返回从offset开始的剩余数据 const dataChunk = buffer.slice(offset); callback(this.RESULT_SUCCESS, dataChunk); }) } })
同样的,建议把isRunning的特征也改成这个逻辑,避免后续数据变大时出现同样问题。
2. 封装Cordova端的完整读取函数
ng-cordova-bluetoothle的原生read不自动拼接分段数据,我们需要封装一个函数,自动读取所有分段并拼接成完整数据:
// 封装:读取BLE特征的完整数据,自动处理分段 function bleReadFull(address, serviceId, charId) { let fullData = new Uint8Array(); function readNextChunk(offset = 0) { const params = { address: address, service: serviceId, characteristic: charId, offset: offset }; return $cordovaBluetoothLE.read(params) .then(response => { // 把当前分段转为Uint8Array const chunk = new Uint8Array(response.value); // 拼接到完整数据中 fullData = new Uint8Array([...fullData, ...chunk]); // 判断是否读取完成:如果当前分段长度小于MTU(默认20),说明是最后一段 if (chunk.length < 20) { return fullData; } else { // 继续读取下一段,偏移量累加当前分段长度 return readNextChunk(offset + chunk.length); } }); } return readNextChunk(); }
3. 修改你的蓝牙读取函数
用上面封装的bleReadFull替代原生的$cordovaBluetoothLE.read,确保返回完整的base64数据:
function bleReadisRunning(){ var service_id = '12ab'; var characteristic_id = '32cd'; return bleReadFull($stateParams.address, service_id, characteristic_id) .then(fullData => { // 把Uint8Array转为base64字符串,和之前的逻辑兼容 const rawStr = String.fromCharCode(...fullData); return { value: btoa(rawStr) }; }); } function bleReadgetTemp(){ var service_id = '12ab'; var characteristic_id = '31cd'; return bleReadFull($stateParams.address, service_id, characteristic_id) .then(fullData => { const rawStr = String.fromCharCode(...fullData); return { value: btoa(rawStr) }; }); }
4. 可选优化:用递归定时器替代$interval(更可靠)
$interval是按固定时间间隔触发,不管前一次异步操作是否完成,如果蓝牙读取耗时超过5秒,会导致多个请求叠加。换成递归setTimeout可以确保前一次读取完成后再等待5秒执行下一次:
vm.startExperimentLoop = function() { vm.loopEnabled = true; executeLoop(); }; vm.apiStop = function(){ vm.loopEnabled = false; } function executeLoop() { if (!vm.loopEnabled) return; getTemp() .then(response => { // 这里处理你的业务逻辑 debugWrite("Response@getTemp: " + response + "\n"); if(!response.status){ // Stop the loop vm.loopEnabled = false; } }) .catch(error => { console.log("getTemp error:", error); }) .finally(() => { // 不管成功失败,只要loop还开启,就5秒后执行下一次 if (vm.loopEnabled) { setTimeout(executeLoop, 5000); } }); }
这样修改后,不管getTemp返回的数据多大,都会先完整读取所有分段,只触发一次then回调,定时器的5秒间隔就能正常工作了。
内容的提问来源于stack exchange,提问作者J. Doe
相关产品推荐
相关产品推荐

