天天看點

【IoT】藍牙 BLE 之 CC254x 特征值 character 解析

1、BLE 特征值相關基礎

1.1、BLE client 和 server

Server(伺服器)就是資料中心,Client(用戶端)就是通路資料者。

但與主/從裝置是獨立的概念:

主裝置既可以充當 Server,又可以充當 Client,從裝置亦然。

Server 首先将一個服務按“屬性/句柄/數值/描述”這種格式予以組織,然後調用 API 函數 GATTServApp_RegisterService 将服務資料進行注冊。

示例:

一個電池電量服務位元組,它允許 Client 讀取,資料為一個 8 比特無符号數(0~100%)。

它的組織如下:

02 25 00 19 2A,

這 5 個位元組資料(小端格式)分别是:

0x02=隻讀屬性,0x0025=句柄;0x2A19=服務UUID。

句柄(Handle)就是服務資料在資料中心的位址,當所有的服務資料組織起來後,它總得有個先後順序,某個服務的位置就是它的句柄。

大緻分三類:

讀取服務的值,需要知道服務的 UUID 或者 Handle;

寫服務的值,需要知道服務的 Hanle;

寫服務描述符,需要知道該 Descriptor 的 Hanle。

根據服務的 UUID 調用 API 函數 GATT_ReadUsingCharUUID 協定棧會傳回該服務的 Handle。

特别注意的是,一個服務的 Descriptor 的 Handle 總是該服務的 Handle+1,如電池電量服務的 Handle 是 0x0025,那麼它的 Descriptor 的 Handle 是 0x0026

藍牙通信中,Server 不能直接通路(讀/寫)Client,但是可以通知(Notification)Client,通知的前提是 Client 通過寫 Descriptor 使能通知功能。

例如,某 Server 發現電池電量已經低于安全閥值,它可以調用 GATT_Notification 通知所有已連接配接的 Client,但是 Client 接收後如果處理是它自己的事情。

1.2、擷取特征值

1) profile 

profile 可以了解為一種規範,一個标準的通信協定,它存在于從機中。

藍牙組織規定了一些标準的 profile,例如 HID OVER GATT ,防丢器,心率計等。

每個 profile 中會包含多個 service,每個 service 代表從機的一種能力。

2) service 服務

service 可以了解為一個服務,在 ble 從機中,通常有多個服務,例如電量資訊服務、系統資訊服務等。

每個 service 中又包含多個 characteristic 特征值。

每個具體的 characteristic 特征值才是 ble 通信的主題。

比如目前的電量是 80%,是以會通過電量的 characteristic 特征值存在從機的 profile 裡,這樣主機就可以通過這個 characteristic 來讀取 80% 這個資料。

3) characteristic 特征值

characteristic 特征值,ble 主從機的通信均是通過 characteristic 來實作,可以了解為一個标簽,通過這個标簽可以擷取或者寫入想要的内容。

4) UUID

UUID 統一識别碼,我們剛才提到的 service 和 characteristic,都需要一個唯一的 UUID 來辨別

2、發現服務與特征值

以按鍵特征值為例:

1) 發現按鍵服務:

在連接配接完成時,主機會判斷是否之前擷取過特征值句柄,如果沒有擷取到,則調用定時器進“START_DISCOVERY_EVT事件”開始發現服務。

【IoT】藍牙 BLE 之 CC254x 特征值 character 解析

發現服務事件:

【IoT】藍牙 BLE 之 CC254x 特征值 character 解析

發現服務函數:

【IoT】藍牙 BLE 之 CC254x 特征值 character 解析

發現服務後,就會進入該函數,此時可以通過特征值的UUID來讀取對應的特征值句柄。預設的是讀取CHAR1的特征值句柄。

【IoT】藍牙 BLE 之 CC254x 特征值 character 解析

2) 獲得按鍵服務的特征值句柄:

讀到特征值句柄後會再次進入該回調函數,此時将特征值句柄儲存下來,随口可用來操作特征值。

【IoT】藍牙 BLE 之 CC254x 特征值 character 解析

3) 根據特征值句柄擷取特征值。

3、添加特征值

特征值是一個變量或者一個數組,它被定義在 server 端,它是 client 端與 server 端之間傳輸資料的緩沖區。

