利用CoreBluetooth原生框架做藍(lán)牙4.0開發(fā)

簡介

最近做了一個智能門鎖利用藍(lán)牙與app交互的項目。整理一下相關(guān)藍(lán)牙知識。下面要講的是利用原生<CoreBluetooth.framework>框架封裝demo,且支持藍(lán)牙4.0。藍(lán)牙官方文檔

背景

藍(lán)牙分為兩種形式: 1)中心者模式 2)管理者模式,一般絕大部分我們都是使用第一種模式,中心者模式是我們手機(jī)作為主機(jī),連接藍(lán)牙外設(shè),而管理者模式是我們手機(jī)自己作為外設(shè),自己創(chuàng)建服務(wù)和特征,然后有其他的設(shè)備連接我們的手機(jī)。

接下來我們就是圍繞第一種模式:中心者模式來講解。

步驟

藍(lán)牙連接可以大致分為以下幾個步驟:
1.建立一個Central Manager實例進(jìn)行藍(lán)牙管理
2.搜索外圍設(shè)備
3.連接外圍設(shè)備
4.獲得外圍設(shè)備的服務(wù)
5.獲得服務(wù)的特征
6.從外圍設(shè)備讀數(shù)據(jù)、給外圍設(shè)備發(fā)送數(shù)據(jù)

簡言之:就是我們的app創(chuàng)建一個藍(lán)牙中心管理者對象,調(diào)用SDK的方法去搜索周圍可發(fā)現(xiàn)的設(shè)備,搜索成功并發(fā)現(xiàn)有可用的設(shè)備后,進(jìn)行連接,連接成功后再獲取設(shè)備的服務(wù)與特征,最后進(jìn)行數(shù)據(jù)的交互。
疑問:什么是服務(wù)?什么是特征?
下面用一張圖進(jìn)行講解~

各層級關(guān)系

簡言之:就是一個外設(shè)有幾個服務(wù),每一個服務(wù)又有幾個特征,我們可以通過服務(wù)判斷這個外設(shè)是不是我們要連接的外設(shè),通過特征獲取想要的數(shù)據(jù)。在特征有一個叫做UUID的屬性,這個屬性可以作為判斷該特征是否是我們想要的特征的依據(jù),這個跟硬件工程師要對接好UUID的值。

代碼

在.h文件中定義一些全局屬性:

//中心管理者
@property (nonatomic, strong) CBCentralManager *centralManager;
//外圍設(shè)備
@property (nonatomic, strong) CBPeripheral *peripheral;
//讀取設(shè)備信息的特征
@property (nonatomic, strong) CBCharacteristic *readCharteristic;
//收取消息的特征
@property (nonatomic, strong) CBCharacteristic *notifyCharteristic;
//發(fā)送消息的特征
@property (nonatomic, strong) CBCharacteristic *writeCharacteristic;

首先,我們要創(chuàng)建一個中心管理者單例

  //queue中傳入nil默認(rèn)就是在主線程
  self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
  //設(shè)置代理
  self.centralManager.delegate = self;

實例化一個中心管理者之后,會自動調(diào)用下面代理方法:

-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
    
    switch (central.state) {
            
        case CBManagerStatePoweredOff:{
   
            NSLog(@"藍(lán)牙關(guān)閉");
            break;
            
        case CBManagerStatePoweredOn:{
            NSLog(@"藍(lán)牙已打開");
            [central scanForPeripheralsWithServices:nil options:nil];
        }
            break;
            
        case CBManagerStateResetting:

            break;

        case CBManagerStateUnauthorized:
            NSLog(@"系統(tǒng)藍(lán)未被授權(quán)");
            break;

        case CBManagerStateUnknown:
            NSLog(@"系統(tǒng)藍(lán)牙當(dāng)前狀態(tài)不明確");
            break;

        case CBManagerStateUnsupported:
             NSLog(@"系統(tǒng)藍(lán)牙設(shè)備不支持");
            break;
            
        default:
            break;     
    }  
}

這個代理方法是判斷系統(tǒng)的藍(lán)牙狀態(tài),如果藍(lán)牙已經(jīng)打開,則調(diào)用下面的代碼:

 //在給services和options都傳入nil則是代表掃描所有條件的外圍設(shè)備
 [central scanForPeripheralsWithServices:nil options:nil];

當(dāng)掃描到外圍設(shè)備后,會調(diào)用下面這個代理方法:(掃描到多少個外設(shè)就執(zhí)行多少次)

- (void)centralManager:(CBCentralManager *)central // 中心管理
didDiscoverPeripheral:(nonnull CBPeripheral *)peripheral  // 外設(shè)
advertisementData:(nonnull NSDictionary<NSString *,id> *)advertisementData // 外設(shè)攜帶的數(shù)據(jù) 
RSSI:(nonnull NSNumber *)RSSI// 外設(shè)發(fā)出的藍(lán)牙信號強(qiáng)度
{
         //注意:這里可以獲取到掃描到外設(shè)的Mac地址,
         NSString *mac = advertisementData[@"kCBAdvDataManufacturerData"]
        //這里也可以過濾掉不需要的服務(wù)
        //接下來可以把我們想要連接的Mac地址與掃描到的外設(shè)的Mac地址進(jìn)行匹配,如果一致就獲取
            if ([self.peripheralMac isEqualToString:mac]) {
                   //這里就是我們要連接的外設(shè)
                   //獲取外部設(shè)備
                   self.peripheral = peripheral;
                   //用中心管理者去調(diào)用連接獲取到的外設(shè)的方法
                   [self.centralManager connectPeripheral:peripheral options:nil];    
                   //停止搜索
                   [self.centralManager stopScan];
            }
}

