CC2640R2F学习笔记(13)——GATT客户端发现服务和特征

159 阅读4分钟

一、背景

1.1 GATT协议

GATT(Generic Attributes Profile)的缩写,中文是通用属性协议,是已连接的低功耗蓝牙设备之间进行通信的协议。

一旦两个设备建立起了连接,GATT 就开始起作用了,这也意味着,你必需完成前面的GAP协议。

GATT使用了 ATT(Attribute Protocol)协议,ATT 协议把 Service,Characteristic 对应的数据保存在一个查找表中,查找表使用 16bit ID 作为每一项的索引。

GATT定义的多层数据结构简要概括起来就是 服务(Service) 可以包含多个 特征(Characteristic),每个特征包含 属性(Properties)值(Value),还可以包含多个 描述(Descriptor)

1.2 属性协议(ATT)

属性协议层 负责数据检索,允许一个设备暴露一些数据块给其他设备,其他设备称之为“属性”。

在ATT环境中,展示属性的设备称之为服务器,与它配对的设备称之为客户端。链路层的主机从机和这里的服务器、客服端是两种概念,主设备既可以是服务器,也可以是客户端。从设备毅然。

1.3 GATT通信中角色

从GATT的角度来看,处于连接状态时的两个设备,它们各自充当两种角色中的一种:

服务端(Server)

包含被GATT客户端读取或写入的特征数据的设备。

客户端(Client)

从GATT服务器中读取数据或向GATT服务器写入数据的设备。

外围设备(从机)作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的定义;

客户端和服务器的GATT角色独立于外围设备和中央设备的GAP角色。外围设备可以是GATT客户端或GATT服务器,中心可以是GATT客户端或GATT服务器

二、配置发现服务和特征参数

2.1 发现服务和特征相关结构体

// 发现状态
// Discovery states
typedef enum {
  BLE_DISC_STATE_IDLE,              // Idle
  BLE_DISC_STATE_MTU,               // Exchange ATT MTU size
  BLE_DISC_STATE_SVC,               // Service discovery
  BLE_DISC_STATE_CHAR               // Characteristic discovery
} discState_t;

// GAP连接时用
// pairing callback event
typedef struct
{
  uint16_t connectionHandle;        // connection Handle
  uint8_t state;                    // state returned from GAPBondMgr
  uint8_t status;                   // status of state
} gapPairStateEvent_t;

// GATT发现服务和特征时用
// discovery information
typedef struct
{
  discState_t discState;            // discovery state
  uint16_t svcStartHdl;             // service start handle
  uint16_t svcEndHdl;               // service end handle
  uint16_t charHdl;                 // characteristic handle
} discInfo_t;

2.2 发现服务相关宏

simpleBLEcentral 工程连接 simpleBLEperipheral 后,发现服务很慢;因为工程在连接之后默认为延时 1秒 才去发现服务,可以缩短这个时间,加快发现服务。

// Default service discovery timer delay in ms
#define DEFAULT_SVC_DISCOVERY_DELAY            1000

2.3 初始化GATT客户端

以SDK2.4 multi_role工程为例,在 multi_role_init() 初始化多角色应用程序函数中,

/*==================================== 客户端 ====================================*/
// 初始化GATT客户端
VOID GATT_InitClient();

// 注册GATT 本地事件和ATT响应等待传输
GATT_RegisterForMsgs(selfEntity);

// 注册当前任务为GATT的notify和indicate的接收端
// 如果不注册,无法接收从机通过GATT_Notification发来的数据
GATT_RegisterForInd(selfEntity);

三、发现服务

3.1 流程

建立连接,产生建立连接完成事件 GAP_LINK_ESTABLISHED_EVENT

multi_role_startDiscovery() 开始发现

multi_role_processGATTMsg() 处理GATT消息和事件,响应GATT发现

multi_role_processGATTDiscEvent() 处理GATT发现事件,陆续更改发现状态

发现服务/特征

3.2 执行发现函数

以SDK2.4 multi_role工程为例,在 multi_role_processRoleEvent() 处理多角色事件函数中,进入建立连接完成事件,执行开始发现函数 multi_role_startDiscovery()

GAP连接查看CC2640R2F学习笔记(10)——GAP主机端连接

switch(pEvent->gap.opcode)
{
    /*===================================== 建立链接事件 =====================================*/
    case GAP_LINK_ESTABLISHED_EVENT:
    {

        if(pEvent->gap.hdr.status == SUCCESS)                           // 如果建立链接成功
        {
            connecting = FALSE;                                         // 清除正在连接标志

            // Add index-to-connHandle mapping entry and update menus
            uint8_t index = multi_role_addMappingEntry(pEvent->linkCmpl.connectionHandle,
                                                       pEvent->linkCmpl.devAddr);

            if(linkDB_NumActive() >= maxNumBleConns)                    // 如果没有活跃链接,则关闭广播
            {
                uint8_t advertEnabled = FALSE;
                GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t),
                                     &advertEnabled, NULL);
            }

            // 开始发现
            multi_role_startDiscovery(pEvent->linkCmpl.connectionHandle);

            // Start periodic clock if this is the first connection
            if(linkDB_NumActive() == 1)
            {
                Util_startClock(&periodicClock);
            }
        }
        else                                                            // 如果建立链接失败
        {
            /* Display_print0(dispHandle, MR_ROW_STATUS1, 0, "Connect Failed"); */
            /* Display_print1(dispHandle, MR_ROW_STATUS2, 0, "Reason: %d", pEvent->gap.hdr.status); */
        }
    }
    break;
}