示例:

添加一個 char6[20],它的值初始化為:

1、2、3、4、5、6、7、8、9、10、11、12、13、14、15、16、17、18、19、20。
           

當 char6 具有讀、寫屬性時,client 端可以通過 GATT_ReadCharValue、GATT_WriteCharValue 進行讀、寫 server 端的 char6。

當 char6 具有 notify 通知屬性時,server 端可以将 char6 的值通知給 client 機。

3.1、增加 char6 的宏定義

替換 simpleGATTprofile.h 中 CONSTANTS 段部分

// Profile Parameters
#define SIMPLEPROFILE_CHAR1                 0          // RW uint8 - Profile Characteristic 1 value 
#define SIMPLEPROFILE_CHAR2                 1          // RW uint8 - Profile Characteristic 2 value
#define SIMPLEPROFILE_CHAR3                 2          // RW uint8 - Profile Characteristic 3 value
#define SIMPLEPROFILE_CHAR4                 3          // RW uint8 - Profile Characteristic 4 value
#define SIMPLEPROFILE_CHAR5                 4          // RW uint8 - Profile Characteristic 5 value
#define SIMPLEPROFILE_CHAR6                 5          // RW uint8 - Profile Characteristic 6 value
  
// Simple Profile Service UUID
#define SIMPLEPROFILE_SERV_UUID             0xFFF0
    
// Key Pressed UUID
#define SIMPLEPROFILE_CHAR1_UUID            0xFFF1
#define SIMPLEPROFILE_CHAR2_UUID            0xFFF2
#define SIMPLEPROFILE_CHAR3_UUID            0xFFF3
#define SIMPLEPROFILE_CHAR4_UUID            0xFFF4
#define SIMPLEPROFILE_CHAR5_UUID            0xFFF5
#define SIMPLEPROFILE_CHAR6_UUID            0xFFF6
  
// Simple Keys Profile Services bit fields
#define SIMPLEPROFILE_SERVICE               0x00000001
 
// Length of Characteristic 5 in bytes
#define SIMPLEPROFILE_CHAR5_LEN           5  
 
// Length of Characteristic 6 in bytes
#define SIMPLEPROFILE_CHAR6_LEN           20
           

3.2、增加特征值 char6 的 UUID

修改 simpleGATTprofile.c 的 GLOBAL VARIABLES 段

// Characteristic 6 UUID: 0xFFF6
CONST uint8 simpleProfilechar6UUID[ATT_BT_UUID_SIZE] =
{ 
  LO_UINT16(SIMPLEPROFILE_CHAR6_UUID), HI_UINT16(SIMPLEPROFILE_CHAR6_UUID)
};
           

将 16 位的 UUID 拆成 2 個位元組放到數組裡。

3.3、增加 char6 的配置屬性

simpleGATTprofile.c 的 Profile Attributes - variables 段中

// Simple Profile Characteristic 6 Properties
static uint8 simpleProfileChar6Props = GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_NOTIFY;
 
// Characteristic 6 Value
static uint8 simpleProfileChar6[SIMPLEPROFILE_CHAR6_LEN] = {0};
 
// Simple Profile Characteristic 6 Configuration Each client has its own
// instantiation of the Client Characteristic Configuration. Reads of the
// Client Characteristic Configuration only shows the configuration for
// that client and writes only affect the configuration of that client.
static gattCharCfg_t simpleProfileChar6Config[GATT_MAX_NUM_CONN]; // 通知開關
 
// Simple Profile Characteristic 6 User Description
static uint8 simpleProfileChar6UserDesp[17] = "Characteristic 6\0";
           

由于屬性包含 GATT_PROP_NOTIFY 方式,是以必須要有個通知開關 simpleProfileChar6Config。

3.4、修改屬性表

1)修改屬性表的大小

simpleGATTprofile.c 的 CONSTANTS 段中

#define SERVAPP_NUM_ATTR_SUPPORTED              21
           

增加上面定義的 char6 的 4 個屬性變量。

2)修改屬性表

simpleGATTprofile.c 的 simpleProfileAttrTbl 數組中

// Characteristic 6 Declaration
{ 
  { ATT_BT_UUID_SIZE, characterUUID },
  GATT_PERMIT_READ, 
  0,
  &simpleProfileChar6Props 
},

