一、背景
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 日
• 參考:青風電子社區