swift 藍牙開發、OTA升級

公司項目需要用到BLE以CBCentralManager的身份和硬件交互,開發過程中解決了一些遇到的問題和一些處理思路,這里簡單記錄一下。如果有什么問題或寫的不對的地方希望大家可以一起討論。
首先了解一下什么是BLE,藍牙低能耗(Bluetooth Low Energy,或稱Bluetooth LE、BLE,舊商標Bluetooth Smart,藍牙版本4.0),也稱低功耗藍牙。相較經典藍牙(藍牙版本2.0),低功耗藍牙旨在保持同等通信范圍的同時顯著降低功耗和成本。


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

與設備的交互使用的是16進制,所以要對發送的數據進行16進制轉換,轉換方法放在末尾
連接和操作一個設備就要持有這個設備對象,系統不維護設備對象的內存管理
發送數據異步回調可以封裝一個任務機制,發送數據后生成一個任務,在收到想要的數據的時候關閉任務或者等待任務超時關閉任務。
iOS更換手機的時候設備的UUID會改變,如果想換手機后依然可以重連設備,就需要讓設備端配合把設備唯一MAC地址放入廣播內容中,給設備擴充MAC屬性,根據MAC來選擇設備進行連接,做到設備MAC和UUID的匹配
本篇只做了簡單的功能介紹和使用,OTA部分需要按照實際協議來做。如果大家有遇到問題或者有好的主意可以找我一起討論,萬分榮幸。


iOS對藍牙庫進行了封裝,封裝在CoreBluetooth庫,所以使用時

import CoreBluetooth

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

CBCentralManager - 中心管理者
CBPeripheralManager - 外設管理者
CBPeripheral - 外設對象
CBService - 外設服務
CBCharacteristic - 外設服務的特征

大致結構如下

image.png

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


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

CBCentralManager()//默認主線程,無代理,無options

如果想自己設置線程和其他條件,則可以通過接下來的初始化方法一次性進行設置

//說明:options包含兩個key
//CBCentralManagerOptionShowPowerAlertKey
//布爾值,表示的是在central manager初始化時,如果當前藍牙沒打開,是否彈出alert框。
//CBCentralManagerOptionRestoreIdentifierKey
//字符串,一個唯一的標示符,用來藍牙的恢復連接的。
CBCentralManager.init(delegate:, queue:, options: )

3.判斷藍牙狀態,通過CBCentralManager的state來獲取

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

4.如果狀態為打開,則可以進行搜索操作

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

注:如果連接和操作一個設備就要持有這個設備對象,系統不維護設備對象的內存管理
接下來就是一系列的代理事件了,我會把主要代理按照流程來進行說明,大致流程如下:


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


接下來對每個代理來進行詳細介紹
CBCentralManagerDelegate:中心管理者代理,負責搜索,設備狀態的一些回調
CBPeripheralDelegate:外設代理,負責對外設的一些操作,特征的訂閱,以及設備信息和消息的更新回調
搜索&連接

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

連接成功&失敗

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

搜索到服務

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

搜索到特征

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

收到外設消息更新

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

斷開設備連接

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

接下來介紹OTA升級
OTA是DFU(Device Firmware Update)的一種類型,準確說,OTA的全稱應該是OTA DFU,就是設備固件升級的意思。只不過大家為了方便起見,直接用OTA來指代固件空中升級(有時候大家也將OTA稱為FOTA)。
OTA升級并不復雜,只需要按照硬件定制的協議,把數據按照正常的寫入方式發送給硬件即可(注意查看硬件是否規定數據的大小端),如果遇到問題可以找我,可以一起討論。

大端模式:是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中,這樣的存儲模式有點類似于把數據當作字符串順序處理:地址由小向大增加,而數據從高位往低位放;這和我們的閱讀習慣一致。
小端模式:是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中,這種存儲模式將地址的高低和數據位權有效地結合起來,高地址部分權值高,低地址部分權值低。


16進制的轉換

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

extension String {
    ///16進制字符串轉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轉16進制字符串
    func hexString() -> String {
        return map { String(format: "%02x", $0) }.joined(separator: "").uppercased()
    }
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容