You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:28:11