NRF52832学习笔记(15)——GATT服务端自定义服务和特征

一、背景

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

  1. 在工程目录下 components\ble\ble_services 创建一个服务的文件夹如 alm_init_profile,并将 alm_init_profile.calm_init_profile.h 加入其中。
  2. alm_init_profile.calm_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(&params, 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, &params);
}


/*********************************************************************
 * 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_tAlmInitProfile_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 新增特征

  1. 每新增一个特征,在 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
  1. 每新增一个特征,在 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
  1. 每新增一个特征,在 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
  1. 每新增一个特征,在 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 = 1addCharParams.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 = 1addCharParams.read_access = SEC_OPEN
开启可写的权限 addCharParams.char_props.write = 1addCharParams.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 = 1addCharParams.read_access = SEC_OPEN
开启可写的权限 addCharParams.char_props.write = 1addCharParams.write_access = SEC_OPEN
开启通知的权限 addCharParams.char_props.notify = 1addCharParams.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(&params, 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, &params);
}

四、加入服务和特征到工程

4.1 修改并配置sdk_config.h

  1. 在 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 中显示勾选了该服务

  1. 在 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 日

• 参考:青风电子社区

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容