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 日

• 參考:青風電子社區

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章