iOS藍牙知識快速入門(詳盡版)

iOS-bluetooth
之前寫過幾篇藍牙相關的文章,可是沒有涉及掃描、收發指令這些基礎功能的實現。因此打算寫一篇儘量詳盡的藍牙知識彙總,一方面給有須要的同窗看,一方面是對本身學習藍牙的一個總結。

這篇文章的目的:教你實現設備的掃描,鏈接,數據收發,藍牙數據解析。若是在實現上面任一功能遇到問題時,歡迎留下你的問題,我將進行補充,對於說法有誤的地方也請老司機予以指正。git

目錄

0、思惟導圖github

一、蘋果對藍牙設備有什麼要求服務器

二、操做藍牙設備使用什麼庫數據結構

三、如何掃描app

四、如何鏈接框架

五、如何發送數據和接收數據iphone

六、如何解析數據ide

七、擴展工具

思惟導圖

思惟導圖
第一次作圖,你們湊合着看哈。這張是我總結的藍牙知識的結構圖,下面的內容將圍繞這些東西展開進行。
鏈接設備流程
這張是藍牙鏈接發送數據的流程圖,下文進入coding階段的講解順序,你們先有個大概印象,等閱讀完本文再回來看這張圖將理解的更深一些。

蘋果對藍牙設備有什麼要求

BLE:bluetouch low energy,藍牙4.0設備由於低功耗,全部也叫做BLE。蘋果在iphone4s及以後的手機型號開始支持藍牙4.0,這也是最多見的藍牙設備。低於藍牙4.0協議的設備須要進行MFI認證,關於MFI認證的申請工做能夠看這裏:關於MFI認證你所必需要知道的事情學習

在進行操做藍牙設備前,咱們先下載一個藍牙工具LightBlue,它能夠輔助咱們的開發,在進行藍牙開發以前建議先熟悉一下LightBlue這個工具。

操做藍牙設備使用什麼庫

蘋果自身有一個操做藍牙的庫CoreBluetooth.framework,這個是大多數人員進行藍牙開發的首選框架,除此以外目前github還有一個比較流行的對原生框架進行封裝的三方庫BabyBluetooth,它的機制是將CoreBluetooth中衆多的delegate寫成了block方法,有興趣的同窗能夠了解下。下面主要介紹的是原生藍牙庫的知識。

中心和外圍設備

central-peripheral

如圖所示,電腦、Pad、手機做爲中心,心跳監聽器做爲外設,這種中心外設模式是最多見的。簡單理解就是,發起鏈接的是中心設備(Central),被鏈接的是外圍設備(Peripheral),對應傳統的客戶機-服務器體系結構。Central可以掃描偵聽到,正在播放廣告包的外設。

服務與特徵

外設能夠包含一個或多個服務(CBService),服務是用於實現裝置的功能或特徵數據相關聯的行爲集合。 而每一個服務又對應多個特徵(CBCharacteristic),特徵提供外設服務進一步的細節,外設,服務,特徵對應的數據結構以下所示

CBService-CBCharacteristic

如何掃描藍牙

在進行掃描以前咱們須要,首先新建一個類做爲藍牙類,例如FYBleManager,寫成單例,做爲處理藍牙操做的管理類。引入頭文件#import <CoreBluetooth/CoreBluetooth.h> CBCentralManager是藍牙中心的管理類,控制着藍牙的掃描,鏈接,藍牙狀態的改變。

一、初始化

dispatch_queue_t centralQueue = dispatch_queue_create(「centralQueue",DISPATCH_QUEUE_SERIAL);
NSDictionary *dic = @{CBCentralManagerOptionShowPowerAlertKey : YES, CBCentralManagerOptionRestoreIdentifierKey : @"unique identifier"};
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:dic];
複製代碼

CBCentralManagerOptionShowPowerAlertKey對應的BOOL值,當設爲YES時,表示CentralManager初始化時,若是藍牙沒有打開,將彈出Alert提示框 CBCentralManagerOptionRestoreIdentifierKey對應的是一個惟一標識的字符串,用於藍牙進程被殺掉恢復鏈接時用的。

二、掃描

//不重複掃描已發現設備
NSDictionary *option = @{CBCentralManagerScanOptionAllowDuplicatesKey : [NSNumber numberWithBool:NO],CBCentralManagerOptionShowPowerAlertKey:YES};

