iOS藍(lán)牙開(kāi)發(fā)(附LightBlue參考指南)

公司的一個(gè)項(xiàng)目提了一些需求, 其中有一項(xiàng)是需要集成藍(lán)牙功能。上一次做藍(lán)牙功能已經(jīng)是剛?cè)胄械臅r(shí)候,于是乘著周末休息的把藍(lán)牙的知識(shí)復(fù)習(xí)復(fù)習(xí), 也給大家簡(jiǎn)單說(shuō)明一下藍(lán)牙的掃描和連接。

1、藍(lán)牙的集中連接方式
2、iOS藍(lán)牙開(kāi)發(fā)的關(guān)鍵詞
3、CoreBluetooth框架
4、代碼實(shí)現(xiàn)

一、iOS設(shè)備藍(lán)牙連接主要有一下幾種實(shí)現(xiàn)方式:

①.參加蘋(píng)果的(MFI)計(jì)劃, 也就是需要得到蘋(píng)果的認(rèn)證, 費(fèi)用高, 有這方面需求的可以自己去了解。

②.CoreBluetooth框架. 只支持4.0的藍(lán)牙設(shè)備, 這是我們iOS從業(yè)人員使用最多的一種方法。
③.GameKit框架. 這個(gè)框架只用用于iOS設(shè)備之間的藍(lán)牙連接通訊, 并不符合我們的需求, 沒(méi)有仔細(xì)研究.

④.私有API(BluetoothManager框架). 公司之前的項(xiàng)目有用過(guò)這個(gè),最后項(xiàng)目是用的企業(yè)包托管在蒲公英上,不過(guò)新項(xiàng)目并不準(zhǔn)備使用私有庫(kù),略過(guò)。

⑤.越獄. 如果你做越獄包得話,這個(gè)就隨意了, 想怎么用怎么用。

二、iOS藍(lán)牙開(kāi)發(fā)的關(guān)鍵詞

