swift 藍(lán)牙開發(fā)、OTA升級

公司項(xiàng)目需要用到BLE以CBCentralManager的身份和硬件交互赐写,開發(fā)過程中解決了一些遇到的問題和一些處理思路鸟蜡,這里簡單記錄一下。如果有什么問題或?qū)懙牟粚Φ牡胤较M蠹铱梢砸黄鹩懻摗?/code>
首先了解一下什么是BLE挺邀,藍(lán)牙低能耗(Bluetooth Low Energy揉忘,或稱Bluetooth LE、BLE端铛,舊商標(biāo)Bluetooth Smart泣矛,藍(lán)牙版本4.0),也稱低功耗藍(lán)牙禾蚕。相較經(jīng)典藍(lán)牙(藍(lán)牙版本2.0)您朽,低功耗藍(lán)牙旨在保持同等通信范圍的同時顯著降低功耗和成本。


討論一些要注意的和一些思路

與設(shè)備的交互使用的是16進(jìn)制换淆,所以要對發(fā)送的數(shù)據(jù)進(jìn)行16進(jìn)制轉(zhuǎn)換哗总,轉(zhuǎn)換方法放在末尾
連接和操作一個設(shè)備就要持有這個設(shè)備對象,系統(tǒng)不維護(hù)設(shè)備對象的內(nèi)存管理
發(fā)送數(shù)據(jù)異步回調(diào)可以封裝一個任務(wù)機(jī)制倍试,發(fā)送數(shù)據(jù)后生成一個任務(wù)讯屈,在收到想要的數(shù)據(jù)的時候關(guān)閉任務(wù)或者等待任務(wù)超時關(guān)閉任務(wù)。
iOS更換手機(jī)的時候設(shè)備的UUID會改變易猫,如果想換手機(jī)后依然可以重連設(shè)備耻煤,就需要讓設(shè)備端配合把設(shè)備唯一MAC地址放入廣播內(nèi)容中具壮,給設(shè)備擴(kuò)充MAC屬性准颓,根據(jù)MAC來選擇設(shè)備進(jìn)行連接,做到設(shè)備MAC和UUID的匹配
本篇只做了簡單的功能介紹和使用棺妓,OTA部分需要按照實(shí)際協(xié)議來做攘已。如果大家有遇到問題或者有好的主意可以找我一起討論,萬分榮幸怜跑。


iOS對藍(lán)牙庫進(jìn)行了封裝样勃,封裝在CoreBluetooth庫吠勘,所以使用時

import CoreBluetooth

接下來是對一些名詞的介紹

CBCentralManager - 中心管理者
CBPeripheralManager - 外設(shè)管理者
CBPeripheral - 外設(shè)對象
CBService - 外設(shè)服務(wù)
CBCharacteristic - 外設(shè)服務(wù)的特征

大致結(jié)構(gòu)如下

image.png

注:一個CBPeripheral可以包含多個CBService ,而一個CBService 也可以包含多個CBCharacteristic 峡眶。


接下來介紹藍(lán)牙從打開到連接到發(fā)送數(shù)據(jù)到接收數(shù)據(jù)的一整個流程
1.首先肯定是權(quán)限設(shè)置剧防,Info.plist里面加入
Privacy - Bluetooth Peripheral Usage Description
2.然后是初始化中心管理者,初始化有三種方式辫樱,我使用的默認(rèn)的初始化方法即

CBCentralManager()//默認(rèn)主線程峭拘,無代理,無options

如果想自己設(shè)置線程和其他條件狮暑,則可以通過接下來的初始化方法一次性進(jìn)行設(shè)置

//說明:options包含兩個key
//CBCentralManagerOptionShowPowerAlertKey
//布爾值鸡挠,表示的是在central manager初始化時,如果當(dāng)前藍(lán)牙沒打開搬男,是否彈出alert框拣展。
//CBCentralManagerOptionRestoreIdentifierKey
//字符串,一個唯一的標(biāo)示符缔逛,用來藍(lán)牙的恢復(fù)連接的备埃。
CBCentralManager.init(delegate:, queue:, options: )

3.判斷藍(lán)牙狀態(tài),通過CBCentralManager的state來獲取

