藍牙實戰總結一,就是乾貨(會持續更新)

背景說明:

由於公司的業務需要在之前積攢了一些關於藍牙開發經驗,在此做過mark , 溫故而知新,也希望能給後來的學習者提供一些幫助,那好,here we go !

本篇文章的學習對象是基於藍牙4.0外設的開發,所以如果涉及到其他的藍牙版本,請移步了。

藍牙4.0介紹

有關藍牙4.0過多的基礎介紹就不在這裏贅述了,移步這裏 百度百科

以下是來自維基:。

藍牙4.0是Bluetooth SIG於2010年7月7日推出的新的規範。其最重要的特性是支持省電;
Bluetooth 4.0,協議組成和當前主流的Bluetooth h2.x+EDR、還未普及的Bluetooth h3.0+HS不同,Bluetooth 4.0是Bluetooth從誕生至今唯一的一個綜合協議規範,
還提出了“低功耗藍牙”、“傳統藍牙”和“高速藍牙”三種模式。
其中:高速藍牙主攻數據交換與傳輸;傳統藍牙則以信息溝通、設備連接爲重點;藍牙低功耗顧名思義,以不需佔用太多帶寬的設備連接爲主。前身其實是NOKIA開發的Wibree技術,本是作爲一項專爲移動設備開發的極低功耗的移動無線通信技術,在被SIG接納並規範化之後重命名爲Bluetooth Low Energy(後簡稱低功耗藍牙)。這三種協議規範還能夠互相組合搭配、從而實現更廣泛的應用模式,此外,Bluetooth 4.0還把藍牙的傳輸距離提升到100米以上(低功耗模式條件下)。
分Single mode與Dual mode。
Single mode只能與BT4.0互相傳輸無法向下兼容(與3.0/2.1/2.0無法相通);Dual mode可以向下兼容可與BT4.0傳輸也可以跟3.0/2.1/2.0傳輸
超低的峯值、平均和待機模式功耗,覆蓋範圍增強,最大範圍可超過100米。
速度:支持1Mbps數據傳輸率下的超短數據包,最少8個八組位,最多27個。所有連接都使用藍牙2.1加入的減速呼吸模式(sniff subrating)來達到超低工作循環。
跳頻:使用所有藍牙規範版本通用的自適應跳頻,最大程度地減少和其他2.4 GHz ISM頻段無線技術的串擾。
主控制:可以休眠更長時間,只在需要執行動作的時候才喚醒。
延遲:最短可在3毫秒內完成連接設置並開始傳輸數據。
健壯性:所有數據包都使用24-bit CRC校驗,確保最大程度抵禦干擾。
安全:使用AES-128 CCM加密算法進行數據包加密和認證。
拓撲:每個數據包的每次接收都使用32位尋址,理論上可連接數十億設備;針對一對一連接最優化,並支持星形拓撲的一對多連接;使用快速連接和斷開,數據可以在網狀拓撲內轉移而無需維持複雜的網狀網絡。

不知道你們作何感想,我反正是沒看懂,或者懶得看,在我的實際開發過程中,在很多節點上會有疑問,只要解決這些疑問,我的邏輯就通了,學起來和做起來就很容易了,當然在解決現有的需求或者問題之後如果有意願就要再深究了,以我們當前的藍牙開發爲例。我的邏輯是 發現 —— 連接 —— 讀取

第一個問題 :我的手機如何發現周邊的藍牙設備?
這個有現代生活常識的都有經驗,打開手機設備通過掃描來發現周邊藍牙設備,

第二個問題:手機設備是通過什麼掃描到周邊藍牙的呢?
通過一個廣播包,任何的藍牙設備都會具有一個廣播包,這是藍牙設置之間建立連接必須的一步,有的設備可能一直處於廣播的狀態,比如現在比較熱的小米手環2,有的設備是需要人爲參與的來開啓廣播,比如計步器需要手動搖動,小鋼炮音響需要開啓開關,綜合來看廣播這一步是藍牙設備彼此發現的最重要一步,從掃描到發現彼此這是不可省去的,所謂的人工干預我的理解就是爲了其他方面的優化比如降低電量等做的一個控制藍牙廣播的開關而已。

