Bootstrap

Bluedroid bta_gattc_start_discover源码剖析

温馨提示:

服务发现的相关流程请看上一篇博客Android Ble discoverServices分析-CSDN博客 本文主要解析Bluedroid bta_gattc_start_discover源码。

1. 前言

当BLE设备作为GATT客户端(GATT Client)连接到GATT服务器(GATT Server)后,它通常需要执行发现过程以了解服务器的GATT数据库结构。这包括服务(Services)、特征(Characteristics)和描述符等。bta_gattc_start_discover 函数正是用于启动这一过程的。

2. 工作流程

  1. 连接建立:首先,GATT客户端需要与GATT服务器建立连接。通过调用BlueDroid BTA_GATTC_Open来完成。
  2. 启动发现:连接建立后,GATT客户端会调用bta_gattc_start_discover来启动发现过程。这个函数可能会触发一系列的GATT请求,如“Read By Group Type Request”来请求主服务(Primary Services)的列表。
  3. 处理响应:当GATT服务器响应这些请求时,客户端会收到相应的GATT响应。这些响应由BlueDroid堆栈中的函数(如gatt_client_handle_server_rsp)处理,并根据需要进行进一步的操作。
  4. 缓存结果:发现过程中获取的信息(如服务列表、特征列表等)会被缓存起来,以便客户端后续使用。
  5. 完成发现:当所有必要的发现操作都完成后,会触发一个完成回调(如bta_gattc_disc_cmpl_cback),表示发现过程已经结束。

3. 源码分析

3.1. GATT实例

为能更直观理解,引入一个gatt实例。

[Service]
UUID: 0x1801
PRIMARY SERVICE
  [Unknown Characteristic]
  UUID: 0x2a05
  Properties: INDICATE
---
[Service]
UUID: 0x1800
PRIMARY SERVICE
  [Unknown Characteristic]
  UUID: 0x2a00
  Properties: READ

  [Unknown Characteristic]
  UUID: 0x2a01
  Properties: READ

  [Unknown Characteristic]
  UUID: 0x2aa6
  Properties: READ
---
[Service]
UUID: 0x5356
PRIMARY SERVICE
  [Unknown Characteristic]
  UUID: 0xfd02
  Properties: READ

  [Unknown Characteristic]
  UUID: 0xfd03
  Properties: NOTIFY
  Descriptors
    [Client Characteristic Configuration]
    UUID: 0x2902
    Properties: NOTIFY

  [Unknown Characteristic]
  UUID: 0xfd01
  Properties: READ, WRITE, NOTIFY

对应这gatt,当发现结束后,bta_gattc_explore_srvc_finished(...)中bta_gattc_display_cache_server()输出的logcat。

<================Start Server Cache =============>
Service: handle=0x0001, end_handle=0x0005, uuid=00001801-0000-1000-8000-00805f9b34fb
    Characteristic: declaration_handle=0x0002, value_handle=0x0003, uuid=00002a05-0000-1000-8000-00805f9b34fb, prop=0x20
Service: handle=0x0014, end_handle=0x001c, uuid=00001800-0000-1000-8000-00805f9b34fb
    Characteristic: declaration_handle=0x0015, value_handle=0x0016, uuid=00002a00-0000-1000-8000-00805f9b34fb, prop=0x02
    Characteristic: declaration_handle=0x0017, value_handle=0x0018, uuid=00002a01-0000-1000-8000-00805f9b34fb, prop=0x02
    Characteristic: declaration_handle=0x0019, value_handle=0x001a, uuid=00002aa6-0000-1000-8000-00805f9b34fb, prop=0x02