// Characteristic Value 6
{ 
  { ATT_BT_UUID_SIZE, simpleProfilechar6UUID },
  GATT_PERMIT_READ | GATT_PERMIT_WRITE,
  0, 
  simpleProfileChar6 
},

// Characteristic 6 configuration
{ 
  { ATT_BT_UUID_SIZE, clientCharCfgUUID },
  GATT_PERMIT_READ | GATT_PERMIT_WRITE, 
  0, 
  (uint8 *)simpleProfileChar6Config 
},

// Characteristic 6 User Description
{ 
  { ATT_BT_UUID_SIZE, charUserDescUUID },
  GATT_PERMIT_READ, 
  0, 
  simpleProfileChar6UserDesp 
},
           

注意:

第一點:讀、寫屬性的隻有 3 個變量,而含有 notify 屬性的特征值會多一個開關 config,是以是 4 個變量。

第二點:特征值屬性的“GATT_PROP_READ”與屬性表的“GATT_PERMIT_READ”的差別

GATT_PERMIT_READ 是針對屬性表使用的,而 GATT_PROP_READ 是針對特征值使用的

打個比方說明,

屬性表是一列火車,它有 SERVAPP_NUM_ATTR_SUPPORTED 這麼多節車廂,GATT_PERMIT_READ 是每節車廂的鑰匙。

此時第 18 ~ 21 節車廂裝的是寶箱 char6,GATT_PROP_READ 是寶箱 char6 的鑰匙。

3.5、修改特征值的參數函數

1)增加 char6 的數值可設定的處理

simpleGATTprofile.c 的 SimpleProfile_SetParameter 函數中

case SIMPLEPROFILE_CHAR6:  
  if ( len == SIMPLEPROFILE_CHAR6_LEN )   
  {  
    VOID osal_memcpy( simpleProfileChar6, value, SIMPLEPROFILE_CHAR6_LEN ); 
  }  
  else  
  {  
    ret = bleInvalidRange;  
  }  
  break;
           

2)增加 char6 的數值可擷取的處理

simpleGATTprofile.c 的 SimpleProfile_GetParameter 函數中

case SIMPLEPROFILE_CHAR6:  
  VOID osal_memcpy( value, simpleProfileChar6, SIMPLEPROFILE_CHAR6_LEN );  
  break; 
           

3.6、修改特征值的讀寫函數

1)增加 char6 的數值讀取的處理

simpleGATTprofile.c 的 simpleProfile_ReadAttrCB 函數中

case SIMPLEPROFILE_CHAR6_UUID:  
  *pLen = SIMPLEPROFILE_CHAR6_LEN;  
  VOID osal_memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR6_LEN );  
  break;  
           

2)增加 char6 的數值寫入的處理

simpleGATTprofile.c 的 simpleProfile_WriteAttrCB 函數中

case SIMPLEPROFILE_CHAR6_UUID:
   // Validate the value  
   // Make sure it's not a blob oper  
   if ( offset == 0 )  
   {  
     if ( len != SIMPLEPROFILE_CHAR6_LEN )  
     {  
       status = ATT_ERR_INVALID_VALUE_SIZE;  
     }  
   }  
   else  
   {  
     status = ATT_ERR_ATTR_NOT_LONG;  
   }  
     
   // Write the value  
   if ( status == SUCCESS )  
   {  
     VOID osal_memcpy( pAttr->pValue, pValue, SIMPLEPROFILE_CHAR6_LEN );  
     notifyApp = SIMPLEPROFILE_CHAR6;  
   }
   break;  
           

3)增加通知開關的處理

替換 simpleGATTprofile.c 的 simpleProfile_WriteAttrCB 函數中的 GATT_CLIENT_CHAR_CFG_UUID 部分

//通知開關管理
case GATT_CLIENT_CHAR_CFG_UUID:  
  // CHAR4 的通知開關
  if(pAttr->handle == simpleProfileAttrTbl[GUA_ATTRTBL_CHAR4_CCC_IDX].handle) 
  {   
    status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,  
                                             offset, GATT_CLIENT_CFG_NOTIFY );  
  }  
  // CHAR6 的通知開關        
  else if(pAttr->handle == simpleProfileAttrTbl[GUA_ATTRTBL_CHAR6_CCC_IDX].handle)   
  {   
    status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,  
                                             offset, GATT_CLIENT_CFG_NOTIFY );  
  }    
  // 其他情況不打開通知開關
  else  
  {  
    status = ATT_ERR_INVALID_HANDLE;  
  }
  break;
           

