iOS藍(lán)牙開(kāi)發(fā)之學(xué)習(xí)筆記(Bluetooth/CoreBluetooth) 附Demo

前言

其實(shí)最近一直在研究iOS藍(lán)牙開(kāi)發(fā)CoreBluetooth牵囤,網(wǎng)上有關(guān)于iOS藍(lán)牙開(kāi)發(fā)一堆一堆的, 本人也是想寫(xiě)個(gè)學(xué)習(xí)筆記,基本闡述一些藍(lán)牙的基本概念以及常規(guī)用法。

iOS藍(lán)牙框架介紹 (CoreBluetooth介紹)

CoreBluetooth.jpeg

在iOS開(kāi)發(fā)中朝捆,實(shí)現(xiàn)藍(lán)牙通信的方法有兩種。分別是GameKit.framework以及CoreBluetooth.framework懒豹,前者在iOS5后基本被淘汰芙盘。

在蘋(píng)果文檔中驯用,寫(xiě)了Communicate with Bluetooth 4.0 low-energy devices,也就是說(shuō)僅支持藍(lán)牙4.0低功耗協(xié)議(BLE)儒老。

對(duì)于iOS10以上的設(shè)備蝴乔,蘋(píng)果注明以下信息:

An iOS app linked on or after iOS 10.0 must include in its Info.plist file the usage description keys for the types of data it needs to access or it will crash. To access Bluetooth peripheral data specifically, it must include NSBluetoothPeripheralUsageDescription.

也就是說(shuō)需要聲明并注冊(cè)藍(lán)牙權(quán)限的使用。

CoreBluetooth協(xié)議

首先提及藍(lán)牙使用驮樊,在此引入兩個(gè)概念:中心設(shè)備和外圍設(shè)備薇正。

中心設(shè)備(客服端):作為中央管理器的設(shè)備,也就是本實(shí)例中的iOS設(shè)備囚衔。
外圍設(shè)備(服務(wù)器):也就是外部設(shè)備挖腰,扮演者產(chǎn)生數(shù)據(jù)的角色。許多傳感器佳魔、藍(lán)牙服務(wù)設(shè)備均是外圍設(shè)備曙聂。本實(shí)例中小米手環(huán)就是外圍設(shè)備晦炊。

外設(shè)鞠鲜、服務(wù)、特征間的關(guān)系

外設(shè)断国、服務(wù)贤姆、特征間的關(guān)系.png

同時(shí)數(shù)據(jù)傳輸還涉及到以下幾個(gè)值:

  • UUID:相當(dāng)與使用這個(gè)模塊對(duì)映的應(yīng)用的標(biāo)識(shí)。
  • RSSI:信號(hào)強(qiáng)度稳衬,利用此信息可進(jìn)行藍(lán)牙測(cè)距霞捡,后面將進(jìn)行講解。

CoreBluetooth中涉及以下對(duì)象類(lèi):

  • CBCentralManager:中心設(shè)備類(lèi)
  • CBPeripheral:外圍設(shè)備類(lèi)
  • CBCharacteristic:設(shè)備特征類(lèi)

「附上Demo地址」如果喜歡請(qǐng)賞賜一枚'Star'

正文

我們得明確一下很重要的幾個(gè)概念

1.當(dāng)前ios中開(kāi)發(fā)藍(lán)牙所運(yùn)用的系統(tǒng)庫(kù)是<CoreBluetooth/CoreBluetooth.h>
2.藍(lán)牙外設(shè)必須為4.0及以上薄疚,否則無(wú)法開(kāi)發(fā)碧信,藍(lán)牙4.0設(shè)備因?yàn)榈秃碾姡砸步凶鯞LE街夭。
3.CoreBluetooth框架的核心其實(shí)是兩個(gè)東西砰碴,peripheralcentral, 可以理解成外設(shè)和中心,就是你的蘋(píng)果手機(jī)就是中心板丽,外部藍(lán)牙稱(chēng)為外設(shè)呈枉。
4.服務(wù)和特征(service and characteristic):簡(jiǎn)而言之,外部藍(lán)牙中它有若干個(gè)服務(wù)service(服務(wù)你可以理解為藍(lán)牙所擁有的能力)埃碱,而每個(gè)服務(wù)service下?lián)碛腥舾蓚€(gè)特征characteristic(特征你可以理解為解釋這個(gè)服務(wù)的屬性)猖辫。
5.Descriptor(描述)用來(lái)描述characteristic變量的屬性。例如砚殿,一個(gè)descriptor可以規(guī)定一個(gè)可讀的描述啃憎,或者一個(gè)characteristic變量可接受的范圍,或者一個(gè)characteristic變量特定的單位似炎。
6.跟硬件親測(cè)荧飞,iOS藍(lán)牙每次最多接收155字節(jié)的數(shù)據(jù)凡人,安卓5.0以下最大接收20字節(jié),5.0以上可以更改最大接收量叹阔,能達(dá)到500多字節(jié)挠轴。

