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