iOS藍(lán)牙開發(fā)(附LightBlue參考指南)

公司的一個項目提了一些需求拯爽, 其中有一項是需要集成藍(lán)牙功能蔫磨。上一次做藍(lán)牙功能已經(jīng)是剛?cè)胄械臅r候,于是乘著周末休息的把藍(lán)牙的知識復(fù)習(xí)復(fù)習(xí)碰凶, 也給大家簡單說明一下藍(lán)牙的掃描和連接缭受。

1胁澳、藍(lán)牙的集中連接方式
2、iOS藍(lán)牙開發(fā)的關(guān)鍵詞
3米者、CoreBluetooth框架
4韭畸、代碼實現(xiàn)

一、iOS設(shè)備藍(lán)牙連接主要有一下幾種實現(xiàn)方式:

①.參加蘋果的(MFI)計劃, 也就是需要得到蘋果的認(rèn)證, 費用高, 有這方面需求的可以自己去了解蔓搞。

②.CoreBluetooth框架. 只支持4.0的藍(lán)牙設(shè)備, 這是我們iOS從業(yè)人員使用最多的一種方法胰丁。
③.GameKit框架. 這個框架只用用于iOS設(shè)備之間的藍(lán)牙連接通訊, 并不符合我們的需求, 沒有仔細(xì)研究.

④.私有API(BluetoothManager框架). 公司之前的項目有用過這個,最后項目是用的企業(yè)包托管在蒲公英上喂分,不過新項目并不準(zhǔn)備使用私有庫锦庸,略過。

⑤.越獄. 如果你做越獄包得話,這個就隨意了, 想怎么用怎么用蒲祈。

二甘萧、iOS藍(lán)牙開發(fā)的關(guān)鍵詞

中心設(shè)備(中心設(shè)備):就是用來掃描周圍藍(lán)牙硬件的設(shè)備,比如通過你手機(jī)的藍(lán)牙來掃描并連接智能手環(huán)梆掸,這時候你的手機(jī)就是中心設(shè)備扬卷。

外設(shè)(Peripheral):被掃描的設(shè)備。比如當(dāng)你用手機(jī)的藍(lán)牙掃描連接智能手環(huán)的時候酸钦,智能手環(huán)就是外設(shè)怪得。

服務(wù)(services):外設(shè)廣播和運行的時候會有服務(wù),可以理解成一個功能模塊卑硫,中心設(shè)備可以讀取服務(wù)徒恋。一個外設(shè)可以有多個服務(wù)。

特征(characteristic):在服務(wù)中的一個單位欢伏,一個服務(wù)可以有多個特征入挣,特征會有一個value,一般讀寫的數(shù)據(jù)就是這個value颜懊。

UUID :UUID在這里有多種意思财岔。設(shè)備自身有硬件的UUID,不同的中心設(shè)備連接到同一個外設(shè)會顯示不同的UUID河爹,此外設(shè)發(fā)送的每個服務(wù)也都有自己的UUID,每個服務(wù)中的特征也有自己所屬的UUID桐款。

LightBlue對照表 version:2.6.4

下面是一張iOS藍(lán)牙的架構(gòu)圖咸这,可以作為參照。

iOS藍(lán)牙CS圖.png

三魔眨、CoreBluetooth框架

藍(lán)牙開發(fā)層次圖

如上圖所示媳维,iOS中的藍(lán)牙開發(fā)框架CoreBluetooth處在藍(lán)牙低功耗協(xié)議棧的上面酿雪,我們開發(fā)的時候只是使用CoreBluetooth這個框架,通過CoreBluetooth可以輕松實現(xiàn)外設(shè)或中心設(shè)備的開發(fā)侄刽。

CoreBluetooth可以分為兩大模塊指黎,中心設(shè)備central,外設(shè)peripheral州丹,它們倆各有自己的一套API供我們使用醋安。


中心設(shè)備和外設(shè)使用

上圖左邊的就是中心設(shè)備的開發(fā)類,我們平時是使用CBCentralManager來進(jìn)行相關(guān)操作墓毒。

CBCentralManager: 藍(lán)牙中心設(shè)備管理類吓揪,用來統(tǒng)一調(diào)度中心設(shè)備的開發(fā)
CBPeripheral :藍(lán)牙外設(shè),例如藍(lán)牙手環(huán)所计、心率監(jiān)測儀柠辞。
CBService :藍(lán)牙外設(shè)的服務(wù),可以有0個或者多個服務(wù)主胧。

