![]() |
![]() |
![]() |
GATT服务器应用程序(GATTServApp)存储和管理应用程序范围的属性表。各种配置文件使用此模块将其特性添加到属性表。蓝牙低功耗协议栈使用此模块来响应GATT客户端的发现请求。例如,GATT客户端可以发送Discover all Primary Characteristics消息。GATT服务器端的蓝牙低功耗协议栈接收到该消息,并使用GATTServApp查找并发送存储在属性表中的所有主要特性。这种类型的功能超出了本文档的范围,它们是在库代码中实现。可以从gattservapp_util.c
中定义配置文件以及BLE Stack API Reference
(GATTServApp部分)中描述的API来查看GATTServApp函数。这些功能包括查找特定属性和阅读或修改客户端特征配置。有关GATTServApp在应用程序中的功能的示例,请参见图46 .
下图示意了属性表的初始化,这幅图其实完全对应了GATT Services和Profile中的属性表。该流程图是程序上下文的具体实现过程。图中可以看出在程序中分别使用前面涉及的GGS_Addservice();GATTServApp_AddService();DevInfo_AddService();SimpleProfile_AddService();就这样一步一步在GATT Server中构建了属性表。
上电或重置时,应用程序使用GATTServApp来创建GATT表来添加服务。每个服务都包含具有UUID,值,权限以及读取和写入回调的属性列表。正如图46所示,所有的这些信息是通过GATTServApp传递到GATT和存储在堆栈中。
属性表初始化必须在应用程序初始化函数中出现,也就是simple_peripheral_init()。
//初始化GATT属性
GGS_AddService (GATT_ALL_SERVICES ); // GAP
GATTServApp_AddService (GATT_ALL_SERVICES ); // GATT属性
GATT属性的每个服务或组必须定义一个固定大小的属性表,该表被传递到GATT中。simple_gatt_profile.c中的此表定义如下。
static gattAttribute_t simpleProfileAttrTbl [ SERVAPP_NUM_ATTR_SUPPORTED ]
此表中的每个属性都是以下类型。
typedef struct attAttribute_t
{
gattAttrType_t type; //!< 属性类型(2或16个八位字节UUID)
uint8 permissions; //!< 属性权限
uint16 handle; //!< 属性句柄-由属性服务器内部分配
uint8* const pValue; //!< 属性值 -属性值的最大长度为512 octets。
} gattAttribute_t;
typedef struct
{
uint8 len ; //!
const uint8 * uuid ; //!<指向UUID的指针
} gattAttrType_t ;
其中长度可以是ATT_BT_UUID_SIZE(2字节)或 ATT_UUID_SIZE(16字节)。*uuid是指向蓝牙SIG(定义在gatt_uuid.c中)或在配置文件中定义的自定义UUID保留的数字的指针。
uint8权限
强制GATT客户端设备如何以及如何访问该属性的值。可能的权限在gatt.h中定义如下:
宏定义 | 16进制数 | 权限 |
---|---|---|
#define GATT_PERMIT_READ | 0x01 | 可读 |
#define GATT_PERMIT_WRITE | 0x02 | 可写 |
#define GATT_PERMIT_AUTHEN_READ | 0x04 | 认证后可读 |
#define GATT_PERMIT_AUTHEN_WRITE | 0x08 | 认证后可写 |
#define GATT_PERMIT_AUTHOR_READ | 0x10 | 授权后可读 |
#define GATT_PERMIT_AUTHOR_WRITE | 0x20 | 授权后可写 |
#define GATT_PERMIT_ENCRYPT_READ | 0x40 | 读需要加密 |
#define GATT_PERMIT_ENCRYPT_WRITE | 0x80 | 写需要加密 |
这里的认证、授权、加密后面GATT内存分配部分还会详细说明。
uint16 handle
handle不可修改,由属性服务器内部分配(顺序分配)。
uint8 * const pValue
pValue是指向属性值的指针。初始化后大小无法更改。最大大小为512个字节。
参考下面simple_gatt_profile service声明
// Simple Profile Service
{
{ ATT_BT_UUID_SIZE , primaryServiceUUID }, // type
GATT_PERMIT_READ , // permissions
0 , // handle
(uint8 * )&simpleProfileService // pValue
}
其中type设置的是蓝牙SIG规范定义的主服务UUID(0x2800)。权限为可读(GATT_PERMIT_READ),允许客户端读取该服务。pValue是指向服务的UUID的指针,自定义为0xFFF0。
// Simple Profile Service属性
static CONST gattAttrType_t simpleProfileService =
{
ATT_BT_UUID_SIZE ,
simpleProfileServUUID
};
参考下面simple_gatt_profile Characteristic1 Declaration。
//特征1声明
{
{ ATT_BT_UUID_SIZE , characterUUID }, // type
GATT_PERMIT_READ , // permissions
0 , // handle
&simpleProfileChar1Props // pValue
},
type设置为蓝牙SIG定义的characteristic UUID(0x2803)。权限为可读(GATT_PERMIT_READ),允许客户端读取该服务。对于characteristic Declaration中的pValue值,传递给GATTServApp,GATTServApp只需要characteristic Value的权限,所以在characteristic Declaration的pValue中值传递了characteristic Value的权限,可读可写(0x0A)。GATTServApp会自动构建characteristic value的handle和UUID。放入characteristic Declaration的pValue的2-4个字节中。
注意:characteristic Declaration中的pValue和characteristic Value中的权限是有区别的,前者是GATT客户端可以读取看见的属性,后者是characteristic Value的真实权限,这意味着他们必须要一致,不然会混淆GATT客户端的开发者。
这部分首先要明白一点,前面说的characteristic value 1,2,3,5都是客户端发送读写命令,然后服务端响应处理然后返回,但是characteristic4 value不具有读写权限,他是通知属性,意思就是characteristic4 value只能是服务端发送给客户端。下面代码设置了characteristic config 的可读可写属性,所以客户端可以配置characteristic4 value,比如当前就是禁用通知。客户端可以设置characteristic config 的值来控制通知属性。
参考simple_gatt_profile中Characteristic4配置。
// 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
},
如GATTServApp 模块所述,应用启动时,需要添加支持的GATT服务。每个配置文件都需要一个全局AddService函数,用于被应用程序调用。其中一些服务在协议栈中定义,如GAP GATT服务和GATT服务。用户定义的服务必须公开自己的AddService函数,该应用程序可以调用配置文件初始化。以SimpleProfile_AddService()为例,这些功能应该如下。
//分配客户端特征配置表
simpleProfileChar4Config = (gattCharCfg_t * )ICall_malloc (sizeof (gattCharCfg_t ) * linkDBNumConns );
if ( simpleProfileChar4Config == NULL )
{
return ( bleMemAllocError );
}
`GATTServApp_InitCharCfg ( INVALID_CONHANDLE , simpleProfileChar4Config );`
//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的注册应用程序回调函数如下:
bStatus_t SimpleProfile_RegisterAppCBs ( simpleProfileCBs_t * appCallbacks )
{
if ( appCallbacks )
{
simpleProfile_AppCBs = appCallbacks ;
返回 ( SUCCESS );
}
else
{
return ( bleAlreadyInRequestedMode );
}
}
其中回调typedef被定义如下。
typedef struct
{
simpleProfileChange_t pfnSimpleProfileChange ; //当特征值更改时调用
} simpleProfileCBs_t ;
然后,应用程序必须定义此类型的回调,并将其传递给具有SimpleProfile_RegisterAppCBs()函数的simple_gatt_profile。这发生在simple_peripheral.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事件被更新,应用程序就可以收到并处理改变的值。
如图所示当接收到来自GATT客户端的读取请求时,协议栈检查属性的权限,如果属性可读,则调用Profile读回调函数。Profile复制该值,并在profile层执行特定处理,由ATT_ReadRsp指令发送给协议栈,协议栈使用ATT_READ_RSP发送给客户端。
当收到来自GATT客户端的给定属性的写请求时,协议栈会检查属性的权限,如果该属性是允许写的,则调用该配置文件的回调。配置文件存储要写入的值,执行profile层响应的处理,并在需要时通知应用程序。下图显示了simple_gatt_profile中simpleprofileChar3的写入过程。
注意:协议栈程序最小化处理非常重要,在该示例中,应用程序通过消息队列的方式,在应用程序上下文中处理value改变之后额外的处理过程。
包含characteristic的配置文件应为应用程序提供set和get抽象功能,以读取和写入配置文件的characteristic。设置参数功能还包括如果相关characteristic有通知或指示属性,则检查并实现通知和指示的逻辑。下面的显示了在simple_gatt_profile中设置simpleProfileChacteristic4的这个例子。
例如,应用程序通过以下方法在simple_peripheral.c中将simpleProfileCharacteristic4初始化为0。
uint8_t charValue4 = 0;
SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR4, sizeof(uint8_t), &charValue4);
此函数的代码显示在以下代码片段中(来自simple_gatt_profile.c)。除了设置静态simpleProfileChar4的值之外,该函数还调用 GATTServApp_ProcessCharCfg(),因为它具有通知属性。此操作会强制GATTServApp检查GATT客户端是否已启用通知。如果是这样,GATTServApp会向GATT客户端发送此属性的通知。
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和ATT有效载荷结构必须动态分配内存。例如,发送GATT_Notification时必须分配缓冲区。这里有两种方法,一种是上面讲过的首选方式调用SimpleProfile_SetParameter();一种是直接调用如果使用发送GATT通知或指示的首选方法,一种是直接使用GATT_Notification()或GATT_Indication();其实本质上SimpleProfile_SetParameter()也是调用的GATT_Notification()或GATT_Indication()。
如果直接使用GATT_Notification()或GATT_Indication(),则必须如下添加内存管理(gattServApp_SendNotiInd中)。
注意:如果通知或指示的返回值为SUCCESS (0x00),则堆栈释放内存。
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_RegisterForMsgs(),可以接收额外的GATT消息来处理某些角落情况。这种情况可以在SimpleBLEPeripheral_processGATTMsg()中看到。目前处理以下三种情况。
// 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);
}
}
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);
}
else if (pMsg->method == ATT_MTU_UPDATED_EVENT)
{
// MTU size updated
DISPLAY_WRITE_STRING_VALUE("MTU Size: $d", pMsg->msg.mtuEvt.MTU, LCD_PAGE5);
}
© Copyright 2017, 成都乐控畅联科技有限公司.