注意:代理方法是不會直接給我們返回外設(shè)的Mac地址的,我們可以通過advertisementData[@"kCBAdvDataManufacturerData"]去得到Mac地址,這個要跟硬件工程師溝通好怎么返回,以什么形式去返回。

連接成功后會自動執(zhí)行下面這個代理方法:

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{

    //獲取到已經(jīng)連接上的外設(shè)之后,設(shè)置代理
    self.peripheral.delegate = self;
    //用外設(shè)去獲取服務(wù)
    [peripheral discoverServices:nil];
}

如果連接失敗,會調(diào)用下面這個方法:

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"連接失敗%@",error.localizedDescription);
}

如果兩個已經(jīng)上的設(shè)置突然斷開了連接,會自動調(diào)用下面的方法:

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
        //這種情況一般是,你藍(lán)牙不穩(wěn)定或者藍(lán)牙斷開等一些外部環(huán)境問題
        NSLog(@"已經(jīng)斷開藍(lán)牙連接");
{

連接外設(shè)成功并調(diào)用獲取外設(shè)服務(wù)的方法后,獲取外設(shè)服務(wù)成功后會調(diào)用下面的方法:

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{

   for (CBService *service in peripheral.services){
        NSLog(@"外設(shè)服務(wù)號:%@",service.UUID.UUIDString);
        if ([service.UUID.UUIDString isEqualToString:kServiceUUID]) {
            //外圍設(shè)備查找指定服務(wù)中的特征,characteristics為nil,表示尋找所有特征
            [peripheral discoverCharacteristics:nil forService:service];
            break;
        }
        
    }
}

當(dāng)獲取外設(shè)服務(wù)的特征成功后,自動執(zhí)行下面的代理方法:在這個方法中,就根據(jù)

-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{

//kWriteCharacteristicUUID、kNotifyCharacteristicUUID、kReadCharacteristicUUID是與硬件對接好的特征的UUID,
 for (CBCharacteristic *characteristic in service.characteristics)
        
    {
        //寫入特征
        if ([characteristic.UUID.UUIDString isEqualToString:kWriteCharacteristicUUID]  ) {
            
            self.writeCharacteristic = characteristic;
        }
        //通知特征
        if ([characteristic.UUID.UUIDString isEqualToString:kNotifyCharacteristicUUID]) {
            
            self.notifyCharteristic = characteristic;
            /* 設(shè)置是否向特征訂閱數(shù)據(jù)實時通知,YES表示會實時多次會調(diào)用代理方法讀取數(shù)據(jù) */
             [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        }
        //讀取特征
        if ([characteristic.UUID.UUIDString isEqualToString:kReadCharacteristicUUID]) {
            
            self.readCharteristic = characteristic;
            /* 讀取特征的數(shù)據(jù),調(diào)用此方法后會調(diào)用一次代理方法讀取數(shù)據(jù) */
            [peripheral readValueForCharacteristic:characteristic];
        }
        
    }
}

注意:kWriteCharacteristicUUID、kNotifyCharacteristicUUID、kReadCharacteristicUUID是與硬件對接好的特征的UUID,用來判斷是不是我們想要的特征,這里分為寫入特征、讀取特征、通知特征,分別是代表app想設(shè)備發(fā)送信息、app從設(shè)備獲取信息、app獲取設(shè)備的信息。

來到這里就已經(jīng)獲取到我們想要的特征了,接下來我們開始最后一步,寫入信息和讀取信息了。
下面的這個代理方法是,設(shè)備所有的數(shù)據(jù)都是從中這個代理方法返回的:

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
   
      //從我們想要的特征中獲取返回的數(shù)據(jù)
      if ([self.notifyCharteristic isEqual:characteristic]) {
           NSLog(@"設(shè)備愛返回的數(shù)據(jù):%@",characteristic.value);
       }

}

當(dāng)我們想向設(shè)備寫入數(shù)據(jù)的時候,調(diào)用下面這個方法:

 if(self.characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse)
    {
        //手機(jī)向外設(shè)發(fā)送數(shù)據(jù),寫數(shù)據(jù)
        [self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}
  

注意:這里寫入的data是一個二進(jìn)制形式

寫到這里就結(jié)束了,上面就是講解了開頭的6個步驟的實現(xiàn)方法,利用原生的api去封裝一個藍(lán)牙通訊的SDK不難,關(guān)鍵是這個藍(lán)牙通訊的SDK結(jié)合自己的項目穿插各種邏輯的時候,就需要謹(jǐn)慎思考了。

謝謝~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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