七的博客

uni-app BLE蓝牙通信常见问题

网络通信JavaScript

uni-app BLE蓝牙通信常见问题

总结下最近在 ble 通信的时候,碰到的一些奇奇怪怪的问题。这里面会涉及 BLE 协议、网络通信、移动端系统等。

1. createBLEConnection:fail already connect

 {"errMsg":"createBLEConnection:fail already connect","errCode":-1,"code":-1}

上一次连接没有断开,APP端要做好连接状态的维护。如果出现这个错误,可以立即调用断开接口进行断开。

2. 数据写入蓝牙成功,但是蓝牙模块无应答

写入代码大致如下:

function write(deviceId, serviceId, characteristicId, arrayBuffer) {
    return new Promise((resolve, reject) => {
        uni.writeBLECharacteristicValue({
            deviceId: deviceId,
            serviceId: serviceId,
            characteristicId: characteristicId,
            value: arrayBuffer,
            success(res) {
                console.log('writeBLECharacteristicValue success', JSON.stringify(res))
                resolve(res)
            },
            fail(err) {
                console.error('writeBLECharacteristicValue error', JSON.stringify(err))
                reject(err)
            }
        })

    })
}

这里出现的情况有好几种,可以一个个排查,但是最简单明了的方式是直接抓包看报文。

2.1 安卓手机端蓝牙抓包

如果是安卓手机,抓包比较方便,直接去【设置】> 【开发者选项】> 【打开启用蓝牙HCI信息收集日志】。 这样手机的所有蓝牙通讯相关日志,就可以被抓到文件中。

不同的蓝牙通讯文件保存位置不一样,通常都是在 /sdcard/btsnoop_hci.log 文件里面。 将这个文件发送到电脑上。

adb shell pull /sdcard/btsnoop_hci.log 

这样就会把文件传输到电脑上,然后可以使用 Wireshark 进行分析。

2.2 蓝牙模块端抓包

蓝牙模块抓包就稍微更难一点,我们之前是去抓串口的数据。 具体的步骤是从蓝牙模块引 2 根线出来,然后通过转换口连接到电脑的 USB 口上。 然后电脑上安装一个串口监控软件即可,通过串口软件监控报文。

接线需要注意,RX 引脚用于接收数据,TX 引脚用于发送数据。接线的时候注意要交叉接线,比如蓝牙模块的 TX 应接到 RX,蓝牙模块的 RX 应接到 TX。

还有个需要注意的是,波特率要保持一致,否则收到的数据将会乱码。

3. writeBLECharacteristicValue:fail property not support

错误应答:

 {  
  "errMsg": "writeBLECharacteristicValue:fail property not support",  
  "errCode": 10007  
}

写入数据的时候要进行延时,延时主要在以下几个地方:

  • 刚连接上设备,不要立即写数据,延时 200-500 毫秒后再进行数据写入。
connectToBleDevice();

sleep(200);

writeDataToBle();

  • 如果应用层分帧,不要连续写入数据,每次写完可以适当休眠。
writeDataToBle();

sleep(200);

writeDataToBle();

4. 超过 20个字节的包发下去无响应

在网上搜了很多资料,都是说 BLE 限制了每包是 20个字节,超过的这个大小的发下去可能会被丢掉。

这个限制来自蓝牙核心规范(Bluetooth Core Specification),由蓝牙技术联盟(Bluetooth Special Interest Group, SIG)制定。

BLE 协议中也有 MTU 概念,MTU(Maximum Transmission Unit)定义了在一个 ATT 包中可以传输的最大数据量。默认的 MTU 大小是 23 字节,包括下面的数据:

  • 3 字节的 ATT 头
  • 20 字节的有效负载(payload)

选择 20 字节作为默认限制是为了在低功耗和传输效率之间取得平衡。

但是从蓝牙 4.2 开始,设备可以协商更大的 MTU,最大可达 512 字节。但这需要双方设备都支持。在 uni-app 中,可以使用 uni.setBLEMTU() 方法来尝试设置更大的 MTU:

uni.setBLEMTU({
  deviceId: '你的设备ID',
  mtu: 512,
  success: function(res) {
    console.log('MTU 设置成功,新的 MTU 大小为:', res.mtu)
  },
  fail: function(err) {
    console.error('MTU 设置失败:', err)
  }
})

不过在实际的测试中,MTU 设置太大大部分都没有效果。 通过 Wireshark 抓包可以看到,设置太大的 MTU 有时候会被拒绝掉,但是应用层又拿不到这个错误。

建议这个值设置 100左右的值,不要太大。另外需要注意的是,安卓跟IOS 处理 MTU 又有差异。

安卓的默认MTU也是23字节,20字节可以存放应用层数据。原生的安卓可以使用 BluetoothGatt.requestMtu(int mtu) 方法请求更大的MTU。理论上,安卓端支持的最大MTU是517字节,512字节应用层数据。 注意,是安卓端自己支持的,对端不一定支持。

iOS的默认MTU也是23字节,20字节可以存放应用层数据。但是iOS会自动进行MTU协商,不需要开发者手动请求,更坑的是开发者无法直接控制MTU大小。根据 IOS 官网文档,iOS支持的最大 MTU 通常是 185字节,180 个字节的应用层数据。

通过上面两点,如果你要做跨端同时支持 Android 以及 IOS ,那么 MTU 你只需要在 Android 环境下进行调整。

5. 获取ble设备服务出错 no service 10004 没有找到指定服务

出现这个错误,大概率是由于连接设备后,立马就去获取服务,这样获取服务是会失败的。

解决办法:连接成功后,增加延时几百毫秒再获取服务列表。

const deviceId = '设备ID';


function connectAndGetServices(deviceId) {
  return new Promise((resolve, reject) => {
    // 先连接设备
    uni.createBLEConnection({
      deviceId: deviceId,
      success: (res) => {
        console.log('设备连接成功');
        
        // 连接成功后,延时 500 毫秒再获取服务
        setTimeout(() => {
          uni.getBLEDeviceServices({
            deviceId: deviceId,
            success: (res) => {
              console.log('获取服务列表成功');
              resolve(res.services);
            },
            fail: (error) => {
              console.error('获取服务列表失败', error);
              reject(error);
            }
          });
        }, 500);
      },
      fail: (error) => {
        console.error('设备连接失败', error);
        reject(error);
      }
    });
  });
}

6. 安卓跟IOS平台获取的设备唯一标识不一样

Android 平台通常使用 MAC 地址作为设备的唯一标识,比如 11:22:33:AA:BB:CC。

但是 IOS 不一样,出于隐私考虑,不直接暴露设备的 MAC 地址。iOS 使用一个 UUID作为设备标识,这个 UUID 是由 iOS 系统生成的,格式类似 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX。

可以区分下处理:

function getDeviceIdentifier(device) {
  const platform = uni.getSystemInfoSync().platform;
  
  if (platform === 'android') {
    // Android 平台使用 deviceId。通常是 MAC 地址
    return device.deviceId;
  } else if (platform === 'ios') {
    // iOS 平台使用 UUID
    return device.uuid;
  } else {
    // 其他平台可能需要特殊处理
    console.warn('Unsupported platform for BLE device identification');
    return device.deviceId || device.uuid || '';
  }
}

7. createBLEConnection:fail operate time out

连接 Ble 设备后断开,再重新连接就有可能碰到这个错误。

解决办法:这个问题测试多次验证,只能通过开启关闭系统蓝牙功能、重启 APP 等方式恢复。

8. ble 设备返回的数据直接打印不出来

Uniapp 接口传入的值是 ArrayBuffer 类型的数据,这种类型不能直接打印或显示。

这种情况有 2 种方式处理:

  • 将 ArrayBuffer 转换为可读的十六进制字符串。
  • 使用 Uint8Array 来查看原始字节值。

一般我们会选择第一种方式打印输出:

arrayBufferToHex(buffer) {
  return Array.from(new Uint8Array(buffer))
    .map(bit => bit.toString(16).padStart(2, '0'))
    .join('');
},

9. 在不同的电脑上调试同一份代码效果不一样

升级 Hbuilder X 的版本,两台电脑保持一致。 不同的版本 Hbuilder 打包出来的程序可能会不一样。