笔记"/>
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";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
直接上代码:
- icce_service.c
/** icce_service.c** Created on: 2023年4月15日* Author: 28596*/
/********************************************************************** INCLUDES*/
#include <string.h>//#include <xdc/runtime/Log.h> // Comment this in to use xdc.runtime.Log
#include <ti/common/cc26xx/uartlog/UartLog.h> // Comment out if using xdc Log#include <icall.h>/* 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 },//2800GATT_PERMIT_READ,0,(uint8_t *)&LOC_DataServiceDecl},// 写特征的申明{{ ATT_BT_UUID_SIZE, characterUUID },//2803GATT_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 },//0x2902GATT_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或者indicationif(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"));// 尝试发送NotificationGATTServApp_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的UUIDelse if(ATT_BT_UUID_SIZE == pAttr->type.len && !memcmp(pAttr->type.uuid, wUserCharact_UUID, pAttr->type.len)){return(wCharact_PARAM_ID);}// 判断是否是rnCharact的UUIDelse 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 0if(changeParamID != 0xFF){if(pAppCBs && pAppCBs->pfnChangeCb){pAppCBs->pfnChangeCb(connHandle, paramID, len + offset, pValue); // Call app function from stack task context.}}
#endifreturn(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 <bcomdef.h>#define wCharact_PARAM_ID 0x00
#define rnCharact_PARAM_ID 0x01extern 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,运行之后用手机就能发现我们这个服务了。
color=gray
更多推荐
CC2642的GGS使用笔记
发布评论