iOS CoreBluetooth 藍牙4.0學習接入筆記

最近公司的項目中提到了藍牙開發(fā),而且現(xiàn)在市面上的藍牙分為普通藍牙和低功耗藍牙(BLE)也就是藍牙4.0

iOS 提供了 <CoreBluetooth/CoreBluetooth.h> 這個框架專門針對藍牙4.0 下面就是我對這個框架查的一些總結和記錄。

話不多說,讓我們進入正題吧:

藍牙常見名稱和縮寫

  • BLE:(Bluetooth low energy)藍牙4.0設備因為低耗電,也叫BLE

  • peripheral,central:外設和中心設備,發(fā)起鏈接的是central(一般是指手機),被鏈接的設備是peripheral(比如運動手環(huán))

  • service and characteristic:(服務和特征)每個設備會提供服務和特征,類似于服務端的API,但是結構不同.每個設備會有很多服務,每個服務中包含很多字段,這些字段的權限一般分為讀(read),寫(write),通知(notify)幾種,就是我們連接設備后具體需要操作的內(nèi)容

  • Description:每個characteristic可以對應一個或者多個Description用于描述characteristic的信息或屬性(eg.范圍,計量單位)

關于上邊的名稱詳情可以參考
Central 和 Peripheral 在藍牙交互中的角色
Core Bluetooth Programming Guide

服務和特征(service and characteristic)

  • 每個設備都會有1個or多個服務
  • 每個服務里都會有1個or多個特征
  • 特征就是具體鍵值對,提供數(shù)據(jù)的地方
  • 每個特征屬性分為:讀,寫,通知等等
外設,服務,特征的關系

BLE中心模式流程

1.建立中心角色
2.掃描外設(Discover Peripheral)
3.連接外設(Connect Peripheral)
4.掃描外設中的服務和特征(Discover Services And Characteristics)
4.1 獲取外設的services
4.2 獲取外設的Characteristics,獲取characteristics的值,,獲取Characteristics的Descriptor和Descriptor的值
5.利用特征與外設做數(shù)據(jù)交互(Explore And Interact)
6.訂閱Characteristic的通知
7.斷開連接(Disconnect)

BLE外設模式流程

1.啟動一個Peripheral管理對象
2.本地peripheral設置服務,特征,描述,權限等等
3.peripheral發(fā)送廣告
4.設置處理訂閱,取消訂閱,讀characteristic,寫characteristic的代理方法

藍牙和版本使用限制

  • 藍牙2.0:越獄設備
  • BLE:iOS6以上
  • MFI認證設備:無限制

這里我把手機作為central 周圍藍牙的設備作為peripheral 根據(jù)BLE中心模式的流程 相關代碼步驟如下:

1.首先導入我們需要的藍牙框架 定義一些需要的宏
#import <CoreBluetooth/CoreBluetooth.h>

#define kServiceUUID @"590A" //服務的UUID
#define kCharacteristicUUID @"590B" //特征的UUID

2.建立中心管理者

- (CBCentralManager *)cMgr
{
    if (!_cMgr) {
        /*
         設置主設備的代理,CBCentralManagerDelegate
         必須實現(xiàn)的:
         - (void)centralManagerDidUpdateState:(CBCentralManager *)central;//主設備狀態(tài)改變調(diào)用,在初始化CBCentralManager的適合會打開設備,只有當設備正確打開后才能使用
         其他選擇實現(xiàn)的代理中比較重要的:
         - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; //找到外設
         - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設成功
         - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設連接失敗
         - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設
         */
        _cMgr = [[CBCentralManager alloc] initWithDelegate:self
                                                     queue:dispatch_get_main_queue() options:nil];
    }
    return _cMgr;
}

調(diào)用懶加載

- (void)viewDidLoad
{
    [super viewDidLoad];
   
    // 調(diào)用get方法,先將中心管理者初始化
    [self cMgr];

}

3.掃描外設 (這里注意 在中心管理者成功開啟后再掃描)


