iOS 藍牙開發(fā)和注意點

前言

  • 藍牙傳輸所用的框架是<CoreBluetooth/CoreBluetooth.h>
  • 藍牙連接需要中心管理者和外部設備萧朝,我們所做的開發(fā)基本是圍繞中心管理來的天通;
  • 藍牙設備發(fā)過來的每個數(shù)據(jù)包督惰,為了保證數(shù)據(jù)在傳輸?shù)臅r候沒有丟失,一般需要包頭旧蛾,包尾张遭,校驗和
  • 有很多藍牙協(xié)議很復雜邓萨,需要把數(shù)據(jù)轉化成二進制進行轉化解析,對于高字節(jié)菊卷,低字節(jié)缔恳,小端模式,大端模式洁闰,符號位歉甚,位運算這些基本概念需要了解清楚

1.關于Mac地址的獲取

自iOS7之后,蘋果不支持獲取Mac地址扑眉,只能用UUID來標識設備纸泄,要注意的是同一個設備在不同手機上顯示的UUID不相同,但有的設備可以通過 “180A”這個服務來發(fā)現(xiàn)特征腰素,再來讀取 “2A23”這個特征值聘裁,可以獲得Mac地址。如果你的藍牙設備不支持這樣獲取,你可以跟硬件工程師溝通弓千,來獲得Mac地址衡便,添加一個獲取地址命令或者增加一個含地址的特征值都可以很容易的獲取。上面獲取地址的前提都是需要先建立連接洋访,如果一定要在掃描的時候獲得Mac地址镣陕,讓硬件工程師把數(shù)據(jù)寫入廣播包里,看是否可行姻政;

2.藍牙連接流程

如果你不是新手呆抑,又不想浪費時間,請直接看第三點 注意點扶歪,核心部分

  • 建立中心設備管理者
  • 掃描外設
  • 連接外設
  • 掃描外設中的服務
  • 掃描外設中的特征
  • 訂閱或讀取特征值
  • 獲取外設中的數(shù)據(jù)

建立中心設備管理者

 // 創(chuàng)建之后會馬上檢查藍牙的狀態(tài),nil默認為主線程
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]

藍牙線程沒必要去開異步線程理肺,在主線程消耗不了什么性能

掃描外設

// 藍牙狀態(tài)發(fā)生改變,這個方法一定要實現(xiàn)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    // 藍牙狀態(tài)可用
    if (central.state == CBCentralManagerStatePoweredOn) {
        
        // 如果藍牙支持后臺模式善镰,一定要指定服務妹萨,否則在后臺斷開連接不上,如果不支持炫欺,可設為nil, option里的CBCentralManagerScanOptionAllowDuplicatesKey默認為NO, 如果設置為YES,允許搜索到重名乎完,會很耗電
        [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:nil];
    }
}