此處非常重要,如果沒添加會導緻通知開關打不開,以至于從機無法主動發送資料到主機。

4)添加兩個通知開關的宏

simpleGATTprofile.c 中:

#define GUA_ATTRTBL_CHAR4_VALUE_IDX             11     
#define GUA_ATTRTBL_CHAR4_CCC_IDX               12     
#define GUA_ATTRTBL_CHAR6_VALUE_IDX             18
#define GUA_ATTRTBL_CHAR6_CCC_IDX               19

char4 與 char6 為通知屬性,是以需要明确該特征值開關的位置。

char4 的特征值資料在屬性表 simpleProfileAttrTbl 中的位置為第 11 位(屬性表是數組,從 0 位開始),是以 GUA_ATTRTBL_CHAR4_VALUE_IDX 宏定義為 11。

char4 的特征值開關在屬性表 simpleProfileAttrTbl 中的位置為第 12 位(屬性表是數組,從 0 位開始),是以 GUA_ATTRTBL_CHAR4_CCC_IDX 宏定義為 12。

char6 的特征值資料在屬性表 simpleProfileAttrTbl 中的位置為第 18 位(屬性表是數組,從 0 位開始),是以 GUA_ATTRTBL_CHAR6_VALUE_IDX 宏定義為 18。

char6 的特征值開關在屬性表 simpleProfileAttrTbl 中的位置為第 19 位(屬性表是數組,從 0 位開始),是以 GUA_ATTRTBL_CHAR6_CCC_IDX 宏定義為 19。
           

3.7、增加 char6 的通知開關初始化

替換 simpleGATTprofile.c 的 SimpleProfile_AddService 函數

//******************************************************************************                
//name:             SimpleProfile_AddService               
//introduce:        通過注冊GATT屬性與GATT服務,進而初始化simple服務           
//parameter:        services: 服務号      
//return:           none             
//author:           甜甜的大香瓜                     
//email:            [email protected]         
//QQ group          香瓜BLE之CC2541(127442605)                      
//changetime:       2016.12.08                     
//******************************************************************************  
bStatus_t SimpleProfile_AddService( uint32 services )
{
  uint8 status = SUCCESS;
 
  // Initialize Client Characteristic Configuration attributes
  GATTServApp_InitCharCfg( INVALID_CONNHANDLE, simpleProfileChar4Config );
  GATTServApp_InitCharCfg( INVALID_CONNHANDLE, simpleProfileChar6Config );      // ADD
  
  // Register with Link DB to receive link status change callback
  VOID linkDB_Register( simpleProfile_HandleConnStatusCB );  
  
  if ( services & SIMPLEPROFILE_SERVICE )
  {
    // Register GATT attribute list and CBs with GATT Server App
    status = GATTServApp_RegisterService( simpleProfileAttrTbl, 
                                          GATT_NUM_ATTRS( simpleProfileAttrTbl ),
                                          &simpleProfileCBs );
  }
 
  return ( status );
}
           

隻有通知屬性的才需要初始化通知開關屬性。

3.8、增加 char6 的通知開關初始化的實時更新

替換 simpleGATTprofile.c 的 simpleProfile_HandleConnStatusCB 函數

//******************************************************************************                
//name:             simpleProfile_HandleConnStatusCB               
//introduce:        simple服務連接配接狀态的改變函數           
//parameter:        connHandle: 連接配接句柄 
//                  changeType: 改變類型   
//return:           none             
//author:           甜甜的大香瓜                     
//email:            [email protected]         
//QQ group          香瓜BLE之CC2541(127442605)                      
//changetime:       2016.12.08                     
//******************************************************************************  
static void simpleProfile_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, simpleProfileChar4Config );
      GATTServApp_InitCharCfg( connHandle, simpleProfileChar6Config );  // ADD      
    }
  }
}
           

會根據裝置連接配接狀态來改變特征值通知開關的狀态。

