iOS 藍(lán)牙開發(fā) --- 本機作為中心設(shè)備角色開發(fā)


顧名思義怒竿,就是本機搜索其他藍(lán)牙設(shè)備進行讀寫砍鸠。
CBCentralManager對象表示中心設(shè)備,用于管理已發(fā)現(xiàn)和已連接的外圍設(shè)備耕驰。包括發(fā)現(xiàn)外圍設(shè)備爷辱、發(fā)起連接、斷開連接朦肘。
CBPeripheral對象表示外圍設(shè)備饭弓。我們要獲取數(shù)據(jù)就需要處理CBPeripheral對象中的服務(wù)(CBService)和特征(CBCharacteristic)

中心設(shè)備角色
中心設(shè)備角色

CBCentralManagerDelegate協(xié)議部分

1. 創(chuàng)建中心設(shè)備管理對象

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// queue 可以為nil,就在主線程中工作
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:queue options:nil];

創(chuàng)建對象后會調(diào)用下面的代理方法媒抠,可以在其中檢查藍(lán)牙的狀態(tài)

/**
 創(chuàng)建好中心設(shè)備對象后的回調(diào)弟断,確定設(shè)備是否支持藍(lán)牙以及是否可用

 *  @constant CBManagerStateUnknown      未知狀態(tài),立即刷新.
 *  @constant CBManagerStateResetting    重置藍(lán)牙趴生,鏈接暫時斷開阀趴,立即刷新.
 *  @constant CBManagerStateUnsupported  設(shè)備不支持藍(lán)牙4.0 .
 *  @constant CBManagerStateUnauthorized 應(yīng)用尚未被授權(quán).
 *  @constant CBManagerStatePoweredOff   藍(lán)牙處于關(guān)閉狀態(tài).
 *  @constant CBManagerStatePoweredOn    藍(lán)牙處于開啟狀態(tài).
 */
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    // NS_ENUM形式的枚舉昏翰,使用 == 判斷
    if (CBManagerStatePoweredOn == central.state) {
        // do something 可以開始掃描什么的
        [self.centralManager scanForPeripheralsWithServices:nil options:nil]
    }
}

2. 搜尋外圍設(shè)備

// 搜索給定UUID服務(wù)的外圍設(shè)備,nil表示搜索所有的
[self.centralManager scanForPeripheralsWithServices:nil options:nil];

options是一個字典刘急,指定兩個選項

  • CBCentralManagerScanOptionAllowDuplicatesKey:默認(rèn)為NO(需要用NSNumber包裝)棚菊,表示不會重復(fù)掃描已發(fā)現(xiàn)藍(lán)牙設(shè)備,否則會增加耗電叔汁。但是筆者在實現(xiàn)時沒發(fā)現(xiàn)有什么不同统求,都在不停掃。o(╯□╰) 關(guān)閉藍(lán)牙重新連接就滿足要求了 o(╯□╰)o
  • CBCentralManagerScanOptionSolicitedServiceUUIDsKey:value是一個包含指定UUID服務(wù)的數(shù)組据块。

當(dāng)掃描到一個外圍設(shè)備時码邻,觸發(fā)代理。提升peripheral為屬性以便之后繼續(xù)使用另假。

