一、简介
本篇以SimpleBLEPeripheral工程为例,介绍如何在工程中添加一个自定义的服务。
二、实验平台
协议栈版本:BLE-CC254x-1.4.0
编译软件:IAR 8.20.2
硬件平台:Smart RF开发板
手机平台:小米1S
APP:TruthBlue2.7
三、版权声明
博主:甜甜的大香瓜
声明:喝水不忘挖井人,转载请注明出处。
原文地址:http://blog.csdn.net/feilusia
联系方式:[email protected]
技术交流QQ群:127442605
四、相关博文
在阅读本文前,请先阅读本博客的以下博文,以便更好理解本文:
1)《CC2541之发现服务与特征值》:http://blog.csdn.net/feilusia/article/details/46909847
2)《CC2541之notify》:http://blog.csdn.net/feilusia/article/details/47020073
3)《CC2541之添加特征值》:http://blog.csdn.net/feilusia/article/details/48235691
五、修改代码
1、编写并添加自定义的服务
1)编写一个GUA_Profile.c(存放在“……\BLE-CC254x-1.4.0\Projects\ble\SimpleBLEPeripheral\Source\GUA”路径下)
//****************************************************************************** //name: GUA_profile.c //introduce: 香瓜自定义的服务,内含一个可读、可写、可通知的特征值 //author: 甜甜的大香瓜 //changetime: 2016.2.23 //email: [email protected] //****************************************************************************** /********************************************************************* * INCLUDES */ #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 "GUA_Profile.h" /********************************************************************* * MACROS */ /********************************************************************* * CONSTANTS */ #define SERVAPP_NUM_ATTR_SUPPORTED 5 //属性在属性表中的偏移值 #define ATTRTBL_GUA_CHAR1_IDX 2 #define ATTRTBL_GUA_CHAR1_CCC_IDX 3 /********************************************************************* * TYPEDEFS */ /********************************************************************* * GLOBAL VARIABLES */ // GUA Service UUID: 0xFFE0 CONST uint8 GUAServUUID[ATT_BT_UUID_SIZE] = { LO_UINT16(GUAPROFILE_SERV_UUID), HI_UINT16(GUAPROFILE_SERV_UUID) }; // GUA char1 UUID: 0xFFE1 CONST uint8 GUAChar1UUID[ATT_BT_UUID_SIZE] = { LO_UINT16(GUAPROFILE_CHAR1_UUID), HI_UINT16(GUAPROFILE_CHAR1_UUID) }; /********************************************************************* * EXTERNAL VARIABLES */ /********************************************************************* * EXTERNAL FUNCTIONS */ /********************************************************************* * LOCAL VARIABLES */ static GUAProfileCBs_t *GUAProfile_AppCBs = NULL; /********************************************************************* * Profile Attributes - variables */ // GUA Service attribute static CONST gattAttrType_t GUAProfile_Service = { ATT_BT_UUID_SIZE, GUAServUUID }; // GUA Characteristic 1 Properties static uint8 GUAProfile_Char1_Props = GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_NOTIFY; // GUA Characteristic 1 Value static uint8 GUAProfile_Char1[GUAPROFILE_CHAR1_LEN] = {0}; // GUA Characteristic 1 Configs static gattCharCfg_t GUAProfile_Char1_Config[GATT_MAX_NUM_CONN]; // GUA Characteristic 1 User Description static uint8 GUAProfile_Char1_UserDesp[10] = "GUA Char1\0"; /********************************************************************* * Profile Attributes - Table */ static gattAttribute_t GUAProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED] = { // GUA Service { { ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */ GATT_PERMIT_READ, /* permissions */ 0, /* handle */ (uint8 *)&GUAProfile_Service /* pValue */ }, // GUA Characteristic 1 Declaration { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &GUAProfile_Char1_Props }, // GUA Characteristic 1 Value { { ATT_BT_UUID_SIZE, GUAChar1UUID }, GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, GUAProfile_Char1 }, // GUA Characteristic 1 configuration { { ATT_BT_UUID_SIZE, clientCharCfgUUID }, GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, (uint8 *)GUAProfile_Char1_Config }, // GUA Characteristic 1 User Description { { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, GUAProfile_Char1_UserDesp }, }; /********************************************************************* * LOCAL FUNCTIONS */ static uint8 GUAProfile_ReadAttrCB( uint16 connHandle, gattAttribute_t *pAttr, uint8 *pValue, uint8 *pLen, uint16 offset, uint8 maxLen ); static bStatus_t GUAProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr, uint8 *pValue, uint8 len, uint16 offset ); static void GUAProfile_HandleConnStatusCB( uint16 connHandle, uint8 changeType ); /********************************************************************* * PROFILE CALLBACKS */ // GUAProfile Service Callbacks CONST gattServiceCBs_t GUAProfileCBs = { GUAProfile_ReadAttrCB, // Read callback function pointer GUAProfile_WriteAttrCB, // Write callback function pointer NULL // Authorization callback function pointer }; /********************************************************************* * PUBLIC FUNCTIONS */ /********************************************************************* * @fn GUAProfile_AddService * * @brief Initializes the GUA service by registering * GATT attributes with the GATT server. * * @param services - services to add. This is a bit map and can * contain more than one service. * * @return Success or Failure */ bStatus_t GUAProfile_AddService( uint32 services ) { uint8 status = SUCCESS; // Initialize Client Characteristic Configuration attributes GATTServApp_InitCharCfg( INVALID_CONNHANDLE, GUAProfile_Char1_Config ); // Register with Link DB to receive link status change callback VOID linkDB_Register( GUAProfile_HandleConnStatusCB ); if ( services & GUAPROFILE_SERVICE ) { // Register GATT attribute list and CBs with GATT Server App status = GATTServApp_RegisterService( GUAProfileAttrTbl, GATT_NUM_ATTRS( GUAProfileAttrTbl ), &GUAProfileCBs ); } return ( status ); } /********************************************************************* * @fn GUAProfile_RegisterAppCBs * * @brief Registers the application callback function. Only call * this function once. * * @param callbacks - pointer to application callbacks. * * @return SUCCESS or bleAlreadyInRequestedMode */ bStatus_t GUAProfile_RegisterAppCBs( GUAProfileCBs_t *appCallbacks ) { if ( appCallbacks ) { GUAProfile_AppCBs = appCallbacks; return ( SUCCESS ); } else { return ( bleAlreadyInRequestedMode ); } } /********************************************************************* * @fn GUAProfile_SetParameter * * @brief Set a GUA Profile parameter. * * @param param - Profile parameter ID * @param len - length of data to right * @param pValue - pointer to data to write. This is dependent on * the parameter ID and WILL be cast to the appropriate * data type (example: data type of uint16 will be cast to * uint16 pointer). * * @return bStatus_t */ bStatus_t GUAProfile_SetParameter( uint8 param, uint8 len, void *pValue ) { bStatus_t ret = SUCCESS; switch ( param ) { case GUAPROFILE_CHAR1: if ( len == GUAPROFILE_CHAR1_LEN ) { VOID osal_memcpy( GUAProfile_Char1, pValue, GUAPROFILE_CHAR1_LEN ); } else { ret = bleInvalidRange; } break; default: ret = INVALIDPARAMETER; break; } return ( ret ); } /********************************************************************* * @fn GUAProfile_GetParameter * * @brief Get a GUA Profile parameter. * * @param param - Profile parameter ID * @param pValue - pointer to data to put. This is dependent on * the parameter ID and WILL be cast to the appropriate * data type (example: data type of uint16 will be cast to * uint16 pointer). * * @return bStatus_t */ bStatus_t GUAProfile_GetParameter( uint8 param, void *pValue ) { bStatus_t ret = SUCCESS; switch ( param ) { case GUAPROFILE_CHAR1: VOID osal_memcpy( pValue, GUAProfile_Char1, GUAPROFILE_CHAR1_LEN ); break; default: ret = INVALIDPARAMETER; break; } return ( ret ); } /********************************************************************* * @fn GUAProfile_ReadAttrCB * * @brief Read an attribute. * * @param connHandle - connection message was received on * @param pAttr - pointer to attribute * @param pValue - pointer to data to be read * @param pLen - length of data to be read * @param offset - offset of the first octet to be read * @param maxLen - maximum length of data to be read * * @return Success or Failure */ static uint8 GUAProfile_ReadAttrCB( uint16 connHandle, gattAttribute_t *pAttr, uint8 *pValue, uint8 *pLen, uint16 offset, uint8 maxLen ) { bStatus_t status = SUCCESS; // If attribute permissions require authorization to read, return error if ( gattPermitAuthorRead( pAttr->permissions ) ) { // Insufficient authorization return ( ATT_ERR_INSUFFICIENT_AUTHOR ); } // Make sure it's not a blob operation (no attributes in the profile are long 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 this type for reads // GUA characteristic does not have read permissions, but because it // can be sent as a notification, it must be included here case GUAPROFILE_CHAR1_UUID: *pLen = GUAPROFILE_CHAR1_LEN; VOID osal_memcpy( pValue, pAttr->pValue, GUAPROFILE_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 ); } /********************************************************************* * @fn GUAProfile_WriteAttrCB * * @brief Validate attribute data prior to a write operation * * @param connHandle - connection message was received on * @param pAttr - pointer to attribute * @param pValue - pointer to data to be written * @param len - length of data * @param offset - offset of the first octet to be written * * @return Success or Failure */ static bStatus_t GUAProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr, uint8 *pValue, uint8 len, uint16 offset ) { bStatus_t status = SUCCESS; uint8 notifyApp = 0xFF; // If attribute permissions require authorization to write, return error if ( gattPermitAuthorWrite( pAttr->permissions ) ) { // Insufficient authorization 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 GUAPROFILE_CHAR1_UUID: if ( offset == 0 ) { if ( len != GUAPROFILE_CHAR1_LEN ) { status = ATT_ERR_INVALID_VALUE_SIZE; } } else { status = ATT_ERR_ATTR_NOT_LONG; } //将接收到的数据写进特征值中,并且置标志位 if ( status == SUCCESS ) { VOID osal_memcpy( pAttr->pValue, pValue, GUAPROFILE_CHAR1_LEN ); notifyApp = GUAPROFILE_CHAR1; } break; case GATT_CLIENT_CHAR_CFG_UUID: //char1通道,则打开notify开关 if ( pAttr->handle == GUAProfileAttrTbl[ATTRTBL_GUA_CHAR1_CCC_IDX].handle )//GUA CHAR1 NOTIFY { 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 a charactersitic value changed then callback function to notify application of change if ( (notifyApp != 0xFF ) && GUAProfile_AppCBs && GUAProfile_AppCBs->pfnGUAProfileChange ) { GUAProfile_AppCBs->pfnGUAProfileChange( notifyApp ); } return ( status ); } /********************************************************************* * @fn GUAProfile_HandleConnStatusCB * * @brief GUA Profile link status change handler function. * * @param connHandle - connection handle * @param changeType - type of change * * @return none */ static void GUAProfile_HandleConnStatusCB( uint16 connHandle, uint8 changeType ) { // Make sure this is not loopback connection if ( connHandle != LOOPBACK_CONNHANDLE ) { // Reset Client Char Config if connection has dropped if ( ( changeType == LINKDB_STATUS_UPDATE_REMOVED ) || ( ( changeType == LINKDB_STATUS_UPDATE_STATEFLAGS ) && ( !linkDB_Up( connHandle ) ) ) ) { GATTServApp_InitCharCfg( connHandle, GUAProfile_Char1_Config ); } } } //****************************************************************************** //name: GUAprofile_Notify //introduce: notify发送函数 //parameter: param:特征值通道参数 // connHandle:连接句柄 // pValue:要通知的数据,范围为0~SIMPLEPROFILE_CHAR6,最多20个字节 // len:要通知的数据的长度 //return: none //****************************************************************************** void GUAprofile_Notify( uint8 param, uint16 connHandle, uint8 *pValue, uint8 len) { attHandleValueNoti_t noti; uint16 value; switch ( param ) { case GUAPROFILE_CHAR1: value = GATTServApp_ReadCharCfg( connHandle, GUAProfile_Char1_Config ); //读出CCC的值 if ( value & GATT_CLIENT_CFG_NOTIFY ) //判断是否打开通知开关,打开了则发送数据 { noti.handle = GUAProfileAttrTbl[ATTRTBL_GUA_CHAR1_IDX].handle; noti.len = len; osal_memcpy( noti.value, pValue, len); //数据 GATT_Notification( connHandle, ¬i, FALSE ); } break; default: break; } }
//****************************************************************************** //name: GUA_profile.h //introduce: 香瓜自定义的服务,内含一个可读、可写、可通知的特征值 //author: 甜甜的大香瓜 //changetime: 2016.2.23 //email: [email protected] //****************************************************************************** #ifndef GUA_PROFILE_H #define GUA_PROFILE_H #ifdef __cplusplus extern "C" { #endif /********************************************************************* * INCLUDES */ /********************************************************************* * CONSTANTS */ // Profile Parameters #define GUAPROFILE_CHAR1 0 // RW uint8 - Profile GUA Characteristic 1 value // GUA Service UUID #define GUAPROFILE_SERV_UUID 0xFFE0 // GUA CHAR1 UUID #define GUAPROFILE_CHAR1_UUID 0xFFE1 // GUA Profile Services bit fields #define GUAPROFILE_SERVICE 0x00000001 // Length of GUA Characteristic 1 in bytes #define GUAPROFILE_CHAR1_LEN 20 /********************************************************************* * TYPEDEFS */ /********************************************************************* * MACROS */ /********************************************************************* * Profile Callbacks */ // Callback when a characteristic value has changed typedef void (*GUAProfileChange_t)( uint8 paramID ); typedef struct { GUAProfileChange_t pfnGUAProfileChange; // Called when characteristic value changes } GUAProfileCBs_t; /********************************************************************* * API FUNCTIONS */ /* * GUAProfile_AddService- Initializes the GUA service by registering * GATT attributes with the GATT server. * * @param services - services to add. This is a bit map and can * contain more than one service. */ extern bStatus_t GUAProfile_AddService( uint32 services ); /* * GUAProfile_RegisterAppCBs - Registers the application callback function. * Only call this function once. * * appCallbacks - pointer to application callbacks. */ extern bStatus_t GUAProfile_RegisterAppCBs( GUAProfileCBs_t *appCallbacks ); /* * GUAProfile_SetParameter - Set a Simple Key Profile parameter. * * param - Profile parameter ID * len - length of data to right * pValue - pointer to data to write. This is dependent on * the parameter ID and WILL be cast to the appropriate * data type (example: data type of uint16 will be cast to * uint16 pointer). */ extern bStatus_t GUAProfile_SetParameter( uint8 param, uint8 len, void *pValue ); /* * GUA_GetParameter - Get a Simple Key Profile parameter. * * param - Profile parameter ID * pValue - pointer to data to write. This is dependent on * the parameter ID and WILL be cast to the appropriate * data type (example: data type of uint16 will be cast to * uint16 pointer). */ extern bStatus_t GUAProfile_GetParameter( uint8 param, void *pValue ); //****************************************************************************** //name: GUAprofile_Notify //introduce: notify发送函数 //parameter: param:特征值通道参数 // connHandle:连接句柄 // pValue:要通知的数据,范围为0~SIMPLEPROFILE_CHAR6,最多20个字节 // len:要通知的数据的长度 //return: none //****************************************************************************** extern void GUAprofile_Notify( uint8 param, uint16 connHandle, uint8 *pValue, uint8 len); /********************************************************************* *********************************************************************/ #ifdef __cplusplus } #endif #endif /* GUA_PROFILE_H */
4)在IAR设置中添加按键驱动源文件路径
$PROJ_DIR$\..\..\SimpleBLEPeripheral\Source\GUA
1)添加GUA_Profile头文件(SimpleBLEPeripheral.c中)
//香瓜 #include "GUA_Profile.h" //香瓜
//香瓜 //增加服务 GUAProfile_AddService(GATT_ALL_SERVICES); //初始化特征值 uint8 GUAProfile_Char1Value[GUAPROFILE_CHAR1_LEN] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; GUAProfile_SetParameter( GUAPROFILE_CHAR1, GUAPROFILE_CHAR1_LEN, &GUAProfile_Char1Value ); //添加回调函数 VOID GUAProfile_RegisterAppCBs( &simpleBLEPeripheral_GUAProfileCBs ); //香瓜
/********************************************************************* * @fn GUAProfileChangeCB * * @brief Callback from GUAProfile indicating a value change * * @param paramID - parameter ID of the value that was changed. * * @return none */ static void GUAProfileChangeCB( uint8 paramID ) { switch( paramID ) { case GUAPROFILE_CHAR1: uint16 notify_Handle; uint8 bBuf[20] = {0}; uint8 *p = bBuf; GAPRole_GetParameter( GAPROLE_CONNHANDLE, ¬ify_Handle); //获取Connection Handle for(uint8 i = 0; i < 20; i++) //写一个20字节的测试缓冲区的数据 { *(p+i) = i; } GUAprofile_Notify(GUAPROFILE_CHAR1, notify_Handle, p, 20); break; default: // should not reach here! break; } }里面添加了测试代码,一旦接收到数据,就把0~19发给主机。
4)声明服务的回调函数(SimpleBLEPeripheral.c中)
//香瓜 static void GUAProfileChangeCB( uint8 paramID ); //香瓜
//香瓜 // GUA Profile Callbacks static GUAProfileCBs_t simpleBLEPeripheral_GUAProfileCBs = { GUAProfileChangeCB // Charactersitic value change callback }; //香瓜
1、所有服务的界面
添加好上面的代码之后,用手机app即可查看到新增的服务ffe0,与该服务下的特征值ffe1。
2、数据收发
在app上任意写20个字节发往从机,从机会回0~19。
注:图中显示的格式是HEX。