注:(本文基于我自己定义的一个服务TEMProfile,但适用其他服务)
1.特征值是什么
一个蓝牙协议栈中,包含了多个服务,一个服务里又包含了多个特征值,每个特征值都有其相关的一些信息。
我们与蓝牙进行通信的时候,就是通过读写这些特征值,来获得数据。
2.特征值的属性
一个特征值里面基本需要的变量是——
1.UUID码
2.权限属性 :基本就是 可读、可写、可通知这些了。(通知是表示允许数据主动发送)
3.内容
4.描述:这个特征值的名称
3.属性表
一个服务里,所有的特征值中的每个变量都有相应的属性,所有的属性都放在一个数组中,这个数组称之为属性表。
一个变量的属性表包含四个内容,
1.type 2.permission 3.handle 4.pValue
属性表其实就是定义了一个 gattAttribute_t类型的数组。
需要注意的是,属性表中,除了特征值的属性,第一个还要添加服务的属性
1 //TEMProfile Service2 {3 {ATT_BT_UUID_SIZE,primaryServiceUUID}, //type4 GATT_PERMIT_READ, //permissions5 0, //handle6 (uint8*)&TEMProfileService //pValue7 },
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 0xFF0110 #define TEMPROFILE_CHAR2_UUID 0xFF0211 12 // Simple Keys Profile Services bit fields13 #define TEMPROFILE_SERVICE 0x0000000114 15 // Length of Characteristic 2 in bytes16 #define TEMPROFILE_CHAR2_LEN 12
其中UUID号有特定的范围,应避免与其他服务UUID冲突。
这里增添了两个特征值,特征值2是数组型的,所以需要定义一个长度TEMPROFILE_CHAR2_LEN。
(2)定义每个特征值的属性变量(以特征值2为例)
1 static uint8 TEMProfileChar2Prop = GATT_PROP_READ ;2 // TEM Profile char2 Value3 static uint8 TEMProfileChar2[TEMPROFILE_CHAR2_LEN] = { 0};4 // TEM Profile char2 Description5 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 Declaration12 {13 {ATT_BT_UUID_SIZE,characterUUID}, 14 GATT_PERMIT_READ, 15 0, 16 &TEMProfileChar1Prop 17 }, 18 19 //char 1 Value20 {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 Description28 {29 {ATT_BT_UUID_SIZE,charUserDescUUID}, 30 GATT_PERMIT_READ, 31 0, 32 TEMProfileChar1Desp 33 }, 34 35 //char 2 Declaration36 {37 {ATT_BT_UUID_SIZE,characterUUID}, 38 GATT_PERMIT_READ, 39 0, 40 &TEMProfileChar2Prop 41 }, 42 43 //char 2 Value44 {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 else12 {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 else23 {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 else27 {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 else47 {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 permisson22 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 else39 {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中已可以发现这两个特征值。