通過(guò)以上關(guān)鍵信息的解釋?zhuān)缓罂匆幌滤{(lán)牙的開(kāi)發(fā)流程:

建立中心管理者

  1. 掃描外設(shè)(discover)
  2. 連接外設(shè)(connect)
  3. 掃描外設(shè)中的服務(wù)和特征(discover)
  4. 4.1 獲取外設(shè)的services
  5. 4.2 獲取外設(shè)的Characteristics,獲取Characteristics的值,
  6. 獲取Characteristics的Descriptor和Descriptor的值
  7. 訂閱Characteristic的通知
  8. 與外設(shè)做數(shù)據(jù)交互(explore and interact)
  9. 斷開(kāi)連接(disconnect)

具體實(shí)例:

1.創(chuàng)建一個(gè)中心管理者
/** 從這個(gè)代理方法中你可以看到所有的狀態(tài)耳幢,其實(shí)我們需要的只有on和off連個(gè)狀態(tài)*/
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    switch (central.state) {
        case CBManagerStateUnknown:
            NSLog(@"__CBManagerStateUnknown__");
            break;
        case CBManagerStateResetting:
            NSLog(@"__CBManagerStateResetting__");
            break;
        case CBManagerStateUnsupported:
            NSLog(@"__CBManagerStateUnsupported__");
            break;
        case CBManagerStateUnauthorized:
            NSLog(@"__CBManagerStateUnauthorized__");
            break;
        case CBManagerStatePoweredOff:
            NSLog(@"__CBManagerStatePoweredOff__");
            break;
        case CBManagerStatePoweredOn:
            NSLog(@"__CBManagerStatePoweredOn__");
            break;
        default:
            break;
    }
}

當(dāng)發(fā)現(xiàn)藍(lán)牙狀態(tài)是開(kāi)啟狀態(tài)岸晦,你就可以利用中央設(shè)備進(jìn)行掃描外設(shè),如果為關(guān)閉狀態(tài)睛藻,系統(tǒng)會(huì)自動(dòng)彈出讓用戶(hù)去設(shè)置藍(lán)牙启上,這個(gè)不需要我們開(kāi)發(fā)者關(guān)心

2.利用中心去掃描外設(shè)
/** 兩個(gè)參數(shù)為nil, 默認(rèn)掃描所有的外設(shè),可以設(shè)置一些服務(wù)店印,進(jìn)行過(guò)濾搜索*/
[self.bluetoothManager scanForPeripheralsWithServices:nil
                                                  options:nil];

2.1當(dāng)掃描到外設(shè)冈在,觸發(fā)以下代理方法

在這里需要說(shuō)明的是,
一.當(dāng)掃描到外設(shè)按摘,我們可以讀到相應(yīng)外設(shè)廣播信息包券,RSSI信號(hào)強(qiáng)度(可以利用RSSI計(jì)算中心和外設(shè)的距離)。
二.我們可以根據(jù)一定的規(guī)則進(jìn)行連接炫贤,一般是默認(rèn)名字或者名字和信號(hào)強(qiáng)度的規(guī)則來(lái)連接溅固。
三.像我現(xiàn)在做的無(wú)鑰匙啟動(dòng)車(chē)輛鎖定車(chē)輛,就需要加密通訊兰珍,不能誰(shuí)來(lái)連接都可以操作車(chē)輛侍郭,需要和外設(shè)進(jìn)行加密通訊,但是一切的通過(guò)算法的校驗(yàn)都是在和外設(shè)連接上的基礎(chǔ)上進(jìn)行掠河,例如連接上了亮元,你發(fā)送一種和硬件約定好的算法數(shù)據(jù),硬件接收到校驗(yàn)通過(guò)了就正常操作唠摹,無(wú)法通過(guò)則由硬件(外設(shè))主動(dòng)斷開(kāi)爆捞。