/**
 搜尋到外圍設(shè)備時調(diào)用冒滩,發(fā)現(xiàn)一個調(diào)用一次
 
 @param advertisementData 掃描時收到的廣告包
 @param RSSI    信號強度,一個負(fù)數(shù)
 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {

    switch (peripheral.state) {
        case CBPeripheralStateDisconnected:
            NSLog(@"鏈接斷開");
            break;
        case CBPeripheralStateConnecting:
            NSLog(@"鏈接ing");
            break;
        case CBPeripheralStateConnected:
            NSLog(@"鏈接建立");
            // 找到想要鏈接的設(shè)備時浪谴,應(yīng)該終止搜索以節(jié)省電量。
            [self.centralManager stopScan];
            break;
        case CBPeripheralStateDisconnecting:
            NSLog(@"鏈接正在斷開");
            break;
        default:
            break;
    }
    // 連接設(shè)備
    [self.centralManager connectPeripheral:peripheral options:nil];
}

Tip

  • 除非特殊情況(如需要根據(jù)信號強度來連接設(shè)備等)不對已發(fā)現(xiàn)設(shè)備重復(fù)掃描因苹。
// 因為該key默認(rèn)是NO苟耻,所以可以將 options 設(shè)為 nil
[self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey: [NSNumber numberWithBool:NO]}];
  • 由于中心設(shè)備開啟掃描后不會自動停止,所以蘋果推薦在匹配到合適設(shè)備時主動停止扶檐⌒渍龋可以通過CBPeripheral的屬性name或者identifier(NSUUID *)來確定我們需要的設(shè)備。一旦確定就可以調(diào)用[self.centralManager stopScan]終止掃描款筑。

3. 連接外圍設(shè)備

// 連接設(shè)備
[self.centralManager connectPeripheral:peripheral options:nil];

遵循協(xié)議CBPeripheralDelegate智蝠,實現(xiàn)對數(shù)據(jù)的讀寫

/**
 鏈接外圍設(shè)備成功時調(diào)用,此時可以開始對外圍設(shè)備進行讀寫了
 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    // 設(shè)定外圍設(shè)備的代理  
    peripheral.delegate = self;
    [peripheral discoverServices:nil];
}

CBPeripheralDelegate協(xié)議部分

4. 獲取外圍設(shè)備可用服務(wù)

// nil 會搜索所有服務(wù)
[peripheral discoverServices:nil]

成功發(fā)現(xiàn)服務(wù)時奈梳,調(diào)用代理

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    // 所有可用服務(wù)都包含在其中
    for (CBService *service in peripheral.services) {
        // 180A 表示 Device infomation
        if ([service.UUID.UUIDString isEqualToString:@"180A"]) {
            [peripheral discoverCharacteristics:nil forService:service];
        }
    }
}

5. 獲取服務(wù)的特征

// 獲取指定服務(wù)的特征, nil 獲取所有
[peripheral discoverCharacteristics:nil forService:service]

獲取成功后杈湾,調(diào)用代理

/**
 發(fā)現(xiàn)特征時調(diào)用,調(diào)用一次
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    // 包含所有特征
    for (CBCharacteristic *characteristic in service.characteristics) {
        // 讀取特征值
    }
}

Tip:因為一個外圍設(shè)備有多個服務(wù)攘须,一個服務(wù)又有多個特征漆撞,而一般我們只需要一部分服務(wù)或特征。所以我們在獲取服務(wù)或特征時于宙,通過UUID僅得到我們需要的即可浮驳。

// nil 會搜索所有服務(wù),但是耗時耗能捞魁,所以盡量傳入服務(wù)uuid
// 獲取服務(wù)
[peripheral discoverServices:數(shù)組<CBUUID *>];

// 獲取指定服務(wù)的特征
[peripheral discoverCharacteristics:數(shù)組<CBUUID *> forService:service];

6. 讀寫操作

特征中就含有我們需要讀寫的數(shù)據(jù)至会。注意不是所有特征值都可讀寫,依賴于外圍設(shè)備對特征的配置谱俭。通過CBCharacteristic的屬性properties是否包含以下枚舉值來判斷是否可讀寫奉件。這是一個CBCharacteristicProperties類型的枚舉宵蛀,NS_OPTIONS表示枚舉值可以組合。

typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
    // 特征的值通過特征的描述符被廣播出去
    CBCharacteristicPropertyBroadcast = 0x01,
    // 可讀
    CBCharacteristicPropertyRead = 0x02,    
    // 可寫瓶蚂,外圍設(shè)備不會回傳寫入是否成功
    CBCharacteristicPropertyWriteWithoutResponse = 0x04,
    // 可寫糖埋,外圍設(shè)備告知中心設(shè)備寫入是否成功,然后CBPeripheral調(diào)用相應(yīng)代理
    CBCharacteristicPropertyWrite = 0x08,
    // 可訂閱,中心設(shè)備是否收到通知不會告知外圍設(shè)備
    CBCharacteristicPropertyNotify = 0x10,
    // 可訂閱,中心設(shè)備是否收到通知會告知外圍設(shè)備窃这,然后CBCentral調(diào)用相應(yīng)代理
    CBCharacteristicPropertyIndicate = 0x20,
    // 寫入不成功時不會回傳錯誤瞳别??
    CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,
    // 可以在特征的屬性描述符中添加額外的特征屬性
    CBCharacteristicPropertyExtendedProperties = 0x80,
    // 只能訂閱受信任設(shè)備廣播的特征的值杭攻,不會告知外圍設(shè)備是否收到通知
    CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0) = 0x100,
    // 只能訂閱受信任設(shè)備廣播的特征的值祟敛,會告知外圍設(shè)備是否收到通知
    CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0) = 0x200
};
讀操作

通過直接讀取或訂閱的方式獲取特征的值。

  • 直接讀取
// NS_OPTIONS形式的枚舉兆解,使用 & 判斷是否包含
if (characteristic.properties & CBCharacteristicPropertyRead) {
    [peripheral readValueForCharacteristic:characteristic];
}
  • 通過訂閱的方式(推薦)馆铁,類似觀察者
      一次值讀取就是一次交互,就要使用設(shè)備信號锅睛。如果一個特征的值可能變化可能不變時埠巨,直接讀取就需要不停調(diào)用readValueForCharacteristic:方法。當(dāng)一段時間內(nèi)數(shù)據(jù)保持不變现拒,使用readValueForCharacteristic:方法就會造成不必要的數(shù)據(jù)交互從而消耗電池辣垒。
      如何避免呢?最好就是當(dāng)特征的值變化時印蔬,外圍設(shè)備通知相應(yīng)的中心設(shè)備勋桶,中心設(shè)備收到通知在去讀取值。
      如何做到侥猬?使用訂閱例驹。
// 動態(tài)變化值使用通知訂閱更高效
// 外圍設(shè)備特征的值改變時,發(fā)出通知退唠,訂閱了該特征值的中心設(shè)備收到通知后更新值
// 不是所有特征值都可訂閱鹃锈,最好判斷一下
if (characteristic.properties & CBCharacteristicPropertyNotify) {
  [peripheral setNotifyValue:YES forCharacteristic:characteristic];
}

啟用訂閱后,調(diào)用代理

/**
 外圍設(shè)備收到啟用或停止通知請求后會調(diào)用
 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {

    
}

讀取或訂閱的特征的值改變后瞧预,調(diào)用代理

/**
 特征值改變時調(diào)用, 兩種讀取方式都要走該代理
 @param error 讀取特征值失敗時
 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    // 得到特征值
    NSData *data = characteristic.value;
    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
寫操作

寫入數(shù)據(jù)時以二進制數(shù)據(jù)操作仪召,需要使用NSData包裝。

一個特征是否可寫入及寫入方式松蒜,可通過properties是否包含CBCharacteristicPropertyWriteWithoutResponseCBCharacteristicPropertyWrite確定扔茅。

if (characteristic.properties & CBCharacteristicPropertyWrite) {
    [peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
    }

CBCharacteristicWriteWithResponseCBCharacteristicWriteWithoutResponse,前者寫入數(shù)據(jù)后調(diào)用下面的代理方法秸苗,告知我們寫入是否成功召娜。后者不會調(diào)用。

/**
 使用 CBCharacteristicWriteWithResponse 寫入方式才調(diào)用該代理方法
 寫入失敗惊楼,則會通過error提示錯誤信息
 */
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {

}