Service: handle=0x0028, end_handle=0xffff, uuid=00005356-0000-1000-8000-00805f9b34fb
    Characteristic: declaration_handle=0x0029, value_handle=0x002a, uuid=0000fd02-0000-1000-8000-00805f9b34fb, prop=0x02
    Characteristic: declaration_handle=0x002b, value_handle=0x002c, uuid=0000fd03-0000-1000-8000-00805f9b34fb, prop=0x10
        Descriptor: handle=0x002d, uuid=00002902-0000-1000-8000-00805f9b34fb
    Characteristic: declaration_handle=0x002e, value_handle=0x002f, uuid=0000fd01-0000-1000-8000-00805f9b34fb, prop=0x1a
<================End Server Cache =============>
  • 进入discover时的bta状态是BTA_GATTC_DISCOVER_ST,经过discover后,进入BTA_GATTC_CONN_ST。

  • discover任务是从ble peripheral获取service、characteristic、descriptor,存储在BLE HAL内存:p_clcb->p_srcb。过程中不会回调JNI函数。

  • discover流程:

    • 一次req/resp搜索出所有primary service,然后对每个service依次进行单一service发现流程。

    • 单一service发现。一次Req/Resp搜索include service;然后一次Req/Resp搜出所有Characteristic;

    • 对每个char,一次Req/Resp搜出Descriptors。

  • 执行完一步操作后,会调用gatt_end_operation。该函数的主要功能是触发下一步操作,而这是通过回调bta_gattc_disc_cmpl_cback。

  • 会把discover到的gatt db存储到cache文件,像/data/misc/bluetooth/gatt_cache_XXX,前提是配对了该peripheral,即条件“btm_sec_is_a_bonded_dev(p_srvc_cb->server_bda)”返回true。以目前常见的手机连接ble peripernal,是不配对的,即不会生成cache文件。

