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

背景說明:

由于公司的業務需要在之前積攢了一些關于藍牙開發經驗,在此做過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(@"對藍牙的指令寫入成功!");

}

、

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容