//??iOS10.0之后更新了藍(lán)牙狀態(tài)枚舉的名字译株,但是枚舉類型未變
//10.0之前
CBCentralManagerState
//10.0之后
CBManagerState
//枚舉類型如下:
case unknown 
case resetting 
case unsupported
case unauthorized
case poweredOff
case poweredOn

4.如果狀態(tài)為打開瓜喇,則可以進(jìn)行搜索操作

//說明:
//serviceUUIDs為硬件端定好的發(fā)送/接受服務(wù),可不填歉糜,不填默認(rèn)搜索全部
//options
scanForPeripherals(withServices serviceUUIDs: [CBUUID]?, options: [String : Any]? = nil)

注:如果連接和操作一個設(shè)備就要持有這個設(shè)備對象乘寒,系統(tǒng)不維護(hù)設(shè)備對象的內(nèi)存管理
接下來就是一系列的代理事件了,我會把主要代理按照流程來進(jìn)行說明匪补,大致流程如下:


搜索-連接-連接成功/失敗(設(shè)置外設(shè)代理伞辛,搜索服務(wù))-搜索到服務(wù)(搜索特征)-搜索到特征-監(jiān)聽需要的特征(讀寫、讀夯缺、寫等根據(jù)情況來確定)-通過外設(shè)讀寫特征寫入指令-收到設(shè)備返回信息-斷開連接


接下來對每個代理來進(jìn)行詳細(xì)介紹
CBCentralManagerDelegate:中心管理者代理蚤氏,負(fù)責(zé)搜索,設(shè)備狀態(tài)的一些回調(diào)
CBPeripheralDelegate:外設(shè)代理踊兜,負(fù)責(zé)對外設(shè)的一些操作竿滨,特征的訂閱,以及設(shè)備信息和消息的更新回調(diào)
搜索&連接

    /// 搜索到新的外設(shè)
    ///
    /// - Parameters:
    ///   - central: 藍(lán)牙中心
    ///   - peripheral: 外設(shè)
    ///   - advertisementData: 外設(shè)廣播內(nèi)容
    ///   - RSSI: 信號
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber){
          if peripheral.name == "你需要連接的設(shè)備名稱" {
            //連接指定設(shè)備
            central.connect(peripheral, options: nil)
            //持有外設(shè)對象捏境,自己管理生命周期
            discoverPeripherals.insert(peripheral)
        }
    }

連接成功&失敗

    /// 連接外設(shè)成功  
    ///
    /// - Parameters:
    ///   - central: 藍(lán)牙中心
    ///   - peripheral: 外設(shè)
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        //設(shè)置外設(shè)代理以便對外設(shè)代理的處理
        peripheral.delegate = self
        //搜索外設(shè)的所有服務(wù)(可指定服務(wù)于游,默認(rèn)為搜索全部服務(wù))
        peripheral.discoverServices(nil)
    }
    
    /// 連接外設(shè)失敗  
    ///
    /// - Parameters:
    ///   - central: 藍(lán)牙中心
    ///   - peripheral: 外設(shè)
    ///   - error: 錯誤
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        //釋放持有的外設(shè)對象
        discoverPeripherals.remove(peripheral)
    }

搜索到服務(wù)

    /// 發(fā)現(xiàn)外設(shè)的服務(wù)
    ///
    /// - Parameters:
    ///   - peripheral: 外設(shè)
    ///   - error: 錯誤
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        peripheral.services?.forEach{
            //搜索所有的設(shè)備特征
            peripheral.discoverCharacteristics(nil, for: $0)}
    }

搜索到特征

    /// 發(fā)現(xiàn)外設(shè)的特征,訂閱特征(讀、寫等)
    ///
    /// - Parameters:
    ///   - peripheral: 外設(shè)
    ///   - service: 外設(shè)的w服務(wù)
    ///   - error: 錯誤
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        service.characteristics?.forEach {
            //properties
            //訂閱自己需要的特征
            if $0.properties == .notify {
                peripheral.setNotifyValue(true, for: $0)
            }
        }
    }

收到外設(shè)消息更新

    /// 收到外設(shè)發(fā)送內(nèi)容    
    ///
    /// - Parameters:
    ///   - peripheral: 外設(shè)
    ///   - characteristic: 特征
    ///   - error: 錯誤
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
 
    }

