注:(本文基於我自己定義的一個服務TEMProfile,但適用其他服務)
1.特征值是什么
一個藍牙協議棧中,包含了多個服務,一個服務里又包含了多個特征值,每個特征值都有其相關的一些信息。
我們與藍牙進行通信的時候,就是通過讀寫這些特征值,來獲得數據。
2.特征值的屬性
一個特征值里面基本需要的變量是——
1.UUID碼
2.權限屬性 :基本就是 可讀、可寫、可通知這些了。(通知是表示允許數據主動發送)
3.內容
4.描述:這個特征值的名稱
3.屬性表
一個服務里,所有的特征值中的每個變量都有相應的屬性,所有的屬性都放在一個數組中,這個數組稱之為屬性表。
一個變量的屬性表包含四個內容,
1.type 2.permission 3.handle 4.pValue
屬性表其實就是定義了一個 gattAttribute_t類型的數組。
需要注意的是,屬性表中,除了特征值的屬性,第一個還要添加服務的屬性
1 //TEMProfile Service 2 { 3 {ATT_BT_UUID_SIZE,primaryServiceUUID}, //type 4 GATT_PERMIT_READ, //permissions 5 0, //handle 6 (uint8*)&TEMProfileService //pValue 7 },
4.增添一個新的特征值
(1)Define出配置屬性的數值,用以填寫配置屬性。
1 // Profile Parameters 2 #define TEMPROFILE_CHAR1 0 3 #define TEMPROFILE_CHAR2 1 4 5 // Simple Profile Service UUID 6 #define TEMPROFILE_SERV_UUID 0xFF00 7 8 // Key Pressed UUID 9 #define TEMPROFILE_CHAR1_UUID 0xFF01 10 #define TEMPROFILE_CHAR2_UUID 0xFF02 11 12 // Simple Keys Profile Services bit fields 13 #define TEMPROFILE_SERVICE 0x00000001 14 15 // Length of Characteristic 2 in bytes 16 #define TEMPROFILE_CHAR2_LEN 12
其中UUID號有特定的范圍,應避免與其他服務UUID沖突。
這里增添了兩個特征值,特征值2是數組型的,所以需要定義一個長度TEMPROFILE_CHAR2_LEN。
(2)定義每個特征值的屬性變量(以特征值2為例)
1 static uint8 TEMProfileChar2Prop = GATT_PROP_READ ; 2 // TEM Profile char2 Value 3 static uint8 TEMProfileChar2[TEMPROFILE_CHAR2_LEN] = {0}; 4 // TEM Profile char2 Description 5 static uint8 TEMProfileChar2Desp[6]="Data\0";
配置權限,內容,描述。
(3)由於屬性表中的Value屬性比較特殊,需要將其UUID號定義出來。具體原因暫時不是很理解。
1 CONST uint8 TEMProfilechar2UUID[ATT_BT_UUID_SIZE]= 2 { 3 LO_UINT16(TEMPROFILE_CHAR2_UUID),HI_UINT16(TEMPROFILE_CHAR2_UUID) 4 };
(4)填寫屬性表
1 static gattAttribute_t TEMProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED]= 2 { 3 //TEMProfile Service 4 { 5 {ATT_BT_UUID_SIZE,primaryServiceUUID}, //type 6 GATT_PERMIT_READ, //permissions 7 0, //handle 8 (uint8*)&TEMProfileService //pValue 9 }, 10 11 //char 1 Declaration 12 { 13 {ATT_BT_UUID_SIZE,characterUUID}, 14 GATT_PERMIT_READ, 15 0, 16 &TEMProfileChar1Prop 17 }, 18 19 //char 1 Value 20 { 21 {ATT_BT_UUID_SIZE,TEMProfilechar1UUID}, // !! Attribue Value UUID need definition 22 GATT_PERMIT_READ | GATT_PERMIT_WRITE, 23 0, 24 &TEMProfileChar1 25 }, 26 27 //char 1 Description 28 { 29 {ATT_BT_UUID_SIZE,charUserDescUUID}, 30 GATT_PERMIT_READ, 31 0, 32 TEMProfileChar1Desp 33 }, 34 35 //char 2 Declaration 36 { 37 {ATT_BT_UUID_SIZE,characterUUID}, 38 GATT_PERMIT_READ, 39 0, 40 &TEMProfileChar2Prop 41 }, 42 43 //char 2 Value 44 { 45 {ATT_BT_UUID_SIZE,TEMProfilechar2UUID}, // !! Attribue Value UUID need definition 46 GATT_PERMIT_READ, 47 0, 48 TEMProfileChar2 49 }, 50 51 //char 2 Description 52 { 53 {ATT_BT_UUID_SIZE,charUserDescUUID}, 54 GATT_PERMIT_READ, 55 0, 56 TEMProfileChar2Desp 57 }, 58 59 };
注意: 這里每個屬性都有一個權限屬性(如GATT_PERMIT_READ),之前定義特征值時也有一個權限變量(如GATT_PROP_WRITE) 兩者作用對象不一樣。
可以這樣理解,每個特征值都是一個大寶箱,里面還有許多個小寶箱,要打開他們需要不同的鑰匙。
至此,一個特征值的基本定義和聲明就已經做完了。但我們需要使用這個特征值,所以要在調用到特征值的函數中,添加上它。
(5)修改Get_Parameter函數和Set_Parameter函數、ReadAttrCB函數、WriteAttrCB函數
在服務中,基本是通過這四個函數對特征值進行讀寫。后兩個是回調函數。
在TEMProfile_SetParameter()函數中,新增一個case。
1 bStatus_t TEMProfile_SetParameter( uint8 param, uint8 len, void *value) 2 { 3 bStatus_t ret = SUCCESS; 4 switch ( param ) 5 { 6 case TEMPROFILE_CHAR1 : 7 if( len == sizeof(uint8) ) 8 { 9 TEMProfileChar1 = *((uint8*)value); 10 } 11 else 12 { 13 ret = bleInvalidRange; 14 } 15 break; 16 17 case TEMPROFILE_CHAR2 : 18 if( len == TEMPROFILE_CHAR2_LEN ) 19 { 20 VOID osal_memcpy( TEMProfileChar2, value, TEMPROFILE_CHAR2_LEN ); 21 } 22 else 23 { 24 ret = bleInvalidRange; 25 } 26 break; 27 28 default : 29 ret = INVALIDPARAMETER; 30 break; 31 } 32 33 return ret; 34 35 }
這是一個設置特征值內容的函數,參數param是特征值,len是內容的長度,value是新內容的地址。
以特征值2為例,先判斷新內容的長度是否符合原先特征值定義的內容長度。如果一致,則將新內容填寫進入特征值的內容TEMProfileChar2。
TEMProfile_GetParameter()函數同理
1 bStatus_t TEMProfile_GetParameter( uint8 param, void *value) 2 { 3 bStatus_t ret = SUCCESS; 4 switch ( param ) 5 { 6 case TEMPROFILE_CHAR1 : 7 *((uint8*)value) = TEMProfileChar1; 8 break; 9 10 case TEMPROFILE_CHAR2 : 11 VOID osal_memcpy( value, TEMProfileChar2, TEMPROFILE_CHAR2_LEN ); 12 break; 13 14 default: 15 ret = INVALIDPARAMETER; 16 break; 17 } 18 19 return (ret); 20 }
然后是TEMProfile_WriteAttrCB()函數
1 static bStatus_t TEMProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr, 2 uint8 *pValue, uint8 len, uint16 offset ) 3 { 4 bStatus_t status = SUCCESS; 5 uint8 notifyApp = 0xFF; 6 7 if ( gattPermitAuthorWrite( pAttr->permissions ) ) 8 { 9 return ( ATT_ERR_INSUFFICIENT_AUTHOR ); 10 } 11 12 if ( pAttr->type.len == ATT_BT_UUID_SIZE ) 13 { 14 uint16 uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]); 15 switch (uuid) 16 { 17 case TEMPROFILE_CHAR1_UUID: 18 19 if( offset == 0 ) 20 { 21 if( len != 1 ) 22 { 23 status = ATT_ERR_INVALID_VALUE_SIZE; 24 } 25 } 26 else 27 { 28 status = ATT_ERR_ATTR_NOT_LONG; 29 } 30 31 32 if ( status == SUCCESS ) 33 { 34 uint8 *pCurValue = (uint8 *)pAttr->pValue; 35 *pCurValue = pValue[0]; 36 notifyApp = TEMPROFILE_CHAR1; 37 } 38 39 break; 40 41 default: 42 status = ATT_ERR_ATTR_NOT_FOUND; 43 break; 44 } 45 } 46 else 47 { 48 status = ATT_ERR_INVALID_HANDLE; 49 } 50 51 if ( (notifyApp != 0xFF ) && TEMProfile_AppCBs && TEMProfile_AppCBs->pfnTEMProfileChange ) 52 { 53 TEMProfile_AppCBs->pfnTEMProfileChange( notifyApp ); 54 } 55 56 return ( status ); 57 }
以及ReadAttrCb函數
1 static uint8 TEMProfile_ReadAttrCB( uint16 connHandle, gattAttribute_t *pAttr, 2 uint8 *pValue, uint8 *pLen, uint16 offset, uint8 maxLen ) 3 { 4 bStatus_t status = SUCCESS; 5 6 if( gattPermitAuthorRead( pAttr->permissions)) 7 { 8 return (ATT_ERR_INSUFFICIENT_AUTHOR); 9 } 10 11 if( offset > 0) 12 { 13 return (ATT_ERR_ATTR_NOT_LONG); 14 } 15 16 if ( pAttr->type.len == ATT_BT_UUID_SIZE ) 17 { 18 uint16 uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]); 19 switch( uuid ) 20 { 21 //must have read permisson 22 case TEMPROFILE_CHAR1_UUID: 23 *pLen =1; 24 pValue[0] = *pAttr->pValue; 25 break; 26 27 case TEMPROFILE_CHAR2_UUID: 28 *pLen = TEMPROFILE_CHAR2_LEN; 29 VOID osal_memcpy( pValue, pAttr->pValue, TEMPROFILE_CHAR2_LEN ); 30 break; 31 32 default: 33 *pLen = 0; 34 status=ATT_ERR_ATTR_NOT_FOUND; 35 break; 36 } 37 } 38 else 39 { 40 *pLen = 0; 41 status=ATT_ERR_INVALID_HANDLE; 42 } 43 44 return (status); 45 46 }
至此服務的特征值已經修改完,接下來需要去應用層進行設置。
(6)在SimpleBLEPeripheral_Init()函數中,初始化特征值。
1 uint8 TEMProfile_Char1Vaule=1; 2 uint8 TEMProfile_Char2Value[TEMPROFILE_CHAR2_LEN]="2017.03.11\0"; 3 TEMProfile_SetParameter( TEMPROFILE_CHAR1, sizeof(uint8), &TEMProfile_Char1Vaule ); 4 TEMProfile_SetParameter( TEMPROFILE_CHAR2, TEMPROFILE_CHAR2_LEN, TEMProfile_Char2Value );
(7)回調函數simpleProfileChangeCB( )中增添特征值。
該函數是當特征值改變時,即會被調用。
本例中,當特征值改變時,LCD上的數據也會隨之改變。
1 static void simpleProfileChangeCB( uint8 paramID ) 2 { 3 uint8 newValue; 4 5 switch( paramID ) 6 { 7 case SIMPLEPROFILE_CHAR1: 8 SimpleProfile_GetParameter( SIMPLEPROFILE_CHAR1, &newValue ); 9 10 #if (defined HAL_LCD) && (HAL_LCD == TRUE) 11 HalLcdWriteStringValue( "Char 1:", (uint16)(newValue), 10, HAL_LCD_LINE_3 ); 12 #endif // (defined HAL_LCD) && (HAL_LCD == TRUE) 13 14 break; 15 16 case SIMPLEPROFILE_CHAR3: 17 SimpleProfile_GetParameter( SIMPLEPROFILE_CHAR3, &newValue ); 18 19 #if (defined HAL_LCD) && (HAL_LCD == TRUE) 20 HalLcdWriteStringValue( "Char 3:", (uint16)(newValue), 10, HAL_LCD_LINE_3 ); 21 #endif // (defined HAL_LCD) && (HAL_LCD == TRUE) 22 23 break; 24 25 default: 26 // should not reach here! 27 break; 28 } 29 }
至此,特征值的新增即完成了。
APP中已可以發現這兩個特征值。