// 只要中心管理者初始化,就會觸發(fā)此代理方法
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    /*
     CBCentralManagerStateUnknown = 0,
     CBCentralManagerStateResetting,
     CBCentralManagerStateUnsupported,
     CBCentralManagerStateUnauthorized,
     CBCentralManagerStatePoweredOff,
     CBCentralManagerStatePoweredOn,
     */
    switch (central.state) {
        case CBCentralManagerStateUnknown:
            NSLog(@"CBCentralManagerStateUnknown");
            break;
        case CBCentralManagerStateResetting:
            NSLog(@"CBCentralManagerStateResetting");
            break;
        case CBCentralManagerStateUnsupported:
            NSLog(@"CBCentralManagerStateUnsupported");
            break;
        case CBCentralManagerStateUnauthorized:
            NSLog(@"CBCentralManagerStateUnauthorized");
            break;
        case CBCentralManagerStatePoweredOff:
            NSLog(@"CBCentralManagerStatePoweredOff");
            break;
        case CBCentralManagerStatePoweredOn:
        {
            NSLog(@"CBCentralManagerStatePoweredOn");
            // 在中心管理者成功開啟后再進行一些操作
            // 搜索外設
            [self.cMgr scanForPeripheralsWithServices:nil // 通過某些服務篩選外設
                                              options:nil]; // dict,條件
            // 搜索成功之后,會調(diào)用我們找到外設的代理方法
            // - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; //找到外設

        }
            break;
            
        default:
            break;
    }
}

4.連接外設

#pragma mark - CBCentralManagerDelegate

// 發(fā)現(xiàn)外設后調(diào)用的 CBCentralManagerDelegate 方法
- (void)centralManager:(CBCentralManager *)central // 中心管理者
 didDiscoverPeripheral:(CBPeripheral *)peripheral // 外設
     advertisementData:(NSDictionary *)advertisementData // 外設攜帶的數(shù)據(jù)
                  RSSI:(NSNumber *)RSSI // 外設發(fā)出的藍牙信號強度
{
    //NSLog(@"%s, line = %d, cetral = %@,peripheral = %@, advertisementData = %@, RSSI = %@", __FUNCTION__, __LINE__, central, peripheral, advertisementData, RSSI);
    
    /*
    peripheral = <CBPeripheral: 0x1742e1800, identifier = 8CAC092C-9D81-47CA-940E-CD705F967A20, name     = 27010010, state = disconnected>, advertisementData = {
     kCBAdvDataIsConnectable = 1;
     kCBAdvDataLocalName = 27010010;
     kCBAdvDataServiceUUIDs =     (
     590A
     );
     }, RSSI = -92
     根據(jù)打印結果,我們可以得到車鎖設備 它的名字叫 27010010

     
     */
    
    // 需要對連接到的外設進行過濾
    // 1.信號強度(40以上才連接, 80以上連接)
    // 2.通過設備名(設備字符串27010010)
    // 在此時我們的過濾規(guī)則是:有OBand前綴并且信號強度大于35
    // 通過打印,我們知道RSSI一般是帶-的
    
    if ([peripheral.name isEqualToString:@"27010010"] && (ABS(RSSI.integerValue) > 35)) {
        // 在此處對我們的 advertisementData(外設攜帶的廣播數(shù)據(jù)) 進行一些處理
        
        // 通常通過過濾,我們會得到一些外設,然后將外設儲存到我們的可變數(shù)組中,
        // 這里由于附近只有1個運動手環(huán), 所以我們先按1個外設進行處理
        
        // 標記我們的外設,讓他的生命周期 = vc
        self.peripheral = peripheral;
        // 發(fā)現(xiàn)完之后就是進行連接
        [self.cMgr connectPeripheral:self.peripheral options:nil];
        NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);
    }
}

連接外設成功或者失敗 會調(diào)用代理方法

// 中心管理者連接外設成功
- (void)centralManager:(CBCentralManager *)central // 中心管理者
  didConnectPeripheral:(CBPeripheral *)peripheral // 外設
{
    NSLog(@"%s, line = %d, %@=連接成功", __FUNCTION__, __LINE__, peripheral.name);
    // 連接成功之后,可以進行服務和特征的發(fā)現(xiàn)
    // 4.1 獲取外設的服務
    // 4.1.1 設置外設的代理
    self.peripheral.delegate = self;
    
    // 4.1.2 外設發(fā)現(xiàn)服務,傳nil代表不過濾
    // 這里會觸發(fā)外設的代理方法 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
    [self.peripheral discoverServices:nil];
}
// 外設連接失敗
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%s, line = %d, %@=外設連接失敗", __FUNCTION__, __LINE__, peripheral.name);
}

// 丟失連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%s, line = %d, %@=外設斷開連接", __FUNCTION__, __LINE__, peripheral.name);
}

5.掃描外設中的服務和特征(Discover Services And Characteristics)

#pragma mark - 外設代理

// 發(fā)現(xiàn)外設的服務后調(diào)用的方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);
    // 判斷沒有失敗
    if (error) {
        NSLog(@"%s, line = %d, error = %@", __FUNCTION__, __LINE__, error.localizedDescription);
        return;
#warning 下面的方法中凡是有error的在實際開發(fā)中,都要進行判斷
    }
    for (CBService *service in peripheral.services) {
// 發(fā)現(xiàn)服務后,讓設備再發(fā)現(xiàn)服務內(nèi)部的特征們 didDiscoverCharacteristicsForService
        [peripheral discoverCharacteristics:nil forService:service];
    }
}