CBCharacteristic :服務(wù)中的特征叭首,每一個藍(lán)牙服務(wù)中可以有0個或多個特征,特征中包含數(shù)據(jù)信息踪栋。
CBUUID:可以理解為服務(wù)或特征的身份證焙格,可以用來選擇需要的服務(wù)和特征。
右邊是外設(shè)開發(fā)相關(guān)類己英,一般是圍繞著CBPeripheralManager來進(jìn)行編碼间螟。

右邊是外設(shè)開發(fā)相關(guān)類,一般是圍繞著CBPeripheralManager來進(jìn)行編碼损肛。

CBPeripheralManager: 藍(lán)牙外設(shè)開發(fā)時使用厢破,用來開發(fā)藍(lán)牙外設(shè)的中心管理類。
CBCentral:藍(lán)牙中心設(shè)備治拿,例如用來連接藍(lán)牙手環(huán)的手機(jī)摩泪。
CBMutableService:外設(shè)開發(fā)的時候可以添加多個服務(wù),所有這里用CBMutableService來創(chuàng)建添加服務(wù)劫谅。
CBMutableCharacteristic:每個服務(wù)中可以有多個特征见坑,外設(shè)開發(fā)給服務(wù)添加特征的時候使用這個類。
CBATTRequest:讀或者寫請求捏检。它的實例對象有一個value屬性荞驴,用來裝載外設(shè)進(jìn)行藍(lán)牙讀取或?qū)懭胝埱髸r的數(shù)據(jù)。一般在外設(shè)寫入或讀取的回調(diào)方法中有這一個參數(shù)贯城。

四熊楼、代碼實現(xiàn)
1、首先導(dǎo)入CoreBluetooth框架能犯,并遵守協(xié)議

#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController () <CBCentralManagerDelegate,CBPeripheralDelegate>

2鲫骗、創(chuàng)建外設(shè)管理對象犬耻,用一個屬性來強(qiáng)引用這個對象。并且在創(chuàng)建的時候設(shè)置代理执泰,聲明放到哪個線程枕磁。

@property (nonatomic, strong) CBPeripheralManager *peripheralManager;

// 創(chuàng)建外設(shè)管理器,會回調(diào)peripheralManagerDidUpdateState方法
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];

3术吝、當(dāng)創(chuàng)建CBPeripheralManager的時候计济,會回調(diào)判斷藍(lán)牙狀態(tài)的方法。當(dāng)藍(lán)牙狀態(tài)沒問題的時候創(chuàng)建外設(shè)的Service(服務(wù))和Characteristics(特征)顿苇。

/** 判斷手機(jī)藍(lán)牙狀態(tài)
    CBManagerStateUnknown = 0,  未知
    CBManagerStateResetting,    重置中
    CBManagerStateUnsupported,  不支持
    CBManagerStateUnauthorized, 未驗證
    CBManagerStatePoweredOff,   未啟動
    CBManagerStatePoweredOn,    可用
 */
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    // 藍(lán)牙可用峭咒,開始掃描外設(shè)
    if (central.state == CBManagerStatePoweredOn) {
        NSLog(@"藍(lán)牙可用");
        // 第一個參數(shù)為nil表掃描所有藍(lán)牙設(shè)備 
        [central scanForPeripheralsWithServices:nil options:nil];
    }
    if(central.state==CBManagerStateUnsupported) {
        NSLog(@"該設(shè)備不支持藍(lán)牙");
    }
    if (central.state==CBManagerStatePoweredOff) {
        NSLog(@"藍(lán)牙已關(guān)閉");
    }
}

4、當(dāng)掃描到外設(shè)之后纪岁,就會回調(diào)下面這個方法凑队,可以在這個方法中繼續(xù)設(shè)置篩選條件,例如根據(jù)外設(shè)名字的前綴來選擇幔翰,如果符合條件就進(jìn)行連接漩氨。

/** 發(fā)現(xiàn)符合要求的外設(shè),回調(diào) */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI {
    // 對外設(shè)對象進(jìn)行強(qiáng)引用
    self.peripheral = peripheral;
    //打印外設(shè)的UUID
    NSLog(@"===%@",peripheral.identifier);
    //打印外設(shè)的名稱
    NSLog(@"===%@",peripheral.name);
    
    //根據(jù)外設(shè)名字來過濾外設(shè)遗增,我的外設(shè)名字叫ble_mp008
        if ([peripheral.name hasPrefix:@"ble"]) {
            [central connectPeripheral:peripheral options:nil];
        }
    
}

