一、 什么是特征值
特征值就是BLE协议栈向外提供的一个数据接口,蓝牙之间的数据传输终落实在特征值上。在BLE协议栈的GATT层中封装了若干服务(service),而在每一个服务中又有若干特征值(characters),特征值可以是任意类型的数据。蓝牙之间的数据传输靠协议栈提供的write和read函数,而这两个函数就是在操作特征值
二、UUID
UUID就是通用唯一识别码。在蓝牙协议栈中可能会有多个服务,每个服务会有多个特征值,而这些服务或者特征值都有一个唯一的ID,这样就可以区分了。这个UUID是其他设备设置蓝牙服务和特征值的唯一方法。
三、增加特征值
在BLE协议栈中,GATT层定义了特征值和服务。下面就以SimpleBLEPeripheral为例,增加一个特征值。在simpleGATTprofile.c中,已经有定义好的特征值,参考已有的特征值就可以顺利添加自己的特征值
1) 修改头文件simpleGATTprofile.h
头文件中定义了特征值的UUID,以及长度和默认值
//特征值UUID
#define SIMPLEPROFILE_CHAR5_UUID 0xFFF5
//特征值长度
#define SIMPLEPROFILE_CHAR5_LEN 5
2)添加特征值相关变量
包括特征值的读写权限、变量名、展现给用户的名字
//特征值初始化
// 特征值属性,读或者写
static uint8 simpleProfileChar6Props = GATT_PROP_READ | GATT_PROP_WRITE;
// 值,可以向其写入数据,也可以读出数据。这里是一个字符数组
static uint8 simpleProfileChar6[SIMPLEPROFILE_CHAR6_LEN] = {0};
// 用户描述,展现给用户的名字
static uint8 simpleProfileChar6UserDesp[17] = "Characteristic 6\0";
//提取uuid,uuid定义在头文件中
CONST uint8 simpleProfilechar5UUID[ATT_BT_UUID_SIZE] =
{
LO_UINT16(SIMPLEPROFILE_CHAR5_UUID), HI_UINT16(SIMPLEPROFILE_CHAR5_UUID)
};
3)将特征值加入属性表
特征值由服务统一管理,所有的特征值都会在一个服务的属性表中呈现出来,每增加一个特征值,它的相关变量就要在添加到属性表中
//特征值初始化
static gattAttribute_t simpleProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED] =
{
// Simple Profile Service
{
{ ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */
GATT_PERMIT_READ, /* permissions */
0, /* handle */
(uint8 *)&simpleProfileService /* pValue */
},
// Characteristic 1 Declaration
{
{ ATT_BT_UUID_SIZE, characterUUID },
GATT_PERMIT_READ,
0,
&simpleProfileChar1Props
},
// Characteristic Value 1
{
{ ATT_BT_UUID_SIZE, simpleProfilechar1UUID },
GATT_PERMIT_AUTHEN_READ | GATT_PERMIT_AUTHEN_WRITE,
0,
&simpleProfileChar1
},
// Characteristic 1 User Description
{
{ ATT_BT_UUID_SIZE, charUserDescUUID },
GATT_PERMIT_READ,
0,
simpleProfileChar1UserDesp
},
。。。。。
// Characteristic 5 Declaration
{
{ ATT_BT_UUID_SIZE, characterUUID },
GATT_PERMIT_READ,
0,
&simpleProfileChar5Props
},
// Characteristic Value 5
{
{ ATT_BT_UUID_SIZE, simpleProfilechar5UUID },
GATT_PERMIT_AUTHEN_READ| GATT_PERMIT_AUTHEN_WRITE,
0,
simpleProfileChar5
},
// Characteristic 5 User Description
{
{ ATT_BT_UUID_SIZE, charUserDescUUID },
GATT_PERMIT_READ,
0,
simpleProfileChar5UserDesp
},
4)修改属性表的长度
每增加一个特征值,属性表的长度也会增加,因此要修改属性表的长度。在文件的一开始就声明了属性表的长度
#define SERVAPP_NUM_ATTR_SUPPORTED 24
5)修改SimpleProfile_SetParamete和SimpleProfile_GetParamete函数
这是操作特征值的两个函数,set函数可以用来初始化特征值,get函数可以用来提取特征值。一般我们定义的特征值都是uint8类型的数组,因此无论set还是get,都可以使用copy函数来完成,同时要注意实际的长度
bStatus_t SimpleProfile_SetParameter( uint8 param, uint8 len, void *value )
{
bStatus_t ret = SUCCESS;
switch ( param )
{
。。。。。。。
case SIMPLEPROFILE_CHAR5:
if ( len <= SIMPLEPROFILE_CHAR5_LEN )
{
//将value值复制到特征值5,同时注意长度
VOID osal_memcpy( simpleProfileChar5, value, len );
}
else
{
ret = bleInvalidRange;
}
break;
default:
ret = INVALIDPARAMETER;
break;
}
return ( ret );
}
bStatus_t SimpleProfile_GetParameter( uint8 param, void *value )
{
bStatus_t ret = SUCCESS;
switch ( param )
{
。。。。。。
case SIMPLEPROFILE_CHAR5:
//将特征值5复制到value
VOID osal_memcpy( value, simpleProfileChar5, osal_strlen(simpleProfileChar5));
break;
default:
ret = INVALIDPARAMETER;
break;
}
return ( ret );
}
6)修改simpleProfile_ReadAttrCB和simpleProfile_WriteAttrCB函数
上面已经有了set和get函数,可以实现对特征值的读写,那么这里为何又来了一对read和write呢???set和get是用来本地读写特征值的,而read和write则是网络上的读写。什么意思呢,当蓝牙网络的另一端想要读取特征值的时候,协议栈就会自动回调这个read函数,然后将读取的结果传输的网络的另一端。当然,写操作也是一样的。
static uint8 simpleProfile_ReadAttrCB( uint16 connHandle, gattAttribute_t *pAttr,
uint8 *pValue, uint8 *pLen, uint16 offset, uint8 maxLen )
{
bStatus_t status = SUCCESS;
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 SIMPLEPROFILE_CHAR5_UUID:
*pLen = osal_strlen(pAttr->pValue);
VOID osal_memcpy( pValue, pAttr->pValue, osal_strlen(pAttr->pValue) );
break;
default:
// Should never get here! (characteristics 3 and 4 do not have read permissions)
*pLen = 0;
status = ATT_ERR_ATTR_NOT_FOUND;
break;
}
}
return ( status );
}
static bStatus_t simpleProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr,
uint8 *pValue, uint8 len, uint16 offset )
{
bStatus_t status = SUCCESS;
uint8 notifyApp = 0xFF;
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 SIMPLEPROFILE_CHAR5_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;
} //若输入长度不对,status为
}
else
{
status = ATT_ERR_ATTR_NOT_LONG;//不是第一字节
}
if ( status == SUCCESS )
{
//清空缓冲区
osal_memset(pAttr->pValue, '\0', SIMPLEPROFILE_CHAR6_LEN) ;
//复制
VOID osal_memcpy( pAttr->pValue, pValue, len);
notifyApp = SIMPLEPROFILE_CHAR6;
}
break;
default:
// Should never get here! (characteristics 2 and 4 do not have write permissions)
status = ATT_ERR_ATTR_NOT_FOUND;
break;
}
}
7)修改simpleBLEPeripheral.c
A、在SimpleBLEPeripheral_Init函数中可以使用set方法对特征值做初始化操作
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR5, 5, charValue5 );
B、修改simpleProfileChangeCB
当特征值被网络的另一端修改之后,协议栈会回调这个函数,通知当前特征值发生变化
static void simpleProfileChangeCB( uint8 paramID )
{
uint8 newValue;
uint8 *val;
switch( paramID )
{
。。。。。。
case SIMPLEPROFILE_CHAR5:
val = osal_msg_allocate(15);
//提取特征值,注意这里使用的是get方式
SimpleProfile_GetParameter( SIMPLEPROFILE_CHAR5, val );
break;
default:
// should not reach here!
break;
}
SerialPrint(val);
}
四、测试
如何验证特征值已经被成功的添加呢,基于上面的例子,我们需要一个BLE主机设备来读取特征值。建议使用手机来完成测试,因为你很难确保你的主机代码是正确的。从网络上下载BLE调试软件,可以轻松的操作特征值。