TimeApp工程共需要寻找如下7个服务及其特征如下:
经过实际测试,TimeApp工程目前无法在安卓手机上工作,不仅如此,TimeApp与iPhone手机连接后也只能实现部分功能。所以说TimeApp工程的最重要的目的其实是让程序员了解GATT的客户端如何寻找GATT服务器上的特征。
TimeApp与iPhone连接后,只有Current Time Service服务的Current Time特征和Battery Service服务的Battery Level特征能够正常工作,其他服务的特征都没有什么用处。所以只要掌握这上述所说的两个服务及特征就可以基本上掌握了整个TimeApp工程了。下面就Current Time和Battery Level两个特征进行分析,讲述如何获取到手机的当前时间和电量。
1、寻找服务特征
首先保证已经获取到了Current Time Service和Battery Service两个服务的特征对应的句柄,这是寻找服务特征部分的内容,在《BLE工程——TimeApp服务特征寻找过程分析》已经详细讲述过了,这里不再赘述。获取到的句柄全部保存在数组timeAppHdlCache[]数组中。其中:
timeAppHdlCache[HDL_CURR_TIME_CT_TIME_START]
是Current Time特征的句柄;
timeAppHdlCache[HDL_BATT_LEVEL_START]
是Battery Level特征的句柄。
由于Current Time和Battery Level特征都具有可通知的属性,所以它们还有专门用于接收这两个特征值通知的特征配置描述(CCCD)所对应的句柄:
timeAppHdlCache[HDL_CURR_TIME_CT_TIME_CCCD]
和
timeAppHdlCache[HDL_BATT_LEVEL_CCCD]。
当获取得到了服务特征的句柄后,就可根据特征的权限做对应的操作了。Current Time和Battery Level特征都具有:可读、可通知两个属性。所以,只要向特征发起一个读请求,就可立即获取到这两个特征值了,如下:
attReadReq_t readReq;
readReq.handle = timeAppHdlCache[HDL_CURR_TIME_CT_TIME_START];
GATT_ReadCharValue( timeAppConnHandle, &readReq, timeAppTaskId );
和
attReadReq_t readReq;
readReq.handle = timeAppHdlCache[HDL_BATT_LEVEL_START];
GATT_ReadCharValue( timeAppConnHandle, &readReq, timeAppTaskId );
另外,配置两个特征的CCCD,打开它的通知功能,就可以以通知的形式收到这两个特征值(当特征值改变时),如下:
attWriteReq_t writeReq;
writeReq.len = 2;
writeReq.value[0] = LO_UINT16(GATT_CLIENT_CFG_NOTIFY);
writeReq.value[1] = HI_UINT16(GATT_CLIENT_CFG_NOTIFY);
writeReq.sig = 0;
writeReq.cmd = 0;
writeReq.handle = timeAppHdlCache[HDL_CURR_TIME_CT_TIME_CCCD];
GATT_WriteCharValue( timeAppConnHandle, &writeReq, timeAppTaskId );
和
attWriteReq_t writeReq;
writeReq.len = 2;
writeReq.value[0] = LO_UINT16(GATT_CLIENT_CFG_NOTIFY);
writeReq.value[1] = HI_UINT16(GATT_CLIENT_CFG_NOTIFY);
writeReq.sig = 0;
writeReq.cmd = 0;
writeReq.handle = timeAppHdlCache[HDL_BATT_LEVEL_CCCD];
GATT_WriteCharValue( timeAppConnHandle, &writeReq, timeAppTaskId );
2、接收特征值
至此,就已经可以获取Current Time和Battery Level两个特征值了。这两个特征值获取的途径有两种:一种是主动发出读请求来得到特征值;另一种是当特征值改变时,以通知的形式收到特征值。TimeApp工程timeapp.c文件的timeAppProcessGattMsg()函数就是处理这两种方式收到的特征值。当满足如下条件时,则说明是以通知的形式收到的特征值,此时,调用timeAppIndGattMsg()来解析处理特征值:
if ( pMsg->method == ATT_HANDLE_VALUE_NOTI ||
pMsg->method == ATT_HANDLE_VALUE_IND )
{
timeAppIndGattMsg( pMsg );
}
当满足如下条件时,则说明是以发送读请求的形式收到的特征值,此时调用timeAppConfigGattMsg()函数来处理:
else if((pMsg->method==ATT_READ_RSP||pMsg->method==ATT_WRITE_RSP )||
( pMsg->method == ATT_ERROR_RSP &&
( pMsg->msg.errorRsp.reqOpcode == ATT_READ_REQ ||
pMsg->msg.errorRsp.reqOpcode == ATT_WRITE_REQ ) ) )
{
timeAppConfigState = timeAppConfigGattMsg ( timeAppConfigState, pMsg );
}
3、处理特征值
当获取到特征值后,就是对特征值进行解析。
Battery Level的特征值非常简单,只有一个字节,如下:
字节 Byte0 说明 电量等级 范围 0~99特征值为5,就表示手机当前的电量为5%。
Current Time的特征值就稍复杂了,每次收到的特征值长度为10字节,如下:
字节 Byte0~Byte1 Byte2 Byte3 Byte4 Byte5 Byte6 Byte7 Byte8 Byte9 说明年 月 日 时 分 秒 星期 1/256秒 校准理由 范围 1589~9999 1~12 1~31 0~23 0~59 0~59 0~7 0~255 -我们可以忽略掉其中的秒字段、1/256秒字段和校准理由字段。每个字段值都很好理解,举个例子就知道了,如收到特征值为:
D1 07 09 17 12 13 01 03 07 02
进行解析后,可得:
特征值(hex) D1 07 09 17 12 13 01 03 07 02 数值(Dec) 0x07d1=2015 0x07d1=2015 9 23 18 19 01 3 7 2 说明 年 年 月 日 时 分 秒 星期 1/256秒 校准理由即当前的时间为:2015年9月23日,18点19分,星期三。
4、显示
TimeApp工程可以运行在SmartRF开发板上,并将当前时间和电量显示在液晶屏上。如下图所示:
电池电量的显示非常简单,直接显示出来就可以了。当手机的电量改变时,TimeApp就会收到电池电量改变的通知,并更新液晶屏上的电量值。如电量从99%降到了98%时,TimeApp会收到值为98%的特征值,此时将98%显示在液晶屏上。
当前时间的显示则需要将收到的Current Time特征值进行解析,然后提取出其中的年、月、日、时、分、星期几个字段,然后显示在液晶屏上。但是需要注意的是,对于Current Time特特征来说,尽管它具有可通知的属,且我们将它打开了它的通知能力,但是实际的情况下,尽管当前时间改变了(无论是年/月/日/时/分/秒)都不会发出时间改变的通知,所以TimeApp工程只会在连接上手机并找到服务特征后,才发送读请求同步一次时间。基于此原因,TimeApp工程在代码一开始就打开了一个60s定时器,如下:
osal_start_timerEx( timeAppTaskId, CLOCK_UPDATE_EVT, DEFAULT_CLOCK_UPDATE_PERIOD );
当1分钟的定时到时,调用timeAppClockDisplay()函数更新显示屏上的时间,并重启定时器,如下(TimeApp_ProcessEvent()函数中):
if ( events & CLOCK_UPDATE_EVT )
{
timeAppClockDisplay();
osal_start_timerEx( timeAppTaskId, CLOCK_UPDATE_EVT,
DEFAULT_CLOCK_UPDATE_PERIOD );
return ( events ^ CLOCK_UPDATE_EVT );
}
所以,要记住电池电量和当前时间的在显示屏的更新方式是不同。