5叫惊、當(dāng)連接成功的時候,就會來到下面這個方法做修。為了省電霍狰,當(dāng)連接上外設(shè)之后,就讓中心設(shè)備停止掃描,并且別忘記設(shè)置連接上的外設(shè)的代理。在這個方法里根據(jù)UUID進(jìn)行服務(wù)的查找脯丝。

/** 連接成功 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    // 可以停止掃描
    [self.centralManager stopScan];
    // 設(shè)置代理
    peripheral.delegate = self;
    // 根據(jù)UUID來尋找服務(wù),我的設(shè)備服務(wù)UUDI是 “FF00”
    [peripheral discoverServices:@[[CBUUID UUIDWithString:@"FF00"]]];
    NSLog(@"連接成功");
}

6宾濒、連接失敗和斷開連接也有各自的回調(diào)方法。在斷開連接的時候屏箍,我們可以設(shè)置自動重連绘梦,根據(jù)項目需求來自定義里面的代碼。

/** 連接失敗的回調(diào) */
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    NSLog(@"連接失敗");
}

/** 斷開連接 */
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error {
    NSLog(@"斷開連接");
    // 斷開連接可以設(shè)置重新連接
    [central connectPeripheral:peripheral options:nil];
}

7赴魁、下面開始處理代理方法卸奉。最開始就是發(fā)現(xiàn)服務(wù)的方法。這個方法里可以遍歷服務(wù)颖御,找到需要的服務(wù)择卦。我這里選擇第一個服務(wù)。
找到服務(wù)之后郎嫁,連貫的動作繼續(xù)根據(jù)特征的UUID尋找服務(wù)中的特征秉继。

/** 發(fā)現(xiàn)服務(wù) */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    
    // 遍歷出外設(shè)中所有的服務(wù)
    for (CBService *service in peripheral.services) {
        NSLog(@"所有的服務(wù):%@",service);
    }
    
    // 這里僅有一個服務(wù),所以直接獲取
    CBService *service = peripheral.services.lastObject;
    // 根據(jù)UUID尋找服務(wù)中的特征
    [peripheral discoverCharacteristics:[CBUUID UUIDWithString:CHARACTERISTIC_UUID]] forService:service];
}

8泽铛、下面這個方法里做的事情不少尚辑。
當(dāng)發(fā)現(xiàn)特征之后,與服務(wù)一樣可以遍歷特征盔腔,根據(jù)外設(shè)開發(fā)人員給的文檔找出不同特征杠茬,做出相應(yīng)的操作。
我這里獲取第一個特征弛随。
再重復(fù)一遍瓢喉,一般開發(fā)中可以設(shè)置兩個特征,一個用來發(fā)送數(shù)據(jù)舀透,一個用來接收中心設(shè)備寫過來的數(shù)據(jù)栓票。
這里用一個屬性引用特征,是為了后面通過這個特征向外設(shè)寫入數(shù)據(jù)或發(fā)送指令愕够。
readValueForCharacteristic方法是直接讀一次這個特征上的數(shù)據(jù)走贪。

/** 發(fā)現(xiàn)特征回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    
    // 遍歷出所需要的特征
    for (CBCharacteristic *characteristic in service.characteristics) {
        NSLog(@"所有特征:%@", characteristic);
        // 從外設(shè)開發(fā)人員那里拿到不同特征的UUID,不同特征做不同事情惑芭,比如有讀取數(shù)據(jù)的特征坠狡,也有寫入數(shù)據(jù)的特征
    }
    
    // 這里只獲取一個特征,寫入數(shù)據(jù)的時候需要用到這個特征
    self.characteristic = service.characteristics.firstObject;
    
    // 直接讀取這個特征數(shù)據(jù)遂跟,會調(diào)用didUpdateValueForCharacteristic
    [peripheral readValueForCharacteristic:self.characteristic];
    
    // 訂閱通知
    [peripheral setNotifyValue:YES forCharacteristic:self.characteristic];
}

setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic方法是對這個特征進(jìn)行訂閱逃沿,訂閱成功之后,就可以監(jiān)控外設(shè)中這個特征值得變化了幻锁。

9凯亮、當(dāng)訂閱的狀態(tài)發(fā)生改變的時候,下面的方法就派上用場了越败。

/** 訂閱狀態(tài)的改變 */
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        NSLog(@"訂閱失敗");
        NSLog(@"%@",error);
    }
    if (characteristic.isNotifying) {
        NSLog(@"訂閱成功");
    } else {
        NSLog(@"取消訂閱");
    }
}