[self.centralManager scanForPeripheralsWithServices:nil options:option];
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;
複製代碼

掃面方法,serviceUUIDs用於第一步的篩選,掃描此UUID的設備 options有兩個經常使用參數:CBCentralManagerScanOptionAllowDuplicatesKey設置爲NO表示不重複掃瞄已發現設備,爲YES就是容許。CBCentralManagerOptionShowPowerAlertKey設置爲YES就是在藍牙未打開的時候顯示彈框

三、CBCentralManagerDelegate代理方法

在初始化的時候咱們調用了代理,在CoreBluetooth中有兩個代理,

  • CBCentralManagerDelegate
  • CBPeripheralDelegate

iOS的命名很友好,咱們經過名字就能看出,上面那個是關於中心設備的代理方法,下面是關於外設的代理方法。咱們這裏先研究CBCentralManagerDelegate中的代理方法

- (void)centralManagerDidUpdateState:(CBCentralManager *)central;
複製代碼

這個方法標了@required是必須添加的,咱們在self.centralManager初始換以後會調用這個方法,回調藍牙的狀態。狀態有如下幾種:

typedef NS_ENUM(NSInteger, CBCentralManagerState{
    CBCentralManagerStateUnknown = CBManagerStateUnknown,//未知狀態
    CBCentralManagerStateResetting = CBManagerStateResetting,//重啓狀態
    CBCentralManagerStateUnsupported = CBManagerStateUnsupported,//不支持
    CBCentralManagerStateUnauthorized = CBManagerStateUnauthorized,//未受權
    CBCentralManagerStatePoweredOff = CBManagerStatePoweredOff,//藍牙未開啓
    CBCentralManagerStatePoweredOn = CBManagerStatePoweredOn,//藍牙開啓} NS_DEPRECATED(NA, NA, 5_0, 10_0, "Use CBManagerState instead」
);
複製代碼

該枚舉在iOS10以後已經廢除了,系統推薦使用CBManagerState,類型都是對應的

typedef NS_ENUM(NSInteger, CBManagerState{
    CBManagerStateUnknown = 0,
    CBManagerStateResetting,
    CBManagerStateUnsupported,
    CBManagerStateUnauthorized,
    CBManagerStatePoweredOff,
    CBManagerStatePoweredOn,
} NS_ENUM_AVAILABLE(NA, 10_0);

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
複製代碼

peripheral是外設類 advertisementData是廣播的值,通常攜帶設備名,serviceUUIDs等信息 RSSI絕對值越大,表示信號越差,設備離的越遠。若是想裝換成百分比強度,(RSSI+100)/100,(這是一個約數,藍牙信號值並不必定是-100 - 0的值,但近似能夠如此表示)

- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *, id> *)dict;
複製代碼

在藍牙於後臺被殺掉時,重連以後會首先調用此方法,能夠獲取藍牙恢復時的各類狀態

如何鏈接

在掃面的代理方法中,咱們鏈接外設名是MI的藍牙設備

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{ 
    NSLog(@"advertisementData:%@,RSSI:%@",advertisementData,RSSI);
    if([peripheral.name isEqualToString:@"MI"]){ 
        [self.centralManager connectPeripheral:peripheral options:nil];//發起鏈接的命令
        self.peripheral = peripheral; 
    }
}
複製代碼

鏈接的狀態 對應另外的CBCentralManagerDelegate代理方法 鏈接成功的回調

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
複製代碼

鏈接失敗的回調

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
複製代碼

鏈接斷開的回調

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
複製代碼

鏈接成功以後並無結束,還記得CBPeripheral中的CBServiceCBService中的CBCharacteristic嗎,對數據的讀寫是由CBCharacteristic控制的。咱們先用lightblue鏈接小米手環爲例,來看一下,手環內部的數據是否是咱們說的那樣。

lightblue

其中ADVERTISEMENT DATA顯示的就是廣播信息。

iOS藍牙沒法直接獲取設備藍牙MAC地址,能夠將MAC地址放到這裏廣播出來

FEEOServiceUUIDs,裏面的FF01FF02CBCharacteristic的UUID

Properties是特徵的屬性,能夠看出FF01具備讀的權限,FF02具備讀寫的權限。特徵擁有的權限類別有以下幾種:

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
};
複製代碼

