CC2640R2F学习笔记(7)——自定义服务和特征

一、背景

1.1 Profile(规范)

profile 可以理解为一种规范,一个标准的通信协议,它存在于蓝牙从机中(服务端);

蓝牙组织规定了一些标准的 profile,例如 HID OVER GATT,防丢器,心率计等;

每个 profile 中会包含多个 service,每个 service 代表从机的一种能力。

1.2 Service(服务)

service 可以理解为一个服务,在 BLE 从机中有多个服务,例如:电量信息服务、系统信息服务等;

每个 service 中又包含多个 characteristic 特征值;

每个具体的 characteristic 特征值才是 BLE 通信的主题,比如当前的电量是 80%,电量的 characteristic 特征值存在从机的 profile 里,这样主机就可以通过这个 characteristic 来读取 80% 这个数据。

1.3 Characteristic(特征)

characteristic 特征,BLE 主从机的通信均是通过 characteristic 来实现,可以理解为一个标签,通过这个标签可以获取或者写入想要的内容。

1.4 UUID(通用唯一识别码)

uuid 通用唯一识别码,我们刚才提到的 service 和 characteristic 都需要一个唯一的 uuid 来标识;

每个从机都会有一个 profile,不管是自定义的 simpleprofile,还是标准的防丢器 profile,他们都是由一些 service 组成,每个 service 又包含了多个 characteristic,主机和从机之间的通信,均是通过characteristic来实现。

1.5 CCC(Client Characteristic Configuration)

Notify 属性的特征值,会比读、写属性的特征值多一个 CCC。
从机要想使用 Notify 函数时能正常发送出数据,就必须保证 CCC 是被打开的。

1.6 AttrTbl(属性表)

它是一个服务中的数组,用来存放该服务下的服务信息、所有特征值信息。每个特征值一般
为 3 个参数,而 Notify 属性的特征值一般为 4 个参数(多一个 Notify 开关的参数)。

1.7 应用场景

举个例子,假设蓝牙设备中有 2 个服务,温湿度服务(假设 UUID 为 0x1110)和电量
服务
(假设 UUID 为 0x2220)。
其中温湿度服务中包含了温度特征(假设 UUID 为 0x1111)、湿度特征(假设 UUID 为
0x1112)。

此时你想用另一个 CC2640R2F 作为主机读取温度值,那么 CC2640R2F 主机会做如下事情:
1)连接设备:扫描并连接你的蓝牙设备从机。
2)发现服务:查找设备的服务中是否包含 UUID 为 0x1110 的服务(温湿度服务)。
3)发现特征:查找 UUID 为 0x1110 的服务(温湿度服务)中是否包含 UUID 为 0x1111 的
特征值(温度特征值)。
4)获得特征句柄:查找到温度特征后,获取问读特征句柄,假设为 0x0038。
5)利用句柄来读取温度值:使用 0x0038 的句柄发送读指令,则此时 CC2640R2F 主机可读取
到 CC2640R2F 从机中的温度值。

二、移植文件

链接:https://pan.baidu.com/s/19_VC_ifUScEXSIGHERjsCQ 提取码:8t9u
alm_gatt_profile.calm_gatt_profile.h 两个文件拖拽至CCS工程的PROFILES文件夹下


添加文件过程中,选项选择如下

2.1 alm_gatt_profile.c

/*********************************************************************
 * INCLUDES
 */
#include <string.h>
 
#include "bcomdef.h"
#include "OSAL.h"
#include "linkdb.h"
#include "att.h"
#include "gatt.h"
#include "gatt_uuid.h"
#include "gattservapp.h"
#include "gapbondmgr.h"

#include "alm_gatt_profile.h"

/*********************************************************************
 * DEFINITIONS
 */
//属性表的数据长度
#define SERVAPP_NUM_ATTR_SUPPORTED              5   // 每新增一个特征要加3(带Notify的特征要加4)
 
//属性在属性表中的偏移值
#define ATTRTBL_ALM_CHAR1_IDX                   2   // 特征1在属性表的偏移值
#define ATTRTBL_ALM_CHAR1_CCC_IDX               3   // 特征1的notify开关在属性表的偏移值
 
/*********************************************************************
 * GLOBAL VARIABLES
 */