中心設(shè)備(中心設(shè)備):就是用來(lái)掃描周?chē){(lán)牙硬件的設(shè)備,比如通過(guò)你手機(jī)的藍(lán)牙來(lái)掃描并連接智能手環(huán),這時(shí)候你的手機(jī)就是中心設(shè)備。

外設(shè)(Peripheral):被掃描的設(shè)備。比如當(dāng)你用手機(jī)的藍(lán)牙掃描連接智能手環(huán)的時(shí)候,智能手環(huán)就是外設(shè)。

服務(wù)(services):外設(shè)廣播和運(yùn)行的時(shí)候會(huì)有服務(wù),可以理解成一個(gè)功能模塊,中心設(shè)備可以讀取服務(wù)。一個(gè)外設(shè)可以有多個(gè)服務(wù)。

特征(characteristic):在服務(wù)中的一個(gè)單位,一個(gè)服務(wù)可以有多個(gè)特征,特征會(huì)有一個(gè)value,一般讀寫(xiě)的數(shù)據(jù)就是這個(gè)value。

UUID :UUID在這里有多種意思。設(shè)備自身有硬件的UUID,不同的中心設(shè)備連接到同一個(gè)外設(shè)會(huì)顯示不同的UUID,此外設(shè)發(fā)送的每個(gè)服務(wù)也都有自己的UUID,每個(gè)服務(wù)中的特征也有自己所屬的UUID。

LightBlue對(duì)照表 version:2.6.4

下面是一張iOS藍(lán)牙的架構(gòu)圖,可以作為參照。

iOS藍(lán)牙CS圖.png

三、CoreBluetooth框架

藍(lán)牙開(kāi)發(fā)層次圖

如上圖所示,iOS中的藍(lán)牙開(kāi)發(fā)框架CoreBluetooth處在藍(lán)牙低功耗協(xié)議棧的上面,我們開(kāi)發(fā)的時(shí)候只是使用CoreBluetooth這個(gè)框架,通過(guò)CoreBluetooth可以輕松實(shí)現(xiàn)外設(shè)或中心設(shè)備的開(kāi)發(fā)。

CoreBluetooth可以分為兩大模塊,中心設(shè)備central,外設(shè)peripheral,它們倆各有自己的一套API供我們使用。


中心設(shè)備和外設(shè)使用

上圖左邊的就是中心設(shè)備的開(kāi)發(fā)類(lèi),我們平時(shí)是使用CBCentralManager來(lái)進(jìn)行相關(guān)操作。

CBCentralManager: 藍(lán)牙中心設(shè)備管理類(lèi),用來(lái)統(tǒng)一調(diào)度中心設(shè)備的開(kāi)發(fā)
CBPeripheral :藍(lán)牙外設(shè),例如藍(lán)牙手環(huán)、心率監(jiān)測(cè)儀。
CBService :藍(lán)牙外設(shè)的服務(wù),可以有0個(gè)或者多個(gè)服務(wù)。

CBCharacteristic :服務(wù)中的特征,每一個(gè)藍(lán)牙服務(wù)中可以有0個(gè)或多個(gè)特征,特征中包含數(shù)據(jù)信息。
CBUUID:可以理解為服務(wù)或特征的身份證,可以用來(lái)選擇需要的服務(wù)和特征。
右邊是外設(shè)開(kāi)發(fā)相關(guān)類(lèi),一般是圍繞著CBPeripheralManager來(lái)進(jìn)行編碼。

右邊是外設(shè)開(kāi)發(fā)相關(guān)類(lèi),一般是圍繞著CBPeripheralManager來(lái)進(jìn)行編碼。

CBPeripheralManager: 藍(lán)牙外設(shè)開(kāi)發(fā)時(shí)使用,用來(lái)開(kāi)發(fā)藍(lán)牙外設(shè)的中心管理類(lèi)。
CBCentral:藍(lán)牙中心設(shè)備,例如用來(lái)連接藍(lán)牙手環(huán)的手機(jī)。
CBMutableService:外設(shè)開(kāi)發(fā)的時(shí)候可以添加多個(gè)服務(wù),所有這里用CBMutableService來(lái)創(chuàng)建添加服務(wù)。
CBMutableCharacteristic:每個(gè)服務(wù)中可以有多個(gè)特征,外設(shè)開(kāi)發(fā)給服務(wù)添加特征的時(shí)候使用這個(gè)類(lèi)。
CBATTRequest:讀或者寫(xiě)請(qǐng)求。它的實(shí)例對(duì)象有一個(gè)value屬性,用來(lái)裝載外設(shè)進(jìn)行藍(lán)牙讀取或?qū)懭胝?qǐng)求時(shí)的數(shù)據(jù)。一般在外設(shè)寫(xiě)入或讀取的回調(diào)方法中有這一個(gè)參數(shù)。

四、代碼實(shí)現(xiàn)
1、首先導(dǎo)入CoreBluetooth框架,并遵守協(xié)議

#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController () <CBCentralManagerDelegate,CBPeripheralDelegate>

2、創(chuàng)建外設(shè)管理對(duì)象,用一個(gè)屬性來(lái)強(qiáng)引用這個(gè)對(duì)象。并且在創(chuàng)建的時(shí)候設(shè)置代理,聲明放到哪個(gè)線程。

@property (nonatomic, strong) CBPeripheralManager *peripheralManager;

// 創(chuàng)建外設(shè)管理器,會(huì)回調(diào)peripheralManagerDidUpdateState方法
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];

3、當(dāng)創(chuàng)建CBPeripheralManager的時(shí)候,會(huì)回調(diào)判斷藍(lán)牙狀態(tài)的方法。當(dāng)藍(lán)牙狀態(tài)沒(méi)問(wèn)題的時(shí)候創(chuàng)建外設(shè)的Service(服務(wù))和Characteristics(特征)。

/** 判斷手機(jī)藍(lán)牙狀態(tài)
    CBManagerStateUnknown = 0,  未知
    CBManagerStateResetting,    重置中
    CBManagerStateUnsupported,  不支持
    CBManagerStateUnauthorized, 未驗(yàn)證
    CBManagerStatePoweredOff,   未啟動(dòng)
    CBManagerStatePoweredOn,    可用
 */
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    // 藍(lán)牙可用,開(kāi)始掃描外設(shè)
    if (central.state == CBManagerStatePoweredOn) {
        NSLog(@"藍(lán)牙可用");
        // 第一個(gè)參數(shù)為nil表掃描所有藍(lán)牙設(shè)備 
        [central scanForPeripheralsWithServices:nil options:nil];
    }
    if(central.state==CBManagerStateUnsupported) {
        NSLog(@"該設(shè)備不支持藍(lán)牙");
    }
    if (central.state==CBManagerStatePoweredOff) {
        NSLog(@"藍(lán)牙已關(guān)閉");
    }
}

