GATT(Generic Attribute Protocol)的实质是由多个Attribute构成,每个Attribute都具有一定的信息量,当多个不同种类的Attribute组合在一起就反映出一个基本的service。

Attribute的基本内容和属性如下:
(1)Attribute type:UUID
UUID用来区分每个Attribute类型,全长为16字节。在/stack/service/hids.h中定义了一些标准的UUID。
(2)Atribute Handle
多个Attrbute组成一个Atribute Table,每个Attribute都有一个Attribute Handle值,用来区分不同的Attribute。slave和master建立连接后,master通过Service Discovery过程解析读取到slave的Attribute Table,并根据Attribute Handle的值来对应每一个不同的Attribute。
(3)Attribute Value
每个Attribute都有对应的Attribute Value,用来作为request、response、notification和indication的数据。
为了实现slave端的GATT service,泰凌微的SDK设计了一个Attribute Table,该Table由多个基本的Attribute组成。
Attribute结构体
typedef struct attribute
{
u16 attNum;
u8 perm;
u8 uuidLen;
u32 attrLen; //4 bytes aligned
u8* uuid;
u8* pAttrValue;
att_readwrite_callback_t w;
att_readwrite_callback_t r;
} attribute_t;
参数:
(1)attNum
表示当前Attribute Table中有效Attribute数目
Attribute Handle的值从0x0001开始,所以Attribute在数组中的下标号即Attribute Handle的值指定当前的service由哪几个Attribute构成
GATT_UUID_PRIMARY_SERVICE(0x2800),这个Attribute的attNum指定从当前Attribute开始往后数总共有attNum个Attribute属于该service的组成部分除了第0项Attribute和每一个service首个Attribute外,其他所有的Attribute的attNum的值都必须设为0
(2)perm
permission,指定当前Attribute被Client访问的权限。权限如下,Attribute的权限可以为如下某个或组合:
#define ATT_PERMISSIONS_READ 0x01
#define ATT_PERMISSIONS_WRITE 0x02
#define ATT_PERMISSIONS_AUTHEN_READ 0x61
#define ATT_PERMISSIONS_AUTHEN_WRITE 0x62
#define ATT_PERMISSIONS_SECURE_CONN_READ 0xE1
#define ATT_PERMISSIONS_READ 0x01
#define ATT_PERMISSIONS_WRITE 0x02
#define ATT_PERMISSIONS_AUTHEN_READ 0x61
#define ATT_PERMISSIONS_AUTHEN_WRITE 0x62
#define ATT_PERMISSIONS_SECURE_CONN_READ 0xE1
(3)uuid、uuidlen
UUID分两种:BLE标准的2 bytes UUID和Telink私有的16 bytes UUID。通过uuid和uuidLen可以同时描述这两种UUID。
uuid是一个指向flash的u8类型的指针,uuidlen为指针开始连续多少个字节(4)pAttrValue、attrLen
表示Attribute的value的指针及其长度,该指针涉及写操作,所以可能在RAM中,也可能在Flash中
(5)回调函数w
typedef int (*att_readwrite_callback_t)(u16 connHandle, void* p);
p指针指向master写命令的具体数值,实际p指向一片内存,结构体为rf_packet_att_data_t
int my_WriteCallback (void *p)
{
rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p;
int len = pw->l2cap - 3;
//add your code
//valid data is pw->dat[0] ~ pw->dat[len-1]
return 1;
}
调用时机:当slave收到的Attribute PDU的Attribute Opcode为
Attribute可以没有回调函数,若不写则填空指针0。没有回调函数,则向pAttrValue指针所指向的区域写入master传来的值,长度为l2caplen-3。
pAttrValue指向flash 或 attrLen长度不够,master的写操作越界等情况需要自己写回调函数(6)回调函数r
typedef int (*att_readwrite_callback_t)(u16 connHandle, void* p);
connHandle为master和slave之间的连接句柄,slave应用填BLS_CONN_HANDLE; master应该填BLM_CONN_HANDLE如果设置了回调函数,根据该函数的返回值决定是否回复Read Response/Read Blob Response。若为1则不回复,其他值则回复pAttrValue区域的长为attrLen的内容(没有设置回调函数也是这样)。
对于每个service都有一个Client Characteristic Configuration属性,它的UUID为0x2902,该描述符是给任何支持通知或指示功能的特性额外增加的,允许GATT服务端配置特征值为Notification或Indication
现在我们希望能够自己声明一个UUID位0x3F00的服务,并设置两个UUID:0x3F01和0x3F02,一个用来读一个用来写。注意每个声明的UUID的前面必须还要声明UUID为0x2803(my_characterUUID)的特性声明,它的属性值指针需要指示这个特性是Read/Write/Notify/Broadcast等属性权限、属性句柄(对端设备可通过属性句柄来访问该属性,起始值为1,然后逐个加1)、还有其16位的UUID。
1、添加Attribute
在app_att.c中找到my_Attributes变量,实际上就是填写前面介绍的Attribute结构体的内容。
static const attribute_t my_Attributes[] = {
{ATT_END_H - 1, 0,0,0,0,0}, // total num of attribute,ATT_END_H 为Attribute Handle的总个数
/* 格式为:属性个数,权限,uuid长度,属性长度,uuid指针,属性值指针,写回调函数,读回调函数 */
......在最下面添加自己的服务......
// APP /
{6,ATT_PERMISSIONS_READ,2,2,(u8*)(&my_primaryServiceUUID),(u8*)(&ServerDecl ), 0}, //①
{0,ATT_PERMISSIONS_READ,2,sizeof(my_AppDataIn),(u8*)(&my_characterUUID), (u8*)(my_AppDataIn), 0}, //②
{0,ATT_PERMISSIONS_WRITE,2,sizeof(DataInVal),(u8*)(&my_appDataInUUID),(u8*)(DataInVal), inComingDataCB, 0}, //③
{0,ATT_PERMISSIONS_READ,2,sizeof(my_AppDataOut),(u8*)(&my_characterUUID), (u8*)(my_AppDataOut), 0}, //④
{0,ATT_PERMISSIONS_RDWR,2,sizeof(DataOutVal), (u8*)(&my_appDataOutUUID),(u8*)(&DataOutVal), 0}, //⑤
{0,ATT_PERMISSIONS_RDWR,2,sizeof(DataCfgCCC),(u8*)(&clientCharacterCfgUUID), (u8*)(DataCfgCCC), cccUpdateCB},//⑥
};
下面来一行一行解释:
①首先需要使用UUID 0x2800来声明一个服务,服务的UUID自己指定,但不能和系统使用的重合
#define GATT_UUID_PRIMARY_SERVICE 0x2800 //!< Primary Service
#define SERVER_SERV_UUID 0x3F00 //自定义的服务UUID
static const u16 my_primaryServiceUUID = GATT_UUID_PRIMARY_SERVICE;
static const u16 ServerDecl = SERVER_SERV_UUID;
②使用UUID 0x2803声明Service下的Characteristic
#define GATT_UUID_CHARACTER 0x2803 //!< Characteristic
static const u16 my_characterUUID = GATT_UUID_CHARACTER;
#define APP_DATA_IN_UUID 0X3F01
static const u8 my_AppDataIn[5] = {
CHAR_PROP_WRITE,
U16_LO(APP_DATA_IN_CD_H), U16_HI(APP_DATA_IN_CD_H),
U16_LO(APP_DATA_IN_UUID), U16_HI(APP_DATA_IN_UUID)
};
CHAR_PROP_WRITE代表该属性是用来写的,即客户端可以写数据到该Attribute中APP_DATA_IN_CD_H是Attribute Handle,它从1开始递增,在app_att.h中的ATT_HANDLE变量中相应增加我们这边添加的6行Attribute的Handle定义③声明刚刚声明的Characteristic的Characteristic Value
static const u16 my_appDataInUUID = APP_DATA_IN_UUID;
/* 如果没有声明后面的回调函数,则客户端写的数据就默认写到数组中 */
static uint8_t DataInVal[SIMPLESTREAMSERVER_DATAINOUT_LEN] = {0};
typedef struct{
u8 type; /* Data Channel Type */
u8 rf_len; /* Data Channel Length */
u16 l2cap; /* L2CAP报文的长度 */
u16 chanid; /* Channel ID */
u8 att; /* Attribute Opcode */
u8 hl; /* Attribute handle低字节 */
u8 hh; /* Attribute handle高字节 */
u8 dat[200]; /* 该值根据自己设置的实际的MTU修改 */
}rf_packet_att_data_t;
/* 写回调函数 */
int inComingDataCB(u16 connHandle, void* p)
{
rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p;
/* L2CAP报文长度减去Attribute Opcode和Attribute Handle的长度即实际传输的数据的长度 */
int len = pw->l2cap - 3;
//数据保存在pw->dat[0] ~ pw->dat[len-1]中
return 1;
}
通过Notification向指定的UUID传输消息的报文格式如下:

这就对应了上面结构体中的内容。
④⑤声明客户端可读的Attribute的Chracteristic和Chracteristic Value,与②③类似
#define APP_DATA_OUT_UUID 0X3F02
static const u16 my_appDataOutUUID = APP_DATA_OUT_UUID;
static uint8_t DataOutVal[SIMPLESTREAMSERVER_DATAINOUT_LEN] = {0};
/* 通知的形式为Notification */
static const u8 my_AppDataOut[5] = {
CHAR_PROP_NOTIFY,
U16_LO(APP_DATA_OUT_CD_H), U16_HI(APP_DATA_OUT_CD_H),
U16_LO(APP_DATA_OUT_UUID), U16_HI(APP_DATA_OUT_UUID)
};
⑥我们还希望能够实时获取客户端Client Characteristic Configuration的使能状态
#define GATT_UUID_CLIENT_CHAR_CFG 0x2902 //!< Client Characteristic Configuration
static const u16 clientCharacterCfgUUID = GATT_UUID_CLIENT_CHAR_CFG;
/* 没有声明回调函数就写到这里 */
static u8 DataCfgCCC[3] = {0,0,0};
/* 客户端的写回调函数 */
int cccUpdateCB(u16 connHandle, void* p)
{
rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p;
int res = (pw->dat[1] << 8) | pw->dat[0];
//------------------
if(res == 0x0001)
{
/* ready */
}
return 1;
}
对于Client Characteristic Configuration的属性值如下:默认为0x0000,Notification使能时为0x0001。

资料:Bluetooth Core Specification V5.0