CC2642的GGS使用笔记

一、前言
我们了解BLE的GATT之前需要了解一些基本的概念:
(1)Profile,字面意思简介、概述、形象印象、轮廓、配置文件,在BLE中,我们可能把它理解成配置文件较好,Profile有一些是BLE SIG规定的,有些可以是我们自定义的。
(2)Service,字面意思服务,在GATT中可以有多个服务,同样地,服务有些是BLE SIG定义的,有些是我们自定义的,习惯性叫这些服务为GATT服务。
(3)Characteristic, 字面意思特征,它存在于Service里面,就好比房间是一个服务,里面的装饰配置之类的是特征,而这些特征决定了房间的用处,也就是服务的用处。
(4)Attribute,字面意思是属性,可以这么理解,它描述的是特征的细节,比如描述特征的读写权限,特征的内容(值),特征的简介描述,还有特征的配置。
二、GGS相关的API
  下面列举几个我们GGS常用的协议栈API:

  • bStatus_t GGS_SetParameter( uint8 param, uint8 len, void *value )
    描述:设置GAP GATT服务参数
    参数
      param: Profile参数ID, 取的值可以参考gapgattserver.h的宏定义,其实就是下面表格列出的值:
GGS_DEVICE_NAME_ATT
GGS_APPEARANCE_ATT
GGS_DEVICE_NAME_ATT
GGS_APPEARANCE_ATT
GGS_PERI_CONN_PARAM_ATT
GGS_CENT_ADDR_RES_ATT
GGS_RESOLVABLE_PRIVATE_ADDRESS_ONLY_ATT

  len: 要写入的字节个数
  value: 指向要写的数据
  比如我们在APP的初始化处初始蓝牙设备的名称,可以这么写:

    uint8_t Ble_deviceName[] = "BoBo.cn";
    GGS_SetParameter(GGS_DEVICE_NAME_ATT, strlen(Ble_deviceName), Ble_deviceName);
  • bStatus_t GAP_SetParamValue(uint16_t paramID, uint16_t paramValue)
    描述:设置GAP参数值
    参数
      paramID, 这个参数可以参考Gap_ParamIDs_t枚举
      paramValue, 值
    bStatus_t GGS_AddService( uint32 services )
    描述:添加一个功能到GGS上
    参数:services,该参数是32位,每个位代表一个功能服务,较常用的是bit1,也就是GAP_SERVICE。
  • bStatus_t GATTServApp_AddService( uint32 services )
    描述:添加一个功能到GATT服务上,我们将一个服务注册到GATT服务上之后,在蓝牙建立连接过程中客户端就能发现该服务,如果注册的回调函数功能正常的话,就能够正常的使用该服务。
    参数
      services,该参数是32位,每个位代表一个功能服务,大多数情况下推荐0xFFFFFFFF,即GATT_ALL_SERVICES
  • bStatus_t GATTServApp_RegisterService( gattAttribute_t *pAttrs, uint16 numAttrs, uint8 encKeySize, CONST gattServiceCBs_t *pServiceCBs )
    描述:向GATT服务应用注册服务的属性列表和回调函数
    参数
      pAttrs, 指向属性列表的指针
      numAttrs, 属性单元个数
      encKeySize, 服务所需的最小加密钥匙字节大小(7~16个字节)
      pServiceCBs, 指向服务回调函数的指针

这个API很重要,我们有必要看看结构体gattAttribute_t和gattServiceCBs_t的原型:

typedef struct attAttribute_t
{
	/// @brief GATT Attribute Type format.
	struct
	{
	    uint8 len;         //!< UUID的长度
	    const uint8 *uuid; //!< 指向UUID
	} type; //!< 属性类型 (2或者16字节UUIDs)
  	uint8 permissions;    //!< 属性的权限
  	uint16 handle;       //!< 属性的句柄 - 由属性服务器内部分配
  	uint8* const pValue; //!< 属性值 - 字节个数不超过512
} gattAttribute_t;

typedef struct
{
  pfnGATTReadAttrCB_t pfnReadAttrCB;           //!< 指向读ATT函数
  pfnGATTWriteAttrCB_t pfnWriteAttrCB;         //!< 指向写ATT函数
  pfnGATTAuthorizeAttrCB_t pfnAuthorizeAttrCB; //!< 指向认证函数
} gattServiceCBs_t;

上面三个函数类型如下:

typedef bStatus_t (*pfnGATTReadAttrCB_t)( uint16 connHandle, gattAttribute_t *pAttr,
                                          uint8 *pValue, uint16 *pLen, uint16 offset,
                                          uint16 maxLen, uint8 method );