4、當(dāng)掃描到外設(shè)之后,就會(huì)回調(diào)下面這個(gè)方法,可以在這個(gè)方法中繼續(xù)設(shè)置篩選條件,例如根據(jù)外設(shè)名字的前綴來(lái)選擇,如果符合條件就進(jìn)行連接。

/** 發(fā)現(xiàn)符合要求的外設(shè),回調(diào) */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI {
    // 對(duì)外設(shè)對(duì)象進(jìn)行強(qiáng)引用
    self.peripheral = peripheral;
    //打印外設(shè)的UUID
    NSLog(@"===%@",peripheral.identifier);
    //打印外設(shè)的名稱
    NSLog(@"===%@",peripheral.name);
    
    //根據(jù)外設(shè)名字來(lái)過(guò)濾外設(shè),我的外設(shè)名字叫ble_mp008
        if ([peripheral.name hasPrefix:@"ble"]) {
            [central connectPeripheral:peripheral options:nil];
        }
    
}

5、當(dāng)連接成功的時(shí)候,就會(huì)來(lái)到下面這個(gè)方法。為了省電,當(dāng)連接上外設(shè)之后,就讓中心設(shè)備停止掃描,并且別忘記設(shè)置連接上的外設(shè)的代理。在這個(gè)方法里根據(jù)UUID進(jìn)行服務(wù)的查找。

/** 連接成功 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    // 可以停止掃描
    [self.centralManager stopScan];
    // 設(shè)置代理
    peripheral.delegate = self;
    // 根據(jù)UUID來(lái)尋找服務(wù),我的設(shè)備服務(wù)UUDI是 “FF00”
    [peripheral discoverServices:@[[CBUUID UUIDWithString:@"FF00"]]];
    NSLog(@"連接成功");
}

6、連接失敗和斷開(kāi)連接也有各自的回調(diào)方法。在斷開(kāi)連接的時(shí)候,我們可以設(shè)置自動(dòng)重連,根據(jù)項(xiàng)目需求來(lái)自定義里面的代碼。

/** 連接失敗的回調(diào) */
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    NSLog(@"連接失敗");
}

/** 斷開(kāi)連接 */
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error {
    NSLog(@"斷開(kāi)連接");
    // 斷開(kāi)連接可以設(shè)置重新連接
    [central connectPeripheral:peripheral options:nil];
}

7、下面開(kāi)始處理代理方法。最開(kāi)始就是發(fā)現(xiàn)服務(wù)的方法。這個(gè)方法里可以遍歷服務(wù),找到需要的服務(wù)。我這里選擇第一個(gè)服務(wù)。
找到服務(wù)之后,連貫的動(dòng)作繼續(xù)根據(jù)特征的UUID尋找服務(wù)中的特征。

/** 發(fā)現(xiàn)服務(wù) */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    
    // 遍歷出外設(shè)中所有的服務(wù)
    for (CBService *service in peripheral.services) {
        NSLog(@"所有的服務(wù):%@",service);
    }
    
    // 這里僅有一個(gè)服務(wù),所以直接獲取
    CBService *service = peripheral.services.lastObject;
    // 根據(jù)UUID尋找服務(wù)中的特征
    [peripheral discoverCharacteristics:[CBUUID UUIDWithString:CHARACTERISTIC_UUID]] forService:service];
}

8、下面這個(gè)方法里做的事情不少。
當(dāng)發(fā)現(xiàn)特征之后,與服務(wù)一樣可以遍歷特征,根據(jù)外設(shè)開(kāi)發(fā)人員給的文檔找出不同特征,做出相應(yīng)的操作。
我這里獲取第一個(gè)特征。
再重復(fù)一遍,一般開(kāi)發(fā)中可以設(shè)置兩個(gè)特征,一個(gè)用來(lái)發(fā)送數(shù)據(jù),一個(gè)用來(lái)接收中心設(shè)備寫(xiě)過(guò)來(lái)的數(shù)據(jù)。
這里用一個(gè)屬性引用特征,是為了后面通過(guò)這個(gè)特征向外設(shè)寫(xiě)入數(shù)據(jù)或發(fā)送指令。
readValueForCharacteristic方法是直接讀一次這個(gè)特征上的數(shù)據(jù)。

