CC2541 寻找ANCS服务
转载:http://www.lofter.com/tag/cc2541
BLE设备想要获取iOS通知,寻找ANCS服务是其中非常关键的一步。而寻找ANCS服务也是代码实现最复杂的部分。就来讲讲如何代码实现寻找ANCS服务。
1、寻找状态
在寻找ANCS的过程中,会存在7种状态:空闲状态、开始寻找状态、寻找服务状态、寻找特征状态、寻找通知源特征CCCD状态、寻找数据源特征CCCD状态、寻找失败状态。写成代码如下:
enum/*NCS Discoverty State*/
{
DISC_IDLE = 0x00,
DISC_ANCS_START = 0x10,
DISC_ANCS_SVC,
DISC_ANCS_CHAR,
DISC_ANCS_NTF_CCCD,
DISC_ANCS_DATA_CCCD,
DISC_FAILED = 0xFF
};
2、特征的句柄
对于BLE设备来说,它能够ANCS服务中的不同特征进行数据通讯,靠的并不是UUID,而是句柄。句柄就类似于网络中客户端服务器“端口”(Port)一样的存在。在寻找ANCS服务过程中,要保存获取的各类句柄,如通知源特征的起始句柄、通知源特征结束句柄、通知源特征CCCD的句柄等等。如下所示:
enum/*ANCS Handle*/
{
HDL_ANCS_NTF_SOURCE_START, /*ANCS Notification Source start handle*/
HDL_ANCS_NTF_SOURCE_END, /*ANCD Notification Source end handle*/
HDL_ANCS_NTF_SOURCE_CCCD, /*ANCD Notification Source CCCD handle*/
HDL_ANCS_DATA_SOURCE_START, /*ANCS Data Source start handle */
HDL_ANCS_DATA_SOURCE_END, /*NCS Data Source end handle*/
HDL_ANCS_DATA_SOURCE_CCCD, /*ANCS Data source CCCD handle*/
HDL_ANCS_CTL_POINT_CTRL, /*ANCS Control Point handle*/
HDL_ANCS_CASCE_LEN /*Number of ANCS handle*/
};
再定义一个数组专门用于保存上面各类句柄的值,如下:
uint16 ANCSHdlCache[HDL_ANCS_CASCE_LEN];
3、ANCS的UUID
在寻找ANCS过程中,服务与特征的UUID是需要的。ANCS存在下面几种UUID。
· 主服务UUID: 7905F431-B5CE-4E99-A40F-4B1E122D00D0
· 通知源特征UUID:9FBF120D-6301-42D9-8C58-25E699A21DBD
· 控制点特征UUID:69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9
· 数据源特征UUID:22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB
主服务的UUID可以用来寻找特征的句柄范围,所以必须要是完整,如下:
#define ANCS_SVC_UUID {0xd0,0x00,0x2d,0x12,0x1e,0x4b,0x0f,0xa4,0x99,0x4e,0xce,0xb5,0x31,0xf4,0x05,0x79}
找到特征的句柄范围后,就可以轻易获得个特征的句柄,这是要依靠UUID来区分是哪种特征,判断时并不需要完整UUID,而只要一小部分就可以了,这里选择最前的16位UUID,如下:
#define ANCS_NTF_SOURCE_CHAR_UUID 0x1DBD
#define ANSC_DATA_SOURCE_CHAR_UUID 0x7BFB
#define ANCS_CTL_POIONT_CHAR_UUID 0xD9D9
4、寻找ANCD服务
接着编写函数ANCSDiscovery()函数用于寻找ANCS服务。该函数包含了寻找ANCS的5个状态:开始寻找状态、寻找服务状态、寻找特征状态、寻找通知源特征CCCD状态、寻找数据源特征CCCD状态。如下:
uint8 ANCSDiscovery(uint8 state, gattMsgEvent_t *pMsg)
{
switch(state)
{
case DISC_ANCS_START:
......
break;
case DISC_ANCS_SVC:
......
break;
case DISC_ANCS_CHAR:
......
break;
case DISC_ANCS_NTF_CCCD:
.......
break;
case DISC_ANCS_DATA_CCCD:
.......
break;
default:
break;
}
return newState;
}
(1)寻找主服务
首先在DISC_ANCS_START下,开始寻找ANCS服务。根据ACCS服务的UUID找到ANCS的主服务,调用GATT_DiscPrimaryServiceByUUID()就可以实现,代码如下:
case DISC_ANCS_START:
{
uint8 uuid[ATT_UUID_SIZE] = ANCS_SVC_UUID;
/* Initialize service discovery variables*/
svcStartHdl = svcEndHdl = 0;
endHdlIdx = 0;
/* Discover service by UUID*/
GATT_DiscPrimaryServiceByUUID( AppleANCSConnHandle, uuid,
ATT_UUID_SIZE, AppleANCSTaskId );
newState = DISC_ANCS_SVC;
}
break;
函数DiscPrimaryServiceByUUID()会向服务器发送获取主服务的请求,并进入下一状态DISC_ANCS_SVC。
(2)寻找特征
当收到服务器的响应时,可以获取到ANCS服务的起始句柄和结束句柄两个参数,再根据这另个参数,就可以调用函数GATT_DiscAllChars()寻找之间的特征,代码如下:
if ( pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
pMsg->msg.findByTypeValueRsp.numInfo > 0 )
{
/*store service handle and end handle */
svcStartHdl = pMsg->msg.findByTypeValueRsp.handlesInfo[0].handle;
svcEndHdl = 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 service found*/
if ( svcStartHdl != 0 )
{
/* Discover all characteristics */
GATT_DiscAllChars( AppleANCSConnHandle, svcStartHdl,
svcEndHdl, AppleANCSTaskId );
newState = DISC_ANCS_CHAR;
}
else
{
/* Service not found*/
newState = DISC_FAILED;
}
}
break;
函数GATT_DiscAllChars()会向服务器发出获取各特征的请求,并进入下一状态DISC_ANCS_CHAR。
(3)寻找通知源特征的CCCD
收到服务的响应后,响应数据携带每个特征的UUID与句柄等信息,并将句柄提取出来。对于控制点特征来说值存在一个句柄,而对于通知源特征和数据源特征来说就不止一个特征了,因为他们还存在CCCD,所以需要保存它们的句柄范围,然后调用函数GATT_DiscAllCharDescs()最先开始需找通知源特征的CCCD,如下所示。
case DISC_ANCS_CHAR:
{
uint8 i;
uint8 *p;
uint16 handle;
uint16 uuid;
/* Characteristics found*/
if ( pMsg->method == ATT_READ_BY_TYPE_RSP &&
pMsg->msg.readByTypeRsp.numPairs > 0 &&
pMsg->msg.readByTypeRsp.len == CHAR_DESC_HDL_UUID128_LEN )
{
/* For each characteristic declaration*/
p = pMsg->msg.readByTypeRsp.dataList;
for ( i = pMsg->msg.readByTypeRsp.numPairs; i > 0; i-- )
{
/* Parse characteristic declaration*/
handle = BUILD_UINT16(p[3], p[4]);
uuid = BUILD_UINT16(p[5], p[6]);
/* If looking for end handle*/
if ( endHdlIdx != 0 )
{
/* End handle is one less than handle of characteristic declaration*/
ANCSHdlCache[endHdlIdx] = BUILD_UINT16(p[0], p[1]) - 1;
endHdlIdx = 0;
}
/* If UUID is of interest, store handle*/
switch ( uuid )
{
/*Store ANCS Notification Source handle*/
case ANCS_NTF_SOURCE_CHAR_UUID:
ANCSHdlCache[HDL_ANCS_NTF_SOURCE_START] = handle;
endHdlIdx = HDL_ANCS_NTF_SOURCE_END;
break;
/*Store ANCS Data Source characteristic handle */
case ANSC_DATA_SOURCE_CHAR_UUID:
ANCSHdlCache[HDL_ANCS_DATA_SOURCE_START] = handle;
endHdlIdx = HDL_ANCS_DATA_SOURCE_END;
break;
/*Store ANCS Control Point characteristic handle*/
case ANCS_CTL_POIONT_CHAR_UUID:
ANCSHdlCache[HDL_ANCS_CTL_POINT_CTRL] = handle;
break;
default:
break;
}
p += CHAR_DESC_HDL_UUID128_LEN;
}
}
/* If procedure complete*/
if ( ( pMsg->method == ATT_READ_BY_TYPE_RSP &&
pMsg->hdr.status == bleProcedureComplete ) ||
( pMsg->method == ATT_ERROR_RSP ) )
{
/* Special case of end handle at end of service*/
if ( endHdlIdx != 0 )
{
ANCSHdlCache[endHdlIdx] = svcEndHdl;
endHdlIdx = 0;
}
/* If didn't find mandatory characteristic*/
if ( ANCSHdlCache[HDL_ANCS_NTF_SOURCE_START] == 0 )
{
newState = DISC_FAILED;
}
else if ( ANCSHdlCache[HDL_ANCS_NTF_SOURCE_START] <
ANCSHdlCache[HDL_ANCS_NTF_SOURCE_END] )
{
/* Discover Notification Source client configuration characteristic descriptors*/
GATT_DiscAllCharDescs( AppleANCSConnHandle,
ANCSHdlCache[HDL_ANCS_NTF_SOURCE_START] + 1,
ANCSHdlCache[HDL_ANCS_NTF_SOURCE_END],
AppleANCSTaskId );
newState = DISC_ANCS_NTF_CCCD;
}
else
{
newState = DISC_IDLE;
}
}
}
break;
函数DiscAllCharDescs()会向服务器发起寻找指定的CCCD请求,并进入DISC_ANCS_NTF_CCCD状态。
(4)寻找数据源CCCD
服务器收到寻找CCCD请求后,就会返回携带CCCD句柄的响应数据,将句柄保存下来,就得到了通知源特征CCCD句柄。然后,再调用一次函数DiscAllCharDescs()来寻找数据源特征的CCCD,代码如下:
case DISC_ANCS_NTF_CCCD:
{
uint8 i;
/* Notification Source client configuration characteristic descriptors found*/
if (pMsg->method == ATT_FIND_INFO_RSP &&
pMsg->msg.findInfoRsp.numInfo > 0 &&
pMsg->msg.findInfoRsp.format == ATT_HANDLE_BT_UUID_TYPE )
{
/*For each handle/uuid pair*/
for (i = 0; i < pMsg->msg.findInfoRsp.numInfo; i++)
{
/* Look for CCCD*/
if((pMsg->msg.findInfoRsp.info.btPair[i].uuid[0] ==
LO_UINT16(GATT_CLIENT_CHAR_CFG_UUID)) &&
(pMsg->msg.findInfoRsp.info.btPair[i].uuid[1] ==
HI_UINT16(GATT_CLIENT_CHAR_CFG_UUID)))
{
/* CCCD found, Store Notification source CCCD handle*/
ANCSHdlCache[HDL_ANCS_NTF_SOURCE_CCCD]=pMsg->msg.findInfoRsp.info.btPair[i].handle;
break;
}
}
}
/* If procedure complete*/
if ((pMsg->method == ATT_FIND_INFO_RSP &&
pMsg->hdr.status == bleProcedureComplete ) ||
(pMsg->method == ATT_ERROR_RSP))
{
/* Discover Data Source client configuration characteristic descriptors*/
GATT_DiscAllCharDescs(AppleANCSConnHandle,
ANCSHdlCache[HDL_ANCS_DATA_SOURCE_START] + 1,
ANCSHdlCache[HDL_ANCS_DATA_SOURCE_END],
AppleANCSTaskId );
newState = DISC_ANCS_DATA_CCCD;
}
}
break;
(5)寻找完成
当收到服务器的响应时,就可得到数据源特征CCCD的句柄,至此ANCS寻找完毕,进入空闲状态DISC_IDLE,并开始接收通知。
case DISC_ANCS_DATA_CCCD:
{
uint8 i;
/* Data source client configuration characteristic descriptors found*/
if (pMsg->method == ATT_FIND_INFO_RSP &&
pMsg->msg.findInfoRsp.numInfo > 0 &&
pMsg->msg.findInfoRsp.format == ATT_HANDLE_BT_UUID_TYPE )
{
/*For each handle/uuid pair*/
for (i = 0; i < pMsg->msg.findInfoRsp.numInfo; i++)
{
/* Look for CCCD*/
if ((pMsg->msg.findInfoRsp.info.btPair[i].uuid[0] ==
LO_UINT16(GATT_CLIENT_CHAR_CFG_UUID)) &&
(pMsg->msg.findInfoRsp.info.btPair[i].uuid[1] ==
HI_UINT16(GATT_CLIENT_CHAR_CFG_UUID)))
{
/* CCCD found, store Data Source CCCD handle*/
ANCSHdlCache[HDL_ANCS_DATA_SOURCE_CCCD] = pMsg->msg.findInfoRsp.info.btPair[i].handle;
break;
}
}
}
/* If procedure complete*/
if ((pMsg->method == ATT_FIND_INFO_RSP &&
pMsg->hdr.status == bleProcedureComplete ) ||
(pMsg->method == ATT_ERROR_RSP))
{
/*ANCS Discovery complete, then listen for notification*/
osal_set_event( AppleANCSTaskId,START_LISENNTF_EVT);/*Listen for Notification Source right away*/
osal_start_timerEx( AppleANCSTaskId,START_LISENNTF_EVT,1000);/*Listen for Data Source 1s later*/
newState = DISC_IDLE;
}
}
break;
ANCS服务寻找完毕后,就可以打开监听通知功能了,这里需要注意的是,不能同一时间打开通知源特征的通知和数据源特征的通知功能,所以这里开启了一个定时任务,让数据源特征在1s后再打来通知功能。