斷開設(shè)備連接

    /// 外設(shè)斷開連接  
    ///
    /// - Parameters:
    ///   - central: 藍(lán)牙中心
    ///   - peripheral: 外設(shè)
    ///   - error: 錯誤
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        //釋放持有的外設(shè)對象
        discoverPeripherals.remove(peripheral)
    }

接下來介紹OTA升級
OTA是DFU(Device Firmware Update)的一種類型垫言,準(zhǔn)確說贰剥,OTA的全稱應(yīng)該是OTA DFU,就是設(shè)備固件升級的意思筷频。只不過大家為了方便起見蚌成,直接用OTA來指代固件空中升級(有時候大家也將OTA稱為FOTA)前痘。
OTA升級并不復(fù)雜,只需要按照硬件定制的協(xié)議担忧,把數(shù)據(jù)按照正常的寫入方式發(fā)送給硬件即可(注意查看硬件是否規(guī)定數(shù)據(jù)的大小端)芹缔,如果遇到問題可以找我,可以一起討論瓶盛。

大端模式:是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中乖菱,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的高地址中,這樣的存儲模式有點(diǎn)類似于把數(shù)據(jù)當(dāng)作字符串順序處理:地址由小向大增加蓬网,而數(shù)據(jù)從高位往低位放窒所;這和我們的閱讀習(xí)慣一致。
小端模式:是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中帆锋,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的低地址中吵取,這種存儲模式將地址的高低和數(shù)據(jù)位權(quán)有效地結(jié)合起來,高地址部分權(quán)值高锯厢,低地址部分權(quán)值低皮官。


16進(jìn)制的轉(zhuǎn)換

16進(jìn)制類型的字符串[A-F,0-9]和Data之間的轉(zhuǎn)換可以使用下面的方法。如果是包含=之類的可以直接用字符串轉(zhuǎn)換Data即可

extension String {
    ///16進(jìn)制字符串轉(zhuǎn)Data
    func hexData() -> Data? {
        var data = Data(capacity: count / 2)
        let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
        regex.enumerateMatches(in: self, range: NSMakeRange(0, utf16.count)) { match, flags, stop in
            let byteString = (self as NSString).substring(with: match!.range)
            var num = UInt8(byteString, radix: 16)!
            data.append(&num, count: 1)
        }
        guard data.count > 0 else { return nil }
        return data
    }
    
    func utf8Data()-> Data? {
        return self.data(using: .utf8)
    }
    
}

extension Data {
    ///Data轉(zhuǎn)16進(jìn)制字符串
    func hexString() -> String {
        return map { String(format: "%02x", $0) }.joined(separator: "").uppercased()
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末实辑,一起剝皮案震驚了整個濱河市捺氢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剪撬,老刑警劉巖摄乒,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異残黑,居然都是意外死亡馍佑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門梨水,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拭荤,“玉大人,你說我怎么就攤上這事疫诽【耸溃” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵奇徒,是天一觀的道長雏亚。 經(jīng)常有香客問我,道長逼龟,這世上最難降的妖魔是什么评凝? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任追葡,我火速辦了婚禮腺律,結(jié)果婚禮上奕短,老公的妹妹穿的比我還像新娘。我一直安慰自己匀钧,他們只是感情好翎碑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著之斯,像睡著了一般日杈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佑刷,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天莉擒,我揣著相機(jī)與錄音,去河邊找鬼瘫絮。 笑死涨冀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的麦萤。 我是一名探鬼主播鹿鳖,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壮莹!你這毒婦竟也來了翅帜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤命满,失蹤者是張志新(化名)和其女友劉穎涝滴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胶台,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狭莱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了概作。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腋妙。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖讯榕,靈堂內(nèi)的尸體忽然破棺而出骤素,到底是詐尸還是另有隱情,我是刑警寧澤愚屁,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布济竹,位于F島的核電站,受9級特大地震影響霎槐,放射性物質(zhì)發(fā)生泄漏送浊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一丘跌、第九天 我趴在偏房一處隱蔽的房頂上張望袭景。 院中可真熱鬧唁桩,春花似錦、人聲如沸耸棒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽与殃。三九已至单山,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間幅疼,已是汗流浹背米奸。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爽篷,地道東北人躏升。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像狼忱,于是被迫代替她去往敵國和親膨疏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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