typedef bStatus_t (*pfnGATTWriteAttrCB_t)( uint16 connHandle, gattAttribute_t *pAttr,
                                           uint8 *pValue, uint16 len, uint16 offset,
                                           uint8 method );
typedef bStatus_t (*pfnGATTAuthorizeAttrCB_t)( uint16 connHandle, gattAttribute_t *pAttr,
                                               uint8 opcode );
  • bStatus_t GATTServApp_ProcessCharCfg( gattCharCfg_t *charCfgTbl, uint8 *pValue, uint8 authenticated, gattAttribute_t *attrTbl, uint16 numAttrs, uint8 taskId, pfnGATTReadAttrCB_t pfnReadAttrCB )
    描述:处理客户端特征配置的改变
    参数
      charCfgTbl,特征配置表
      pValue, 指向属性内容
       authenticated, 是否需要认证
      attrTbl, 服务属性列表
      numAttrs, 服务属性列表单元个数
      taskId, 确认通知的任务ID
      pfnReadAttrCB, Att读回调函数,GATTServApp_ProcessCharCfg最后会通过该函数给客户端发送数据
  • bStatus_t GATTServApp_ProcessCCCWriteReq( uint16 connHandle, gattAttribute_t *pAttr, uint8 *pValue, uint16 len, uint16 offset, uint16 validCfg )
    描述:处理客户端特征配置写请求,这个函数配合UUID 0x2902使用
    参数
      connHandle, 客户端的连接句柄
      pAttr, 指向属性表
      pValue, 指向写入的数据
      Len, 待写的字节数
      offset,偏移字节写入
      validCfg, 配置选项,值可以是GATT_CLIENT_CFG_NOTIFY或者GATT_CLIENT_CFG_INDICATE,或者两者都选(或逻辑)。

三、实现一个数据透传服务
  下面写一个服务,两个特征,一个支持写,一个支持读和notify
CC2642的GGS使用笔记_第1张图片
直接上代码:

  • icce_service.c
/*
 * icce_service.c
 *
 *  Created on: 2023年4月15日
 *      Author: 28596
 */
/*********************************************************************
 * INCLUDES
 */
#include 

//#include  // Comment this in to use xdc.runtime.Log
#include   // Comment out if using xdc Log

#include 

/* This Header file contains all BLE API and icall structure definition */
#include "icall_ble_api.h"

#include "icce_service.h"
#define DataServiceUUID 0xFFF5
#define DataWrite_UUID 0x0001
#define DataRead_UUID  0x0002
#define ATT_BT_UUID_SIZE 2
#define u8DATALEN     247
#define DATA_SERVICE_SERV_UUID_BASE16(uuid) LO_UINT16(uuid), HI_UINT16(uuid)

#define DS_UUID_BASE16(uuid)  LO_UINT16(uuid), HI_UINT16(uuid)

static uint8_t wCharact_Props = GATT_PROP_WRITE;
static uint8_t rnCharact_Props = GATT_PROP_NOTIFY;

static uint8_t ds_icall_rsp_task_id = INVALID_TASK_ID;
// Write Characteristic Value
static uint8_t LOC_InputVal[u8DATALEN] = {0};
static uint8_t LOC_InputValLen = 0;
static uint8_t LOC_OutputVal[u8DATALEN] = {0};
static uint8_t LOC_OutputValLen = 0;
static ICCEDataServiceCBs_t *pAppCBs = NULL;
// Notify Characteristic 客户端特征配置描述符
static gattCharCfg_t *NotifyCharactConfig;//占用字节大小由可连接设备个数决定
// 数据服务的服务UUID Service UUID
CONST uint8_t icce_DataServiceUUID[ATT_BT_UUID_SIZE] =
{
    DATA_SERVICE_SERV_UUID_BASE16(DataServiceUUID)
};
// 数据服务之下的写UUID
CONST uint8_t wUserCharact_UUID[ATT_BT_UUID_SIZE] =
{
    DS_UUID_BASE16(DataWrite_UUID)
};
// 数据服务之下的读&Notify UUID
CONST uint8_t rnUserCharact_UUID[ATT_BT_UUID_SIZE] =
{
    DS_UUID_BASE16(DataRead_UUID)
};