第三個問題:選擇某個設備建立了連接,如何獲取數據呢?
我們可以大致的分爲三步:第一步,我們要獲取藍牙設備的服務(service);第二步,在獲取到服務之後,我們要獲取到服務下的特徵(characteristics);第三部,通過對特徵的屬性判斷和UUID,來進行設置通知獲取數據,直接發指令進行讀取,或者寫入指令。當然在進行讀寫等操作的時候,要完成API需要實現的代理方法。

說到這裏呢,基本上關於在藍牙開發流程上的簡單邏輯就這些了,因人而異如果大家還有其他的問題,可以留言交流。接下來,我們通過實例的形式過一下功能。

首先我們要明確需求:我們要在列表頁面搜索到周邊的藍牙設備,然後選中列表中的一個設計進行點擊,手機和藍牙設備進行連接,讀取到設備中的各種服務和特徵。之後斷開,再次啓動 app,能夠自動連接上我們之前連接過的藍牙設備。

1:創建項目,在這裏就省略了。
2:在 ViewController 中創建列表界面

_tableView = [[UITableView alloc] initWithFrame:self.view.frame];
_tableView.backgroundView.backgroundColor = [UIColor whiteColor];
_tableView.delegate = self;
_tableView.dataSource = self;
[self.view addSubview:_tableView];

創建一個集合用來存放掃描到的藍牙外設

  _deviceArray = [[NSMutableArray alloc] init];

3:創建一個藍牙中心

 //創建一箇中心藍牙的管理器
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil]; //先使用主隊列進行開發

參數說明:
delegate : 有經驗的同學就不用過多解釋了,設置好代理,各種回調方法的使用
queue : 多線程隊列,如果傳入了隊列參數,中心藍牙會在該線程中處理和接收事件 如果爲nil的話,默認在主隊列裏面
options : 一系列的參數設置 :
|CBCentralManagerOptionShowPowerAlertKey | 表示的是在central manager初始化時,如果當前藍牙沒打開,是否彈出alert框 |

 |CBCentralManagerOptionRestoreIdentifierKey | 一個唯一的標示符,用來藍牙的恢復連接的。在後臺的長連接中可能會用到。
 就是說,如果藍牙程序進入後臺,程序會被掛起,可能由於memory pressure,程序被系統kill了,那麼代理方法就不會執行了。這時候可以使用State Preservation & Restoration,這樣程序會重新加載進入後臺。|

注意:這裏有兩個概念需要澄清下,中心藍牙(central)和 外設藍牙(Peripheral) ,分別是什麼概念呢?我的理解:在藍牙設備的連接中存在一個角色的定位問題,一個藍牙是即可以當中心藍牙也可以當外設藍牙,這取決於在需求實現上,誰來主導業務的走向,舉例,我們手機設備要鏈接計步器進行步數的收集和上傳,此時手機是要先確定自己的中心角色,然後發起掃描,發現周邊的藍牙設備時,周邊的藍牙設備都是以外設藍牙的角色存在,及時搜索到的是手機。

4:設置參數發起掃描

    /**
 serviceUUIDs : 包含一個或者多個服務UUID的集合,會根據服務的UUID來進行設備的掃描,
 options : 一系列的參數設置
 | CBCentralManagerScanOptionAllowDuplicatesKey       | |
 | CBCentralManagerScanOptionSolicitedServiceUUIDsKey | |
 */
 NSDictionary *option = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[_centralManager scanForPeripheralsWithServices:nil options:option];