如何發送並接收數據

經過上面的步驟咱們發現CBCentralManagerDelegate提供了藍牙狀態監測、掃描、鏈接的代理方法,可是CBPeripheralDelegate的代理方法卻還沒使用。別急,立刻就要用到了,經過名稱判斷這個代理的做用,確定是跟Peripheral有關,咱們進入系統API,看它的代理方法都有什麼,由於這裏的代理方法較多,我就挑選幾個經常使用的拿出來講明一下。

一、代理方法

//發現服務的回調
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;
//發現特徵的回調
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;
//讀數據的回調
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
//是否寫入成功的回調
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
複製代碼

二、步驟

經過這幾個方法咱們構建一個流程:鏈接成功->獲取指定的服務->獲取指定的特徵->訂閱指定特徵值->經過具備寫權限的特徵值寫數據->在didUpdateValueForCharacteristic回調中讀取藍牙反饋值

解釋一下訂閱特徵值:特徵值具備Notify權限才能夠進行訂閱,訂閱以後該特徵值的value發生變化纔會回調didUpdateValueForCharacteristic

三、實現上面流程的實例代碼

//鏈接成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    //鏈接成功以後尋找服務,傳nil會尋找全部服務
    [peripheral discoverServices:nil];
}

//發現服務的回調
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
   if (!error) { 
     for (CBService *service in peripheral.services) {     
         NSLog(@"serviceUUID:%@", service.UUID.UUIDString); 
            if ([service.UUID.UUIDString isEqualToString:ST_SERVICE_UUID]) {
            //發現特定服務的特徵值
               [service.peripheral discoverCharacteristics:nil forService:service]; 
            } 
        } 
    }
}

//發現characteristics,由發現服務調用(上一步),獲取讀和寫的characteristics
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    for (CBCharacteristic *characteristic in service.characteristics) { 
        //有時讀寫的操做是由一個characteristic完成 
        if ([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_READ]) {
            self.read = characteristic;
            [self.peripheral setNotifyValue:YES forCharacteristic:self.read]; 
        } else if ([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_WRITE]) {
            self.write = characteristic; 
        } 
     }
}

//是否寫入成功的代理
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if (error) { 
        NSLog(@"===寫入錯誤:%@",error); 
    }else{
        NSLog(@"===寫入成功"); 
    }
}

//數據接收
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { 
    if([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_READ]){          //獲取訂閱特徵回覆的數據
        NSData *value = characteristic.value;
        NSLog(@"藍牙回覆:%@",value);
    }
}
複製代碼

好比咱們要獲取藍牙電量,由硬件文檔查詢得知該指令是**0x1B9901**,那麼獲取電量的方法就能夠寫成

- (void)getBattery{
    Byte value[3]={0};
    value[0]=x1B;
    value[1]=x99;
    value[2]=x01;
    NSData * data = [NSData dataWithBytes:&value length:sizeof(value)];
    //發送數據
    [self.peripheral writeValue:data forCharacteristic:self.write type:CBCharacteristicWriteWithoutResponse];
}
複製代碼

若是寫入成功,咱們將會在didUpdateValueForCharacteristic方法中獲取藍牙回覆的信息。

如何解析藍牙數據

若是你順利完成了上一步的操做,而且看到了藍牙返回的數據,那麼恭喜你,藍牙的經常使用操做你已經瞭解大半了。由於藍牙的任務大部分就是圍繞發送指令,獲取指令,將藍牙數據呈現給用戶。上一步咱們已經獲取了藍牙指令,可是獲取的倒是0x567b0629這樣的數據,這是什麼意思呢。這時咱們參考硬件文檔,看到這樣一段:

device-document
那麼咱們就能夠得出設備電量是 60%。

對數據解析的流程就是:判斷校驗和是否正確,是否是一條正確的數據->該條數據是否是咱們須要的電量數據,即首字節爲0x567b->根據定義規則解析電量,傳給view顯示。其中第一步校驗數據,視狀況而定,也有不須要的狀況。

擴展

iOS藍牙中的進制轉換

藍牙固件升級

nRF芯片設備DFU升級

參考Demo

*轉載請私信聯繫

相關文章
相關標籤/搜索