/* 属性类型的定义(UUID的字节长度,UUID)*/
static CONST gattAttrType_t LOC_DataServiceDecl = { ATT_BT_UUID_SIZE, icce_DataServiceUUID };
/* 服务列表 */
static gattAttribute_t ICCE_Data_ServiceAttrTbl[] =
{
    // 数据服务服务什么
    {
        { ATT_BT_UUID_SIZE, primaryServiceUUID },//2800
        GATT_PERMIT_READ,
        0,
        (uint8_t *)&LOC_DataServiceDecl
    },
    // 写特征的申明
    {
        { ATT_BT_UUID_SIZE, characterUUID },//2803
        GATT_PERMIT_READ,
        0,
        &wCharact_Props
    },
    // 写特征的内容
    {
        { ATT_BT_UUID_SIZE, wUserCharact_UUID },
        GATT_PERMIT_WRITE,
        0,
        LOC_InputVal
    },
    // 读/通知特征的声明
    {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &rnCharact_Props
    },
    // 读/通知特征的内容
    {
        { ATT_BT_UUID_SIZE, rnUserCharact_UUID },
        GATT_PERMIT_WRITE,//写权限允许,数组可以更改
        0,
        LOC_OutputVal
    },
    // 通知特征的 CCCD
    {
        { ATT_BT_UUID_SIZE, clientCharCfgUUID },//0x2902
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        (uint8_t *)&NotifyCharactConfig
    },
};


/*********************************************************************
 * 静态函数声明
 */
static bStatus_t ICCE_Data_Service_ReadAttrCB(uint16_t connHandle,
                                         gattAttribute_t *pAttr,
                                         uint8_t *pValue,
                                         uint16_t *pLen,
                                         uint16_t offset,
                                         uint16_t maxLen,
                                         uint8_t method);
static bStatus_t ICCE_Data_Service_WriteAttrCB(uint16_t connHandle,
                                          gattAttribute_t *pAttr,
                                          uint8_t *pValue,
                                          uint16_t len,
                                          uint16_t offset,
                                          uint8_t method);

/*********************************************************************
 * 配置文件的回调
 */
CONST gattServiceCBs_t LOC_ICCE_Data_ServiceCBs =
{
    ICCE_Data_Service_ReadAttrCB,
    ICCE_Data_Service_WriteAttrCB,
    NULL
};

/*
 * ICCE数据服务添加函数 - 向Gatt服务器里注册Gatt属性
 * 传参:rspTaskId - ICALL任务ID
 */
extern bStatus_t ICCE_DataService_AddService(uint8_t rspTaskId)
{
    uint8_t status;

    // 初始化,为客户端特征配置表动态分配一块内存
    NotifyCharactConfig = (gattCharCfg_t *)ICall_malloc( sizeof(gattCharCfg_t) * linkDBNumConns);
    if(NotifyCharactConfig == NULL)
    {
        return(bleMemAllocError);
    }

    // 初始化客户端特征配置属性,默认设置为无效
    GATTServApp_InitCharCfg(LINKDB_CONNHANDLE_INVALID, NotifyCharactConfig);
    // 向GATT服务器添加我们写好的服务
    status = GATTServApp_RegisterService(ICCE_Data_ServiceAttrTbl,
                                         GATT_NUM_ATTRS(ICCE_Data_ServiceAttrTbl),
                                         GATT_MAX_ENCRYPT_KEY_SIZE,
                                         &LOC_ICCE_Data_ServiceCBs);
    Log_info1("Registered service, %d attributes",
              GATT_NUM_ATTRS(ICCE_Data_ServiceAttrTbl));
    ds_icall_rsp_task_id = rspTaskId;

    return(status);
}
/*
 * 数据服务设置参数 - 设置服务里面的属性内容.
 * 传参:param -配置文件参数ID,目前我们ICCE数据服务里面有两个参数,我们把ID的范围取0:1
 *      len   - 带写入的数据长度
 *      value - 指向带写入的数据.
 */