5:在掃描到設置之後,會調用代理方法:

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

  /*
   在中心藍牙掃描到外設藍牙後調用該方法。
   該方法每次只返回一個藍牙外設的信息
   第一個參數:中心藍牙對象
   第二個參數:本次掃描到的藍牙外設
   第三個參數:藍牙外設中的額外信息,——藍牙外設的廣播包中的信息。
   第四個參數:代表信號強度的參數,RSSI:(要做詳細介紹)

   注意:掃描的藍牙設備有以下幾種情況:
     1:掃描的藍牙是無用的藍牙。
     2:掃描的藍牙是重複掃描到的藍牙。(存在一種可能就是重複掃描到的藍牙有變化,這種變化不是指藍牙外設攜帶的數據發生變化,是指藍牙外設本身的參數發生變化)

   所有針對上述的兩種情況,我們需要一段代碼進行邏輯上的處理:
     1:剔除無用的藍牙。
     2:替換到的舊信息藍牙外設,插入新的藍牙外設信息。
   */


  - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI
{
/*
 思考:所有的藍牙外設都必須要有 name 嗎?
 */
if (peripheral.name.length <= 0) {
    return;
}

//打印參數信息
NSLog(@"發現外部設備");
NSLog(@"接收到的廣播信息:%@",advertisementData);
NSLog(@"藍牙外設信息:設備identifier : %@ 設備名稱:%@ 信號強度:%@", peripheral.identifier,peripheral.name, RSSI);


if (_deviceArray.count <= 0) {

    NSDictionary *peripheralDic = [NSDictionary dictionaryWithObjectsAndKeys:peripheral,@"peripheral",RSSI,@"RSSI", nil];
    [_deviceArray addObject:peripheralDic];

}else{
    bool isExist = NO;
    for (int i = 0; i < _deviceArray.count ; i ++) {
        NSDictionary *peripheralDic = [_deviceArray objectAtIndex:i];
        CBPeripheral *peripheralFromArray = [peripheralDic objectForKey:@"peripheral"];
        if ([peripheralFromArray.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]) {
            isExist = YES;
            NSDictionary *newPeripheralDic = [NSDictionary dictionaryWithObjectsAndKeys:peripheral,@"peripheral",RSSI,@"RSSI", nil];
            [_deviceArray replaceObjectAtIndex:i withObject:newPeripheralDic];
        }
    }
    if (!isExist) {
        NSDictionary *newPeripheralDic = [NSDictionary dictionaryWithObjectsAndKeys:peripheral,@"peripheral",RSSI,@"RSSI", nil];
        [_deviceArray addObject:newPeripheralDic];
    }

}

[_tableView reloadData];

}

6:在搜索到的設備列表中,選擇其中的一個設備進行建立連接操作,

/**
 peripheral : 要進行連接的外設
 options :在鏈接過程中的一系列參數設置
 CBConnectPeripheralOptionNotifyOnConnectionKey :連接通知
 CBConnectPeripheralOptionNotifyOnDisconnectionKey : 斷開連接通知
 CBConnectPeripheralOptionNotifyOnNotificationKey :
 */

 [_centralManager connectPeripheral:peripheral options:nil];

//點擊某個cell,進行外設的連接
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{


NSDictionary *peripheralDic = (NSDictionary *)[_deviceArray objectAtIndex:indexPath.row];
CBPeripheral *peripheral = (CBPeripheral *)[peripheralDic objectForKey:@"peripheral"];

//連接某個藍牙外設
[_centralManager connectPeripheral:peripheral options:nil];
//設置藍牙外設的代理;
peripheral.delegate = self;
//停止中心藍牙的掃描動作
[_centralManager stopScan];
}

7:當中心藍牙設備和外設建立完成

/**
 中心藍牙和某個外設連接成功。
 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"和外設鏈接成功");
//設備連接成功,開始查找建立連接的藍牙外設的服務;此處要注意,在建立連接之後,是通過藍牙外設的對象去發現服務而非中心藍牙。
[peripheral discoverServices:nil];
}


/**
 中心藍牙和某個外設連接失敗。
 */
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error
{
NSLog(@"和外設鏈接失敗");
}

8:在第7步中已經發起了對服務的掃描
9:發現服務後,執行回調方法