連接外設

 /**
 * 發(fā)現(xiàn)設備
 * @param peripheral 設備
 * @param advertisementData 廣播內(nèi)容
 * @param RSSI 信號強度
 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
    // 判斷是否是你需要連接的設備
    if ([peripheral.name isEqualToString:kPeripheralName]) {
        peripheral.delegate = self;
        // 一定要記得把外設保存起來
        self.selectedPeripheral = peripheral;
        // 開始連接設備
        [self.centralManager connectPeripheral:self.selectedPeripheral options:nil];
    }
}

掃描外設中的服務

/**
 * 已經(jīng)連接上設備
 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    // 停止掃描
    [self.centralManager stopScan];
    // 發(fā)現(xiàn)服務
    [self.selectedPeripheral discoverServices:nil];
}

掃描外設中的特征

/**
 * 已經(jīng)發(fā)現(xiàn)服務
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    for (CBService *service in peripheral.services) {
        if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
            // 根據(jù)你要的那個服務去發(fā)現(xiàn)特性
            [self.selectedPeripheral discoverCharacteristics:nil forService:service];
        }
        
        // 這里我是根據(jù) 180A 用來獲取Mac地址,沒什么實際作用品洛,可刪掉
        if ([service.UUID isEqual:[CBUUID UUIDWithString:@"180A"]]) {
            [self.selectedPeripheral discoverCharacteristics:nil forService:service];
        }
    }
}

訂閱或讀取特征值

/**
 * 已經(jīng)發(fā)現(xiàn)特性
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    for (CBCharacteristic *characteristic in service.characteristics) {
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A23"]]) {
            // 這里是讀取Mac地址树姨, 可不要摩桶, 數(shù)據(jù)固定, 用readValueForCharacteristic帽揪, 不用setNotifyValue:setNotifyValue
            [self.selectedPeripheral readValueForCharacteristic:characteristic];
        }
        
        
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
            // 訂閱特性硝清,當數(shù)據(jù)頻繁改變時,一般用它转晰, 不用readValueForCharacteristic
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
            
            // 獲取電池電量
            unsigned char send[4] = {0x5d, 0x08, 0x01, 0x3b};
            NSData *sendData = [NSData dataWithBytes:send length:4];
            
            // 這里的type類型有兩種 CBCharacteristicWriteWithResponse CBCharacteristicWriteWithoutResponse芦拿,它的屬性枚舉可以組合
            [self.selectedPeripheral writeValue:sendData forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
            
            /*
             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
             };
             */
            
            NSLog(@"%@",characteristic);
            // 打印結果為 <CBCharacteristic: 0x1702a2a00, UUID = FFF6, properties = 0x16, value = (null), notifying = NO>
            
            //  我的結果 為 0x16  (0x08 & 0x16)結果不成立, (0x04 & 0x16)結果成立查邢,那寫入類型就是 CBCharacteristicPropertyWriteWithoutResponse
        }
    }
}

獲取外設中的數(shù)據(jù)

