# GATT ServApp 模块 # GATT 服务器应用程序(GATT ServApp)用于存储和管理应用程序范围的属性表,各种配置文件使用此模块将其特性添加到属性表。 蓝牙低功耗协议栈使用此模块来响应 GATT 客户端的发现请求。例如,GATT 客户端可以发送** Discover all Primary Characteristics **消息,GATT 服务器端的蓝牙低功耗协议栈接收到该消息,使用 GATT ServApp 查找并发送存储在属性表中的所有主要特性。 这种类型的功能超出了本文档的范围,它们是在库代码中实现的。您可以从 `gattservapp_util.c` 中定义配置文件,也可以从 `BLE Stack API Reference`( GATT ServApp 部分)中描述的 API 来查看 GATT ServApp 函数,函数功能包括查找特定属性和阅读修改客户端特征配置。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/stack/gatt_servapp_module/Images/Attribute.png) 图1. GATT ServApp 的功能示例 GATT ServApp 在应用程序中的功能示例如图 1,图中示意了属性表的初始化,对应着 GATT Services 和 Profile 中的属性表。该流程图是程序上下文的具体实现过程,程序中分别使用了 GGS_Addservice()、GATTServApp_AddService()、DevInfo_AddService()、SimpleProfile_AddService(),一步一步在 GATT Server 中构建了属性表。 ## 构建属性表 ## 上电或重置时,应用程序通过 GATT ServApp 创建 GATT 表添加服务,每个服务都包含具体 UUID 值、权限以及读取和写入回调的属性列表。正如图 1 所示,所有这些信息是通过 GATT ServApp 传递到 GATT 并存储在堆栈中。 属性表初始化必须在应用程序初始化函数中出现,也就是 simple_peripheral_init()。 ````C //初始化 GATT 属性 GGS_AddService (GATT_ALL_SERVICES ); // GAP GATTServApp_AddService (GATT_ALL_SERVICES ); // GATT 属性 ```` ## 在属性表中实现配置文件 ## ### 属性表的定义 ### GATT 属性的每个服务或组必须定义一个固定大小的属性表,该表被传递到 GATT 中。 simple_gatt_profile.c 中的此表定义如下: ````C static gattAttribute_t simpleProfileAttrTbl [ SERVAPP_NUM_ATTR_SUPPORTED ] ```` 表中的每个属性都是以下类型: ````C typedef struct attAttribute_t { gattAttrType_t type; //!< 属性类型(2 或 16 个八位字节 UUID) uint8 permissions; //!< 属性权限 uint16 handle; //!< 属性句柄-由属性服务器内部分配 uint8* const pValue; //!< 属性值 -属性值的最大长度为 512 octets。 } gattAttribute_t; ```` - ** gattAttrType_t 类型** type 是与放置在表中的属性相关联的 UUID 。gattAttrType_t 本身定义为: ````C typedef struct { uint8 len ; //!注意:characteristic Declaration 中的 pValue 和 characteristic Value 中的权限是有区别的,前者是 GATT 客户端可以读取看见的属性,后者是 characteristic Value 的真实权限,这意味着他们必须要一致,不然会混淆 GATT 客户端的开发者。 ### 客户端 Characteristic Configuration ### 首先要明白一点,前面说的 characteristic value 1、2、3、5 都是客户端发送读写命令,然后服务端响应处理并返回。但 characteristic 4 value 不具有读写权限,它仅仅是通知属性,只能是服务端发送给客户端。 下面代码设置了 characteristic config 的可读可写属性,客户端可以配置 characteristic 4 value,比如当前就是禁用通知。客户端可以设置 characteristic config 的值来控制通知属性。 参考 simple_gatt_profile 中 Characteristic 4 配置: ````C // Characteristic Value 4 { { ATT_BT_UUID_SIZE, simpleProfilechar4UUID }, 0, 0, &simpleProfileChar4 }, //Characteristic 4 configuration { { ATT_BT_UUID_SIZE , clientCharCfgUuID }, GATT_PERMIT_READ | GATT_PERMIT_WRITE , 0 , (uint8 * )&simpleProfileChar4Config }, ```` ## 添加服务功能 ## 如 GATT ServApp 模块所述,应用启动时需要添加支持的 GATT 服务。每个配置文件都需要一个全局 AddService 函数,用于被应用程序调用。其中一些服务在协议栈中定义,如 GAP GATT 服务和 GATT 服务。用户定义的服务必须公开自己的 AddService 函数,该应用程序可以调用配置文件初始化。以 SimpleProfile_AddService()为例,这些功能应该如下: - 为客户端特征配置(CCC)阵列分配空间。作为示例,指向这些数组之一的指针在配置文件中初始化,如 client_characteristic_configuration 中所述。 AddService 函数需要声明支持的连接,并为每个数组分配内存。只有一个 CCC 在 simple_gatt_profile 中定义,但可以有多个 CCC 。当有多个设备连接服务端,服务端的 simpleProfileChar4Config 必须为每个连接了的客户端分配一个内存,用于为每个设备保存通知配置。 ````C //分配客户端特征配置表 simpleProfileChar4Config = (gattCharCfg_t * )ICall_malloc (sizeof (gattCharCfg_t ) * linkDBNumConns ); if ( simpleProfileChar4Config == NULL ) { return ( bleMemAllocError ); } ```` - 使用下面的函数初始化 CCC 数组,这样 CCC 值在掉电之后也能够保存下来。可以在连接设备时尝试使用之前保存的值,如果连接设备是新的设备则使用默认值。 ````C `GATTServApp_InitCharCfg ( INVALID_CONHANDLE , simpleProfileChar4Config );` ```` - 使用 GATTServApp_RegisterService 注册配置文件。该函数功能是将配置文件的属性表传递给 GATT ServApp,以便将配置文件的属性添加到由协议栈管理的应用程序属性表中,并为每个属性分配句柄。这也将配置文件的回调指针传递给堆栈,以启动 GATT ServApp 和配置文件之间的通信。 ````C //Register GATT attribute list and CBs with GATT Server App status = GATTServApp_RegisterService ( simpleProfileAttrTbl , GATT_NUM_ATTRS ( simpleProfileAttrTbl ), GATT_MAX_ENCRYPT_KEY_SIZE , &simpleProfileCBs ); ```` ## 注册应用程序回调函数 ## 配置文件可以使用回调将邮件中继到应用程序。在 simple_peripheral 项目中,只要 GATT 客户端写入特征值,simple_gatt_profile 将调用应用程序回调。如果要使用这些应用程序回调,配置文件必须定义一个注册应用程序回调函数,该应用程序在初始化期间用于设置回调。simple_gatt_profile 的注册应用程序回调函数如下: ````C bStatus_t SimpleProfile_RegisterAppCBs ( simpleProfileCBs_t * appCallbacks ) { if ( appCallbacks ) { simpleProfile_AppCBs = appCallbacks ; 返回 ( SUCCESS ); } else { return ( bleAlreadyInRequestedMode ); } } ```` 其中回调 typedef 被定义为: ````C typedef struct { simpleProfileChange_t pfnSimpleProfileChange ; //当特征值更改时调用 } simpleProfileCBs_t ; ```` 然后,应用程序必须定义此类型的回调,并将其传递给具有 SimpleProfile_RegisterAppCBs()函数的 simple_gatt_profile 。这发生在 simple_peripheral.c 中,代码如下所示: ````C //简单的 GATT 配置文件回调 #ifndef FEATURE_OAD_ONCHIP static simpleProfileCBs_t SimpleBLEPeripheral_simpleProfileCBs = { SimpleBLEPeripheral_charValueChangeCB //特征值更改回调 }; #endif //!FEATURE_OAD_ONCHIP // ... //使用 SimpleGATTprofile 注册回调 SimpleProfile_RegisterAppCB (&SimpleBLEPeripheral_simpleProfileCBs ); ```` 上述操作完成之后,一旦 characteristic 中 value 改变,SBP_CHAR_CHANGE_EVT 事件被更新,应用程序就可以收到并处理改变的值。 ## 客户端读请求 ## 如图 2 所示当接收到来自 GATT 客户端的读取请求时,协议栈先检查属性的权限。如果属性可读,则调用 Profile 读回调函数。Profile 复制该值,并在 profile 层执行特定处理,由 ATT_ReadRsp 指令发送给协议栈,协议栈使用 ATT_READ_RSP 发送给客户端。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/stack/gatt_servapp_module/Images/read.png) 图2. 客户端读请求流程 ## 客户端写请求 ## 当收到来自 GATT 客户端给定属性的写请求时,协议栈会检查属性的权限。如果该属性是允许写的,则调用该配置文件的回调。配置文件存储要写入的值,执行 profile 层的响应处理,并在需要时通知应用程序。图 3 显示了 simple_gatt_profile 中 simpleprofileChar3 的写入过程。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/stack/gatt_servapp_module/Images/write.png) 图3. 客户端写请求流程 > 注意:协议栈程序最小化处理非常重要,在该示例中,应用程序在上下文中通过消息队列的方式处理 value 改变之后的额外过程。 ## 获取和设置函数 ## 包含 characteristic 的配置文件应为应用程序提供 set 和 get 抽象功能,以读取和写入配置文件的 characteristic 。设置参数功能还包括如果相关 characteristic 有通知或指示属性,则检查并实现通知和指示的逻辑。 下图 4 是在 simple_gatt_profile 中设置 simpleProfile Chacteristic 4 的例子。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/stack/gatt_servapp_module/Images/setandget.png) 图4. 设置 Chacteristic 4 示例 应用程序在 simple_peripheral.c 中将 simpleProfile Characteristic 4 初始化为 0 ,代码如下: ````C uint8_t charValue4 = 0; SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR4, sizeof(uint8_t), &charValue4); ```` 以下代码片段在 simple_gatt_profile.c 中,除了设置静态 simpleProfileChar4 的值之外,该函数还调用 GATTServApp_ProcessCharCfg()。这个调用操作会强制 GATT ServApp 检查 GATT 客户端是否已启用通知,如果启用,GATT ServApp 会向 GATT 客户端发送此属性的通知。 ````C bStatus_t SimpleProfile_SetParameter( uint8 param, uint8 len, void *value ) { bStatus_t ret = SUCCESS switch ( param ) { case SIMPLEPROFILE_CHAR4: if ( len == sizeof ( uint8 ) ) { simpleProfileChar4 = *((uint8*)value); // See if Notification has been enabled GATTServApp_ProcessCharCfg( simpleProfileChar4Config, &simpleProfileChar4, FALSE, simpleProfileAttrTbl, GATT_NUM_ATTRS( simpleProfileAttrTbl ), INVALID_TASK_ID, simpleProfile_ReadAttrCB ); } ```` ## 队列写 ## 当接收到多个写指令是,GATT 服务端通过排队方式处理更多的有效数据。默认队列大小为 5 ,默认 MTU 为 23,有效载荷为 18 字节,最多可以接收 90 个字节的有效载荷。有关排队写入的更多信息,请参阅蓝牙核心规范版本 5.0 的排队写入部分([Vol 3],F部分,第3.4.6节)。 使用 GATTServApp_SetParameter()与参数调整队列大小 GATT_PARAM_NUM_PREPARE_WRITES。虽然没有指定的限制,但它由可用的 HEAPMGR 空间限定。 ## 为 GATT 程序分配内存 ## GATT 和 ATT 有效载荷结构必须动态分配内存。例如,发送 GATT_Notification 时必须分配缓冲区。这里有两种方法,一种是上面讲过的首选方式调用 SimpleProfile_SetParameter(),另一种是直接使用 GATT_Notification()或 GATT_Indication()。其实本质上S impleProfile_SetParameter()也是调用的 GATT_Notification()或 GATT_Indication()。 如果直接使用 GATT_Notification()或 GATT_Indication(),则必须如下添加内存管理(gattServApp_SendNotiInd中)。 1. 尝试使用 GATT_bm_alloc()为通知或指示有效载荷分配内存。 2. 如果分配成功,则使用 GATT_Notification()或 GATT_Indication()发送通知或指示。 > 注意:如果通知或指示的返回值为 SUCCESS (0x00),则堆栈释放内存。 3. 如果返回值不是 SUCCESS,则使用 GATT_bm_free()来释放内存。以下是 GATTServApp_SendNotiInd gattservapp_util.c 文件中函数的一个示例。 ````C noti.pValue = (uint8 *)GATT_bm_alloc( connHandle, ATT_HANDLE_VALUE_NOTI, GATT_MAX_MTU, &len ); if ( noti.pValue != NULL ) { status = (*pfnReadAttrCB)( connHandle, pAttr, noti.pValue, ¬i.len, 0, len, GATT_LOCAL_READ ); if ( status == SUCCESS ) { noti.handle = pAttr->handle; if ( cccValue & GATT_CLIENT_CFG_NOTIFY ) { status = GATT_Notification( connHandle, ¬i, authenticated ); } else // GATT_CLIENT_CFG_INDICATE { status = GATT_Indication( connHandle, (attHandleValueInd_t *)¬i, authenticated, taskId ); } } if ( status != SUCCESS ) { GATT_bm_free( (gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI ); } } else { status = bleNoResources; } ```` ## 注册接收应用程序中的其他 GATT 事件 ## 使用 GATT_RegisterForMsgs() 可以接收额外的 GATT 消息处理某些角落情况,这些情况可以在 SimpleBLEPeripheral_processGATTMsg() 中看到,目前处理以下三种情况。 - 堆栈中的 GATT 服务器无法发送 ATT 响应(由于缺少可用的 HCI 缓冲区):尝试在下一个连接间隔发送。另外,如果在蓝牙核心规范版本 5.0 中指定的 ATT 事务在 30 秒内未完成,则会发送 bleTimeout 状态。 ````C // See if GATT server was unable to transmit an ATT response if (pMsg->hdr.status == blePending) { //No HCI buffer was available. Let's try to retransmit the response //on the next connection event. if (HCI_EXT_ConnEventNoticeCmd(pMsg->connHandle, selfEntity, SBP_CONN_EVT_END_EVT) == SUCCESS) { //First free any pending response SimpleBLEPeripheral_freeAttRsp(FAILURE); //Hold on to the response message for retransmission pAttRsp = pMsg; //Don't free the response message yet return (FALSE); } } ```` - ATT 流量控制违规:通知应用程序连接的设备违反 ATT 流量控制规范,例如发送指示确认之前发送读取请求。在连接过程中,无需更多的 ATT 请求或指示。应用程序可能希望由于此违规而终止连接。作为 simple_peripheral 的一个例子,display 被更新。 ````C else if (pMsg->method == ATT_FLOW_CTRL_VIOLATED_EVENT) { //ATT request-response or indication-confirmation flow control is //violated. All subsequent ATT requests or indications will be dropped. //The app is informed in case it wants to drop the connection. //Display the opcode of the message that caused the violation. DISPLAY_WRITE_STRING_VALUE("FC Violated: %d", pMsg->msg.flowCtrlEvt.opcode, LCD_PAGE5); } ```` - 更新 ATT MTU大小:通知应用程序,以防任何方式影响其处理。有关 MTU 的更多信息,请参见最大传输单元(MTU)。作为 simple_peripheral 的一个例子,display 被更新。 ````C else if (pMsg->method == ATT_MTU_UPDATED_EVENT) { // MTU size updated DISPLAY_WRITE_STRING_VALUE("MTU Size: $d", pMsg->msg.mtuEvt.MTU, LCD_PAGE5); } ```` ## 加入我们 ## 文章所有代码、工具、文档开源。加入我们[**QQ群 591679055**](http://shang.qq.com/wpa/qunwpa?idkey=d94f12d37c3b37892af4b757c6dc34bea140f3f3128a8d68e556a3d728148e85)获取更多支持,共同研究CC2640R2F&BLE5.0。

CC2640R2F&BLE5.0-乐控畅联 © Copyright 2017, 成都乐控畅联科技有限公司.