bStatus_t ICCEDataService_SetParameter(uint8_t param, uint16_t len, void *value)
{
    bStatus_t ret = SUCCESS;
    uint8_t  *pAttrVal;
    uint8_t  *pValLen;
    uint16_t valMinLen;
    uint16_t valMaxLen;
    uint8_t sendNotiInd = FALSE;
    gattCharCfg_t *attrConfig;
    uint8_t needAuth;

    switch(param)
    {
    case wCharact_PARAM_ID:
        pAttrVal = LOC_InputVal;
        pValLen = &LOC_InputValLen;
        valMinLen = 0;
        valMaxLen = sizeof(LOC_InputVal);
        break;

    case rnCharact_PARAM_ID:
        pAttrVal = LOC_OutputVal;
        pValLen = &LOC_OutputValLen;
        valMinLen = 0;
        valMaxLen = sizeof(LOC_OutputVal);
        sendNotiInd = TRUE;
        attrConfig = NotifyCharactConfig;
        needAuth = FALSE;  // 这里发送设置成不需要认证
        break;

    default:
        Log_error1("SetParameter: Parameter #%d not valid.", param);
        return(INVALIDPARAMETER);
    }
    // 更新数据和发送notification或者indication
    if(len <= valMaxLen && len >= valMinLen)
    {
        memcpy(pAttrVal, value, len);
        *pValLen = len;

        if(sendNotiInd)
        {
            Log_info2("Trying to send noti/ind: connHandle %x, %s",
                      attrConfig[0].connHandle,
                      (uintptr_t)((attrConfig[0].value ==
                                   0) ? "\x1b[33mNoti/ind disabled\x1b[0m" :
                                  (attrConfig[0].value ==
                                   1) ? "Notification enabled" :
                                  "Indication enabled"));
            // 尝试发送Notification
            GATTServApp_ProcessCharCfg(attrConfig, pAttrVal, needAuth,
                                       ICCE_Data_ServiceAttrTbl,
                                       GATT_NUM_ATTRS(ICCE_Data_ServiceAttrTbl),
                                       ds_icall_rsp_task_id,
                                       ICCE_Data_Service_ReadAttrCB);
        }
    }
    else
    {
        Log_error3("Length outside bounds: Len: %d MinLen: %d MaxLen: %d.", len,
                   valMinLen,
                   valMaxLen);
        ret = bleInvalidRange;
    }

    return(ret);
}


static uint8_t ICCEData_Service_findCharParamId(gattAttribute_t *pAttr)
{
    // Is this a Client Characteristic Configuration Descriptor?
    if(ATT_BT_UUID_SIZE == pAttr->type.len && GATT_CLIENT_CHAR_CFG_UUID ==
       *(uint16_t *)pAttr->type.uuid)
    {
        return(ICCEData_Service_findCharParamId(pAttr - 1));
    }
    // 判断是否是wCharact的UUID
    else if(ATT_BT_UUID_SIZE == pAttr->type.len && !memcmp(pAttr->type.uuid, wUserCharact_UUID, pAttr->type.len))
    {
        return(wCharact_PARAM_ID);
    }
    // 判断是否是rnCharact的UUID
    else if(ATT_BT_UUID_SIZE == pAttr->type.len && !memcmp(pAttr->type.uuid, rnUserCharact_UUID, pAttr->type.len))
    {
        return(rnCharact_PARAM_ID);
    }
    else
    {
        return(0xFF);
    }
}

/*********************************************************************
 * @fn          ICCE_Data_Service_ReadAttrCB
 * @brief       读取一个属性
 * @param       connHandle - 连接句柄,客户端和服务器建立连接之后的句柄
 * @param       pAttr - 指向属性的指针
 * @param       pValue - 指向待读的数据
 * @param       pLen - 指向读取的字节个数
 * @param       offset - 偏移字节读取
 * @param       maxLen - 读取的最大长度
 * @param       method - 读取的方式
 * @return      SUCCESS, blePending or Failure
 */
static bStatus_t ICCE_Data_Service_ReadAttrCB(uint16_t connHandle,
                                         gattAttribute_t *pAttr,
                                         uint8_t *pValue, uint16_t *pLen,
                                         uint16_t offset,
                                         uint16_t maxLen,
                                         uint8_t method)
{
    bStatus_t status = SUCCESS;
    uint16_t valueLen;
    uint8_t paramID = 0xFF;

    paramID = ICCEData_Service_findCharParamId(pAttr);
    switch(paramID)
    {
    case rnCharact_PARAM_ID:
        valueLen = LOC_OutputValLen;
        break;

    default:
        Log_error0("Attribute was not found.");
        return(ATT_ERR_ATTR_NOT_FOUND);
    }
    if(offset > valueLen)
    {
        Log_error0("An invalid offset was requested.");
        status = ATT_ERR_INVALID_OFFSET;
    }
    else
    {
        *pLen = MIN(maxLen, valueLen - offset);
        memcpy(pValue, pAttr->pValue + offset, *pLen);
    }

    return(status);
}

/*********************************************************************
 * @fn      ICCE_Data_Service_WriteAttrCB
 * @brief   在写操作之前验证属性的数据
 * @param   connHandle - 连接句柄
 * @param   pAttr - 指向属性的指针
 * @param   pValue - 指向待写的数据
 * @param   len - 待写入的数据长度
 * @param   offset - 偏移字节写入
 * @param   method - 写类型
 * @return  SUCCESS, blePending or Failure
 */