/*--------------------- 特征相关 -------------------------*/
uint8 g_ALMProfile_Char1Value[ALMPROFILE_CHAR1_LEN] = {0};              // 特征1值
uint8 g_ALMProfile_Char2Value[ALMPROFILE_CHAR2_LEN] = {0};              // 特征2值
uint8 g_ALMProfile_Char3Value[ALMPROFILE_CHAR3_LEN] = {0};              // 特征3值

// ALM GATT Profile 服务 UUID: 0xFFE0
CONST uint8 g_ALMProfileServUUID[ATT_BT_UUID_SIZE] =
{
    LO_UINT16(ALMPROFILE_SERV_UUID), HI_UINT16(ALMPROFILE_SERV_UUID)
};
 
// 特征1 UUID: 0xFFE1
CONST uint8 g_ALMProfileChar1UUID[ATT_BT_UUID_SIZE] =
{
    LO_UINT16(ALMPROFILE_CHAR1_UUID), HI_UINT16(ALMPROFILE_CHAR1_UUID)
};
 
/*********************************************************************
 * LOCAL VARIABLES
 */
static almProfileCBs_t *s_pALMProfile_AppCBs = NULL;
 
/*********************************************************************
 * Profile Attributes - variables
 */
 
// ALM Profile 服务属性
static CONST gattAttrType_t s_ALMProfileService = { ATT_BT_UUID_SIZE, g_ALMProfileServUUID };
 
// 特征1的权限
static uint8 s_ALMProfileChar1Props = GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_NOTIFY;
 
// 特征1的值
static uint8 s_ALMProfileChar1[ALMPROFILE_CHAR1_LEN] = {0};
 
// 特征1的配置
static gattCharCfg_t *s_pALMProfileChar1Config;
 
// 特征1的用户描述
static uint8 s_ALMProfileChar1UserDesp[10] = "ALM Char1\0";
 
/*********************************************************************
 * Profile Attributes - Table
 */
 
