[toc]
當(dāng)前開發(fā)的智能硬件項(xiàng)目中涉及藍(lán)牙通信的目前有三處:
- 配網(wǎng)時(shí)手機(jī)端向硬件端請(qǐng)求獲取wifi列表
- 配網(wǎng)時(shí)手機(jī)將ssid、pwd、userid的信息告知硬件,同時(shí)硬件端告知配網(wǎng)結(jié)果
- 特定模式下硬件端向手機(jī)端請(qǐng)求信息(涉及項(xiàng)目隱私隱去)
本項(xiàng)目中BLE通信分三層設(shè)計(jì):藍(lán)牙層、傳輸層、應(yīng)用層。
- 藍(lán)牙層:主要封裝藍(lán)牙基本的通信方式,包括藍(lán)牙開啟/關(guān)閉的通知、掃描、讀數(shù)據(jù)等;
- 傳輸層:按照智能硬件的BLE通信協(xié)議規(guī)范實(shí)現(xiàn)數(shù)據(jù)發(fā)送時(shí)的拆包和接收時(shí)的組包,借助藍(lán)牙層實(shí)現(xiàn)基本的收發(fā);
- 應(yīng)用層:對(duì)數(shù)據(jù)通信結(jié)果做封裝,暴露給上層用戶調(diào)用。
通信過程中的基本數(shù)據(jù)結(jié)構(gòu)有兩類,Packet和Slice,其中Slice組成Packet,下文會(huì)做詳細(xì)描述。
藍(lán)牙層
BLE基礎(chǔ)
BLE是Bluetooth Low Energy——藍(lán)牙低功耗技術(shù)的簡(jiǎn)稱,基于藍(lán)牙4.0規(guī)范實(shí)現(xiàn)。值得一提的是,基于4.0之前規(guī)范實(shí)現(xiàn)的藍(lán)牙技術(shù)稱為傳統(tǒng)藍(lán)牙。在BLE開發(fā)中,有兩種角色:中央設(shè)備(Central, 如手機(jī))和外圍設(shè)備(Peripheral,如智能硬件)。
BLE技術(shù)是基于GATT(Generic Attribute Profile,一種屬性傳輸協(xié)議)進(jìn)行通信的:
- 每個(gè)GATT由完成不同功能的服務(wù)(Service)組成;
- 每個(gè)Service由不同的特征(Characteristic)組成;
- 每個(gè)Characteristic由一個(gè)value和一個(gè)或多個(gè)描述(Descriptor)組成。
項(xiàng)目中智能硬件就是一個(gè)外圍設(shè)備,它包括三個(gè)Service,其中UUID為0xFFF0的Service是我們要掃描的目標(biāo),該Service中包括了寫、讀和控制三個(gè)Characteristic。
typedef NS_ENUM(NSInteger, VBUUIDType)
{
VBUUIDNone = 0,
VBUUIDService = 0xFFF0,
VBUUIDTxCharacteristic = 0xFFF1, // 手機(jī)向硬件發(fā)送BLE數(shù)據(jù)的鏈路
VBUUIDRxCharacteristic = 0xFFF2, // 手機(jī)從硬件接收BLE數(shù)據(jù)的鏈路
VBUUIDCtsCharacteristic = 0xFFF3, // 標(biāo)識(shí)手機(jī)是否可以繼續(xù)向硬件發(fā)送數(shù)據(jù)的鏈路,
};
BLE寫操作
BLE寫操作是指手機(jī)端向硬件端發(fā)送數(shù)據(jù),寫操作可分為有響應(yīng)和無響應(yīng)兩種,后者寫數(shù)據(jù)較快。項(xiàng)目中手機(jī)給硬件發(fā)送數(shù)據(jù)是通過tx Characteristic鏈路來寫的,并且是無響應(yīng)式, 根據(jù)《PV1低功耗藍(lán)牙通信架構(gòu)模塊設(shè)計(jì)文檔》,寫數(shù)據(jù)前cts和rx鏈路都必須確認(rèn)處于開啟狀態(tài),才能保證寫數(shù)據(jù)后快速收到硬件的數(shù)據(jù):
- (BOOL)canSendDataForPeripheral:(CBPeripheral *)peripheral {
BOOL isRxCharacterNotify = NO;
BOOL isCtsCharacterNotify = NO;
for (CBCharacteristic *character in [peripheral.services.firstObject characteristics]) {
VBUUIDType uuidType = [VBUUIDUtil typeForUUID:character.UUID];
switch (uuidType) {
case VBUUIDRxCharacteristic:
isRxCharacterNotify = character.isNotifying;
break;
case VBUUIDCtsCharacteristic:
isCtsCharacterNotify = character.isNotifying;
default:
break;
}
}
// 只有在cts和rx都開啟的情況下,才能發(fā)送數(shù)據(jù)
BOOL canSendData = isRxCharacterNotify && isCtsCharacterNotify;
return canSendData;
}
// Method from VBDataSender class
/// 發(fā)送當(dāng)前數(shù)據(jù)
- (void)sendCurrentPacket
{
if (_curPacketIndex >= _packetsToSend.count)
{
return;
}
NELogVerbose(@"發(fā)送第%ld個(gè)包", (_curPacketIndex+1));
VBPacket *curPacket = _packetsToSend[_curPacketIndex];
_state = VBSenderStateWritePackets;
NSArray<NSData *> *slices = [curPacket splitIntoSlices];
for (NSData *slice in slices)
{
[_peripheral writeValue:slice forCharacteristic:_writeCharacteristic type:CBCharacteristicWriteWithoutResponse];
}
_state = VBSenderStateWaitPacketAck;
[self startTimer];
}
BLE讀操作
BLE讀操作是指手機(jī)讀取硬件指定Characteristic的值, 項(xiàng)目中是通過實(shí)現(xiàn)CBPeripheralDelegate的方法來讀取值的,代碼如下:
// Method from VBBluetoothManager class
// 3. 讀取特征的值
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
[[NSNotificationCenter defaultCenter] postNotificationName:VBBluetoothBLEDidReceivePeripheralResponse object:characteristic userInfo:nil];
if (!characteristic.value)
{
return;
}
NELogVerbose(@"%s %@", __func__, characteristic.value);
VBDataBridge *bridge = [self findDataBridgeByPeripheral:peripheral];
if (bridge) {
[bridge handlePeripheralResponse:characteristic.value];
} else {
NELogError(@"data bridge為空了....");
}
}
BLE通知
BLE通知是指硬件主動(dòng)給手機(jī)發(fā)送數(shù)據(jù),手機(jī)接收硬件rx鏈路的數(shù)據(jù)通過setNotify來實(shí)現(xiàn),參考代碼如下:
// Method from VBBluetoothManager class
- (void)constructDataBridges:(CBPeripheral *)peripheral {
CBService *primaryService = peripheral.services.firstObject;
NSArray<CBCharacteristic *> *characteristics = primaryService.characteristics;
if (!characteristics)
{
return;
}
CBCharacteristic *txWriteCharacter;
CBCharacteristic *rxReceiveCharacter;
for (CBCharacteristic *character in characteristics)
{
VBUUIDType uuidType = [VBUUIDUtil typeForUUID:character.UUID];
if (uuidType == VBUUIDNone) {
break;
} else if (uuidType == VBUUIDTxCharacteristic) {
txWriteCharacter = character;
} else {
if (uuidType == VBUUIDRxCharacteristic) {
rxReceiveCharacter = character;
}
if (!character.isNotifying) {
[peripheral setNotifyValue:YES forCharacteristic:character];
}
}
}
// 省略
...
}
其他注意事項(xiàng)
手機(jī)的讀和寫操作均通過調(diào)用Core Bluetooth框架中的API完成,項(xiàng)目中寫數(shù)據(jù)分成兩個(gè)步驟:
- 手機(jī)發(fā)送數(shù)據(jù)給硬件
- 硬件回復(fù)手機(jī)數(shù)據(jù)接收成功
只有第2步硬件返回表示寫數(shù)據(jù)成功的Ack才表示數(shù)據(jù)寫成功。
傳輸層
傳輸層的主要作?用是接收來?自應(yīng)?層的數(shù)據(jù),經(jīng)過拆包后,發(fā)送給智能硬件; 同
時(shí)接收智能硬件回復(fù)的結(jié)果信息,組包后,結(jié)果回調(diào)給應(yīng)?用層。
傳輸層可以分為發(fā)送端和接收端兩個(gè)部分,發(fā)送端負(fù)責(zé)發(fā)送相關(guān)命令數(shù)據(jù)給?箱,接收端負(fù)責(zé)接收硬件回復(fù)的結(jié)果信息,并通過接?回調(diào)給發(fā)送端。
基本數(shù)據(jù)結(jié)構(gòu)
手機(jī)和硬件通信過程中有兩個(gè)基本的數(shù)據(jù)結(jié)構(gòu):數(shù)據(jù)包(Packet)和切? (Slice)。Packet規(guī)定了一個(gè)標(biāo)準(zhǔn)的數(shù)據(jù)結(jié)構(gòu),?論發(fā)送還是接收,均需要按照這個(gè)結(jié)構(gòu)發(fā)送數(shù)據(jù),Slice則是將Packet按照20字節(jié)等?長(zhǎng)切分后的結(jié)果,設(shè)成20字節(jié)主要原因Android端同學(xué)說BLE寫限制一次最多只能寫20字節(jié),但是經(jīng)過我查找相關(guān)資料驗(yàn)證,iOS的限制受諸多因素影響,并且可以超過20字節(jié),甚至達(dá)到100多字節(jié),具體可參考這篇文章。
Packet
一個(gè)Packet的結(jié)構(gòu)包含包頭Header和包體Payload兩部分,示意圖如下:

本項(xiàng)目中和嵌入式協(xié)定Packet有如下特點(diǎn):
- 每個(gè)Packet的長(zhǎng)度限制為3000字節(jié),頭部長(zhǎng)度固定占用12字節(jié),故包體Payload部分最多可有2988字節(jié);
- 每接收到一個(gè)Packet均需校驗(yàn),同時(shí)回復(fù)該P(yáng)acket(即Packet Ack),Packet Ack不超過20字節(jié),因此發(fā)送Packet Ack時(shí)無需拆包;
包頭
傳輸層報(bào)文的包頭格式定義:
typedef struct pkgTransHeader{
Uint8 magic;
Uint8 ver;
Uint8 rev;
Uint8 seq;
Uint16 cmdid;
Uint16 checksum;
Uint32 length;
} pkgTransHeaderSdef;

對(duì)應(yīng)的OC實(shí)現(xiàn)類是VBTransportHeader:
#import "VBTransportHeader.h"
#import "VBPacketUtil.h"
#import <NESafeKit/NSData+NESafeKit.h>
UInt32 const VBTransportHeaderLength = 12;
UInt8 const VBTransportHeaderOkMagic = 0xEF;
UInt8 const VBTransportHeaderOkVer = 0x01;
UInt8 const VBTransportHeaderOkRev = 0x00;
static const UInt8 VBTransportHeaderDefaultSeq = 0x01;
@interface VBTransportHeader ()
{
// 前導(dǎo)標(biāo)識(shí)
UInt8 _magic; // 前導(dǎo)標(biāo)識(shí)
UInt8 _ver; // 版本號(hào)
UInt8 _rev; // 保留
UInt8 _seq; // 序列號(hào)
VBCmdId _cmdid; // 命令號(hào)
UInt32 _length; // 包頭+包體的長(zhǎng)度
UInt16 _checksum; // 包體校驗(yàn)和
NSData *_data; // 包頭數(shù)據(jù)
}
@end
@implementation VBTransportHeader
- (instancetype)init
{
self = [super init];
if (self)
{
_magic = VBTransportHeaderOkMagic;
_ver = VBTransportHeaderOkVer;
_rev = VBTransportHeaderOkRev;
_seq = VBTransportHeaderDefaultSeq;
}
return self;
}
@end
其中通信的命令枚舉定義如下:
/**
app和硬件通信的各個(gè)指令
- VBCmdCentralToPeripheral: 中央設(shè)備(手機(jī))向硬件發(fā)送數(shù)據(jù)
- VBCmdPeripheralToCentral: 硬件向中央設(shè)備發(fā)送數(shù)據(jù)
- VBCmdWifiConfigNetRequest: 手機(jī)向硬件發(fā)送配網(wǎng)請(qǐng)求
- VBCmdWifiConfigNetResponse: 硬件回復(fù)配網(wǎng)結(jié)果響應(yīng)
- VBCmdNearbyWifiListRequest: 手機(jī)向硬件請(qǐng)求附近的wifi列表
- VBCmdNearbyWifiListResponse: 硬件向手機(jī)回復(fù)獲取的wifi列表結(jié)果
- VBCmdLoopDataRequest: 透?jìng)鳎O(shè)備會(huì)將手機(jī)發(fā)送的包原封不動(dòng)傳回來
- VBCmdLoopDataResponse: 透?jìng)鞯捻憫?yīng)
*/
typedef NS_ENUM(UInt16, VBCmdId)
{
VBCmdCentralToPeripheral = 0xE001,
VBCmdPeripheralToCentral = 0xE002,
VBCmdWifiConfigNetRequest = 0x1001,
VBCmdWifiConfigNetResponse = 0x1002,
VBCmdNearbyWifiListRequest = 0x1003,
VBCmdNearbyWifiListResponse = 0x1004,
VBCmdLoopDataRequest = 0x0100,
VBCmdLoopDataResponse = 0x0101
};
包體
傳輸層報(bào)文中根據(jù)傳輸?shù)陌w內(nèi)容可以將報(bào)文劃分為兩類:
- 鏈路控制報(bào)文,包體內(nèi)容為鏈路控制響應(yīng)碼
- 數(shù)據(jù)傳輸報(bào)文,包體內(nèi)容為應(yīng)用層協(xié)議包或者其分包
鏈路控制報(bào)文與數(shù)據(jù)傳輸報(bào)文使用cmdid來區(qū)分:
| 類型 | cmdid |
|---|---|
| 鏈路控制報(bào)文 | 0xE001 手機(jī)向設(shè)備, 0xE002設(shè)備向手機(jī) |
| 數(shù)據(jù)報(bào)文 | 其他 |
其中鏈路控制報(bào)文的ack/nack響應(yīng)碼定義如下:
typedef struct ackbody
{
Uint8 ackCode;
}ackBodySdef;