/** 這里默認(rèn)掃到MI,主動(dòng)連接跃闹,當(dāng)然也可以手動(dòng)觸發(fā)連接*/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    NSLog(@"掃描連接外設(shè):%@ %@", peripheral.name, RSSI);
    
    if ([peripheral.name hasSuffix:@"MI"]) {
        /** 保存外設(shè)嵌削,并停止掃描,達(dá)到節(jié)電效果*/
        self.pripheral = peripheral;
        [central stopScan];
        /** 進(jìn)行連接*/
        [central connectPeripheral:peripheral options:nil];
    }
}
3.當(dāng)連接到外設(shè)望艺,會(huì)調(diào)用以下代理方法

這里需要說(shuō)明的是
當(dāng)成功連接到外設(shè)苛秕,需要設(shè)置外設(shè)的代理,為了掃描服務(wù)調(diào)用相應(yīng)代理方法

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    
    NSLog(@"連接外設(shè)成功找默!%@", peripheral.name);
    [peripheral setDelegate:self];
    [peripheral discoverServices:nil];
}

/** 連接外設(shè)失敗*/
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"連接到外設(shè) 失斖Ы佟!名字:%@ 錯(cuò)誤信息:%@", [peripheral name], [error localizedDescription]);
}
4.掃描外設(shè)中的服務(wù)和特征
/** 掃描到服務(wù)*/
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    if (error)
    {
        NSLog(@"掃描外設(shè)服務(wù)出錯(cuò):%@-> %@", peripheral.name, [error localizedDescription]);
        return;
    }
    NSLog(@"掃描到外設(shè)服務(wù):%@ -> %@",peripheral.name,peripheral.services);
    for (CBService *service in peripheral.services) {
        [peripheral discoverCharacteristics:nil forService:service];
    }
    NSLog(@"開(kāi)始掃描外設(shè)服務(wù)的特征 %@...",peripheral.name);
}

/** 掃描到特征*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    if (error)
    {
        NSLog(@"掃描外設(shè)的特征失敵图ぁ店煞!%@->%@-> %@", peripheral.name, service.UUID, [error localizedDescription]);
        return;
    }
    
    NSLog(@"掃描到外設(shè)服務(wù)特征有:%@->%@->%@", peripheral.name, service.UUID, service.characteristics);
    //獲取Characteristic的值
    for (CBCharacteristic *characteristic in service.characteristics){
        
        //這里外設(shè)需要訂閱特征的通知蟹演,否則無(wú)法收到外設(shè)發(fā)送過(guò)來(lái)的數(shù)據(jù)
        [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        
        //需要說(shuō)明的是UUID是硬件定義好給你,如果硬件也是個(gè)新手顷蟀,那你可以先打印出所有的UUID, 找出有用的
        //步數(shù)
        if ([characteristic.UUID.UUIDString isEqualToString:@"FF06"])
        {
            [peripheral readValueForCharacteristic:characteristic];
        }
        
        //電池電量
        else if ([characteristic.UUID.UUIDString isEqualToString:@"FF0C"])
        {
            [peripheral readValueForCharacteristic:characteristic];
        }
        
        else if ([characteristic.UUID.UUIDString isEqualToString:@"2A06"])
        {
            //震動(dòng)
            self.characteristic = characteristic;
        }
    }
}


/** 掃描到具體的值->通訊主要的獲取數(shù)據(jù)的方法*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
    if (error) {
        NSLog(@"掃描外設(shè)的特征失斁魄搿!%@-> %@", peripheral.name, [error localizedDescription]);
        return;
    }
    NSLog(@"%@ %@", characteristic.UUID.UUIDString, characteristic.value);
    if ([characteristic.UUID.UUIDString isEqualToString:@"FF06"]) {
        Byte *steBytes = (Byte *)characteristic.value.bytes;
        int steps = bytesValueToInt(steBytes);
        NSLog(@"%d", steps);
    } else if ([characteristic.UUID.UUIDString isEqualToString: @"FF0C"])
    {
        Byte *bufferBytes = (Byte *)characteristic.value.bytes;
        int buterys = bytesValueToInt(bufferBytes)&0xff;
        NSLog(@"電池:%d%%",buterys);
    } else if ([characteristic.UUID.UUIDString isEqualToString:@"2A06"])
    {
        Byte *infoByts = (Byte *)characteristic.value.bytes;
        NSLog(@"%s", infoByts);
        
        //這里解析infoByts得到設(shè)備信息
    }
}
5.與外設(shè)做數(shù)據(jù)交互

需要說(shuō)明的是蘋(píng)果官方提供發(fā)送數(shù)據(jù)的方法很簡(jiǎn)單,只需要調(diào)用下面的方法

- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type
{
    /**
     我們只需要在搜索每個(gè)服務(wù)的特征鸣个,記錄這個(gè)特征羞反,然后向這個(gè)特征發(fā)送數(shù)據(jù)就可以了。
     */
}
6.斷開(kāi)連接