10触幼、外設(shè)可以發(fā)送數(shù)據(jù)給中心設(shè)備,中心設(shè)備也可以從外設(shè)讀取數(shù)據(jù)究飞,當(dāng)發(fā)生這些事情的時候置谦,就會回調(diào)這個方法。通過特種中的value屬性拿到原始數(shù)據(jù)亿傅,然后根據(jù)需求解析數(shù)據(jù)媒峡。

/** 接收到數(shù)據(jù)回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    // 拿到外設(shè)發(fā)送過來的數(shù)據(jù)
    NSData *data = characteristic.value;
}

11、中心設(shè)備可以向外設(shè)寫入數(shù)據(jù)葵擎,也可以向外設(shè)發(fā)送請求或指令谅阿,當(dāng)需要進(jìn)行這些操作的時候該怎么辦呢。

首先把要寫入的數(shù)據(jù)轉(zhuǎn)化為NSData格式,然后根據(jù)上面拿到的寫入數(shù)據(jù)的特征签餐,運用方法writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type來進(jìn)行數(shù)據(jù)的寫入寓涨。

當(dāng)寫入數(shù)據(jù)的時候,系統(tǒng)也會回調(diào)這個方法peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error

/** 寫入數(shù)據(jù) */
    // 用NSData類型來寫入
    NSData *data = [要寫入的值 dataUsingEncoding:NSUTF8StringEncoding];
    // 根據(jù)上面的特征self.characteristic來寫入數(shù)據(jù)
    [self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}


/** 寫入數(shù)據(jù)回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
    NSLog(@"寫入成功");
}

12氯檐、中心設(shè)備如何主動從外設(shè)讀取數(shù)據(jù)呢戒良。

用正在連接的外設(shè)對象來調(diào)用readValueForCharacteristic方法,并且把將要讀取數(shù)據(jù)的特征作為參數(shù)冠摄,這樣就可以主動拿一次數(shù)據(jù)了糯崎。
去到第10步的回調(diào)方法中,在特征的value屬性中拿到這次的數(shù)據(jù)河泳。

 [self.peripheral readValueForCharacteristic:self.characteristic];

后記

到此為止就是一套iOS藍(lán)牙讀寫的流程沃呢,具體細(xì)化的東西也許就需要硬件工程師或者其他工作人員的配合,比如我們這就就提供好了相應(yīng)的SDK進(jìn)行后續(xù)操作拆挥,這些就不寫出來了薄霜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市竿刁,隨后出現(xiàn)的幾起案子黄锤,更是在濱河造成了極大的恐慌,老刑警劉巖食拜,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸵熟,死亡現(xiàn)場離奇詭異,居然都是意外死亡负甸,警方通過查閱死者的電腦和手機(jī)流强,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呻待,“玉大人打月,你說我怎么就攤上這事〔献剑” “怎么了奏篙?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長迫淹。 經(jīng)常有香客問我秘通,道長,這世上最難降的妖魔是什么敛熬? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任肺稀,我火速辦了婚禮,結(jié)果婚禮上应民,老公的妹妹穿的比我還像新娘话原。我一直安慰自己夕吻,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布繁仁。 她就那樣靜靜地躺著涉馅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪改备。 梳的紋絲不亂的頭發(fā)上控漠,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機(jī)與錄音悬钳,去河邊找鬼。 笑死偶翅,一個胖子當(dāng)著我的面吹牛默勾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播聚谁,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼母剥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了形导?” 一聲冷哼從身側(cè)響起环疼,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎朵耕,沒想到半個月后炫隶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡阎曹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年伪阶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片处嫌。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡栅贴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出熏迹,到底是詐尸還是另有隱情檐薯,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布注暗,位于F島的核電站坛缕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏友存。R本人自食惡果不足惜祷膳,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屡立。 院中可真熱鬧直晨,春花似錦搀军、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至敛摘,卻和暖如春门烂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兄淫。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工屯远, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捕虽。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓慨丐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親泄私。 傳聞我的和親對象是個殘疾皇子房揭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

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