6.獲取外設的services
獲取外設的Characteristics,獲取characteristics的值,,獲取Characteristics的Descriptor和Descriptor的值
利用特征與外設做數(shù)據(jù)交互(Explore And Interact)
訂閱Characteristic的通知

// 發(fā)現(xiàn)外設服務里的特征的時候調(diào)用的代理方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    //根據(jù)自己的需要篩選ServiceUUID]
    if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
        
        for (CBCharacteristic *characteristic in service.characteristics) {
            
            NSLog(@"特征UUID:%@",characteristic.UUID);

            if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
                NSLog(@"監(jiān)聽特征:%@",characteristic);//監(jiān)聽特征
                self.openLockCharacteristic = characteristic;
            }
            
            if (self.openLockCharacteristic) {
                //characteristic 包含了 service 要傳輸?shù)臄?shù)據(jù)。例如溫度設備中表達溫度的 characteristic,就可能包含著當前溫度值。這時我們就可以通過讀取 characteristic,來得到里面的數(shù)據(jù)。
                //可以直接獲取特征的值  使用下面的方法
                //[peripheral readValueForCharacteristic:self.openLockCharacteristic];
                //當調(diào)用上面這方法后,會回調(diào)peripheral:didUpdateValueForCharacteristic:error:方法,其中包含了要讀取的數(shù)據(jù)。如果讀取正確,在該方法中獲取到值
               
               //也可以訂閱通知
               //其實使用readValueForCharacteristic:方法并不是實時的??紤]到很多實時的數(shù)據(jù),比如心率這種,那就需要訂閱 characteristic 了。
               //當訂閱成功以后,那數(shù)據(jù)便會實時的傳回了,數(shù)據(jù)的回調(diào)依然和之前讀取 characteristic 的回調(diào)相同 
                [self.peripheral setNotifyValue:YES forCharacteristic:self.openLockCharacteristic];//拿到讀特征
                
                //向openLockCharacteristic 發(fā)送數(shù)據(jù) 這里的數(shù)據(jù)根據(jù)自己硬件的實際情況發(fā)送
                [self sj_peripheral:self.peripheral didWriteData:[self stringToByte:@""] forCharacteristic:self.openLockCharacteristic];
                
            }
        }
    }
}

獲取外設發(fā)來的數(shù)據(jù)

// 更新特征的value的時候會調(diào)用
//中心接受外設信息,外設通知手機連接成功的時候并不會調(diào)用這個函數(shù);手機向外設發(fā)送消息后,外設回應數(shù)據(jù),這個函數(shù)會接受到數(shù)據(jù)。
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);
    
    NSString *value = [[NSString alloc] initWithData:characteristic.value encoding:NSASCIIStringEncoding];
    NSLog(@"收到藍牙的消息=======%@",value);//這個就是藍牙給我們發(fā)的數(shù)據(jù);
    
}

// 更新特征的描述的值的時候會調(diào)用
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error
{
    NSLog(@"%s, line = %d, descriptor.UUID = %@ ,descriptor.value = %@", __FUNCTION__, __LINE__,descriptor.UUID ,descriptor.value);
    
    // 這里當描述的值更新的時候,直接調(diào)用此方法即可
    [peripheral readValueForDescriptor:descriptor];
}

//如果是訂閱,成功與否的回調(diào)是peripheral:didUpdateNotificationStateForCharacteristic:error:,讀取中的錯誤會以 error 形式傳回:
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error {
      if (error) {
        NSLog(@"Error changing notification state: %@", [error localizedDescription]);
    }else{
        NSLog(@"訂閱成功");
    }
}

//用于檢測中心向外設寫數(shù)據(jù)是否成功
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    if (error) {
        NSLog(@"=======%@",error.userInfo);
        [self updateLog:[error.userInfo JSONString]];
    }else{
        NSLog(@"發(fā)送數(shù)據(jù)成功");
        [self updateLog:@"發(fā)送數(shù)據(jù)成功"];
    }

    /* When a write occurs, need to set off a re-read of the local CBCharacteristic to update its value */
    [peripheral readValueForCharacteristic:characteristic];
}

還有一些自定義方法 向外設寫數(shù)據(jù) 通知訂閱 斷開連接

#pragma mark - 自定義方法

//外設寫數(shù)據(jù)到特征中