/** 發(fā)現(xiàn)特征回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    
    // 遍歷出所需要的特征
    for (CBCharacteristic *characteristic in service.characteristics) {
        NSLog(@"所有特征:%@", characteristic);
        // 從外設(shè)開(kāi)發(fā)人員那里拿到不同特征的UUID,不同特征做不同事情,比如有讀取數(shù)據(jù)的特征,也有寫(xiě)入數(shù)據(jù)的特征
    }
    
    // 這里只獲取一個(gè)特征,寫(xiě)入數(shù)據(jù)的時(shí)候需要用到這個(gè)特征
    self.characteristic = service.characteristics.firstObject;
    
    // 直接讀取這個(gè)特征數(shù)據(jù),會(huì)調(diào)用didUpdateValueForCharacteristic
    [peripheral readValueForCharacteristic:self.characteristic];
    
    // 訂閱通知
    [peripheral setNotifyValue:YES forCharacteristic:self.characteristic];
}

setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic方法是對(duì)這個(gè)特征進(jìn)行訂閱,訂閱成功之后,就可以監(jiān)控外設(shè)中這個(gè)特征值得變化了。

9、當(dāng)訂閱的狀態(tài)發(fā)生改變的時(shí)候,下面的方法就派上用場(chǎng)了。

/** 訂閱狀態(tài)的改變 */
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        NSLog(@"訂閱失敗");
        NSLog(@"%@",error);
    }
    if (characteristic.isNotifying) {
        NSLog(@"訂閱成功");
    } else {
        NSLog(@"取消訂閱");
    }
}

10、外設(shè)可以發(fā)送數(shù)據(jù)給中心設(shè)備,中心設(shè)備也可以從外設(shè)讀取數(shù)據(jù),當(dāng)發(fā)生這些事情的時(shí)候,就會(huì)回調(diào)這個(gè)方法。通過(guò)特種中的value屬性拿到原始數(shù)據(jù),然后根據(jù)需求解析數(shù)據(jù)。

/** 接收到數(shù)據(jù)回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    // 拿到外設(shè)發(fā)送過(guò)來(lái)的數(shù)據(jù)
    NSData *data = characteristic.value;
}

11、中心設(shè)備可以向外設(shè)寫(xiě)入數(shù)據(jù),也可以向外設(shè)發(fā)送請(qǐng)求或指令,當(dāng)需要進(jìn)行這些操作的時(shí)候該怎么辦呢。

首先把要寫(xiě)入的數(shù)據(jù)轉(zhuǎn)化為NSData格式,然后根據(jù)上面拿到的寫(xiě)入數(shù)據(jù)的特征,運(yùn)用方法writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type來(lái)進(jìn)行數(shù)據(jù)的寫(xiě)入。

當(dāng)寫(xiě)入數(shù)據(jù)的時(shí)候,系統(tǒng)也會(huì)回調(diào)這個(gè)方法peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error

/** 寫(xiě)入數(shù)據(jù) */
    // 用NSData類(lèi)型來(lái)寫(xiě)入
    NSData *data = [要寫(xiě)入的值 dataUsingEncoding:NSUTF8StringEncoding];
    // 根據(jù)上面的特征self.characteristic來(lái)寫(xiě)入數(shù)據(jù)
    [self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}


/** 寫(xiě)入數(shù)據(jù)回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
    NSLog(@"寫(xiě)入成功");
}

12、中心設(shè)備如何主動(dòng)從外設(shè)讀取數(shù)據(jù)呢。

用正在連接的外設(shè)對(duì)象來(lái)調(diào)用readValueForCharacteristic方法,并且把將要讀取數(shù)據(jù)的特征作為參數(shù),這樣就可以主動(dòng)拿一次數(shù)據(jù)了。
去到第10步的回調(diào)方法中,在特征的value屬性中拿到這次的數(shù)據(jù)。

 [self.peripheral readValueForCharacteristic:self.characteristic];

后記

到此為止就是一套iOS藍(lán)牙讀寫(xiě)的流程,具體細(xì)化的東西也許就需要硬件工程師或者其他工作人員的配合,比如我們這就就提供好了相應(yīng)的SDK進(jìn)行后續(xù)操作,這些就不寫(xiě)出來(lái)了。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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