對(duì)應(yīng)的OC枚舉是VBTransportAck:
typedef NS_ENUM(UInt8, VBTransportAck)
{
VBTransportAckOk = 0x00,
VBTransportAckOtherError = 0x01,
VBTransportAckReservedUsage = 0x02,
VBTransportAckChecksumError = 0x03,
VBTransportAckHeaderIncorrect = 0x04,
VBTransportAckOutOfMemory = 0x05,
VBTransportAckTimeoutReassemble = 0x06,
VBTransportAckSequenceIncorrect = 0x07,
VBTransportAckCmdidInconsistent = 0x08,
VBTransportAckTimeoutReceive = 0x09,
};
Packet校驗(yàn)
接收到一個(gè)Packet時(shí),需要進(jìn)行如下流程的校驗(yàn):
- 是否是ack包
- 是ack包的情況下,依次檢查頭部的合法性,校驗(yàn)和的正確性
- 當(dāng)條件1、2都滿足時(shí),還需校驗(yàn)ack是否是表示傳輸層正確接收的結(jié)果
對(duì)應(yīng)實(shí)現(xiàn)代碼如下:
/// 是否是ack回復(fù)
///
/// - Parameter data: 待檢驗(yàn)的數(shù)據(jù)
/// - Returns: true是ack,false不是
+ (BOOL)isAck:(NSData *)data
{
if (data.length != [VBPacket packetAckSize])
{
return NO;
}
// 獲取cmd, 第4和5字節(jié)是cmd
VBCmdId cmd = 0;
[data ne_getBytes:&cmd range:NSMakeRange(4, 2)];
// 獲取長(zhǎng)度
UInt32 length = 0;
[data ne_getBytes:&length range:NSMakeRange(8, 4)];
NSData *header = [data ne_subdataWithRange:NSMakeRange(0, VBTransportHeaderLength)];
BOOL isValidHeader = [VBPacketUtil isValidHeader:header] && cmd == VBCmdPeripheralToCentral && length == [VBPacket packetAckSize];
return isValidHeader;
}
/// 校驗(yàn)Packet是否有效
///
/// - Parameter data: 外圍設(shè)備返回的數(shù)據(jù)
/// - Returns: true有效, false無效
+ (VBPacketCheckResult *)isValidPacket:(NSData *)data
{
VBPacketCheckResult *result = [VBPacketCheckResult new];
if (!data || data.length < VBTransportHeaderLength)
{
return result;
}
// 檢查頭部是否合法
NSData *headerData = [data ne_subdataWithRange:NSMakeRange(0, VBTransportHeaderLength)];
if (![self isValidHeader:headerData])
{
return result;
}
// 檢查長(zhǎng)度是否合法
VBTransportHeader *transportHeader = [[VBTransportHeader alloc] initWithHeaderData:headerData];
if (!transportHeader || transportHeader.length != data.length)
{
return result;
}
// 檢查校驗(yàn)和
NSUInteger payloadLen = data.length - VBTransportHeaderLength;
NSData *payload = [data ne_subdataWithRange:NSMakeRange(VBTransportHeaderLength, payloadLen)];
result.valid = transportHeader.checksum == [self generateChecksumWithHeaderData:[transportHeader dataWithZeroChecksum] payload:payload];
result.cmd = transportHeader.cmdid;
result.code = payload;
return result;
}
- (BOOL)isAckOk:(NSData *)data
{
if (![VBPacketUtil isAck:data])
{
return NO;
}
VBPacketCheckResult *packet = [VBPacketUtil isValidPacket:data];
if (!packet.valid)
{
return NO;
}
VBTransportAck ack = VBTransportAckOk;
[packet.code ne_getBytes:&ack length:sizeof(ack)];
NSError *error = [NSError vb_errorWithBLEError:ack];
NELogVerbose(@"%@",error.localizedDescription);
return ack == VBTransportAckOk;
}
Slice
項(xiàng)目中,手機(jī)與硬件的所有讀寫操作,我們都認(rèn)為是Slice傳輸,Slice特點(diǎn)如下:
- Slice組成Packet(有些Packet比較短,可能小于20字節(jié),所以有時(shí)候一個(gè)Slice就是一個(gè)Packet)
- Slice沒有頭部,在寫數(shù)據(jù)時(shí)最長(zhǎng)20字節(jié),讀數(shù)據(jù)時(shí)取決于硬件一次發(fā)的數(shù)據(jù)量
傳輸層
傳輸層的設(shè)計(jì)核心類圖如下:

應(yīng)用層需要發(fā)送數(shù)據(jù),后者接收到的數(shù)據(jù)需要處理時(shí),實(shí)際是通過VBDataBridge橋接類去進(jìn)行相應(yīng)的分發(fā)處理:
- 發(fā)送數(shù)據(jù)時(shí),橋接類調(diào)用自己持有的
sender類去發(fā)送數(shù)據(jù) - 接收數(shù)據(jù)時(shí),橋接類調(diào)用自己持有的
receiver類處理數(shù)據(jù),receiver處理完,將對(duì)應(yīng)的結(jié)果回調(diào)給橋接類,橋接類再回調(diào)給上層應(yīng)用層。
發(fā)送類VBDataSender
VBDataSender類的主要作用是接收通過橋接類轉(zhuǎn)發(fā)的應(yīng)用層字節(jié)流數(shù)據(jù),然后切成Packet發(fā)送(即拆包);在收到硬件返回的結(jié)果后,結(jié)束整個(gè)發(fā)送過程,流程圖如下:

每步操作說明如下:
-
sender初始時(shí)處于idle狀態(tài),此時(shí)處于空閑狀態(tài),沒有數(shù)據(jù)發(fā)送; -
sender接收橋接類轉(zhuǎn)發(fā)的字節(jié)流,進(jìn)入split packet狀態(tài),切成N個(gè)符合規(guī)范的Packet,準(zhǔn)備依次發(fā)送; -
sender發(fā)送Packet時(shí),進(jìn)入write packet狀態(tài),開始發(fā)送第i個(gè)Packet; - 發(fā)送第i個(gè)Packet后,進(jìn)入wait packet ack狀態(tài),等待該P(yáng)acket的ack(該P(yáng)acket的ack,實(shí)際是
receiver在接收到Packet的ack后,通過橋接類接口告訴sender發(fā)送下一個(gè)包的過程); - 發(fā)送第i個(gè)Packet成功后,重新進(jìn)入write packet狀態(tài),發(fā)送第i+1個(gè)Packet;
-
sender收到的ACK顯示硬件收到的該P(yáng)acket有誤,視為第i個(gè)Packet發(fā)送失敗,這時(shí)sender重新進(jìn)?write packet狀態(tài),重新發(fā)送第i個(gè)Packet; 如果重試2次后都失敗,則判定為整個(gè)發(fā)送過程失敗,錯(cuò)誤信息回調(diào)應(yīng)?用層; -
sender在N個(gè)Packet都發(fā)送成功后,進(jìn)?wait response狀態(tài)等待硬件返回相應(yīng)命令的結(jié)果; -
sender在收到硬件反饋的結(jié)果后回到idle狀態(tài),整個(gè)發(fā)送過程結(jié)束,橋接類將結(jié)果回調(diào)給應(yīng)?層。
需注意細(xì)節(jié)如下:
-
sender在等待每一個(gè)Packet的ack時(shí),均需要設(shè)計(jì)超時(shí)時(shí)間,默認(rèn)是3s。如果發(fā)送了了一個(gè)Packet后,等待了了3s后沒有收到ACK,則判定為發(fā)送失敗,嘗試重新發(fā)送; -
sender在等待硬件返回結(jié)果時(shí),也需要設(shè)計(jì)超時(shí)時(shí)間,默認(rèn)30s,如果等待30s后,沒有收到響應(yīng)結(jié)果,判定為整個(gè)發(fā)送過程失敗,錯(cuò)誤信息通過橋接類回調(diào)給應(yīng)用層。
拆包過程
@implementation NSData (VBPacket)
- (NSArray<VBPacket *> *)splitIntoPacketsWithCmdId:(VBCmdId)cmdId
{
NSMutableArray<VBPacket *> *packets = [NSMutableArray array];
NSUInteger maxPayloadSize = VBPacketMaxPacketSize - VBTransportHeaderLength;
NSUInteger packetNum = self.length == 0 ? 1 : (self.length / maxPayloadSize + (self.length % maxPayloadSize == 0 ? 0 : 1));
for (int i = 0; i < packetNum; ++i)
{
NSUInteger length = 0;
UInt8 seq = i + 1;
if (i == packetNum - 1)
{
length = self.length - i * maxPayloadSize;
seq = VBPacketLastSeq;
}
else
{
length = maxPayloadSize;
}
// 即使payload為空也可以發(fā)送數(shù)據(jù),因?yàn)榘^的data一定不為空
NSData *payload = [self ne_subdataWithRange:NSMakeRange(i * maxPayloadSize, length)];
VBPacket *packet = [[VBPacket alloc] initWithSeq:seq cmdId:cmdId payload:payload];
[packets addObject:packet];
}
return [packets copy];
}
@end
切片過程
// Method from VBPacket class
- (NSArray<NSData *> *)splitIntoSlices
{
NSMutableArray<NSData *> *slices = [NSMutableArray array];
NSData *data = [self data];
NSUInteger sliceNum = data.length / VBPackeMaxSliceSize + (data.length % VBPackeMaxSliceSize == 0 ? 0 : 1);
for (int i = 0; i < sliceNum; ++i)
{
NSUInteger length = 0;
if (i == sliceNum - 1)
{
length = data.length - i * VBPackeMaxSliceSize;
}
else
{
length = VBPackeMaxSliceSize;
}
NSData *sliceData = [data ne_subdataWithRange:NSMakeRange(i * VBPackeMaxSliceSize, length)];
if (sliceData)
{
[slices addObject:sliceData];
}
}
return [slices copy];
}
接收類VBDataReceiver
VBDataReceiver類的主要作用是處理接收到的硬件數(shù)據(jù),包括硬件回復(fù)的Packet ack和相應(yīng)命令對(duì)應(yīng)的響應(yīng)結(jié)果,并把結(jié)果通過橋接類回調(diào)給上層應(yīng)用層,接收流程圖如下:

每步操作說明如下:
- 數(shù)據(jù)發(fā)送完畢等待接收數(shù)據(jù)的狀態(tài)有兩種:wait packet ack和wait packet response;
- 當(dāng)前狀態(tài)是wait packet ack時(shí),
receiver判斷是不是表示成功接收的ack,是則通知發(fā)送端發(fā)送下一個(gè)數(shù)據(jù)包,否則返回錯(cuò)誤回調(diào)給上層; - 當(dāng)前狀態(tài)是wait packet response時(shí),
receiver判斷當(dāng)前待接收的Packet序號(hào)是否滿足條件,滿足才接收; - 接收Packet的過程是組包的過程,首先會(huì)判斷當(dāng)前接收的數(shù)據(jù)是否含有Packet Header以及頭部檢查是否已經(jīng)check過,未check過則從頭部中獲取要接收的目標(biāo)數(shù)據(jù)的長(zhǎng)度;
- 已經(jīng)check過則判斷當(dāng)前接收數(shù)據(jù)長(zhǎng)度是否小于目標(biāo)數(shù)據(jù)長(zhǎng)度,進(jìn)而決定是繼續(xù)接收數(shù)據(jù)還是組裝數(shù)據(jù)回調(diào)給上層。
需注意以下細(xì)節(jié):
- 手機(jī)端在接收到?箱返回的Packet ack后?無需再做回復(fù);
- 無論是?手機(jī)端還是硬件端,對(duì)于Packet的回復(fù)都在20字節(jié)之內(nèi),所以可以省去復(fù)雜的組包和拆包操作,發(fā)送?次即可;
- 手機(jī)在接收硬件發(fā)送的一個(gè)Packet的過程中,會(huì)出現(xiàn)下述情況:假如頭部指明了了待接收的Packet是150字節(jié)長(zhǎng),已經(jīng)接收了7個(gè)Slice,共7 * 20 = 140字節(jié)?,最后一個(gè)Slice硬件發(fā)送過來的長(zhǎng)度依舊是20字節(jié)長(zhǎng)的情況,這個(gè)時(shí)候需要對(duì)最后一個(gè)Slice切割,前10個(gè)字節(jié)和前?面的140字節(jié)長(zhǎng)組成一個(gè)完整的字節(jié),后10個(gè)字節(jié)存儲(chǔ)起來等待和后面的數(shù)據(jù)拼接。
組包過程
// Method from VBDataReceiver class
- (void)processResponseData:(NSData *)data
{
NSMutableData *curSlice = [NSMutableData data];
if (_lastSliceTailData.length > 0)
{
[curSlice appendData:_lastSliceTailData];
// remove all data
[_lastSliceTailData setData:[NSData new]];
}
[curSlice appendData:data];
// 當(dāng)前已接收數(shù)據(jù)和待接收數(shù)據(jù)總長(zhǎng)度
NSUInteger curLength = _curPacketLen + curSlice.length;
VBHeaderCheckResult *checkResult = [VBPacketUtil containRespHeader:curSlice];
if (!_hasCheckedHeader && checkResult.hasHeader)
{
_hasCheckedHeader = YES;
_targetPacketLen = checkResult.length;
}
// 已接收和待接收的數(shù)據(jù)總長(zhǎng)度小于目標(biāo)接收長(zhǎng)度
if (curLength < _targetPacketLen)
{
[_receivedSlices appendData:curSlice];
_curPacketLen = curLength;
}
else
{
NSUInteger wantingLen = _targetPacketLen - _curPacketLen;
NSData *frontData = [data ne_subdataWithRange:NSMakeRange(0, wantingLen)];
if (frontData.length > 0)
{
[_receivedSlices appendData:frontData];
[self collectPacket];
}
NSUInteger tailLen = data.length - wantingLen;
NSData *tailData = [data ne_subdataWithRange:NSMakeRange(wantingLen, tailLen)];
if (tailData.length > 0)
{
[_lastSliceTailData appendData:tailData];
}
}
}
應(yīng)用層
應(yīng)用層對(duì)數(shù)據(jù)收發(fā)的橋接類做進(jìn)一步封裝,類圖設(shè)計(jì)如下:

以上就是智能硬件BLE通信技術(shù)的設(shè)計(jì)架構(gòu)和大概實(shí)現(xiàn),所有以和嵌入式端定義的協(xié)議文檔為實(shí)現(xiàn)依據(jù)。