static gattAttribute_t s_ALMProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED] =
{
    // ALM Profile 服务
    {
        { ATT_BT_UUID_SIZE, primaryServiceUUID },   /* type */
        GATT_PERMIT_READ,                           /* permissions */
        0,                                          /* handle */
        (uint8 *)&s_ALMProfileService               /* pValue */
    },
 
    // 特征1的权限
    {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &s_ALMProfileChar1Props
    },
 
    // 特征1的值
    {
        { ATT_BT_UUID_SIZE, g_ALMProfileChar1UUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        s_ALMProfileChar1
    },
 
    // 特征1的配置
    {
        { ATT_BT_UUID_SIZE, clientCharCfgUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        (uint8 *)&s_pALMProfileChar1Config
    },
 
    // 特征1的用户描述
    {
        { ATT_BT_UUID_SIZE, charUserDescUUID },
        GATT_PERMIT_READ,
        0,
        s_ALMProfileChar1UserDesp
    },
};
 
/*********************************************************************
 * LOCAL FUNCTIONS
 */
/**
 @brief 读属性
 @param connHandle 连接句柄
 @param pAttr 指向属性
 @param pValue 指向读出的数据
 @param pLen 被读出的数据长度
 @param offset 偏移量
 @param maxLen 被读出的数据最大长度
 @return SUCCESS - 成功;FAILURE - 失败
*/
static uint8 almProfile_ReadAttrCB(uint16 connHandle,
                                   gattAttribute_t *pAttr,
                                   uint8 *pValue, uint16 *pLen,
                                   uint16 offset, uint16 maxLen,
                                   uint8_t method)
{
    bStatus_t status = SUCCESS;
 
    // 如果属性没有读的权限,返回错误
    if(gattPermitAuthorRead(pAttr->permissions))
    {
        // 权限不足
        return (ATT_ERR_INSUFFICIENT_AUTHOR);
    }
 
    // 确保不是错误操作 (没有长属性)
    if(offset > 0)
    {
        return (ATT_ERR_ATTR_NOT_LONG);
    }
 
    if(pAttr->type.len == ATT_BT_UUID_SIZE)
    {
        // 16-bit UUID
        uint16 uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
        switch(uuid)
        {
        // No need for "GATT_SERVICE_UUID" or "GATT_CLIENT_CHAR_CFG_UUID" cases;
        // gattserverapp handles those reads
        // 特征1有读权限
        case ALMPROFILE_CHAR1_UUID:
            *pLen = ALMPROFILE_CHAR1_LEN;
            VOID memcpy(pValue, pAttr->pValue, ALMPROFILE_CHAR1_LEN);
            break;
 
        default:
            // Should never get here!
            *pLen = 0;
            status = ATT_ERR_ATTR_NOT_FOUND;
            break;
        }
    }
    else
    {
        // 128-bit UUID
        *pLen = 0;
        status = ATT_ERR_INVALID_HANDLE;
    }
 
    return (status);
}

/**
 @brief 写属性,在写入之前验证属性数据
 @param connHandle 连接句柄
 @param pAttr 指向属性
 @param pValue 指向写入的数据
 @param len 写入的数据长度
 @param offset 偏移量
 @return SUCCESS - 成功;FAILURE - 失败
*/
static bStatus_t almProfile_WriteAttrCB(uint16 connHandle,
                                        gattAttribute_t *pAttr,
                                        uint8 *pValue, uint16 len,
                                        uint16 offset, uint8_t method)
{
    bStatus_t status = SUCCESS;
    uint8 notifyApp = 0xFF;
 
    // 如果属性没有写的权限,返回错误
    if(gattPermitAuthorWrite(pAttr->permissions))
    {
        // 权限不足
        return (ATT_ERR_INSUFFICIENT_AUTHOR);
    }
 
    if(pAttr->type.len == ATT_BT_UUID_SIZE)
    {
        // 16-bit UUID
        uint16 uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
        switch(uuid)
        {
        case ALMPROFILE_CHAR1_UUID:
            if(offset == 0)
            {
                if(len != ALMPROFILE_CHAR1_LEN)
                {
                    status = ATT_ERR_INVALID_VALUE_SIZE;
                }
            }
            else
            {
                status = ATT_ERR_ATTR_NOT_LONG;
            }
 
            // 将接收到的数据写进特征值中,并且置标志位
            if(status == SUCCESS)
            {
                VOID memcpy(pAttr->pValue, pValue, ALMPROFILE_CHAR1_LEN);
                notifyApp = ALMPROFILE_CHAR1;
            }
            break;
 
        case GATT_CLIENT_CHAR_CFG_UUID:
            // 特征1通道,则打开notify开关
            if(pAttr->handle == s_ALMProfileAttrTbl[ATTRTBL_ALM_CHAR1_CCC_IDX].handle)
            {
                status = GATTServApp_ProcessCCCWriteReq(connHandle, pAttr, pValue, len,
                                                        offset, GATT_CLIENT_CFG_NOTIFY);
            }
            else
            {
                status = ATT_ERR_INVALID_HANDLE;
            }
            break;
 
        default:
            status = ATT_ERR_ATTR_NOT_FOUND;
            break;
        }
    }
    else
    {
        // 128-bit UUID
        status = ATT_ERR_INVALID_HANDLE;
    }
 
    // 如果特征值被更改,则通过回调函数通知应用程序更改
    if((notifyApp != 0xFF) && s_pALMProfile_AppCBs
            && s_pALMProfile_AppCBs->pfnALMProfileChange)
    {
        s_pALMProfile_AppCBs->pfnALMProfileChange(notifyApp);
    }
 
    return (status);
}

/*********************************************************************
 * PROFILE CALLBACKS
 */
// ALM Profile 服务回调
CONST gattServiceCBs_t almProfileCBs =
{
    almProfile_ReadAttrCB,        // Read callback function pointer
    almProfile_WriteAttrCB,       // Write callback function pointer
    NULL                          // Authorization callback function pointer
};

/*********************************************************************
 * PUBLIC FUNCTIONS
 */

/**
 @brief 通过向GATT服务端注册GATT属性来初始化ALM Profile服务
 @param services 被添加的服务
 @return SUCCESS - 成功;FAILURE - 失败
*/
bStatus_t ALMProfile_AddService(uint32 services)
{
    uint8 status = SUCCESS;

    // 为客户端特征配置表分配内存
    s_pALMProfileChar1Config = (gattCharCfg_t *)ICall_malloc(sizeof(gattCharCfg_t) *
                                                            linkDBNumConns);
    if(s_pALMProfileChar1Config == NULL)
    {
        return (bleMemAllocError);
    }

    // 初始化客户端特征配置属性
    GATTServApp_InitCharCfg(INVALID_CONNHANDLE, s_pALMProfileChar1Config);


    if(services & ALMPROFILE_SERVICE)
    {
        // 向GATT服务端应用注册GATT属性列表和回调
        status = GATTServApp_RegisterService(s_ALMProfileAttrTbl,
                                             GATT_NUM_ATTRS(s_ALMProfileAttrTbl),
                                             GATT_MAX_ENCRYPT_KEY_SIZE,
                                             &almProfileCBs);
    }

    return (status);
}

/**
 @brief 注册应用程序回调函数。只调用这个函数一次
 @param callbacks 指向应用程序的回调
 @return SUCCESS - 成功;bleAlreadyInRequestedMode - 失败
*/
bStatus_t ALMProfile_RegisterAppCBs(almProfileCBs_t *appCallbacks)
{
    if(appCallbacks)
    {
        s_pALMProfile_AppCBs = appCallbacks;

        return (SUCCESS);
    }
    else
    {
        return (bleAlreadyInRequestedMode);
    }
}

/**
 @brief 设置ALM Profile参数
 @param param Profile参数ID
 @param len 写入的数据长度
 @param pValue 指向写入的数据
 @return SUCCESS - 成功;FAILURE - 失败
*/
bStatus_t ALMProfile_SetParameter(uint8 param, uint8 len, void *pValue)
{
    bStatus_t ret = SUCCESS;
    switch(param)
    {
    case ALMPROFILE_CHAR1:
        if(len == ALMPROFILE_CHAR1_LEN)
        {
            VOID memcpy(s_ALMProfileChar1, pValue, ALMPROFILE_CHAR1_LEN);
        }
        else
        {
            ret = bleInvalidRange;
        }
        break;

    default:
        ret = INVALIDPARAMETER;
        break;
    }

    return (ret);
}

/**
 @brief 获取ALM Profile参数
 @param param Profile参数ID
 @param pValue 指向读出的数据
 @return SUCCESS - 成功;FAILURE - 失败
*/
bStatus_t ALMProfile_GetParameter(uint8 param, void *pValue)
{
    bStatus_t ret = SUCCESS;
    switch(param)
    {
    case ALMPROFILE_CHAR1:
        VOID memcpy(pValue, s_ALMProfileChar1, ALMPROFILE_CHAR1_LEN);
        break;

    default:
        ret = INVALIDPARAMETER;
        break;
    }

    return (ret);
}

/**
 @brief Notify发送函数
 @param param 特征值通道参数
 @param connHandle 连接句柄
 @param pValue 指向要通知的数据
 @param len 要通知的数据长度,范围为0~SIMPLEPROFILE_CHAR6,最多20个字节
 @return SUCCESS - 成功;FAILURE - 失败
*/
bStatus_t ALMProfile_Notify(uint8 param, uint16 connHandle, uint8 *pValue, uint8 len)
{
    attHandleValueNoti_t  attHandleValueNoti;
    uint16 value;
    bStatus_t ret = SUCCESS;
 
    switch(param)
    {
    // 特征1
    case ALMPROFILE_CHAR1:
        // 读出CCC
        value = GATTServApp_ReadCharCfg(connHandle, s_pALMProfileChar1Config);
 
        // 判断CCC是否被打开
        if(value & GATT_CLIENT_CFG_NOTIFY)
        {
            // 分配发送数据缓冲区
            attHandleValueNoti.pValue = GATT_bm_alloc(connHandle,
                                                      ATT_HANDLE_VALUE_NOTI,
                                                      ALMPROFILE_CHAR1_LEN, NULL);
 
            // 分配成功,则发送数据
            if(attHandleValueNoti.pValue != NULL)
            {
                // 填充数据
                attHandleValueNoti.handle = s_ALMProfileAttrTbl[ATTRTBL_ALM_CHAR1_IDX].handle;
                attHandleValueNoti.len = len;
                memcpy(attHandleValueNoti.pValue, value, len);
 
                // 发送数据
                if(GATT_Notification(connHandle, &attHandleValueNoti, FALSE) != SUCCESS)
                {
                    GATT_bm_free((gattMsg_t *)&attHandleValueNoti, ATT_HANDLE_VALUE_NOTI);
                }
            }
            else
                ret = FAILURE;
        }
        else
            ret = FAILURE;
        break;
 
    default:
        ret = FAILURE;
        break;
    }

    return (ret);
}

/*************************************END OF FILE*************************************/

2.2 alm_gatt_profile.h

#ifndef _ALM_GATT_PROFILE_H_
#define _ALM_GATT_PROFILE_H_
 
#ifdef __cplusplus
extern "C"
{
#endif
 
/*********************************************************************
 * DEFINITIONS
 */
 
// Profile Parameters
#define ALMPROFILE_CHAR1                       0  // RW uint8 - Profile Characteristic 1 value
 
// ALM Profile Service UUID
#define ALMPROFILE_SERV_UUID                   0xFFE0
 
// CHAR1 UUID
#define ALMPROFILE_CHAR1_UUID                  0xFFE1
 
// ALM Profile Services bit fields
#define ALMPROFILE_SERVICE                     0x00000001
 
// Length of Characteristic in bytes
#define ALMPROFILE_CHAR1_LEN                   8        // CHAR1 LEN
#define ALMPROFILE_CHAR2_LEN                   1        // CHAR2 LEN
#define ALMPROFILE_CHAR3_LEN                   1        // CHAR3 LEN

/*********************************************************************
 * TYPEDEFS
 */
 
/*********************************************************************
 * Profile Callbacks
 */
// Callback when a characteristic value has changed
typedef void (*almProfileChange_t)(uint8 paramID);
 
typedef struct
{
    almProfileChange_t  pfnALMProfileChange;  // Called when characteristic value changes
} almProfileCBs_t;
 
/*********************************************************************
 * API FUNCTIONS
 */
extern bStatus_t ALMProfile_AddService(uint32 services);
extern bStatus_t ALMProfile_RegisterAppCBs(almProfileCBs_t *appCallbacks);
extern bStatus_t ALMProfile_SetParameter(uint8 param, uint8 len, void *pValue);
extern bStatus_t ALMProfile_GetParameter(uint8 param, void *pValue);
extern bStatus_t ALMProfile_Notify(uint8 param, uint16 connHandle, uint8 *pValue, uint8 len);
 
#ifdef __cplusplus
}
#endif
 
#endif /* _ALM_GATT_PROFILE_H_ */

三、API调用

需包含头文件 alm_gatt_profile.h

ALMProfile_AddService

功能 通过向GATT服务端注册GATT属性来初始化ALM Profile服务
函数定义 bStatus_t ALMProfile_AddService(uint32 services)
参数 services:被添加的服务
返回 SUCCESS - 成功;FAILURE - 失败

ALMProfile_RegisterAppCBs

功能 注册应用程序回调函数。只调用这个函数一次
函数定义 bStatus_t ALMProfile_RegisterAppCBs(almProfileCBs_t *appCallbacks)
参数 callbacks:指向应用程序的回调
返回 SUCCESS - 成功;FAILURE - 失败

ALMProfile_SetParameter

功能 设置ALM Profile参数
函数定义 bStatus_t ALMProfile_SetParameter(uint8 param, uint8 len, void *pValue)
参数1 param:Profile参数ID
参数2 len:写入的数据长度
参数3 pValue:指向写入的数据
返回 SUCCESS - 成功;FAILURE - 失败

ALMProfile_GetParameter

功能 获取ALM Profile参数
函数定义 bStatus_t ALMProfile_GetParameter(uint8 param, void *pValue)
参数1 param:Profile参数ID
参数2 pValue:指向读出的数据
返回 SUCCESS - 成功;FAILURE - 失败

ALMProfile_Notify

功能 Notify发送函数
函数定义 bStatus_t ALMProfile_Notify(uint8 param, uint16 connHandle, uint8 *pValue, uint8 len)
参数1 param:特征值通道参数
参数2 connHandle:连接句柄
参数3 pValue:指向要通知的数据
参数4 len:要通知的数据长度
返回 SUCCESS - 成功;FAILURE - 失败

四、新增特征

4.1 添加只读特征

在DEFINITIONS中修改,原来5,新增一个带读的特征+3

#define SERVAPP_NUM_ATTR_SUPPORTED              8  // 原来5,新增一个带读的特征+3

在GLOBAL VARIABLES中新增

// 特征2 UUID: 0xFFE2
CONST uint8 g_ALMProfileChar2UUID[ATT_BT_UUID_SIZE] =
{
    LO_UINT16(ALMPROFILE_CHAR2_UUID), HI_UINT16(ALMPROFILE_CHAR2_UUID)
};

在Profile Attributes - variables中新增

/*--------------------- 特征2 ---------------------*/
static uint8 s_ALMProfileChar2Props = GATT_PROP_READ;           // 特征权限
static uint8 s_ALMProfileChar2[ALMPROFILE_CHAR2_LEN] = {0};     // 特征值
static uint8 s_ALMProfileChar2UserDesp[10] = "ALM Char2\0";     // 特征用户描述

在Profile Attributes - Table中新增

/*--------------------- 特征2 ---------------------*/
    // 特征2的权限
    {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &s_ALMProfileChar2Props
    },
    // 特征2的值
    {
        { ATT_BT_UUID_SIZE, g_ALMProfileChar2UUID },
        GATT_PERMIT_READ,
        0,
        s_ALMProfileChar2
    },
    // 特征2的用户描述
    {
        { ATT_BT_UUID_SIZE, charUserDescUUID },
        GATT_PERMIT_READ,
        0,
        s_ALMProfileChar2UserDesp
    },

在almProfile_ReadAttrCB回调函数中,switch(uuid)增加一个case,因为特征2有读权限

case ALMPROFILE_CHAR2_UUID:
            *pLen = ALMPROFILE_CHAR2_LEN;
            VOID memcpy(pValue, pAttr->pValue, ALMPROFILE_CHAR2_LEN);
            break;

在ALMProfile_SetParameter函数中新增

    case ALMPROFILE_CHAR2:
        if(len == ALMPROFILE_CHAR2_LEN)
        {
            VOID memcpy(s_ALMProfileChar2, pValue, ALMPROFILE_CHAR2_LEN);
        }
        else
        {
            ret = bleInvalidRange;
        }
        break;

在ALMProfile_GetParameter函数中新增

case ALMPROFILE_CHAR2:
        VOID memcpy(pValue, s_ALMProfileChar2, ALMPROFILE_CHAR2_LEN);
        break;

在alm_gatt_profile.h头文件中新增

#define ALMPROFILE_CHAR2                       1
#define ALMPROFILE_CHAR2_UUID                  0xFFE2   // CHAR2 UUID
#define ALMPROFILE_CHAR2_LEN                   1

4.2 添加可读写特征

在DEFINITIONS中修改,原来8,新增一个带读的特征+3

#define SERVAPP_NUM_ATTR_SUPPORTED              11  // 原来8,新增一个带读的特征+3

在GLOBAL VARIABLES中新增

// 特征3 UUID: 0xFFE3
CONST uint8 g_ALMProfileChar3UUID[ATT_BT_UUID_SIZE] =
{
    LO_UINT16(ALMPROFILE_CHAR3_UUID), HI_UINT16(ALMPROFILE_CHAR3_UUID)
};

在Profile Attributes - variables中新增

/*--------------------- 特征3 ---------------------*/
static uint8 s_ALMProfileChar3Props = GATT_PROP_READ |          // 特征权限
                                      GATT_PROP_WRITE;
static uint8 s_ALMProfileChar3[ALMPROFILE_CHAR3_LEN] = {0};     // 特征值
static uint8 s_ALMProfileChar3UserDesp[10] = "ALM Char3\0";     // 特征用户描述

在Profile Attributes - Table中新增

/*--------------------- 特征3 ---------------------*/
    // 特征3的权限
    {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &s_ALMProfileChar3Props
    },
    // 特征2的值
    {
        { ATT_BT_UUID_SIZE, g_ALMProfileChar3UUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        s_ALMProfileChar3
    },
    // 特征3的用户描述
    {
        { ATT_BT_UUID_SIZE, charUserDescUUID },
        GATT_PERMIT_READ,
        0,
        s_ALMProfileChar3UserDesp
    },

在almProfile_ReadAttrCB回调函数中,switch(uuid)增加一个case,因为特征3有读权限

case ALMPROFILE_CHAR3_UUID:
            *pLen = ALMPROFILE_CHAR3_LEN;
            VOID memcpy(pValue, pAttr->pValue, ALMPROFILE_CHAR3_LEN);
            break;

在almProfile_WriteAttrCB回调函数中,switch(uuid)增加一个case,因为特征3有写权限

 case ALMPROFILE_CHAR3_UUID:
            if(offset == 0)
            {
                if(len != ALMPROFILE_CHAR3_LEN)
                {
                    status = ATT_ERR_INVALID_VALUE_SIZE;
                }
            }
            else
            {
                status = ATT_ERR_ATTR_NOT_LONG;
            }

            // 将接收到的数据写进特征值中,并且置标志位
            if(status == SUCCESS)
            {
                VOID memcpy(pAttr->pValue, pValue, len);    // len - 原ALMPROFILE_CHAR3_LEN
                notifyApp = ALMPROFILE_CHAR3;
            }
            break;

在ALMProfile_SetParameter函数中新增

    case ALMPROFILE_CHAR3:
        if(len == ALMPROFILE_CHAR3_LEN)
        {
            VOID memcpy(s_ALMProfileChar3, pValue, ALMPROFILE_CHAR3_LEN);
        }
        else
        {
            ret = bleInvalidRange;
        }
        break;

在ALMProfile_GetParameter函数中新增

case ALMPROFILE_CHAR3:
        VOID memcpy(pValue, s_ALMProfileChar3, ALMPROFILE_CHAR3_LEN);
        break;

在alm_gatt_profile.h头文件中新增

#define ALMPROFILE_CHAR3                       2
#define ALMPROFILE_CHAR3_UUID                  0xFFE3   // CHAR3 UUID
#define ALMPROFILE_CHAR3_LEN                   1

4.3 添加可读可写可通知特征

如原来的特征1

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

5.1 添加头文件

#include "alm_gatt_profile.h"

5.2 添加初始化代码

例在multi_role.c 的 multi_role_init 函数末尾中

ALMProfile_AddService(GATT_ALL_SERVICES);   // 自定义服务

// ALM GATT Profile
{
     // 初始化特征值
    ALMProfile_SetParameter(ALMPROFILE_CHAR1, ALMPROFILE_CHAR1_LEN, &g_ALMProfile_Char1Value);

    // 初始化特征2
    ALMProfile_SetParameter(ALMPROFILE_CHAR2, ALMPROFILE_CHAR2_LEN, &g_ALMProfile_Char2Value);

    // 初始化特征3
    ALMProfile_SetParameter(ALMPROFILE_CHAR3, ALMPROFILE_CHAR3_LEN, &g_ALMProfile_Char3Value);

    // 注册ALM GATT Profile的回调函数
    VOID ALMProfile_RegisterAppCBs(&simpleBLEPeripheral_ALMProfileCBs);
}

5.3 定义服务的回调函数及处理函数

static void ALM_Profile_ChangeCB(uint8 paramId)
{
    multi_role_enqueueMsg(MR_ALM_CHAR_CHANGE_EVT, paramId);
}

static void ALM_Profile_CharValueChangeEvt(uint8 paramId)
{
    uint16 connHandle;
    uint8 buffer[20] = {0};
    uint8* pValue = buffer;
    // 判断是哪个特征值
    switch(paramId)
    {
    // 特征值 1
    case ALMPROFILE_CHAR1:
    {
        // 获取连接句柄,GAPROLE_CONNHANDLE在主从一体工程在multi.h中或从机工程在peripheral.h中
        GAPRole_GetParameter(GAPROLE_CONNHANDLE, &connHandle);
        // 写一个 20 字节的测试缓冲区的数据
        for(uint8 i = 0; i < 20; i++)
        {
            *(pValue + i) = i;
        }
        // 发送数据
        ALMProfile_Notify(ALMPROFILE_CHAR1, connHandle, pValue, 20);
        break;
    }
    // 特征值 2
    case ALMPROFILE_CHAR2:
        break;
    // 特征值 3
    case ALMPROFILE_CHAR3:
        break;
    // 其他
    default:
        break;
    }
}

5.4 声明服务的回调函数和处理函数

static void ALM_Profile_ChangeCB(uint8 paramId);
static void ALM_Profile_CharValueChangeEvt(uint8 paramId);

5.5 注册回调函数

static almProfileCBs_t simpleBLEPeripheral_ALMProfileCBs =
{
  ALM_Profile_ChangeCB    // Charactersitic value change callback
};

5.6 添加自定义服务处理事件

① 添加自定义服务处理事件的宏定义

#define MR_ALM_CHAR_CHANGE_EVT               Event_Id_08

② 添加自定义服务处理事件的处理部分
例在multi_role.c的multi_role_processAppMsg函数中添加

 case MR_ALM_CHAR_CHANGE_EVT:
        ALM_Profile_CharValueChangeEvt(*(pMsg->pData));
        break;

5.7 运行结果

参考【CC2640R2F】香瓜CC2640R2F之自定义服务 中实验结果部分


• 由 Leung 写于 2019 年 3 月 13 日

• 参考:【CC2640R2F】香瓜CC2640R2F之自定义服务

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容