// 需要注意的是特征的屬性是否支持寫數(shù)據(jù)
- (void)sj_peripheral:(CBPeripheral *)peripheral didWriteData:(NSData *)data forCharacteristic:(nonnull CBCharacteristic *)characteristic
{
    /*
     typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
     CBCharacteristicPropertyBroadcast                                              = 0x01,
     CBCharacteristicPropertyRead                                                   = 0x02,
     CBCharacteristicPropertyWriteWithoutResponse                                   = 0x04,
     CBCharacteristicPropertyWrite                                                  = 0x08,
     CBCharacteristicPropertyNotify                                                 = 0x10,
     CBCharacteristicPropertyIndicate                                               = 0x20,
     CBCharacteristicPropertyAuthenticatedSignedWrites                              = 0x40,
     CBCharacteristicPropertyExtendedProperties                                     = 0x80,
     CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)        = 0x100,
     CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)  = 0x200
     };
     
     打印出特征的權限(characteristic.properties),可以看到有很多種,這是一個NS_OPTIONS的枚舉,可以是多個值
     常見的又read,write,noitfy,indicate.知道這幾個基本夠用了,前倆是讀寫權限,后倆都是通知,倆不同的通知方式
     */
    NSLog(@"%s, line = %d, char.pro = %lu", __FUNCTION__, __LINE__, (unsigned long)characteristic.properties);
    // 此時由于枚舉屬性是NS_OPTIONS,所以一個枚舉可能對應多個類型,所以判斷不能用 = ,而應該用包含&
    if (characteristic.properties & CBCharacteristicPropertyWrite) {
        // 核心代碼在這里
        [peripheral writeValue:data // 寫入的數(shù)據(jù)
             forCharacteristic:characteristic // 寫給哪個特征
                          type:CBCharacteristicWriteWithResponse];// 通過此響應記錄是否成功寫入
        
        
    }
}
// 6.通知的訂閱和取消訂閱
// 實際核心代碼是一個方法
// 一般這兩個方法要根據(jù)產(chǎn)品需求來確定寫在何處
- (void)sj_peripheral:(CBPeripheral *)peripheral regNotifyWithCharacteristic:(nonnull CBCharacteristic *)characteristic
{
    // 外設為特征訂閱通知 數(shù)據(jù)會進入 peripheral:didUpdateValueForCharacteristic:error:方法
    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
- (void)sj_peripheral:(CBPeripheral *)peripheral CancleRegNotifyWithCharacteristic:(nonnull CBCharacteristic *)characteristic
{
    // 外設取消訂閱通知 數(shù)據(jù)會進入 peripheral:didUpdateValueForCharacteristic:error:方法
    [peripheral setNotifyValue:NO forCharacteristic:characteristic];
}

// 7.斷開連接
- (void)sj_dismissConentedWithPeripheral:(CBPeripheral *)peripheral
{
    // 停止掃描
    [self.cMgr stopScan];
    // 斷開連接
    [self.cMgr cancelPeripheralConnection:peripheral];
}

再次連接 peripheral

Core Bluetooth 提供了三種再次連接 peripheral 的方式:

調(diào)用 retrievePeripheralsWithIdentifiers: 方法,重連已知的 peripheral 列表中的 peripheral(以前發(fā)現(xiàn)的,或者以前連接過的)。
調(diào)用 retrieveConnectedPeripheralsWithServices: 方法,重新連接當前【系統(tǒng)】已經(jīng)連接的 peripheral。
調(diào)用 scanForPeripheralsWithServices:options: 方法,連接搜索到的 peripheral。

以上也是我看視頻加上網(wǎng)上找資料得出的 后續(xù)項目中用到的話 在增加 和修改可能的錯誤 :)
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

  • (一) iOS藍牙開發(fā)藍牙相關基礎知識 藍牙常見名稱和縮寫 MFI ======= make for ipad ...
    雷鳴1010閱讀 5,117評論 2 12
  • 這里我們具體說明一下中心模式的應用場景。主設備(手機去掃描連接外設,發(fā)現(xiàn)外設服務和屬性,操作服務和屬性的應用。一般...
    丶逝水流年閱讀 2,396評論 3 4
  • 由于最近工作的東家是一家物聯(lián)網(wǎng)公司,網(wǎng)上BLE相關的資料確實比較少,尤其我還做了一些調(diào)試和加密相關的工作.由于調(diào)試...
    陳長見閱讀 2,161評論 5 11
  • 1,給學員的一封信 2,晚上8點分享 3,跟21期教練團相聚 4,找蘇蘇姐對課程 5,收集學員班委申請,跟班長申請...
    她期待ROSE閱讀 155評論 0 0
  • 有個奶爸說起他第一次送娃上學的情形,孩子哭得特別厲害,看著實在不忍心,只好又將娃送回家了,結果被孩子他媽罵了一頓。...
    一佑媽媽閱讀 258評論 0 0

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