3.9、增加 char6 的發送通知資料的函數

1)定義 char6 的發送通知資料的函數(simpleGATTprofile.c中)

//******************************************************************************                
//name:             GUA_SimpleGATTprofile_Char6_Notify               
//introduce:        發送char6通道的資料           
//parameter:        nGUA_ConnHandle: 連接配接句柄 
//                  npGUA_Value: 要通知的資料,範圍為0~SIMPLEPROFILE_CHAR6,最多20個位元組 
//                  nGUA_Len: 要通知的資料的長度  
//return:           none             
//author:           甜甜的大香瓜                     
//email:            [email protected]         
//QQ group          香瓜BLE之CC2541(127442605)                      
//changetime:       2016.12.29                     
//******************************************************************************   
void GUA_SimpleGATTprofile_Char6_Notify(uint16 nGUA_ConnHandle, uint8 *pGUA_Value, uint8 nGUA_Len)  
{  
  attHandleValueNoti_t  stGUA_Noti;  
  uint16 nGUA_Return;  
  
  //讀出CCC的值 
  nGUA_Return = GATTServApp_ReadCharCfg(nGUA_ConnHandle, simpleProfileChar6Config); 
  
  //判斷是否打開通知開關,打開了則發送資料  
  if (nGUA_Return & GATT_CLIENT_CFG_NOTIFY) 
  {  
    //填充資料
    stGUA_Noti.handle = simpleProfileAttrTbl[GUA_ATTRTBL_CHAR6_VALUE_IDX].handle;  
    stGUA_Noti.len = nGUA_Len;  
    osal_memcpy(stGUA_Noti.value, pGUA_Value, nGUA_Len);
    
    //發送資料
    GATT_Notification(nGUA_ConnHandle, &stGUA_Noti, FALSE);  
  }  
}
           

注意,本函數僅适用于協定棧 1.3.2 和 1.4.0 版本。

1.4.2 版本的 attHandleValueNoti_t 結構體發生變化,需要多一條配置設定發送資料緩沖區的代碼。

//配置設定發送資料緩沖區  
stGUA_Noti.pValue = GATT_bm_alloc(nGUA_ConnHandle, ATT_HANDLE_VALUE_NOTI, GUAPROFILE_CHAR6_LEN, NULL);
           

2)聲明 char6 的發送通知資料的函數(simpleGATTprofile.h中)

extern void GUA_SimpleGATTprofile_Char6_Notify(uint16 nGUA_ConnHandle, uint8 *pGUA_Value, uint8 nGUA_Len);
           

3.10、應用層修改

1)修改特征值初始化的數值(simpleBLEPeripheral.c的SimpleBLEPeripheral_Init函數中)

// Setup the SimpleProfile Characteristic Values
{
  uint8 charValue1 = 1;
  uint8 charValue2 = 2;
  uint8 charValue3 = 3;
  uint8 charValue4 = 4;
  uint8 charValue5[SIMPLEPROFILE_CHAR5_LEN] = { 1, 2, 3, 4, 5 };
  uint8 charValue6[SIMPLEPROFILE_CHAR6_LEN] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};    
  SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR1, sizeof ( uint8 ), &charValue1 );
  SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR2, sizeof ( uint8 ), &charValue2 );
  SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR3, sizeof ( uint8 ), &charValue3 );
  SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR4, sizeof ( uint8 ), &charValue4 );
  SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR5, SIMPLEPROFILE_CHAR5_LEN, charValue5 );
  SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR6, SIMPLEPROFILE_CHAR6_LEN, charValue6 );    
}
           

2)修改應用層的回調函數(simpleBLEPeripheral.c的simpleProfileChangeCB函數中)