7. 斷開連接

由于藍(lán)牙廣播或掃描時玖瘸,使用的是設(shè)備信號秸讹。而設(shè)備信號也用于WIFI等其他功能。同時信號強度又會影響電池使用雅倒。為減小影響璃诀,需要最小化對信號的使用。

當(dāng)中心設(shè)備訂閱的特征不再發(fā)送通知后(CBPeripheral對象的isNotifying屬性)或者 我們已經(jīng)拿到所有需要的數(shù)據(jù)后蔑匣,應(yīng)該及時取消訂閱然后斷開和外圍設(shè)備的連接劣欢。

// 為每一個訂閱過的特征 設(shè)置 值 為 NO
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
// 僅斷開當(dāng)前app與外圍設(shè)備的連接,不會關(guān)閉藍(lán)牙裁良。因為可能有其他app也在使用藍(lán)牙
[self.centralManager cancelPeripheralConnection:self.peripheral];

斷開連接后凿将,調(diào)用代理

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {

    NSLog(@"disconnect error: %@", error.userInfo);
    // 異常斷開,可以做重連
}

8. 重新連接

有三種重新獲取外圍設(shè)備的方式:

  • 所需外圍設(shè)備僅和本地app斷連价脾,但并未和中心設(shè)備斷開連接牧抵,則應(yīng)使用服務(wù)(CBUUID *)嘗試獲取CBPeripheral。成功 array 就不為空侨把,從中選擇需要的 peripheral犀变。否則嘗試后面兩種方法。