/**
 * 數(shù)據(jù)更新的回調(diào)
 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    // 這里收到的數(shù)據(jù)都是16進制蔗崎,有兩種轉換,一種就直接轉字符串扰藕,另一種是轉byte數(shù)組缓苛,看用哪種方便
    
    // 直接轉字符串
    NSString *orStr = characteristic.value.description;
    NSString *str = [orStr substringWithRange:NSMakeRange(1, orStr.length - 2)];
    NSString *dataStr = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"dataStr = %@",dataStr);
    
    // 轉Byte數(shù)組
    Byte *byte = (Byte *)characteristic.value.bytes;
    
    //_______________________________________________________________________________________________________________
    // 解析你的協(xié)議,附幾個解協(xié)議或許能用到的函數(shù)
}

設備連接斷開

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    // 讓它自動重連
    [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:nil];
}

這是系統(tǒng)代理方法邓深,如果要主動斷開需要調(diào)用 - (void)cancelPeripheralConnection:(CBPeripheral *)peripheral; 這個方法

寫入數(shù)據(jù)成功的回調(diào)

- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
        // 讀取數(shù)據(jù)
    [self.selectedPeripheral readValueForCharacteristic:characteristic];
}

如果類型是CBCharacteristicWriteWithoutResponse未桥,不會走這個方法;

3.注意點庐完,核心部分钢属,請仔細看

  1. 做藍牙前一定要去商城下個LightBlue,一個設備有很多服務,服務中又有很多特性门躯,特性中又分讀的淆党,寫的等,有了LightBlue讶凉,你可以很快的找到你需要的特性染乌;

    LightBlue截圖

    從上圖中我們可以清晰的看到每個服務中又多少個特性,特性的屬性Read懂讯、Write荷憋、Write Without Response、Notify等也標明的很清楚褐望,

  2. 一般的藍牙都要支持重連和后臺運行勒庄,如果掃描設備的時候,用這個方法- (void)scanForPeripheralsWithServices:options:沒有指定特定的服務,而是用nil代替瘫里,設備在后臺斷開的時候是不會重連的实蔽;

  3. 藍牙是可以同時連接多個外部設備

  4. 關于readValueForCharacteristicsetNotifyValue:forCharacteristic: 的區(qū)別, readValueForCharacteristic適合用來讀取數(shù)據(jù)不怎么更新的特征值谨读, 如果獲取的數(shù)據(jù)是經(jīng)常更新的局装,那就 一定要用setNotifyValue:forCharacteristic:來訂閱這個特征;

  5. 當我們寫入命令時writeValue:forCharacteristic:type:,這個type類型到時是用CBCharacteristicWriteWithResponse還是用CBCharacteristicWriteWithoutResponse會有疑惑,先看一下特性屬性的枚舉铐尚,它們是可以組合的

        /*
          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
          };
    

再來看看我打印的兩個特征值拨脉,第一個是獲取Mac地址的特性,另一個是獲取數(shù)據(jù)的特性
<CBCharacteristic: 0x1700b8ae0, UUID = System ID, properties = 0x2, value = (null), notifying = NO>
<CBCharacteristic: 0x1702a2a00, UUID = FFF6, properties = 0x16, value = (null), notifying = NO>
第一個0x2對應只可讀宣增, 第二個 (0x16 & 0x08)不成立玫膀,(0x16 & 0x04)成立,所以用CBCharacteristicWriteWithoutResponse统舀,而且這個特征值還可讀匆骗,可以通知

  1. 代理方法- (void)centralManagerDidUpdateState:(CBCentralManager *)central;一定要調(diào)用,否則會報錯誉简,這個方法只要設置中心設備的代理之后,就一定會走盟广,我們最開始的掃描外設應放在這個方法里闷串;

  2. 對于是否要單獨創(chuàng)建一個工具類來獲取藍牙數(shù)據(jù),如果只是一個界面需要用到藍牙數(shù)據(jù)筋量,我覺得完全沒必要烹吵,如果是多個界面的話,最好還是創(chuàng)建一個工具類桨武。

  3. 如果藍牙支持要支持后臺模式肋拔,只需要去把藍牙后臺模式打開

    后臺運行藍牙

    記住只要勾選Uses Bluetooth LE accessories就行了,別勾選Acts As a Bluetooth LE accessory,除非你把你的手機當做外部設備使用呀酸;

  4. 如果藍牙設備是是手環(huán)或者手表凉蜂,一般支持ANCS協(xié)議,使得可以收到蘋果手機的來電性誉、短信及各種應用的通知信息窿吩,但這是要求配對的,配對后的設備错览,只要你手機開著藍牙纫雁,它都會自動連接,你會發(fā)現(xiàn)怎么都搜索不到設備倾哺,你每次連接設備前需要用retrieveConnectedPeripheralsWithServices方法來查看設備是否已經(jīng)連接上轧邪;
    推薦的連接流程

    ReconnectingToAPeripheral_2x.png

簡單又詳細的Demo地址!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市羞海,隨后出現(xiàn)的幾起案子忌愚,更是在濱河造成了極大的恐慌,老刑警劉巖扣猫,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菜循,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機癌幕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門衙耕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人勺远,你說我怎么就攤上這事橙喘。” “怎么了胶逢?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵厅瞎,是天一觀的道長。 經(jīng)常有香客問我初坠,道長和簸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任碟刺,我火速辦了婚禮锁保,結果婚禮上,老公的妹妹穿的比我還像新娘半沽。我一直安慰自己爽柒,他們只是感情好,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布者填。 她就那樣靜靜地躺著浩村,像睡著了一般。 火紅的嫁衣襯著肌膚如雪占哟。 梳的紋絲不亂的頭發(fā)上心墅,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機與錄音重挑,去河邊找鬼嗓化。 笑死,一個胖子當著我的面吹牛谬哀,可吹牛的內(nèi)容都是我干的刺覆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼史煎,長吁一口氣:“原來是場噩夢啊……” “哼谦屑!你這毒婦竟也來了?” 一聲冷哼從身側響起篇梭,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤氢橙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后恬偷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悍手,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了坦康。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竣付。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖滞欠,靈堂內(nèi)的尸體忽然破棺而出古胆,到底是詐尸還是另有隱情,我是刑警寧澤筛璧,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布逸绎,位于F島的核電站,受9級特大地震影響夭谤,放射性物質發(fā)生泄漏棺牧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一朗儒、第九天 我趴在偏房一處隱蔽的房頂上張望陨帆。 院中可真熱鬧,春花似錦采蚀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至亥鸠,卻和暖如春妆够,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背负蚊。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工神妹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人家妆。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓鸵荠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親伤极。 傳聞我的和親對象是個殘疾皇子蛹找,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容