//******************************************************************************                
//name:             simpleProfileChangeCB               
//introduce:        simple服務的回調函數
//parameter:        paramID: 特征值ID 
//return:           none             
//author:           甜甜的大香瓜                     
//email:            [email protected]         
//QQ group          香瓜BLE之CC2541(127442605)                      
//changetime:       2016.12.08                     
//******************************************************************************  
static void simpleProfileChangeCB( uint8 paramID )
{
  uint16 nGUA_ConnHandle;
  uint8 nbGUA_Char6[20] = {0};  
  
  switch( paramID )
  {
    //char1
    case SIMPLEPROFILE_CHAR1:
    {
      break;      
    }
 
    //char3
    case SIMPLEPROFILE_CHAR3:
    {
      break;      
    }
 
    //char6
    case SIMPLEPROFILE_CHAR6:  
    {
      //擷取連接配接句柄
      GAPRole_GetParameter(GAPROLE_CONNHANDLE, &nGUA_ConnHandle);
      
      //讀取char6的數值
      SimpleProfile_GetParameter(SIMPLEPROFILE_CHAR6, &nbGUA_Char6);
 
      //發送資料  
      GUA_SimpleGATTprofile_Char6_Notify(nGUA_ConnHandle, nbGUA_Char6, 20);
      
      break;      
    }        
      
    default:
    {
      break;
    }  
  }
}
           

4、擷取多個特征值句柄

主機通過“從機服務的 UUID”發現從機相對應的服務,再通過分别發送“特征值的 UUID”依次擷取到特征值句柄。

由于有些特征值句柄是擷取不到的:

示例擷取 char1、char2、char4(需要修改從機)和 char6(從機要有 char6)的特征值句柄。

char3 的特征值句柄擷取不到,我們可以通過擷取到 char1 的特征值句柄,再進行偏移計算獲得 char3 的特征值句柄。

假設 char1 的特征值句柄是 0x25,通過查詢屬性表可知 char1 的 value 位置與 char3 的 value 位置相差 6,則 char3 的特征值句柄=0x25+6=0x2B。

4.1、增加多個特征值狀态的宏(sImpleBLECentral.c)

// Discovery states
enum
{
  BLE_DISC_STATE_IDLE,                // Idle
  BLE_DISC_STATE_SVC,                 // Service discovery
  BLE_DISC_STATE_CHAR1,               // Characteristic discovery 1
  BLE_DISC_STATE_CHAR2,               // Characteristic discovery 2
  BLE_DISC_STATE_CHAR3,               // Characteristic discovery 3
  BLE_DISC_STATE_CHAR4,               // Characteristic discovery 4
  BLE_DISC_STATE_CHAR5,               // Characteristic discovery 5  
  BLE_DISC_STATE_CHAR6                // Characteristic discovery 6
};
           

預設的特征值狀态的宏隻有 BLE_DISC_STATE_CHAR,是以示例在這裡修改為 6 個。

4.2、定義一個自己存放特征句柄的數組(sImpleBLECentral.c)

// Discovered characteristic handle
static uint16 simpleBLECharHdl = 0;
static uint16 GUA_charHdl[6] = {0};     // 6個特征值句柄儲存位置
           

4.3、修改發現事件的處理函數 simpleBLEGATTDiscoveryEvent(sImpleBLECentral.c)

/*********************************************************************
 * @fn      simpleBLEGATTDiscoveryEvent
 *
 * @brief   Process GATT discovery event
 *
 * @return  none
 */