/**
 在發現服務時,進行調用
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error
{
NSString *UUID = [peripheral.identifier UUIDString];
NSLog(@"在didDiscoverServices方法中,peripheral.identifier = %@",UUID);

//遍歷所提供的服務
for (CBService *service in peripheral.services) {
    CBUUID *serviceUUID = service.UUID;
    NSLog(@"serviceUUID = %@",[serviceUUID UUIDString]);

    /**
     如果我們知道要查詢的特性的 CBUUID,可以在第一個參數中傳入 CBUUID 的數組
     發現在服務下的特徵
     */
    [peripheral discoverCharacteristics:nil forService:service];
}
}

10:在發現服務之後,發現服務中的特徵
11:發現特徵後的回調

注意:在特徵這一層,特徵的讀寫變更等都是有相應的屬性,我們可以通過數據判斷之後,來進行數據的讀寫操作。

/**
 在發現服務中的特徵後,調用該方法

 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error
{
if (error) {
    NSLog(@"在檢索服務中的特徵時出錯");
    return;
}

NSLog(@"在 didDiscoverCharacteristicsForService 方法中遍歷服務 %@ 中的特徵",[service.UUID UUIDString]);
for (CBCharacteristic *characteristic in service.characteristics) {

    NSLog(@"特徵 UUID = %@",[characteristic.UUID UUIDString]);

    /**
     在這裏要注意,很多同學看到是枚舉類型就是用的 == ,這是不對的,
     學習鏈接:http://lecason.com/2015/08/19/Objective-C-Find-Conbine/
     */

    if (characteristic.properties & CBCharacteristicPropertyExtendedProperties) {
        NSLog(@"具備可拓展特性。");
    }
    if (characteristic.properties & CBCharacteristicPropertyRead) {
        NSLog(@"具備可讀特性,即可以讀取特徵的 value 值");
        //對該特徵進行讀取
        [peripheral readValueForCharacteristic:characteristic];
    }
    if (characteristic.properties & CBCharacteristicPropertyWrite) {
        NSLog(@"具備可寫特徵,會有響應");

        NSString *stringD = @"要寫入的數據";
        [peripheral writeValue:[ViewController stringToByte:stringD] forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
    }
    if (characteristic.properties & CBCharacteristicPropertyNotify) {
        NSLog(@"具備通知特性,無響應");
        //對該特徵設置通知的監聽
        [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        [peripheral readValueForCharacteristic:characteristic];
    }
    if (characteristic.properties & CBCharacteristicPropertyIndicate) {
        NSLog(@"具備指示特性");
    }
    if (characteristic.properties & CBCharacteristicPropertyBroadcast) {
        NSLog(@"具備廣播特性");
        //對該特徵設置通知的監聽
        [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        [peripheral readValueForCharacteristic:characteristic];
    }
    if (characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) {
        NSLog(@"具備可寫,又不會有響應的特性");
    }
}
}

12:讀取特徵中的數據回調方法

/**
 獲取特徵的值後調用的方法
 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
NSLog(@"didUpdateValueForCharacteristic : characteristic.uuid = %@",[characteristic.UUID UUIDString]);
if (error) {
    NSLog(@"讀取特徵失敗!");
}
NSData *data = characteristic.value;
if (data.length <= 0) {
    return;
}

Byte *plainTextByte = (Byte *)[data bytes];
NSString *hexStr=@"";
for(int i=0;i<[data length];i++)
{
    NSString *newHexStr = [NSString stringWithFormat:@"%x",plainTextByte[i]&0xff];///16進制數
    if([newHexStr length]==1)
        hexStr = [NSString stringWithFormat:@"%@0%@",hexStr,newHexStr];
    else
        hexStr = [NSString stringWithFormat:@"%@%@",hexStr,newHexStr];
}

//    NSString *info = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(@"hexStr = %@",hexStr);

}

13:寫入特徵數據回調方法

  /**
  在向藍牙設備中寫完指令後,調用的回調方法。
 */
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
if (error) {
    NSLog(@"didWriteValueForCharacteristic,在寫入指令時發生錯誤");
    return;
}
NSLog(@"對藍牙的指令寫入成功!");

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章