一、背景
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]