iOS端智能硬件BLE通信技術(shù)實(shí)現(xiàn)

[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è)步驟:

  1. 手機(jī)發(fā)送數(shù)據(jù)給硬件
  2. 硬件回復(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兩部分,示意圖如下:


8949C43A-FE55-419A-866D-7B6964E643CA.png

本項(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;
56FEE129-44CE-49BF-A2A8-B9C2E6D3F185.png

對(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;
AF939E68-427E-46B9-9999-B29D68E105DD.png

對(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):

  1. 是否是ack包
  2. 是ack包的情況下,依次檢查頭部的合法性,校驗(yàn)和的正確性
  3. 當(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ì)核心類圖如下:


VBDataBridge.png

應(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ā)送過程,流程圖如下:

VBDataSender.png

每步操作說明如下:

  1. sender初始時(shí)處于idle狀態(tài),此時(shí)處于空閑狀態(tài),沒有數(shù)據(jù)發(fā)送;
  2. sender接收橋接類轉(zhuǎn)發(fā)的字節(jié)流,進(jìn)入split packet狀態(tài),切成N個(gè)符合規(guī)范的Packet,準(zhǔn)備依次發(fā)送;
  3. sender發(fā)送Packet時(shí),進(jìn)入write packet狀態(tài),開始發(fā)送第i個(gè)Packet;
  4. 發(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è)包的過程);
  5. 發(fā)送第i個(gè)Packet成功后,重新進(jìn)入write packet狀態(tài),發(fā)送第i+1個(gè)Packet;
  6. 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)?用層;
  7. sender在N個(gè)Packet都發(fā)送成功后,進(jìn)?wait response狀態(tài)等待硬件返回相應(yīng)命令的結(jié)果;
  8. 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)用層,接收流程圖如下:

VBBluetooth.png

每步操作說明如下:

  1. 數(shù)據(jù)發(fā)送完畢等待接收數(shù)據(jù)的狀態(tài)有兩種:wait packet ack和wait packet response;
  2. 當(dāng)前狀態(tài)是wait packet ack時(shí),receiver判斷是不是表示成功接收的ack,是則通知發(fā)送端發(fā)送下一個(gè)數(shù)據(jù)包,否則返回錯(cuò)誤回調(diào)給上層;
  3. 當(dāng)前狀態(tài)是wait packet response時(shí),receiver判斷當(dāng)前待接收的Packet序號(hào)是否滿足條件,滿足才接收;
  4. 接收Packet的過程是組包的過程,首先會(huì)判斷當(dāng)前接收的數(shù)據(jù)是否含有Packet Header以及頭部檢查是否已經(jīng)check過,未check過則從頭部中獲取要接收的目標(biāo)數(shù)據(jù)的長(zhǎng)度;
  5. 已經(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ì)如下:

VBBluetoothManager.png

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

參考

  1. PV1低功耗藍(lán)牙通信架構(gòu)模塊設(shè)計(jì)文檔
  2. Android端智能硬件XX BLE配網(wǎng)技術(shù)文檔
  3. iOS Bluetooth Low Energy and Custom Hardware — Part 3: Optimizing Data Throughput
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容