BLE蓝牙调用setCharacteristicChangeNotification接口报错2900007或2900099
=============================================================

#### 问题现象

在BLE蓝牙应用开发过程中，调用setCharacteristicChangeNotification接口时出现2900007或2900099报错，该如何排查解决？

#### 背景知识

*   [2900007](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/errorcode-bluetoothmanager#section2900007)表示接口调用超时。当client端向server端发起了请求，在一定时间内（约10s）client端没有收到server端的应答，client端就会返回此错误码。
*   [2900099](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/errorcode-bluetoothmanager#section2900099)表示接口调用操作失败。一般接口调用阻塞，会返回此错误码。
*   [setCharacteristicChangeNotification](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#setcharacteristicchangenotification)接口提供了client端启用或者禁用接收server端特征值内容变更通知的能力，使用前需仔细阅读接口下方说明。
*   调用setCharacteristicChangeNotification接口后，底层会默认通过描述符的形式向server端写入一次数据请求，server端可通过[descriptorWrite](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#ondescriptorwrite)接收请求，然后调用[sendResponse](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#sendresponse)接口向client返回数据，client成功接收到数据后，即一个完整的setCharacteristicChangeNotification接口请求流程才算完毕。

#### 问题定位

*   排查server端（server端以HarmonyOS NEXT设备为例）是否创建了[on('descriptorWrite')](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#ondescriptorwrite)监听。若server端没有创建此监听，将无法接收到client端发来的描述符请求，client端setCharacteristicChangeNotification接口将会处于持续请求的阻塞状态。
*   排查server端接收到client端发来的描述符请求后，是否及时应答（检查日志是否返回OnSetNotifyCharacteristic关键字）。若server端在接收到client端发来的描述符请求后没有及时调用sendResponse接口应答，client端setCharacteristicChangeNotification接口同样会处于持续请求的阻塞状态。参考错误日志如下：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/f9/v3/K3Y3QJHiSUqQI5AuIYLq1w/zh-cn_image_0000002628772390.png?HW-CC-KV=V1&HW-CC-Date=20260709T134216Z&HW-CC-Expire=86400&HW-CC-Sign=DFEFF4EF487299F5E80091ED29A5CE326580B17C6F508C53DB9BD48D23E9C3B6 "点击放大")
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/da/v3/vIQh6MQQR-O7Zb2D4QkzaQ/zh-cn_image_0000002658971711.png?HW-CC-KV=V1&HW-CC-Date=20260709T134216Z&HW-CC-Expire=86400&HW-CC-Sign=30065CEBBC1B3810646A124BA0751F17E80AB7571FB37B02342EBECB33D7E110 "点击放大")
    
*   检查client端调用setCharacteristicChangeNotification接口时，是否有其它异步接口调用未完成，导致setCharacteristicChangeNotification接口调用被阻塞。排查方式如下：
    *   通过在接口回调中设置日志打印，查看接口调用的完整顺序流程。从创建对象实例到数据传输，BLE蓝牙client端接口调用顺序参考如下：
        1.  调用[createGattClientDevice](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#blecreategattclientdevice)接口创建client实例。
        2.  创建BLE蓝牙连接状态监听、MTU变化监听、特征值变化监听等接口。
        3.  调用[connect](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#connect)接口连接BLE蓝牙。
        4.  调用[setBLEMtuSize](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#setblemtusize)接口协商MTU。
        5.  调用[getServices](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#getservices-1)接口获取server端支持的所有服务能力。
        6.  调用setCharacteristicChangeNotification接口设置server端特征值内容变更通知的能力。
        7.  调用[writeCharacteristicValue](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#writecharacteristicvalue)接口向server端写入特征值数据。
    *   排查系统日志输出。可在问题复现后[生成hilog日志](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hilog#hilog日志生成)，查看日志中各接口调用开始/完成时，系统日志输出的时间点，从而判断是否出现了接口调用阻塞情况。
        
        如：setCharacteristicChangeNotification接口调用开始时，系统日志中会打印出关键字setCharacteristicChangeNotification。接口调用完成时，可通过setCharacteristicChangeNotification接口Callback回调中自定义的日志进行判断。参考问题日志如下：
        
        ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/ea/v3/EUkUgx6mQc6-u9WSH-OMUQ/zh-cn_image_0000002628612500.png?HW-CC-KV=V1&HW-CC-Date=20260709T134216Z&HW-CC-Expire=86400&HW-CC-Sign=4AEBD75C124AE37F1C89A559F5C0053A379F81E7EDA5F237DB0026432CD1D862 "点击放大")
        

#### 分析结论

*   server端没有创建on('descriptorWrite')监听，或接收到client端发来的描述符请求后，没有及时应答。
*   在调用setCharacteristicChangeNotification接口前一般会先调用setBLEMtuSize异步接口，与server端协商MTU数据传输大小。然后再调用getServices接口，获取server端的特征值服务列表。
    
    因此，需要在setBLEMtuSize和getServices接口依次调用成功后，才可以调用setCharacteristicChangeNotification接口，设置接收server端特征值内容变更通知的能力。
    

#### 修改建议

*   client端调用setCharacteristicChangeNotification接口前，需开启server端订阅client的描述符写请求事件监听，同时在接收到描述符请求后，及时做出消息回复。
*   可对setBLEMtuSize、getServices和setCharacteristicChangeNotification接口调用顺序进行修改，并添加MTU变更事件监听，同时通过一一赋值的方式创建characteristic对象。

import { ble, constant } from '@kit.ConnectivityKit';
import { abilityAccessCtrl, common, PermissionRequestResult } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct setCharacteristicChangeNotification {
  @State gattClient: ble.GattClientDevice | undefined \= undefined;

  aboutToAppear(): void {
    let atManager: abilityAccessCtrl.AtManager \= abilityAccessCtrl.createAtManager();
    atManager.requestPermissionsFromUser(this.getUIContext()?.getHostContext() as common.UIAbilityContext,
      \['ohos.permission.ACCESS\_BLUETOOTH'\], (err: BusinessError, data: PermissionRequestResult) \=\> {
        if (err) {
          console.error(\`requestPermissionsFromUser fail, err-\>${JSON.stringify(err)}\`);
        } else {
          console.info(\`data:${JSON.stringify(data)}\`);
        }
      });
  }

  connect() {
   _ // 创建client端实例，server端虚拟mac地址,使用时,需要根据实际进行修改_
    try {
      this.gattClient \= ble.createGattClientDevice('deviceMAC');
    } catch (error) {
      console.error(\`createGattClientDevice error:${error}\`);
    }
   _ // 订阅ble蓝牙连接状态监听事件_
    this.onBLEConnectionStateChange();
  _  // 订阅MTU监听事件_
    this.BLEMtuChange();
 _   // 连接server端ble蓝牙_
    try {
      this.gattClient?.connect();
    } catch (error) {
      console.error(\`connect error:${error}\`);
    }
  }

  onBLEConnectionStateChange() {
    try {
      this.gattClient?.on('BLEConnectionStateChange', (state: ble.BLEConnectionChangeState) \=\> {
        if (state.state \=== constant.ProfileConnectionState.STATE\_CONNECTED) {
       _   // 连接成功，先与server协商MTU，参数范围23~517_
          try {
            this.gattClient?.setBLEMtuSize(128);
          } catch (error) {
            console.error(\`error:${JSON.stringify(error)}\`);
          }
        }
      });
    } catch (error) {
      console.error(\`on BLEConnectionStateChange error:${error}\`);
    }
  }

  BLEMtuChange() {
    try {
      this.gattClient?.on('BLEMtuChange', (mtu: number) \=\> {
     _   // MTU协商成功,调用getServices接口获取server服务。_
        console.info(\`协商成功,mtu参数为:${mtu}\`);
        this.getServices();
      });
    } catch (error) {
      console.error(\`on BLEMtuChange error:${error}\`);
    }
  }

  getServices() {
    this.gattClient?.getServices().then((result: Array<ble.GattService\>) \=\> {
      result.filter(item \=\> {
      _  // 筛选出指定特征值服务，并设置通知变更能力。_
        _// server端指定服务uuid,使用时,需要根据实际进行修改_
        if (item.serviceUuid \=== 'uuid') {
          let descriptors: Array<ble.BLEDescriptor\> = \[\];
          let arrayBuffer \= new ArrayBuffer(8);
          let descV \= new Uint8Array(arrayBuffer);
          descV\[0\] \= 11;
          let arrayBufferC \= new ArrayBuffer(8);
     _     // 通过一一赋值的方式创建characteristic对象_
          let characteristic: ble.BLECharacteristic \= {
            serviceUuid: item.serviceUuid,
            characteristicUuid: item.characteristics\[0\].characteristicUuid,
            characteristicValue: arrayBufferC,
            descriptors: descriptors
          };
          this.gattClient?.setCharacteristicChangeNotification(characteristic, true, (err: BusinessError) \=\> {
            if (err) {
              console.error('notifyCharacteristicChanged callback failed');
            } else {
              console.info('notifyCharacteristicChanged callback successful');
            }
          });
        }
      });
    }).catch((error: BusinessError) \=\> {
      console.error(\`getServices error:${error}\`);
    });
  }

  build() {
    Column() {
      Button('连接BLE蓝牙，并发起请求')
        .onClick(() \=\> {
         _ // 连接BLE蓝牙，并发起setCharacteristicChangeNotification__请求_
          this.connect();
        });
    }.height('100%')
    .width('100%')
    .justifyContent(FlexAlign.SpaceAround);
  }
}

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/9e/v3/gcL9LU37T4WPYN5agO1E5g/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134216Z&HW-CC-Expire=86400&HW-CC-Sign=C4E690B03CD50F71E88FE7BEBEC8CC707DC508D7B11EC8C34EAAC1DDF44583A1)

使用蓝牙能力时，需要在module.json5中添加ohos.permission.ACCESS\_BLUETOOTH权限。

#### 常见FAQ

Q：BLE蓝牙writeCharacteristicValue接口写入数据时，2900099报错是什么原因？

A：关于BLE写入数据报错2900099，有如下原因：

*   当上一个非监听类BLE蓝牙接口（setBLEMtuSize、getServices和setCharacteristicChangeNotification）回调还未返回时写入数据，会出现2900099报错提示，导致写入数据失败。因此，需要保证在其它非监听类BLE接口回调触发完成后，再调用writeCharacteristicValue接口写入数据。
*   每次重连GATT设备时都会重新创建新的gattClient对象，建立一路新的GATT连接。若每次连接关闭后不及时调用[close](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#close-1)接口销毁gattClient对象实例，则会导致每次重连时重复多次调用setCharacteristicChangeNotification和getService，出现busy现象，进而导致报错2900099。因此，每次连接关闭后，需及时销毁gattClient对象。
*   参数错误，系统日志会同时打印Invalid parameters，需要排查是否按GATT规范传入了正确的[BLECharacteristic](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-bluetooth-ble#blecharacteristic)。

Q：一个setCharacteristicChangeNotification接口能否同时设置多个特征值服务？

A：一个setCharacteristicChangeNotification只能设置一个特征值服务变更通知，如果需要对多个特征值服务进行设置，需要多次调用setCharacteristicChangeNotification接口。

Q：早期存量设备没有描述值不能提供descriptors字段，怎么调用setCharacteristicChangeNotification接口呢？

A：descriptor可以传入\[\]空列表调用setCharacteristicChangeNotification接口。