重要概念点总结
1.profile 规范。包含有service服务,如电量。
2.service,每一个服务可能包含一个或多个特征值。
3.characteristic 特征值。通信载体,电量为20%,20%即是特征值的value。主从机之间通信,通过读写特征值实现。
4.UUID 统一识别码。刚才提到的service和characteristic,都需要一个唯一的uuid来标识。
连接与通信过程中,用到协议栈这两层
GAP 主要进行建立连接的过程,类似握手。
GATT 传输的数据段通用规范。这些很短的数据段被称为属性(Attribute),主要包括service服务、characteristic 特征值,特征值描述characteristic descriptors。
通信过程:由中心设备( 客户端client,主站)发起,外围设备(服务器server,从站)响应。一般情况下,客户端进行读写特征值,实现通信。服务器还可以通过发“通知”(Notification)或“指示”(indication)进行通信。
Notification 服务器发出数据,无需客户端回复。
indication 服务器发出数据,需要客户端回复后再继续发。
代码分析
Main Task 是主任务,初始化。
App_Thread 和Main Task共用堆栈。处理协议栈发送的所有事件和消息。
App_Thread 主要通过App_HandleHostMessageInput处理协议栈发送给APP的队列。通过回调机制处理队列中的消息。
开启调用流程:
App_Thread->App_HandleHostMessageInput->BleApp_GenericCallback->BleApp_Config->BleApp_Start->App_StartScanning or BleApp_Advertise。
以下以主站代码进行分析:
一. 协议栈开启前注册回调函数
App_RegisterGattServerCallback(BleApp_GattServerCallback);
App_RegisterGattClientProcedureCallback(BleApp_GattClientCallback);
GattServer_RegisterHandlesForWriteNotifications(NumberOfElements((mCharMonitoredHandles)), mCharMonitoredHandles);
BleServDisc_RegisterCallback(BleApp_ServiceDiscoveryCallback);
App_RegisterGattClientNotificationCallback(BleApp_GattNotificationCallback);
//App_RegisterGattClientIndicationCallback(BleApp_GattNotificationCallback);
作为主站,重点关注
1.BleApp_GattClientCallback
2.BleApp_ServiceDiscoveryCallback
这俩个回调,前者是客户端通用回调,客户端的所有操作的响应都会在这个回调中触发,在如客户端发现服务,读写特征值,读写特征值描述等等。
后者是服务发现回调,在发现服务后,可对服务里的特征值句柄进行保存。
二. 开启扫描函数:
App_StartScanning(&gScanParams, BleApp_ScanningCallback, FALSE);
开启扫描或广播后,在对应回调函数中进行判断并建立连接。
static void BleApp_ScanningCallback (gapScanningEvent_t* pScanningEvent)
{
bleResult_t result = gBleStatusBase_c;
switch (pScanningEvent->eventType)
{
case gDeviceScanned_c:
{
if (BleApp_CheckScanEvent(&pScanningEvent->eventData.scannedDevice))
{
gConnReqParams.peerAddressType = pScanningEvent->eventData.scannedDevice.addressType;
FLib_MemCpy(gConnReqParams.peerAddress,
pScanningEvent->eventData.scannedDevice.aAddress,
sizeof(bleDeviceAddress_t));
Gap_StopScanning();
result = App_Connect(&gConnReqParams, BleApp_ConnectionCallback);
}
}
break;
case gScanStateChanged_c:
{
mScanningOn = !mScanningOn;
/* Node starts scanning */
if (mScanningOn)
{
/* Start advertising timer */
TMR_StartLowPowerTimer(mAppTimerId,
gTmrLowPowerSecondTimer_c,
TmrSeconds(gScanningTime_c),
ScaningTimerCallback, NULL);
Led1Flashing();
}
/* Node is not scanning */
else
{
TMR_StopTimer(mAppTimerId);
}
}
break;
case gScanCommandFailed_c:
{
panic(0, 0, 0, 0);
break;
}
default:
break;
}
}
扫描回调主要有两种事件:
1.已经扫描到设备(gDeviceScanned_c )
2.扫描状态改变(gScanStateChanged_c),①从一开始的不扫描到开始扫描,②从扫描到停止扫描。
注意这里开启扫描后开了一个定时器,时间到后调用回调ScaningTimerCallback,这里面会关闭扫描,这样就实现了扫描超时机制。但调试时发现并不是一开启扫描就可以扫到对应设备,所以可以将关闭扫描的代码干掉,让主机一直扫。
三 . 扫描设备校验函数:
BleApp_CheckScanEvent(&pScanningEvent->eventData.scannedDevice)
在这个函数中校验需要连接的从站MAC地址,返回TURE,即可开启连接。
四. 连接回调
BleApp_ConnectionCallback
连接回调函数中调用开启状态机
BleApp_StateMachineHandler(peerDeviceId, mAppEvt_PeerConnected_c);
五. 状态机
BleApp_StateMachineHandler
在mAppExchangeMtu_c
状态下发现服务,可以通过UUID发现特定服务,也可以直接发现所有Primary服务。
BleServDisc_Start(peerDeviceId); //封装后的发现所有服务函数
BleServDisc_FindService(peerDeviceId,
gBleUuidType128_c,
(bleUuid_t*) &uuid_service_wireless_uart)
//封装后的通过UUID发现特定服务
六 .发现服务回调
发现服务后其实先回调了BleApp_GattClientCallback
,在BleServDisc_SignalGattClientEvent
中发现服务的所有特征值。这步无需修改。
需要关注的是BleApp_ServiceDiscoveryCallback
,在这个回调中校验发现服务的UUID,然后校验特征值的UUID,保存服务需要的特征值句柄。
eg.保存一个16bitUUID的gBleSig_BatteryLevel_d特征值句柄
static void BleApp_StoreServiceHandles (deviceId_t peerDeviceId, gattService_t *pService)
{
maPeerInformation[peerDeviceId].clientInfo.hService = pService->startHandle;
for (uint8_t i = 0; i < pService->cNumCharacteristics; i++)
{
if ((pService->aCharacteristics[i].value.uuidType == gBleUuidType16_c) &&
(pService->aCharacteristics[i].value.uuid.uuid16 == gBleSig_BatteryLevel_d))
//校验服务中的特征值UUID
{
maPeerInformation[peerDeviceId].clientInfo.hUartStream = pService->aCharacteristics[i].value.handle; //保存特征值句柄
}
}
}
有了句柄之后 可以在状态机中对特征值进行读写操作。完成通信。✌
主站开启通知的流程
在和一个从站通信过程中,从站在不停发送通知,但是主站无法触发通知接收的回调
App_RegisterGattClientNotificationCallback(BleApp_GattNotificationCallback);
这时主站需要使能通知接收,就是修改特征值描述的CCCD描述。使能流程:
1.确定从站发送通知的服务的特征值,读取对应特征值句柄。
2.通过特征值句柄发现特征值描述
myChar. aDescriptors = aDescriptors;
myChar.value.handle = maPeerInformation[peerDeviceId].clientInfo.hUartStream;
bleResult_t result = GattClient_DiscoverAllCharacteristicDescriptors
(
peerDeviceId,
&myChar,
0xFFFF,
mcMaxDescriptors_c
);
3.在客户端回调函数中,在发现的特征值描述中找到CCCD,将使能通知的FLAG写入特征值描述
case gGattProcDiscoverAllCharacteristicDescriptors_c:
if (gGattProcSuccess_c == procedureResult)
{
/* Find CCCD */
for ( uint8_t j = 0; j < myChar. cNumDescriptors ; j++)
{
if (gBleUuidType16_c == myChar. aDescriptors [j]. uuidType
&& gBleSig_CCCD_d == myChar. aDescriptors [j]. uuid . uuid16 )
{
uint16_t value = gCccdNotification_c;
//packTwoByteValue(gCccdNotification_c, cccdValue); //官方给的伪代码有问题
bleResult_t result = GattClient_WriteCharacteristicDescriptor
(
serverDeviceId,
&myChar. aDescriptors [j],
sizeof(value),
(void*)&value
);
if (gBleSuccess_c != result)
{
/* Handle error */
}
break;
}
}
}
4.继续在客户端回调中,读取特征值写入结果
case gGattProcWriteCharacteristicDescriptor_c:
if (gGattProcSuccess_c == procedureResult)
{
int text = 1; //写入成功
/* Notification successfully activated */
}
else
{
/* Handle error */
}
至此对应特征值的通知接收已经被使能,可通过通知接收回调函数接收通知数据。
扩展:
1.建立连接后最重要的一个步骤是交互MTU,主从站都可以发起。GattClient_ExchangeMtu()发起交互,在client_callback中回调。若从站发起,主站会在server_callback()中回调。