調(diào)用以下代碼,需要說(shuō)明的是中心斷開(kāi)與外設(shè)的連接囤萤。

- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral
{
    /*
      以上呢是整個(gè)藍(lán)牙的開(kāi)發(fā)過(guò)程昼窗,系統(tǒng)提供的框架api就這么多;
     */
}

「附上Demo地址」如果喜歡請(qǐng)賞賜一枚'Star'

結(jié)語(yǔ): 目前先總結(jié)到這里涛舍。
擴(kuò)展: 正在研究iOS應(yīng)用與Siri交互
例如:對(duì)著Siri說(shuō) “支付寶付款碼” 會(huì)自動(dòng)訪(fǎng)問(wèn)支付寶并且彈出付款碼澄惊。 或者 對(duì)著Siri說(shuō)“給xxx發(fā)送一條微信” 會(huì)彈出自定義UI頁(yè)面 然后可以直接操作, 不需要在打開(kāi)微信 等相關(guān)功能

在之后會(huì)更新學(xué)習(xí)成果富雅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末掸驱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吹榴,更是在濱河造成了極大的恐慌亭敢,老刑警劉巖滚婉,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件图筹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡让腹,警方通過(guò)查閱死者的電腦和手機(jī)远剩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)骇窍,“玉大人瓜晤,你說(shuō)我怎么就攤上這事「鼓桑” “怎么了痢掠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)嘲恍。 經(jīng)常有香客問(wèn)我足画,道長(zhǎng),這世上最難降的妖魔是什么佃牛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任淹辞,我火速辦了婚禮,結(jié)果婚禮上俘侠,老公的妹妹穿的比我還像新娘象缀。我一直安慰自己蔬将,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布央星。 她就那樣靜靜地躺著霞怀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莉给。 梳的紋絲不亂的頭發(fā)上里烦,一...
    開(kāi)封第一講書(shū)人閱讀 51,258評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音禁谦,去河邊找鬼胁黑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛州泊,可吹牛的內(nèi)容都是我干的丧蘸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼遥皂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼力喷!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起演训,我...
    開(kāi)封第一講書(shū)人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤弟孟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后样悟,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拂募,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年窟她,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了陈症。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡震糖,死狀恐怖录肯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吊说,我是刑警寧澤论咏,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站颁井,受9級(jí)特大地震影響厅贪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蚤蔓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一卦溢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦单寂、人聲如沸贬芥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蘸劈。三九已至,卻和暖如春尊沸,著一層夾襖步出監(jiān)牢的瞬間威沫,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工洼专, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棒掠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓屁商,卻偏偏與公主長(zhǎng)得像烟很,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜡镶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354