static bStatus_t ICCE_Data_Service_WriteAttrCB(uint16_t connHandle,
                                          gattAttribute_t *pAttr,
                                          uint8_t *pValue, uint16_t len,
                                          uint16_t offset,
                                          uint8_t method)
{
    bStatus_t status = SUCCESS;
    uint8_t paramID = 0xFF;
    uint8_t changeParamID = 0xFF;
    uint16_t writeLenMin;
    uint16_t writeLenMax;
    uint8_t *pValueLenVar;

    if(ATT_BT_UUID_SIZE == pAttr->type.len && GATT_CLIENT_CHAR_CFG_UUID == *(uint16_t *)pAttr->type.uuid)
    {
        // Allow notification and indication, but do not check if really allowed per CCCD.
        status = GATTServApp_ProcessCCCWriteReq(
            connHandle, pAttr, pValue, len,
            offset,
            GATT_CLIENT_CFG_NOTIFY | GATT_CLIENT_CFG_INDICATE);
        if(SUCCESS == status && pAppCBs && pAppCBs->pfnCfgChangeCb)
        {
            pAppCBs->pfnCfgChangeCb(connHandle,
                                    ICCEData_Service_findCharParamId(pAttr), len, pValue);
        }

        return(status);
    }

    paramID = ICCEData_Service_findCharParamId(pAttr);
    switch(paramID)
    {
    case wCharact_PARAM_ID:
        writeLenMin = 0;
        writeLenMax = sizeof(LOC_InputVal);
        pValueLenVar = &LOC_InputValLen;

        Log_info5(
            "WriteAttrCB : %s connHandle(%d) len(%d) offset(%d) method(0x%02x)",
            (uintptr_t)"String",
            connHandle,
            len,
            offset,
            method);
        break;

    default:
        Log_error0("Attribute was not found.");
        return(ATT_ERR_ATTR_NOT_FOUND);
    }
    if(offset >= writeLenMax)
    {
        Log_error0("An invalid offset was requested.");
        status = ATT_ERR_INVALID_OFFSET;
    }
    else if(offset + len > writeLenMax)
    {
        Log_error0("Invalid value length was received.");
        status = ATT_ERR_INVALID_VALUE_SIZE;
    }
    else if(offset + len < writeLenMin &&
            (method == ATT_EXECUTE_WRITE_REQ || method == ATT_WRITE_REQ))
    {
        Log_error0("Invalid value length was received.");
        status = ATT_ERR_INVALID_VALUE_SIZE;
    }
    else
    {
        memcpy(pAttr->pValue + offset, pValue, len);
        if(offset + len >= writeLenMin)
        {
            changeParamID = paramID;
            *pValueLenVar = offset + len; // Update data length.
        }
    }
#if 0
    if(changeParamID != 0xFF)
    {
        if(pAppCBs && pAppCBs->pfnChangeCb)
        {
            pAppCBs->pfnChangeCb(connHandle, paramID, len + offset, pValue); // Call app function from stack task context.
        }
    }
#endif
    return(status);
}

extern void ICCE_test(void)
{
    uint8_t value[10] = {0};
    static uint64_t vl = 0;
    vl++;
    memcpy(value, (uint8_t *)&vl, sizeof(vl));
    ICCEDataService_SetParameter(rnCharact_PARAM_ID, 10, value);
}


  • icce_service.h
/*
 * icce_service.h
 *
 *  Created on: 2023年4月15日
 *      Author: 28596xx
 */

#ifndef PROFILES_ICCE_SERVICE_H_
#define PROFILES_ICCE_SERVICE_H_


#ifdef __cplusplus
extern "C"
{
#endif

/*********************************************************************
 * INCLUDES
 */
#include 

#define wCharact_PARAM_ID 0x00
#define rnCharact_PARAM_ID 0x01

extern bStatus_t ICCE_DataService_AddService(uint8_t rspTaskId);
extern bStatus_t ICCEDataService_SetParameter(uint8_t param, uint16_t len, void *value);

extern void ICCE_test(void);//用于测试notify是否正常
#endif /* PROFILES_ICCE_SERVICE_H_ */

将上面这两个文件添加到工程,ICCE_test() 用一个定时器每5s执行一次。编译下载到CC2642,运行之后用手机就能发现我们这个服务了。
CC2642的GGS使用笔记_第2张图片

color=gray

你可能感兴趣的:(笔记,CC26X2,BLE,笔记)