通用属性配置文件协议(GATT)是在属性协议(ATT)之上构建的为传输的数据建立共同的操作规范,数据以ATT协议的形式存储。
Gatt定义了两个角色:服务器和客户。 Gatt角色不一定与特定的角色有关,而是可以通过较高的profile指定。gatt和att不特定用于BR/EDR和LE的传输,但是,因为Gatt和ATT用于发现服务,在LE中是必须的。
GATT server存储通过ATT协议传输的数据,以及接受来自的ATT协议的请求、命令和来自client的确认。GATT server发送对请求的响应,当被配置时,向client发送指示和异步通知。
GATT Profile定义了数据传输的数据结构,如server(服务)、characteristic(特征),如下如所示,一个GATT Profile由一个或者多个server组成,每个server由一个或者多个characteristic组成,由于GATT是基于ATT协议的,组成数据的最基本单位是attribute,在attribute之上封装得到server和characteristic,我们读写的对象就是这些attribute。可以说,gatt编程,是对attribute的读写。
gatt的server和attribute通过uuid唯一标识,SIG制定了不同行业应用的uuid标准,开发相应应用时,需满足这些标准,如心率检测的uuid是0x2A37。
在ble应用中,角色分为中心设备和外设,中心设备一般是手机、笔记本电脑,连接中心设备的外围设备是外设(如键盘、鼠标、传感器),在gatt的角色划分中,外设一般作为server,中心设备作为client通过ATT协议访问外设的属性。
fr8016的协议栈为用户提供了gatt_add_service用于注册服务,用户向协议栈注册属性表gatt_attribute_t和属性操作回调gatt_msg_handler_t,当对端设备发起读写请求时,协议栈会回调gatt_msg_handler,由用户处理对端的读写请求。
typedef struct
{
const gatt_attribute_t *p_att_tb; //!< Service's attributes table to add to system attribute database.
uint8_t att_nb; //!< Service's attributes number.
gatt_msg_handler_t gatt_msg_handler; //!< Read request callback function.
} gatt_service_t;
用户填充gatt_service_t结构体,调用gatt_add_service完成服务的注册,不需要关心协议的实现,只需要关心业务的处理。
进入components/ble/profile文件夹,可以看到很多profile的实现,现在以心率检测为例。
调用gatt_add_service注册心率检测服务。
void hr_gatt_add_service(void)
{
static gatt_service_t service;
service.p_att_tb = heart_rate_att_table;
service.att_nb = HEART_RATE_NB;
service.gatt_msg_handler = hr_gatt_msg_handler;
hr_svc_id = gatt_add_service(&service);
}
示例profile提供了对BODY_SENSOR_LOCATION_VAL属性的读请求响应,向请求端返回位置数据,如下所示。
static void hr_gatt_read_cb(uint8_t* p_read,uint16_t * len,uint16_t att_idx)
{
switch(att_idx)
{
case BODY_SENSOR_LOCATION_VAL:
for(int i= 0;i < HR_SERVICE_DATA_VAL_LEN;i++)
hr_bodysensor_vlaue[i] = hr_bodysensor_vlaue[0] + i + 1;
memcpy(p_read,hr_bodysensor_vlaue,HR_SERVICE_DATA_VAL_LEN);
*len = HR_SERVICE_DATA_VAL_LEN;
default:
break;
}
}
示例profile提供了对HEARTRATE_CONTROL_POINT_VAL属性的写请求响应,接收client的控制信息,如下所示。
static void hr_gatt_write_cb(uint8_t *write_buf,uint16_t len,uint16_t att_idx)
{
for(int i = 0; i < len; i++)
{
co_printf(" HR Write request: len: %d, 0x%x \r\n", len, write_buf[i]);
}
if(att_idx == HEARTRATE_CONTROL_POINT_VAL)
memcpy(hr_control_val,write_buf,len);
uint16_t uuid = BUILD_UINT16(heart_rate_att_table[att_idx].uuid.p_uuid[0],heart_rate_att_table[att_idx].uuid.p_uuid[1]);
if(uuid == GATT_CLIENT_CHAR_CFG_UUID)
{
hr_measurement_buf[1] = write_buf[1];
}
}
上面介绍了心率检测服务的读写请求,如下函数则是用户向协议栈注册的服务处理函数。
static uint16_t hr_gatt_msg_handler(gatt_msg_t *p_msg)
{
switch(p_msg->msg_evt)
{
case GATTC_MSG_READ_REQ:
hr_gatt_read_cb((uint8_t*)(p_msg->param.msg.p_msg_data ),&(p_msg->param.msg.msg_len),p_msg->att_idx);
break;
case GATTC_MSG_WRITE_REQ:
hr_gatt_write_cb((uint8_t*)(p_msg->param.msg.p_msg_data),(p_msg->param.msg.msg_len),p_msg->att_idx);
break;
default:
break;
}
return p_msg->param.msg.msg_len;
}
HEART_RATE_SERVICE有三个characteristic:
属性表定义了心率检测服务提供给对端操作的属性,如下所示。
const gatt_attribute_t heart_rate_att_table[HEART_RATE_NB] = {
//Simple gatt Service Declaration
[HEART_RATE_SERVICE] = {
{UUID_SIZE_2, UUID16_ARR(GATT_PRIMARY_SERVICE_UUID)},
GATT_PROP_READ,
UUID_SIZE_2,
(uint8_t*)HtRt_svc_uuid,
},
//Characteristic 1 Declaration
[HEART_RATE_MEASUREMENT_CHAR] = {
{UUID_SIZE_2,UUID16_ARR(GATT_CHARACTER_UUID)},
GATT_PROP_READ,
0,
NULL,
},
//Characteristic 1 value
[HEART_RATE_MEASUREMENT_VAL] = {
{UUID_SIZE_2,UUID16_ARR(HEARTRATE_MEAS_UUID)},
GATT_PROP_NOTI,
HR_SERVICE_DATA_VAL_LEN,
NULL,
},
//Characteristic 1 client configration
[HEART_RATE_MENSUREMENT_CFG] = {
{UUID_SIZE_2,UUID16_ARR(GATT_CLIENT_CHAR_CFG_UUID)},
GATT_PROP_READ | GATT_PROP_WRITE,
0x02,
NULL,
},
//Characteristic 1 Description
[HEART_RATE_MENSUREMENT_DESC] = {
{UUID_SIZE_2,UUID16_ARR(GATT_CHAR_USER_DESC_UUID)},
GATT_PROP_READ,
HR_SERVICE_DATA_DESC_LEN,
(uint8_t*)HEART_RATE_MENSUREMENT,
},
//Characteristic 2 Declaration
[BODY_SENSOR_LOCATION_CHAR] = {
{UUID_SIZE_2,UUID16_ARR(GATT_CHARACTER_UUID)},
GATT_PROP_READ,
0,
NULL,
},
//Characteristic 2 value
[BODY_SENSOR_LOCATION_VAL] = {
{UUID_SIZE_2,UUID16_ARR(BODY_SENSOR_LOC_UUID)},
GATT_PROP_READ,
HR_SERVICE_DATA_VAL_LEN,
NULL,
},
//Characteristic 2 Desciption
[BODY_SENSOR_LOCATION_DESC] = {
{UUID_SIZE_2,UUID16_ARR(GATT_CHAR_USER_DESC_UUID)},
GATT_PROP_READ,
HR_SERVICE_DATA_DESC_LEN,
(uint8_t*)BODY_SENSOR_LOCATION,
},
//Characteristic 3 Declation
[HEARTRATE_CONTROL_POINT_CHAR] = {
{UUID_SIZE_2,UUID16_ARR(GATT_CHARACTER_UUID)},
GATT_PROP_READ,
0,
NULL,
},
//Characteristic 3 value
[HEARTRATE_CONTROL_POINT_VAL] = {
{UUID_SIZE_2,UUID16_ARR(HEARTRATE_CTRL_PT_UUID)},
GATT_PROP_WRITE,
HR_SERVICE_DATA_VAL_LEN,
NULL,
},
//Characteristic 3 Description
[HEARTRATE_CONTROL_POINT_DESC] = {
{UUID_SIZE_2,UUID16_ARR(GATT_CHAR_USER_DESC_UUID)},
GATT_PROP_READ,
HR_SERVICE_DATA_DESC_LEN,
(uint8_t*)HEARTRATE_CONTROL_POINT,
},
};