一、背景
1.1 Profile(规范)
profile 可以理解为一种规范,建立的蓝牙应用任务,蓝牙任务实际上分为两类:标准蓝牙任务规范 profile(公有任务),非标准蓝牙任务规范 profile(私有任务)。
-
标准蓝牙任务规范 profile:指的是从蓝牙特别兴趣小组 SIG 的官网上已经发布的 GATT 规范列表,包括警告通知(alert notification),血压测量(blood pressure),心率(heart rate),电池(battery)等等。它们都是针对具体的低功耗蓝牙的应用实例来设计的。目前蓝牙技术联盟还在不断的制定新的规范,并且发布。在 nrf52 SDK 开发软件包里包含了一个文件夹,定义了这些标准应用规范。
- 非标准蓝牙任务规范 profile:指的是供应商自定义的任务,在蓝牙 SIG 小组内未定义的任务规范。
1.2 Service(服务)
service 可以理解为一个服务,在 BLE 从机中有多个服务,例如:电量信息服务、系统信息服务等;
每个 service 中又包含多个 characteristic 特征值;
每个具体的 characteristic 特征值才是 BLE 通信的主题,比如当前的电量是 80%,电量的 characteristic 特征值存在从机的 profile 里,这样主机就可以通过这个 characteristic 来读取 80% 这个数据。
GATT 服务一般包含几个具有相关的功能,比如特定传感器的读取和设置,人机接口的输入输出。组织具有相关的特性到服务中既实用又有效,因为它使得逻辑上和用户数据上的边界变得更加清晰,同时它也有助于不同应用程序间代码的重用。
1.3 Characteristic(特征)
characteristic 特征,BLE 主从机的通信均是通过 characteristic 来实现,可以理解为一个标签,通过这个标签可以获取或者写入想要的内容。
1.4 UUID(通用唯一识别码)
uuid 通用唯一识别码,我们刚才提到的 service 和 characteristic 都需要一个唯一的 uuid 来标识;
每个从机都会有一个 profile,不管是自定义的 simpleprofile,还是标准的防丢器 profile,他们都是由一些 service 组成,每个 service 又包含了多个 characteristic,主机和从机之间的通信,均是通过characteristic来实现。
1.5 应用场景
举个例子,假设蓝牙设备中有 2 个服务,温湿度服务(假设 UUID 为 0x1110)和电量服务(假设 UUID 为 0x2220)。其中温湿度服务中包含了温度特征(假设 UUID 为 0x1111)、湿度特征(假设 UUID 为0x1112)。
此时你想用另一个 NRF52832 作为主机读取温度值,那么 NRF52832 主机会做如下事情:
1)连接设备:扫描并连接你的蓝牙设备从机。
2)发现服务:查找设备的服务中是否包含 UUID 为 0x1110 的服务(温湿度服务)。
3)发现特征:查找 UUID 为 0x1110 的服务(温湿度服务)中是否包含 UUID 为 0x1111 的特征值(温度特征值)。
4)获得特征句柄:查找到温度特征后,获取问读特征句柄,假设为 0x0038。
5)利用句柄来读取温度值:使用 0x0038 的句柄发送读指令,则此时 NRF52832 主机可读取到 NRF52832 从机中的温度值。
二、移植文件
注意:以下出现缺失common.h文件错误,去除即可。uint8改为uint8_t或unsigned char或自己宏定义
链接:https://pan.baidu.com/s/1XrsBpbX7KvrntZCHAH07Ag 提取码:e547
- 在工程目录下 components\ble\ble_services 创建一个服务的文件夹如 alm_init_profile,并将 alm_init_profile.c 和 alm_init_profile.h 加入其中。
- 将 alm_init_profile.c 和 alm_init_profile.h 两个文件加入工程的 nRF_BLE_Services 文件夹下
2.1 alm_init_profile.c
/*********************************************************************
* INCLUDES
*/
#include "sdk_common.h"
#if NRF_MODULE_ENABLED(BLE_ALMINITPROFILE)
#include "ble_srv_common.h"
#include "alm_init_profile.h"
#include "common.h"
static void almInitProfile_writeAttrCallback(AlmInitProfile_t *pAlmInitProfile, ble_evt_t const *pBleEvent);
/*********************************************************************
* LOCAL VARIABLES
*/
BLE_ALMINITPROFILE_DEF(m_almInitProfile);
/*********************************************************************
* EXTERN FUNCTIONS
*/
// 在主函数定义
extern void AlmInitProfile_HandleCharValue(uint16 connHandle, uint8 charId, AlmInitProfile_t *pAlmInitProfile,
const uint8 *pCharValue, uint16 length);
/*********************************************************************
* PUBLIC FUNCTIONS
*/
/**
@brief 添加ALM Init Profile服务
@param 无
@return NRF_SUCCESS - 成功;其他值 - 失败
*/
uint32 AlmInitProfile_AddService(void)
{
AlmInitProfileCallback_t almInitProfileCallback = {0};
almInitProfileCallback.almInitProfileCharWriteHandler = AlmInitProfile_HandleCharValue; // 调用外部函数:应用层处理特征值
return AlmInitProfile_RegisterAppCallback(&m_almInitProfile, &almInitProfileCallback);
}
/**
@brief 注册应用程序回调函数。只调用这个函数一次
@param pAlmInitProfile -[out] ALM初始化服务结构体
@param pAppCallback -[in] 指向应用程序的回调
@return NRF_SUCCESS - 成功;其他值 - 失败
*/
uint32 AlmInitProfile_RegisterAppCallback(AlmInitProfile_t *pAlmInitProfile, const AlmInitProfileCallback_t *pAppCallback)
{
uint32 errCode;
ble_uuid_t bleUuid;
ble_add_char_params_t addCharParams;
// 初始化服务结构体
pAlmInitProfile->almInitProfileCharWriteHandler = pAppCallback->almInitProfileCharWriteHandler;
/*--------------------- 服务 ---------------------*/
ble_uuid128_t baseUuid = {ALMINITPROFILE_UUID_BASE};
errCode = sd_ble_uuid_vs_add(&baseUuid, &pAlmInitProfile->uuidType);
VERIFY_SUCCESS(errCode);
bleUuid.type = pAlmInitProfile->uuidType;
bleUuid.uuid = ALMINITPROFILE_UUID_SERVICE;
errCode = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &bleUuid, &pAlmInitProfile->serviceHandle);
VERIFY_SUCCESS(errCode);
/*--------------------- 特征1 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR1;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR1_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.char_props.write = 1; // 可写
addCharParams.char_props.notify = 1; // 可通知
addCharParams.read_access = SEC_OPEN;
addCharParams.write_access = SEC_OPEN;
addCharParams.cccd_write_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char1Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
/*--------------------- 特征2 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR2;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR2_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.char_props.write = 1; // 可写
addCharParams.read_access = SEC_OPEN;
addCharParams.write_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char2Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
/*--------------------- 特征3 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR3;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR3_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.char_props.write = 1; // 可写
addCharParams.read_access = SEC_OPEN;
addCharParams.write_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char3Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
/*--------------------- 特征4 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR4;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR4_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.char_props.write = 1; // 可写
addCharParams.read_access = SEC_OPEN;
addCharParams.write_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char4Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
return errCode;
}
/**
@brief 处理来自蓝牙协议栈的应用事件
@param pBleEvent -[in] 来自蓝牙协议栈的事件
@param pContext -[in] ALM初始化服务结构体
@return 无
*/
void HandleAlmInitProfileOnBleStackEvent(ble_evt_t const *pBleEvent, void *pContext)
{
AlmInitProfile_t *pAlmInitProfile = (AlmInitProfile_t *)pContext;
switch(pBleEvent->header.evt_id)
{
case BLE_GATTS_EVT_WRITE:
almInitProfile_writeAttrCallback(pAlmInitProfile, pBleEvent);
break;
default:
// No implementation needed.
break;
}
}
/**
@brief 推送通知数据
@param connHandle -[in] 连接句柄
@param pAlmInitProfile -[in] ALM初始化服务结构体
@param pData -[in] 通知内容
@param dataLen -[in] 通知内容长度
@return NRF_SUCCESS - 成功;其他值 - 失败
*/
uint32 AlmInitProfile_PushNotifyData(uint16 connHandle, AlmInitProfile_t *pAlmInitProfile, uint8 *pData, uint16 dataLen)
{
ble_gatts_hvx_params_t params;
if(connHandle == BLE_CONN_HANDLE_INVALID)
{
return NRF_ERROR_NOT_FOUND;
}
if(dataLen > ALMINITPROFILE_CHAR1_LEN)
{
return NRF_ERROR_INVALID_PARAM;
}
memset(¶ms, 0, sizeof(params));
params.type = BLE_GATT_HVX_NOTIFICATION;
params.handle = pAlmInitProfile->char1Handle.value_handle;
params.p_data = pData;
params.p_len = &dataLen;
return sd_ble_gatts_hvx(connHandle, ¶ms);
}
/*********************************************************************
* LOCAL FUNCTIONS
*/
/**
@brief 写属性,在写入之前验证属性数据
@param pAlmInitProfile -[in] ALM初始化服务结构体
@param pBleEvent -[in] 来自蓝牙协议栈的事件
@return 无
*/
static void almInitProfile_writeAttrCallback(AlmInitProfile_t *pAlmInitProfile, ble_evt_t const *pBleEvent)
{
ble_gatts_evt_write_t const *pEventWrite = &pBleEvent->evt.gatts_evt.params.write;
uint8 characteristicId;
/*--------------------- 特征1 ---------------------*/
if((pEventWrite->handle == pAlmInitProfile->char1Handle.value_handle)
&& (pEventWrite->len == ALMINITPROFILE_CHAR1_LEN)
&& (pAlmInitProfile->almInitProfileCharWriteHandler != NULL))
{
characteristicId = ALMINITPROFILE_CHAR1;
pAlmInitProfile->almInitProfileCharWriteHandler(pBleEvent->evt.gap_evt.conn_handle, characteristicId,
pAlmInitProfile, pEventWrite->data, pEventWrite->len);
}
/*--------------------- 特征2 ---------------------*/
else if((pEventWrite->handle == pAlmInitProfile->char2Handle.value_handle)
&& (pEventWrite->len == ALMINITPROFILE_CHAR2_LEN)
&& (pAlmInitProfile->almInitProfileCharWriteHandler != NULL))
{
characteristicId = ALMINITPROFILE_CHAR2;
pAlmInitProfile->almInitProfileCharWriteHandler(pBleEvent->evt.gap_evt.conn_handle, characteristicId,
pAlmInitProfile, pEventWrite->data, pEventWrite->len);
}
/*--------------------- 特征3 ---------------------*/
else if((pEventWrite->handle == pAlmInitProfile->char3Handle.value_handle)
&& (pEventWrite->len == ALMINITPROFILE_CHAR3_LEN)
&& (pAlmInitProfile->almInitProfileCharWriteHandler != NULL))
{
characteristicId = ALMINITPROFILE_CHAR3;
pAlmInitProfile->almInitProfileCharWriteHandler(pBleEvent->evt.gap_evt.conn_handle, characteristicId,
pAlmInitProfile, pEventWrite->data, pEventWrite->len);
}
/*--------------------- 特征4 ---------------------*/
else if((pEventWrite->handle == pAlmInitProfile->char4Handle.value_handle)
&& (pEventWrite->len == ALMINITPROFILE_CHAR4_LEN)
&& (pAlmInitProfile->almInitProfileCharWriteHandler != NULL))
{
characteristicId = ALMINITPROFILE_CHAR4;
pAlmInitProfile->almInitProfileCharWriteHandler(pBleEvent->evt.gap_evt.conn_handle, characteristicId,
pAlmInitProfile, pEventWrite->data, pEventWrite->len);
}
}
#endif // NRF_MODULE_ENABLED(BLE_ALMINITPROFILE)
/****************************************************END OF FILE****************************************************/
2.2 alm_init_profile.h
#ifndef _ALM_INIT_PROFILE_H_
#define _ALM_INIT_PROFILE_H_
/*********************************************************************
* INCLUDES
*/
#include <stdint.h>
#include <stdbool.h>
#include "ble.h"
#include "ble_srv_common.h"
#include "nrf_sdh_ble.h"
#include "common.h"
/*********************************************************************
* DEFINITIONS
*/
#ifdef __cplusplus
extern "C" {
#endif
#define BLE_ALMINITPROFILE_DEF(_name) \
static AlmInitProfile_t _name; \
NRF_SDH_BLE_OBSERVER(_name ## _obs, \
BLE_ALMINITPROFILE_BLE_OBSERVER_PRIO, \
HandleAlmInitProfileOnBleStackEvent, &_name)
#define ALMINITPROFILE_UUID_BASE {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, \
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
#define ALMINITPROFILE_UUID_SERVICE 0xFEF0
#define ALMINITPROFILE_UUID_CHAR1 0xFEFF
#define ALMINITPROFILE_UUID_CHAR2 0xFE00
#define ALMINITPROFILE_UUID_CHAR3 0xFE10
#define ALMINITPROFILE_UUID_CHAR4 0xFE31
// Profile Parameters
#define ALMINITPROFILE_CHAR1 0 // uint8 - Profile Characteristic 1 value
#define ALMINITPROFILE_CHAR2 1 // uint8 - Profile Characteristic 2 value
#define ALMINITPROFILE_CHAR3 2 // uint8 - Profile Characteristic 3 value
#define ALMINITPROFILE_CHAR4 3 // uint8 - Profile Characteristic 4 value
#define ALMINITPROFILE_CHAR5 4 // uint8 - Profile Characteristic 5 value
// Length of Characteristic in bytes
#define ALMINITPROFILE_CHAR1_LEN 20 // CHAR1 LEN
#define ALMINITPROFILE_CHAR2_LEN 20 // CHAR2 LEN
#define ALMINITPROFILE_CHAR3_LEN 20 // CHAR3 LEN
#define ALMINITPROFILE_CHAR4_LEN 20 // CHAR4 LEN
#define ALMINITPROFILE_CHAR5_LEN 20 // CHAR5 LEN
/*********************************************************************
* TYPEDEFS
*/
// Forward declaration of the AlmInitProfile_t type.
typedef struct almInitProfileService_s AlmInitProfile_t;
typedef void (*AlmInitProfileCharWriteHandler_t)(uint16 connHandle, uint8 charId, AlmInitProfile_t *pAlmInitProfile,
const uint8 *pCharValue, uint8 length);
/*
ALM Init Service init structure.
This structure contains all options
and data needed for initialization of the service.
*/
typedef struct
{
AlmInitProfileCharWriteHandler_t almInitProfileCharWriteHandler; // Event handler to be called when the Characteristic is written.
} AlmInitProfileCallback_t;
/*
ALM Init Service structure.
This structure contains various status information for the service.
*/
struct almInitProfileService_s
{
uint16 serviceHandle; // Handle of ALM Init Service (as provided by the BLE stack).
ble_gatts_char_handles_t char1Handle; // Handles related to the Characteristic 1.
ble_gatts_char_handles_t char2Handle; // Handles related to the Characteristic 2.
ble_gatts_char_handles_t char3Handle; // Handles related to the Characteristic 3.
ble_gatts_char_handles_t char4Handle; // Handles related to the Characteristic 4.
uint8 uuidType; // UUID type for the ALM Init Service.
AlmInitProfileCharWriteHandler_t almInitProfileCharWriteHandler; // Event handler to be called when the Characteristic is written.
};
/*********************************************************************
* API FUNCTIONS
*/
uint32 AlmInitProfile_AddService(void);
uint32 AlmInitProfile_RegisterAppCallback(AlmInitProfile_t *pAlmInitProfile, const AlmInitProfileCallback_t *pAppCallback);
void HandleAlmInitProfileOnBleStackEvent(ble_evt_t const *pBleEvent, void *pContext);
uint32 AlmInitProfile_PushNotifyData(uint16 connHandle, AlmInitProfile_t *pAlmInitProfile, uint8 *pData, uint16 dataLen);
#ifdef __cplusplus
}
#endif
#endif /* _ALM_INIT_PROFILE_H_ */
三、代码实现
3.1 服务数据结构体设置
用到的数据结构 AlmInitProfileCallback_t 和 AlmInitProfile_t
3.1.1 AlmInitProfileCallback_t
自定义服务不依赖于任何启动或停止,所以只使用一个函数作为回调函数,当特征被写入时调用该处理。
/*
ALM Init Service init structure.
This structure contains all options
and data needed for initialization of the service.
*/
typedef struct
{
AlmInitProfileCharWriteHandler_t almInitProfileCharWriteHandler; // Event handler to be called when the Characteristic is written.
} AlmInitProfileCallback_t;
在这个结构体中,函数类型的定义如下(在头文件中必须在 AlmInitProfileCallback_t 定义之前添加)
typedef void (*AlmInitProfileCharWriteHandler_t)(uint16 connHandle, uint8 charId, AlmInitProfile_t *pAlmInitProfile,
const uint8 *pCharValue, uint8 length);
3.1.2 AlmInitProfile_t
该结构体包括:
- 服务句柄
- 特征句柄(有多少个特征加多少个句柄)
- UUID类型
- 写入特征的回调函数
// Forward declaration of the AlmInitProfile_t type.
typedef struct almInitProfileService_s AlmInitProfile_t;
/*
ALM Init Service structure.
This structure contains various status information for the service.
*/
struct almInitProfileService_s
{
uint16 serviceHandle; // Handle of ALM Init Service (as provided by the BLE stack).
ble_gatts_char_handles_t char1Handle; // Handles related to the Characteristic 1.
ble_gatts_char_handles_t char2Handle; // Handles related to the Characteristic 2.
ble_gatts_char_handles_t char3Handle; // Handles related to the Characteristic 3.
ble_gatts_char_handles_t char4Handle; // Handles related to the Characteristic 4.
uint8 uuidType; // UUID type for the ALM Init Service.
AlmInitProfileCharWriteHandler_t almInitProfileCharWriteHandler; // Event handler to be called when the Characteristic is written.
};
3.2 服务初始化
AlmInitProfile_AddService 添加服务函数,相当于服务初始化。这个函数主要完成上面定义的结构体 AlmInitProfileCallback_t 的调用。
3.2.1 AlmInitProfile_AddService
首先定义了一个服务回调的结构体,然后指定应用层的处理函数(可在 main.c 定义,在这里调用),然后初始化服务结构体的一些参数。
/**
@brief 添加ALM Init Profile服务
@param 无
@return NRF_SUCCESS - 成功;其他值 - 失败
*/
uint32 AlmInitProfile_AddService(void)
{
AlmInitProfileCallback_t almInitProfileCallback = {0};
almInitProfileCallback.almInitProfileCharWriteHandler = AlmInitProfile_HandleCharValue; // 调用外部函数:应用层处理特征值
return AlmInitProfile_RegisterAppCallback(&m_almInitProfile, &almInitProfileCallback);
}
}
3.2.2 AlmInitProfile_RegisterAppCallback
在这里对服务结构体的参数进行初始化配置
/**
@brief 注册应用程序回调函数。只调用这个函数一次
@param pAlmInitProfile -[out] ALM初始化服务结构体
@param pAppCallback -[in] 指向应用程序的回调
@return NRF_SUCCESS - 成功;其他值 - 失败
*/
uint32 AlmInitProfile_RegisterAppCallback(AlmInitProfile_t *pAlmInitProfile, const AlmInitProfileCallback_t *pAppCallback)
{
uint32 errCode;
ble_uuid_t bleUuid;
ble_add_char_params_t addCharParams;
// 初始化服务结构体
pAlmInitProfile->almInitProfileCharWriteHandler = pAppCallback->almInitProfileCharWriteHandler;
/*--------------------- 服务 ---------------------*/
ble_uuid128_t baseUuid = {ALMINITPROFILE_UUID_BASE};
errCode = sd_ble_uuid_vs_add(&baseUuid, &pAlmInitProfile->uuidType);
VERIFY_SUCCESS(errCode);
bleUuid.type = pAlmInitProfile->uuidType;
bleUuid.uuid = ALMINITPROFILE_UUID_SERVICE;
errCode = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &bleUuid, &pAlmInitProfile->serviceHandle);
VERIFY_SUCCESS(errCode);
/*--------------------- 特征1 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR1;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR1_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.char_props.write = 1; // 可写
addCharParams.char_props.notify = 1; // 可通知
addCharParams.read_access = SEC_OPEN;
addCharParams.write_access = SEC_OPEN;
addCharParams.cccd_write_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char1Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
/*--------------------- 特征2 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR2;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR2_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.char_props.write = 1; // 可写
addCharParams.read_access = SEC_OPEN;
addCharParams.write_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char2Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
/*--------------------- 特征3 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR3;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR3_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.char_props.write = 1; // 可写
addCharParams.read_access = SEC_OPEN;
addCharParams.write_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char3Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
/*--------------------- 特征4 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR4;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR4_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.char_props.write = 1; // 可写
addCharParams.read_access = SEC_OPEN;
addCharParams.write_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char4Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
return errCode;
}
3.3 设置服务的基础UUID
在 AlmInitProfile_RegisterAppCallback 函数中,对服务的基础 UUID 进行了设置。
/*--------------------- 服务 ---------------------*/
ble_uuid128_t baseUuid = {ALMINITPROFILE_UUID_BASE};
errCode = sd_ble_uuid_vs_add(&baseUuid, &pAlmInitProfile->uuidType);
VERIFY_SUCCESS(errCode);
bleUuid.type = pAlmInitProfile->uuidType;
bleUuid.uuid = ALMINITPROFILE_UUID_SERVICE;
errCode = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &bleUuid, &pAlmInitProfile->serviceHandle);
VERIFY_SUCCESS(errCode);
在 alm_init_profile.h 中对 128 位的基础 UUID 和 16 位的服务 UUID 进行了宏定义。
#define ALMINITPROFILE_UUID_BASE {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, \
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
#define ALMINITPROFILE_UUID_SERVICE 0xFEF0
3.4 新增特征
- 每新增一个特征,在 alm_init_profile.h 宏定义一个特征的 UUID
#define ALMINITPROFILE_UUID_CHAR1 0xFEFF
#define ALMINITPROFILE_UUID_CHAR2 0xFE00
#define ALMINITPROFILE_UUID_CHAR3 0xFE10
#define ALMINITPROFILE_UUID_CHAR4 0xFE31
- 每新增一个特征,在 alm_init_profile.h 宏定义一个特征的序号
// Profile Parameters
#define ALMINITPROFILE_CHAR1 0 // uint8 - Profile Characteristic 1 value
#define ALMINITPROFILE_CHAR2 1 // uint8 - Profile Characteristic 2 value
#define ALMINITPROFILE_CHAR3 2 // uint8 - Profile Characteristic 3 value
#define ALMINITPROFILE_CHAR4 3 // uint8 - Profile Characteristic 4 value
#define ALMINITPROFILE_CHAR5 4 // uint8 - Profile Characteristic 5 value
- 每新增一个特征,在 alm_init_profile.h 宏定义一个特征值的长度
// Length of Characteristic in bytes
#define ALMINITPROFILE_CHAR1_LEN 20 // CHAR1 LEN
#define ALMINITPROFILE_CHAR2_LEN 20 // CHAR2 LEN
#define ALMINITPROFILE_CHAR3_LEN 20 // CHAR3 LEN
#define ALMINITPROFILE_CHAR4_LEN 20 // CHAR4 LEN
#define ALMINITPROFILE_CHAR5_LEN 20 // CHAR5 LEN
- 每新增一个特征,在 alm_init_profile.h 的结构体 almInitProfileService_s 中定义一个特征的句柄
/*
ALM Init Service structure.
This structure contains various status information for the service.
*/
struct almInitProfileService_s
{
uint16 serviceHandle; // Handle of ALM Init Service (as provided by the BLE stack).
ble_gatts_char_handles_t char1Handle; // Handles related to the Characteristic 1.
ble_gatts_char_handles_t char2Handle; // Handles related to the Characteristic 2.
ble_gatts_char_handles_t char3Handle; // Handles related to the Characteristic 3.
ble_gatts_char_handles_t char4Handle; // Handles related to the Characteristic 4.
uint8 uuidType; // UUID type for the ALM Init Service.
AlmInitProfileCharWriteHandler_t almInitProfileCharWriteHandler; // Event handler to be called when the Characteristic is written.
};
3.4.1 添加只读特征
开启可读的权限 addCharParams.char_props.read = 1
和 addCharParams.read_access = SEC_OPEN
/*--------------------- 特征1 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR1;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR1_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.read_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char1Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
3.4.2 添加可读可写特征
开启可读的权限 addCharParams.char_props.read = 1
和 addCharParams.read_access = SEC_OPEN
开启可写的权限 addCharParams.char_props.write = 1
和 addCharParams.write_access = SEC_OPEN
/*--------------------- 特征2 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR2;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR2_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.char_props.write = 1; // 可写
addCharParams.read_access = SEC_OPEN;
addCharParams.write_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char2Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
特征被写入时的回调函数,在这里对写入长度进行验证
/**
@brief 写属性,在写入之前验证属性数据
@param pAlmInitProfile -[in] ALM初始化服务结构体
@param pBleEvent -[in] 来自蓝牙协议栈的事件
@return 无
*/
static void almInitProfile_writeAttrCallback(AlmInitProfile_t *pAlmInitProfile, ble_evt_t const *pBleEvent)
{
ble_gatts_evt_write_t const *pEventWrite = &pBleEvent->evt.gatts_evt.params.write;
uint8 characteristicId;
/*--------------------- 特征1 ---------------------*/
if((pEventWrite->handle == pAlmInitProfile->char1Handle.value_handle)
&& (pEventWrite->len == ALMINITPROFILE_CHAR1_LEN)
&& (pAlmInitProfile->almInitProfileCharWriteHandler != NULL))
{
characteristicId = ALMINITPROFILE_CHAR1;
pAlmInitProfile->almInitProfileCharWriteHandler(pBleEvent->evt.gap_evt.conn_handle, characteristicId,
pAlmInitProfile, pEventWrite->data, pEventWrite->len);
}
/*--------------------- 特征2 ---------------------*/
else if((pEventWrite->handle == pAlmInitProfile->char2Handle.value_handle)
&& (pEventWrite->len == ALMINITPROFILE_CHAR2_LEN)
&& (pAlmInitProfile->almInitProfileCharWriteHandler != NULL))
{
characteristicId = ALMINITPROFILE_CHAR2;
pAlmInitProfile->almInitProfileCharWriteHandler(pBleEvent->evt.gap_evt.conn_handle, characteristicId,
pAlmInitProfile, pEventWrite->data, pEventWrite->len);
}
}
3.4.3 添加可读可写可通知特征
开启可读的权限 addCharParams.char_props.read = 1
和 addCharParams.read_access = SEC_OPEN
开启可写的权限 addCharParams.char_props.write = 1
和 addCharParams.write_access = SEC_OPEN
开启通知的权限 addCharParams.char_props.notify = 1
和 addCharParams.cccd_write_access = SEC_OPEN
/*--------------------- 特征1 ---------------------*/
memset(&addCharParams, 0, sizeof(addCharParams));
addCharParams.uuid = ALMINITPROFILE_UUID_CHAR1;
addCharParams.uuid_type = pAlmInitProfile->uuidType;
addCharParams.init_len = sizeof(uint8_t);
addCharParams.max_len = ALMINITPROFILE_CHAR1_LEN; // 特征长度
addCharParams.char_props.read = 1; // 可读
addCharParams.char_props.write = 1; // 可写
addCharParams.char_props.notify = 1; // 可通知
addCharParams.read_access = SEC_OPEN;
addCharParams.write_access = SEC_OPEN;
addCharParams.cccd_write_access = SEC_OPEN;
errCode = characteristic_add(pAlmInitProfile->serviceHandle, &addCharParams, &pAlmInitProfile->char1Handle);
if(errCode != NRF_SUCCESS)
{
return errCode;
}
添加推送通知的函数
/**
@brief 推送通知数据
@param connHandle -[in] 连接句柄
@param pAlmInitProfile -[in] ALM初始化服务结构体
@param pData -[in] 通知内容
@param dataLen -[in] 通知内容长度
@return NRF_SUCCESS - 成功;其他值 - 失败
*/
uint32 AlmInitProfile_PushNotifyData(uint16 connHandle, AlmInitProfile_t *pAlmInitProfile, uint8 *pData, uint16 dataLen)
{
ble_gatts_hvx_params_t params;
if(connHandle == BLE_CONN_HANDLE_INVALID)
{
return NRF_ERROR_NOT_FOUND;
}
if(dataLen > ALMINITPROFILE_CHAR1_LEN)
{
return NRF_ERROR_INVALID_PARAM;
}
memset(¶ms, 0, sizeof(params));
params.type = BLE_GATT_HVX_NOTIFICATION;
params.handle = pAlmInitProfile->char1Handle.value_handle;
params.p_data = pData;
params.p_len = &dataLen;
return sd_ble_gatts_hvx(connHandle, ¶ms);
}
四、加入服务和特征到工程
4.1 修改并配置sdk_config.h
- 在 sdk_config.h 的 529 行左右加上自定义服务的使能配置
#ifndef BLE_TPS_ENABLED
#define BLE_TPS_ENABLED 0
#endif
// <q> BLE_ALMINITPROFILE_ENABLED - ALM Init Service
#ifndef BLE_ALMINITPROFILE_ENABLED
#define BLE_ALMINITPROFILE_ENABLED 1
#endif
完成后在 Configuration Wizard 中显示勾选了该服务
- 在 sdk_config.h 的 11332 行左右加上自定义服务的优先级
// <o> BLE_TPS_BLE_OBSERVER_PRIO
// <i> Priority with which BLE events are dispatched to the TX Power Service.
#ifndef BLE_TPS_BLE_OBSERVER_PRIO
#define BLE_TPS_BLE_OBSERVER_PRIO 2
#endif
// <o> BLE_ALMINITPROFILE_BLE_OBSERVER_PRIO
// <i> Priority with which BLE events are dispatched to the ALM Init Service.
#ifndef BLE_ALMINITPROFILE_BLE_OBSERVER_PRIO
#define BLE_ALMINITPROFILE_BLE_OBSERVER_PRIO 2
#endif
4.2 修改RAM空间
改写原则:,每增加一个独立的 128bit UUID 服务,RAM 空间起始增加 0x10,同时应用空间减少 0x10。
如图,Start 起始位 0x20002A98+0x10;Size 空间 0xD568-0x10。
4.3 添加头文件
#include "alm_init_profile.h"
4.4 添加服务
在服务初始化函数 services_init 中添加自定义服务
/**@brief Function for initializing services that will be used by the application.
*/
static void services_init(void)
{
ret_code_t err_code;
nrf_ble_qwr_init_t qwr_init = {0};
// Initialize Queued Write Module.
qwr_init.error_handler = nrf_qwr_error_handler;
err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
APP_ERROR_CHECK(err_code);
#if NRF_MODULE_ENABLED(BLE_ALMINITPROFILE)
// Initialize ALM Init Service.
err_code = AlmInitProfile_AddService();
APP_ERROR_CHECK(err_code);
#endif
}
4.5 处理写入特征值
在 main.c 中加入
/**
@brief 自定义初始化服务处理特征值函数
@param connHandle -[in] 连接句柄
@param charId -[in] 特征值ID
@param pAlmInitProfile -[in] ALM初始化服务结构体
@param pCharValue -[in] 写入特征值
@param length -[in] 写入特征长度
@return 无
*/
void AlmInitProfile_HandleCharValue(uint16 connHandle, uint8 charId, AlmInitProfile_t *pAlmInitProfile,
const uint8 *pCharValue, uint16 length)
{
switch(charId)
{
case ALMINITPROFILE_CHAR1: // 通知功能
AlmInitProfile_PushNotifyData(m_conn_handle, pAlmInitProfile, (uint8 *)pCharValue, ALMINITPROFILE_CHAR1_LEN); // 通知
break;
case ALMINITPROFILE_CHAR2:
NRF_LOG_INFO("ss2");
break;
case ALMINITPROFILE_CHAR3:
AlmInitProfile_PushNotifyData(m_conn_handle, pAlmInitProfile, (uint8 *)pCharValue, ALMINITPROFILE_CHAR3_LEN); // 通知
break;
case ALMINITPROFILE_CHAR4:
break;
default:
break;
}
}
• 由 Leung 写于 2020 年 3 月 7 日
• 参考:青风电子社区