NSArray *array = [central retrieveConnectedPeripheralsWithServices:數(shù)組<CBUUID *>];
  • 本機保存(NSUserDefault等)著連接過的外圍設(shè)備的(identifier(NSUUID*)秋柄,通過它嘗試獲取CBPeripheral获枝。成功 array 就不為空,從中選擇需要的 peripheral华匾。否則嘗試重新掃描。
NSArray *array = [central retrievePeripheralsWithIdentifiers:數(shù)組<NSUUID *>];
  • 通過以上方式都未能重新連接机隙,就只有重新掃描
    scanForPeripheralsWithServices: options: 之后調(diào)用代理
    centralManager: didDiscoverPeripheral: advertisementData: RSSI: 就能獲取掃描到的peripheral

通過以上三種方式獲取到需要的peripheral后仍然使用connectPeripheral: options:請求重新連接蜘拉,最后調(diào)用相應(yīng)代理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末有鹿,一起剝皮案震驚了整個濱河市旭旭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌葱跋,老刑警劉巖持寄,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異娱俺,居然都是意外死亡稍味,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門荠卷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來模庐,“玉大人,你說我怎么就攤上這事油宜〉嗉睿” “怎么了怜姿?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疼燥。 經(jīng)常有香客問我沧卢,道長,這世上最難降的妖魔是什么醉者? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任但狭,我火速辦了婚禮,結(jié)果婚禮上湃交,老公的妹妹穿的比我還像新娘熟空。我一直安慰自己,他們只是感情好搞莺,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布息罗。 她就那樣靜靜地躺著,像睡著了一般才沧。 火紅的嫁衣襯著肌膚如雪迈喉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天温圆,我揣著相機與錄音挨摸,去河邊找鬼。 笑死岁歉,一個胖子當(dāng)著我的面吹牛得运,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锅移,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼熔掺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了非剃?” 一聲冷哼從身側(cè)響起置逻,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎备绽,沒想到半個月后券坞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡肺素,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年恨锚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倍靡。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡眠冈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜗顽,我是刑警寧澤布卡,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站雇盖,受9級特大地震影響忿等,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜崔挖,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一贸街、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狸相,春花似錦薛匪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瘸右,卻和暖如春娇跟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背太颤。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工苞俘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人龄章。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓吃谣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親做裙。 傳聞我的和親對象是個殘疾皇子岗憋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

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

  • 備注:下面說到的內(nèi)容都基于藍(lán)牙4.0標(biāo)準(zhǔn)以上,主要以實踐為主菇用。 ~ CoreBluetooth.framework...
    未_漆小七閱讀 1,624評論 1 8
  • 這里我們具體說明一下中心模式的應(yīng)用場景澜驮。主設(shè)備(手機去掃描連接外設(shè)陷揪,發(fā)現(xiàn)外設(shè)服務(wù)和屬性惋鸥,操作服務(wù)和屬性的應(yīng)用。一般...
    丶逝水流年閱讀 2,267評論 3 4
  • ? 倪萍說:“我一生最大的錯誤就是放棄了形象”悍缠!現(xiàn)在離婚率有增無減卦绣,80%的原因:男人在外優(yōu)秀,女人在家生銹飞蚓。有的...
    時尚朵以涇陽店閱讀 626評論 0 0
  • 連續(xù)20多天的外出培訓(xùn)后滤港,感覺特別累,特別想調(diào)整一下疲憊的身體,但教師進修學(xué)校的嚴(yán)格要求溅漾,和強烈的學(xué)習(xí)愿望促使我一...
    月亮彎彎1閱讀 329評論 0 1
  • 韓愈說,“術(shù)業(yè)有專攻”暮胧,指的是每個人都有自己的優(yōu)勢和不足锐借,要揚長避短,集中精力發(fā)揮個人優(yōu)勢去學(xué)習(xí)往衷,日積月累便會取得...
    i茶桑閱讀 1,503評論 0 1