static void simpleBLEGATTDiscoveryEvent( gattMsgEvent_t *pMsg )
{
  attReadByTypeReq_t req;
  
  if ( simpleBLEDiscState == BLE_DISC_STATE_SVC )
  {
    // Service found, store handles
    if ( pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
         pMsg->msg.findByTypeValueRsp.numInfo > 0 )
    {
      simpleBLESvcStartHdl = pMsg->msg.findByTypeValueRsp.handlesInfo[0].handle;
      simpleBLESvcEndHdl = pMsg->msg.findByTypeValueRsp.handlesInfo[0].grpEndHandle;
    }
    
    // If procedure complete
    if ( ( pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP  && 
           pMsg->hdr.status == bleProcedureComplete ) ||
         ( pMsg->method == ATT_ERROR_RSP ) )
    {
      if ( simpleBLESvcStartHdl != 0 )
      {
        // Discover characteristic
        simpleBLEDiscState = BLE_DISC_STATE_CHAR1;
        
        req.startHandle = simpleBLESvcStartHdl;
        req.endHandle = simpleBLESvcEndHdl;
        req.type.len = ATT_BT_UUID_SIZE;
        req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR1_UUID);
        req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR1_UUID);

        GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId );  // 啟動發現特征值 char1
      }
    }
  }
  
  else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR1 )        // 發現 char1
  {
    //讀出char1的handle并儲存到GUA_charHdl
    if ( pMsg->method == ATT_READ_BY_TYPE_RSP &&                
         pMsg->msg.readByTypeRsp.numPairs > 0 )
    {
      GUA_charHdl[0] = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],
                                       pMsg->msg.readByTypeRsp.dataList[1] );

      simpleBLEProcedureInProgress = TRUE;                     // 此時仍在程序中
    }

    //發送指令讀取下一個特征值的句柄      
    else
    {                                                          // 注意這裡一定要 else,當 numPairs=0 時才能再讀下一個
      simpleBLEDiscState = BLE_DISC_STATE_CHAR2;
          
      req.startHandle = simpleBLESvcStartHdl;
      req.endHandle = simpleBLESvcEndHdl;
      req.type.len = ATT_BT_UUID_SIZE;
      req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR2_UUID);
      req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR2_UUID);

      GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId );
    }
  }  
  
  else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR2 )        // 發現 char2
  {
    //讀出char2的handle并儲存到GUA_charHdl
    if ( pMsg->method == ATT_READ_BY_TYPE_RSP &&                
         pMsg->msg.readByTypeRsp.numPairs > 0 )
    {
      GUA_charHdl[1] = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],
                                       pMsg->msg.readByTypeRsp.dataList[1] );
 
 
      simpleBLEProcedureInProgress = TRUE;                     // 此時仍在程序中
    }
 
 
    //發送指令讀取下一個特征值的句柄      
    else{                                                           
      simpleBLEDiscState = BLE_DISC_STATE_CHAR4;
          
      req.startHandle = simpleBLESvcStartHdl;
      req.endHandle = simpleBLESvcEndHdl;
      req.type.len = ATT_BT_UUID_SIZE;
      req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR4_UUID);
      req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR4_UUID);
 
 
      GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId );    
    }
  }  
/*    
  else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR3 )        // 發現 char3
  {
    //讀出char3的handle并儲存到GUA_charHdl
    if ( pMsg->method == ATT_READ_BY_TYPE_RSP &&                
         pMsg->msg.readByTypeRsp.numPairs > 0 )
    {
      GUA_charHdl[2] = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],
                                       pMsg->msg.readByTypeRsp.dataList[1] );
      simpleBLEProcedureInProgress = TRUE;                     // 此時仍在程序中
    }
    //發送指令讀取下一個特征值的句柄    
    else{                                                          
      simpleBLEDiscState = BLE_DISC_STATE_CHAR4;
          
      req.startHandle = simpleBLESvcStartHdl;
      req.endHandle = simpleBLESvcEndHdl;
      req.type.len = ATT_BT_UUID_SIZE;
      req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR4_UUID);
      req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR4_UUID);
      GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId ); 
    }
  }    
*/
  else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR4 )        // 發現 char4
  {
    //讀出char3的handle并儲存到GUA_charHdl
    if ( pMsg->method == ATT_READ_BY_TYPE_RSP &&                
         pMsg->msg.readByTypeRsp.numPairs > 0 )
    {
      GUA_charHdl[3] = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],
                                       pMsg->msg.readByTypeRsp.dataList[1] );
 
 
      simpleBLEProcedureInProgress = TRUE;                     // 此時仍在程序中
    }
    
    //發送指令讀取下一個特征值的句柄    
    else{                                                         
      simpleBLEDiscState = BLE_DISC_STATE_CHAR6;
          
      req.startHandle = simpleBLESvcStartHdl;
      req.endHandle = simpleBLESvcEndHdl;
      req.type.len = ATT_BT_UUID_SIZE;
      req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR6_UUID);
      req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR6_UUID);
 
 
      GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId ); 
    }
  } 
