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

一、背景

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]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容