3.3 开始发现函数

以SDK2.4 multi_role工程为例,在multi_role.c中

/**
 @brief 开始发现
 @param connHandle 连接句柄
 @return none
*/
static void multi_role_startDiscovery(uint16_t connHandle)
{
    attExchangeMTUReq_t req;                                            // 交换MTU请求

    connIndex = multi_role_mapConnHandleToIndex(connHandle);            // 将连接句柄映射到索引

    if(connIndex < maxNumBleConns)                                      // 检查是否超过最大连接限制
    {
        discInfo[connIndex].discState= BLE_DISC_STATE_MTU;              // 更新此连接的发现状态

        discInfo[connIndex].svcStartHdl = 0;                            // 初始化缓存句柄
        discInfo[connIndex].svcEndHdl = 0;
    }

    req.clientRxMTU = maxPduSize - L2CAP_HDR_SIZE;                      // 发现GATT服务器的RX MTU大小

    VOID GATT_ExchangeMTU(connHandle, &req, selfEntity);                // ATT MTU大小应设置为客户端RX MTU和服务器RX MTU值的最小值
}

3.4 响应GATT发现

以SDK2.4 multi_role工程为例,在 multi_role_processGATTMsg() 处理GATT消息和事件函数中,执行发现函数 multi_role_startDiscovery() 后,进入这里

/*----------------------------------- GATT发现响应 -----------------------------------*/
else if(discInfo[connIndex].discState != BLE_DISC_STATE_IDLE)
{
    multi_role_processGATTDiscEvent(pMsg);    // 处理GATT发现事件
}

3.5 处理GATT发现事件

以SDK2.4 multi_role工程为例,在 multi_role_processGATTDiscEvent() GATT发现事件处理函数中,经过交换MTU大小后,更改发现状态,开始发现服务

发现状态更改流程:

BLE_DISC_STATE_IDLE // 初始化时为空闲状态

BLE_DISC_STATE_MTU // 交换MTU大小状态

BLE_DISC_STATE_SVC // 发现服务状态

BLE_DISC_STATE_CHAR // 发现特征状态

BLE_DISC_STATE_IDLE // 断连后恢复空闲状态

修改以下SIMPLEPROFILE_CHAR1_UUID(服务UUID)可以发现不同服务

static void multi_role_processGATTDiscEvent(gattMsgEvent_t *pMsg)
{
    ······
    ······
    /*--------------------------- 发现服务 ---------------------------*/
    else if(discInfo[connIndex].discState == BLE_DISC_STATE_SVC)
    {
        if(pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&            // 发现服务,存储句柄
            pMsg->msg.findByTypeValueRsp.numInfo > 0)
        {
            discInfo[connIndex].svcStartHdl =
                        ATT_ATTR_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
            discInfo[connIndex].svcEndHdl =
                        ATT_GRP_END_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
        }

        if(((pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP) &&         // 如果处理完成
            (pMsg->hdr.status == bleProcedureComplete))  ||
            (pMsg->method == ATT_ERROR_RSP))
        {
            if(discInfo[connIndex].svcStartHdl != 0)                // 如果已经发现了服务
            {
                attReadByTypeReq_t req;

                discInfo[connIndex].discState = BLE_DISC_STATE_CHAR;// 推进状态(发现特征)
                req.startHandle = discInfo[connIndex].svcStartHdl;
                req.endHandle = discInfo[connIndex].svcEndHdl;
                req.type.len = ATT_BT_UUID_SIZE;
                req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR1_UUID);
                req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR1_UUID);

                VOID GATT_DiscCharsByUUID(pMsg->connHandle,         // 发送发现特征请求
                                            &req, selfEntity);
            }
        }
    }
    ······
    ······
}

四、发现特征

以SDK2.4 multi_role工程为例,在 multi_role_processGATTDiscEvent() GATT发现事件处理函数中,经过发现服务后,更改发现状态,开始发现特征

static void multi_role_processGATTDiscEvent(gattMsgEvent_t *pMsg)
{
    ······
    ······
    /*--------------------------- 发现特征 ---------------------------*/
    else if(discInfo[connIndex].discState == BLE_DISC_STATE_CHAR)
    {
        if((pMsg->method == ATT_READ_BY_TYPE_RSP) &&                // 发现服务,存储句柄
            (pMsg->msg.readByTypeRsp.numPairs > 0))
        {
            discInfo[connIndex].charHdl = BUILD_UINT16(pMsg->msg.readByTypeRsp.pDataList[3],
                                                        pMsg->msg.readByTypeRsp.pDataList[4]);

            /* Display_print0(dispHandle, MR_ROW_STATUS1, 0, "Simple Svc Found"); */
        }
    }   
    ······
    ······
}

• 由 Leung 写于 2019 年 4 月 3 日

• 参考:simplelink_cc2640r2_sdk_2_40_00_32 [提取码:3pg6]