/*
  else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR5 )        // 發現char5
  {
    //讀出char3的handle并儲存到GUA_charHdl
    if ( pMsg->method == ATT_READ_BY_TYPE_RSP &&                
         pMsg->msg.readByTypeRsp.numPairs > 0 )
    {
      GUA_charHdl[4] = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],
                                       pMsg->msg.readByTypeRsp.dataList[1] );
      simpleBLEProcedureInProgress = TRUE;                     // 此時仍在程序中
    }
    
    
    //發送指令讀取下一個特征值的句柄    
    else{                                                          
      simpleBLEDiscState = BLE_DISC_STATE_CHAR6;
          
      req.startHandle = simpleBLESvcStartHdl;
      req.endHandle = simpleBLESvcEndHdl;
      req.type.len = ATT_BT_UUID_SIZE;
      req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR6_UUID);
      req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR6_UUID);
      GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId ); 
    }   
  } 
*/  
  else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR6 )        // 發現 char6
  {
    // Characteristic found, store handle
    if ( pMsg->method == ATT_READ_BY_TYPE_RSP && 
         pMsg->msg.readByTypeRsp.numPairs > 0 )
    {
      GUA_charHdl[5] = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],
                                       pMsg->msg.readByTypeRsp.dataList[1] );
      
      LCD_WRITE_STRING( "Char5 Found", HAL_LCD_LINE_6 );
      simpleBLEProcedureInProgress = FALSE;                     // 注意最後一個特征值時需要指派
    }
    
    simpleBLEDiscState = BLE_DISC_STATE_IDLE;                   // 讀完最後的char6,就可以傳回閑置模式了
  }    
}
           

注意:

1)主機端隻能獲得“特征值屬性為讀、通知,并且屬性表中為可讀”的特征值句柄。

特征值的屬性要為 GATT_PROP_READ 或 GATT_PROP_NOTIFY,且屬性表中對應的值的屬性要為 GATT_PERMIT_READ,主機端才能擷取到它的特征值句柄。

示例 1:

SimpleBLEPeripheral 工程的 char1 的屬性是可讀可寫(滿足條件)

// Simple Profile Characteristic 1 Properties
static uint8 simpleProfileChar1Props = GATT_PROP_READ | GATT_PROP_WRITE; // 特征值屬性
           

屬性表中的屬性是 GATT_PERMIT_READ(滿足條件)

// Characteristic Value 1
{ 
  { ATT_BT_UUID_SIZE, simpleProfilechar1UUID },
  GATT_PERMIT_READ | GATT_PERMIT_WRITE, 
  0, 
  &simpleProfileChar1 
},
           

是以,主機端可擷取到同時滿足兩個條件的 char1 的特征值句柄。

示例 2:

SimpleBLEPeripheral 工程的 char3 的屬性是可寫,不可讀(不滿足條件)

// Simple Profile Characteristic 3 Properties
static uint8 simpleProfileChar3Props = GATT_PROP_WRITE;
           

并且屬性表中的屬性也是GATT_PERMIT_WRITE(不滿足條件)

// Characteristic Value 3
{ 
  { ATT_BT_UUID_SIZE, simpleProfilechar3UUID },
  GATT_PERMIT_WRITE, 
  0, 
  &simpleProfileChar3 
},
           

是以,主機端不能擷取 char3 的特征值句柄。

如果想擷取,需要 char3 修改為 GATT_PROP_READ 和 GATT_PERMIT_READ.

示例 3:

SimpleBLEPeripheral 工程的 char4 的屬性是通知(滿足條件)

// Simple Profile Characteristic 4 Properties
static uint8 simpleProfileChar4Props = GATT_PROP_NOTIFY;
           

但是屬性表中的屬性是0,即不可讀不可寫(不滿足條件)

// Characteristic Value 4
{ 
  { ATT_BT_UUID_SIZE, simpleProfilechar4UUID },
  0, 
  0, 
  &simpleProfileChar4 
},
           

是以,主機端不能擷取 char4 的特征值句柄。

如果想擷取,需要 char4 的屬性表修改為 GATT_PERMIT_READ。

2)連續讀取特征值句柄時,如果中間某個特征值句柄讀取失敗,則會導緻後續的特征值也讀取不到。

3)char5 的屬性表的值的屬性是 GATT_PERMIT_AUTHEN_READ 是需要加密認證才能讀取。

refer:

https://wenku.baidu.com/view/32637475240c844769eaeea8.html

https://blog.csdn.net/feilusia/article/details/46909847

https://blog.csdn.net/feilusia/article/details/48235691

https://blog.csdn.net/feilusia/article/details/48314165

繼續閱讀