对示例,1 + (2+1) + (2+3) + (2+3) = 14,须14个packet req/resp。以下是每次req/resp对应的功能,最后一列是gatt_client_handle_server_rsp中执行的函数。

  1. 搜出3个service,每个service有s_handle、e_handle、uuid。gatt_client_handle_server_rsp中执行的是gatt_process_error_rsp。

  2. [#0单一service发现]

    1. 搜索include service,结果没有。gatt_process_error_rsp。

    2. 搜索所有Characteristic,有1个。gatt_process_read_by_type_rsp。

    3. 搜索#0号Char中的descriptor,结果没有。gatt_process_read_by_type_rsp。

  3. [#1单一service发现]

    1. 搜索include service,结果没有。gatt_process_error_rsp。

    2. [#1单一service发现]搜索所有Characteristic,有3个。gatt_process_read_by_type_rsp。

    3. [#1单一service发现]搜索#0号Char中的descriptor,结果没有。gatt_process_error_rsp。

    4. [#1单一service发现]搜索#1号Char中的descriptor,结果没有。gatt_process_error_rsp。

    5. [#1单一service发现]搜索#2号Char中的descriptor,结果没有。gatt_process_error_rsp。

  4. [#2单一service发现]

    1. 搜索include service,结果没有。gatt_process_error_rsp。

    2. [#2单一service发现]搜索所有Characteristic,有3个。gatt_process_read_by_type_rsp。

    3. [#2单一service发现]搜索#0号Char中的descriptor,结果没有。gatt_process_error_rsp。

    4. [#2单一service发现]搜索#1号Char中的descriptor,有一个。gatt_process_read_info_rsp。

    5. [#2单一service发现]搜索#2号Char中的descriptor,结果没有。gatt_process_error_rsp。

 接下开始bta_gattc_start_discover过程解析。

3.2. bta_gattc_start_discover

/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_act.cc
/** Start a discovery on server */
void bta_gattc_start_discover(tBTA_GATTC_CLCB* p_clcb,
                              UNUSED_ATTR const tBTA_GATTC_DATA* p_data) {
  log::verbose("conn_id:{} p_clcb->p_srcb->state:{}",
               loghex(p_clcb->bta_conn_id), p_clcb->p_srcb->state);

  if (((p_clcb->p_q_cmd == NULL ||
        p_clcb->auto_update == BTA_GATTC_REQ_WAITING) &&
       p_clcb->p_srcb->state == BTA_GATTC_SERV_IDLE) ||
      p_clcb->p_srcb->state == BTA_GATTC_SERV_DISC)
  /* no pending operation, start discovery right away */
  {
    p_clcb->auto_update = BTA_GATTC_NO_SCHEDULE; // 表示没有自动更新的计划

    if (p_clcb->p_srcb == NULL) {
      log::error("unknown device, can not start discovery");
      return;
    }

    /* set all srcb related clcb into discovery ST */
    // 复位一些标记位和数据, 包括把p_clcb->state改为BTA_GATTC_DISCOVER_ST
    bta_gattc_set_discover_st(p_clcb->p_srcb);

    // Before clear mask, set is_svc_chg to
    // 1. true, invoked by service changed indication
    // 2. false, invoked by connect API
    bool is_svc_chg = p_clcb->p_srcb->srvc_hdl_chg; //这个值决定了服务发现是由服务变更指示触发的,还是由连接API触发的

    /* clear the service change mask */
    p_clcb->p_srcb->srvc_hdl_chg = false;
    p_clcb->p_srcb->update_count = 0;
    p_clcb->p_srcb->state = BTA_GATTC_SERV_DISC_ACT;
    p_clcb->p_srcb->disc_blocked_waiting_on_version = false;

    auto cache_support =
        GetRobustCachingSupport(p_clcb, p_clcb->p_srcb->gatt_database);
    if (cache_support == RobustCachingSupport::W4_REMOTE_VERSION) {
      log::info(
          "Pausing service discovery till remote version is read conn_id:{}",
          p_clcb->bta_conn_id);
      p_clcb->p_srcb->disc_blocked_waiting_on_version = true;
      p_clcb->p_srcb->blocked_conn_id = p_clcb->bta_conn_id;
      return;
    }

    bta_gattc_continue_with_version_and_cache_known(p_clcb, cache_support,
                                                    is_svc_chg);
  }
  /* pending operation, wait until it finishes */
  else {
    p_clcb->auto_update = BTA_GATTC_DISC_WAITING;

    if (p_clcb->p_srcb->state == BTA_GATTC_SERV_IDLE)
      p_clcb->state = BTA_GATTC_CONN_ST; /* set clcb state */
  }
}

bta_gattc_start_discoverBLE是客户端在建立与服务器的连接后,用于启动启动对远端蓝牙设备的服务发现过程。根据当前的状态和条件决定是否立即开始服务发现,或者等待之前的操作完成后再进行。同时,它也处理了与服务变更和缓存支持相关的逻辑。

继续服务发现流程:如果不需要等待对端版本信息,则调用bta_gattc_continue_with_version_and_cache_known继续服务发现流程,根据缓存支持和是否由服务变更触发来决定后续操作。

3.3.bta_gattc_continue_with_version_and_cache_known

/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_act.cc
void bta_gattc_continue_with_version_and_cache_known(
    tBTA_GATTC_CLCB* p_clcb, RobustCachingSupport cache_support,
    bool is_svc_chg) {
  if (cache_support == RobustCachingSupport::UNSUPPORTED ||
      (IS_FLAG_ENABLED(skip_unknown_robust_caching) &&
       cache_support == RobustCachingSupport::UNKNOWN)) {
    // Skip initial DB hash read if no DB hash is known, or if
    // we have strong reason (due to interop,
    // or a prior discovery) to believe that it is unsupported.
    p_clcb->p_srcb->srvc_hdl_db_hash = false; //表示不需要处理数据库哈希
  }

  /* read db hash if db hash characteristic exists */
  if (bta_gattc_is_robust_caching_enabled() &&
      p_clcb->p_srcb->srvc_hdl_db_hash &&
      bta_gattc_read_db_hash(p_clcb, is_svc_chg)) {
    log::info("pending service discovery, read db hash first conn_id:{}",
              loghex(p_clcb->bta_conn_id));
    p_clcb->p_srcb->srvc_hdl_db_hash = false;
    return;
  }
  bta_gattc_start_discover_internal(p_clcb);
}
  • 鲁棒缓存是一种优化机制,允许GATTC在重新连接时快速恢复之前的服务发现结果,而无需重新发现所有服务。但是,这需要GATT服务器支持该特性,并提供数据库哈希值以便验证缓存的有效性。

  • 读取数据库哈希是可选的,取决于对端GATT服务器的支持情况、配置选项以及是否检测到服务变更。

  • 如果决定读取数据库哈希,则服务发现过程会暂时挂起,直到数据库哈希读取完成并验证缓存的有效性。 

3.4.bta_gattc_start_discover_internal

packages/modules/Bluetooth/system/bta/gatt/bta_gattc_act.cc
void bta_gattc_start_discover_internal(tBTA_GATTC_CLCB* p_clcb) {
  if (p_clcb->transport == BT_TRANSPORT_LE)
    L2CA_LockBleConnParamsForServiceDiscovery(p_clcb->p_srcb->server_bda, true);//锁定连接参数,以便在服务发现期间保持稳定的连接质量。这个步骤是可选的,取决于蓝牙堆栈的实现和配置
  //初始化与远程服务器相关的缓存。这个缓存用于存储服务发现的结果,以便在后续的连接中快速恢复。
  bta_gattc_init_cache(p_clcb->p_srcb);
  p_clcb->status = bta_gattc_discover_pri_service(
      p_clcb->bta_conn_id, p_clcb->p_srcb, GATT_DISC_SRVC_ALL); //启动主要服务的发现过程
  if (p_clcb->status != GATT_SUCCESS) {
    log::error("discovery on server failed");
    bta_gattc_reset_discover_st(p_clcb->p_srcb, p_clcb->status);
  } else
    p_clcb->disc_active = true; //表示当前正在进行服务发现
}

 最终执行了bta_gattc_discover_pri_service,其中参数disc_type是GATT_DISC_SRVC_ALL,指示GATTC启动一个过程来发现远程 GATTS上所有的主要服务(Primary Services)。

在BLE中,服务是数据的集合,这些数据通过一组特征(Characteristics)来暴露给 GATTC。主要服务是指那些可以在服务发现过程中被直接发现的服务,而次要服务(Secondary Services)则通常被包含在某个主要服务内部,并需要额外的步骤来发现。

GATT_DISC_SRVC_ALL 是一个发现类型,它告诉 GATTC使用 GATT Discover All Primary Services by UUID 过程(如果 UUID 设置为 NULL,则表示不特定于任何 UUID)来查找远程服务器上的所有主要服务。这个过程通常涉及发送一个包含特定 PDU的GATT请求给对端服务器,然后等待服务器响应包含服务列表的响应。

一旦 GATTC收到这些服务的列表,它就可以进一步发现每个服务的特征(Characteristics)和描述符(Descriptors),从而能够读取、写入或订阅这些服务的特定数据。

注意,尽管 GATT_DISC_SRVC_ALL 指示发现所有服务,但远程服务器可能不包含任何服务,或者出于安全或隐私原因,某些服务可能对特定的 GATTC不可见。

3.5.bta_gattc_discover_pri_service

/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_cache.cc
/** Start primary service discovery */
tGATT_STATUS bta_gattc_discover_pri_service(uint16_t conn_id,
                                            tBTA_GATTC_SERV* p_server_cb,
                                            tGATT_DISC_TYPE disc_type) {
  tBTA_GATTC_CLCB* p_clcb = bta_gattc_find_clcb_by_conn_id(conn_id);
  if (!p_clcb) return GATT_ERROR;

  if (p_clcb->transport == BT_TRANSPORT_LE) {
    return GATTC_Discover(conn_id, disc_
;