什么是profile
profile就是配置文件,包含一个或者多个服务。分为公有profile和私有profile。例如心率配置文件定义了两种角色,心率传感器必须包含的服务,建议的广播参数,绑定和非绑定下建立连接的过程
什么是service
服务是一组特征和通过他们所公开的行为的集合,一个服务可以包含多个特征。是用于组织数据传输的最小单元。分为公有服务和私有服务。一个服务可以包含一个或者多个特征。 服务建立在GATT层上的
特征和描述符
特征包括声明(GATT属性),数值(心率数据),描述符( 可以0或者多个,如果使能了指示或者通知,则必须有CCCD,也就是说CCCD就是描述符)
特性参数包括:读写通知指示,CCCD(也有读写授权,客户端特征描述符,CCCD默认UID为0x2902)
GATT属性包括:UUID,属性参数(安全等级,读写授权),属性特征值(数据长度,具体的数据)
特征声明格式如下:
特征性质表明数值权限,读,写,通知,指示等
以实际工程项目心率服务为例来说明
1.首先阅读SIG发布的心率相关的文档,如下所示
2.通过心率profile定义了两类角色,心率传感器是GATT服务器,集中器则是GATT客户端(手机)
3.需求分析
3.1设备名称,heratsensor
3.2服务UUID 0x180D(心率服务UUID),0x180A(设备信息服务UUID)。广播包含全部服务的UUID
3.3外观,心率腕带,833
3.4flags 默认一般可发现模式,不支持BR/EDR
3.5广播参数,首选连接参数,连接参数协商采用默认
3.6GAP服务 名称,全名
3.7包含的服务,心率服务和设备信息服务是强制要求包含的,电池服务是可选
3.8APP定时器,电池定时器,心率定时器(定时发送通知),电量采集定时器,心率传感器定时器(传感器状态更新)
3.9心率服务包含的特征
3.10设备信息服务包含的特征
4.数据传输方向
从机–>主机:(需要使用CCCD)
通知:不管主机有没有收到
指示:等待主机回应,才发下一条数据
主机–>从机:
读:读取从机的特征值和特征描述符,从而获取从机数据
写:写入从机的特征值和特征描述符,从而将数据发送给从机
5.程序框架
6.程序步骤(记得在sdk_config.h图形界面勾选配置服务)
1.在timers_init函数创建所需要的APP定时器,统一管理,有模板
2.在services_init添加初始化服务结构体,并赋值,设置安全模式,水平,调用XX_init(先定义服务结构体,心率模块(实例化一个模块)。设备信息服务就不需要,因为不用主动上传数据,主机读读厂商名字也不会改变,也无需关心协议栈事件,只是单向交互。因为主机去读的时候必然和心率传感器建立了连接,并且发现服务和特征),具体参考心电服务代码(这部分代码在SDK官方提供,套模板就行)
3.在application_timers_start启动第一步的APP定时器
7.心率数据格式?(hrm_encode函数将数据打包)
先看flag每个bit表示什么意思,从而确定需要包含哪些字段
8.数据封装好了,什么时候发送?
定时器溢出就会执行回调函数,先启动传感器测量数据,然后把数据打包上传。注意,没有接收函数,接收是由协议栈完成的
9.BLE回调事件
SDK例程中关注了GAP连接和断开事件,是因为发送心率特征值时需要通过心率服务连接句柄判断连接是否有效。关注GATTS写事件,是因为接收主机写CCCD,从而开启或者关闭通知
电池服务代码解析
使能服务,使能ADC,使能兼容的ADC
电池电量转换为百分比数据,加上一个偏移,应该是二极管的损耗
static void bas_init(void)
{
ret_code_t err_code;
ble_bas_init_t bas_init_obj;
memset(&bas_init_obj, 0, sizeof(bas_init_obj));
bas_init_obj.evt_handler = on_bas_evt;//触发回调函数,也就是注册模块里面会被调用的,需要自己去调用,也可以设置为NULL
bas_init_obj.support_notification = true;
bas_init_obj.p_report_ref = NULL;
bas_init_obj.initial_batt_level = 100;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&bas_init_obj.battery_level_char_attr_md.cccd_write_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&bas_init_obj.battery_level_char_attr_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(&bas_init_obj.battery_level_char_attr_md.write_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&bas_init_obj.battery_level_report_read_perm);
err_code = ble_bas_init(&m_bas, &bas_init_obj);
APP_ERROR_CHECK(err_code);
}
// Update database.更新数据
err_code = sd_ble_gatts_value_set(BLE_CONN_HANDLE_INVALID,
p_bas->battery_level_handles.value_handle,&gatts_value);