swift 藍(lán)牙集成開(kāi)發(fā)

前一階段剛剛做完了對(duì)接體脂稱和智能音箱,空下來(lái)整理一下藍(lán)牙的簡(jiǎn)單集成,不瞎叭叭了,進(jìn)入正題。

一、概況

藍(lán)牙4.0標(biāo)準(zhǔn)包括傳統(tǒng)藍(lán)牙部分和低功耗藍(lán)牙模塊部分

二、藍(lán)牙模塊

藍(lán)牙模塊是指集成藍(lán)牙功能的芯片基本電路集合,用于短距離2.4G的無(wú)線通訊模塊。
對(duì)于終端用戶來(lái)說(shuō),藍(lán)牙模塊是半成品,通過(guò)在模塊的基礎(chǔ)上做功能二次開(kāi)發(fā)、封裝外殼等工序,實(shí)現(xiàn)能夠利用藍(lán)牙通訊的最終產(chǎn)品。

藍(lán)牙模塊按照應(yīng)用和支持協(xié)議劃分主要分為兩種:

1、經(jīng)典藍(lán)牙模塊(BT):

泛指支持藍(lán)牙協(xié)議在4.0以下的模塊,一般用于數(shù)量比較的傳輸,如:語(yǔ)音、音樂(lè)等較高數(shù)據(jù)量傳輸。

經(jīng)典藍(lán)牙模塊可再細(xì)分為傳統(tǒng)藍(lán)牙模塊和高速藍(lán)牙模塊

高速模塊相比于傳輸藍(lán)牙模塊速率提高到約24Mbps,是傳統(tǒng)藍(lán)牙模塊的八倍,可以用于錄像機(jī)、高清電視、PC、PMP、UMPC和打印機(jī)之間的資料傳輸。

2、低功耗藍(lán)牙模塊(BLE):

指支持藍(lán)牙協(xié)議4.0或更高的模塊,也成為BLE模塊,最大的特點(diǎn)是成本和功效的降低,應(yīng)用于實(shí)時(shí)性要去比較高的產(chǎn)品中,比如:智能家居類(藍(lán)牙鎖、藍(lán)牙燈)、傳感設(shè)備的數(shù)據(jù)發(fā)送(血壓計(jì)、溫度傳感器)、消費(fèi)類電子(電子煙、遙控玩具)等。
藍(lán)牙低功耗技術(shù)采用可變連接時(shí)間間隔,這個(gè)間隔根據(jù)具體應(yīng)用可以設(shè)置為幾毫秒到幾秒不等。
另外,因?yàn)锽LE技術(shù)采用非??焖俚倪B接方式,因此可以處于“非連接”狀態(tài)(節(jié)省能源),此時(shí)鏈路兩端只有在必要時(shí)才能開(kāi)啟鏈路,然后在盡可能短的時(shí)間內(nèi)關(guān)閉鏈路。

三、藍(lán)牙模塊對(duì)比

1、按協(xié)議劃分

單模藍(lán)牙模塊:是指支持藍(lán)牙某一種協(xié)議的模塊
雙模藍(lán)牙模塊:是指同時(shí)支持經(jīng)典藍(lán)牙(BT)和低功耗藍(lán)牙(BLE) 協(xié)議的模塊

2、按應(yīng)用分

藍(lán)牙數(shù)據(jù)模塊:一般多使用BLE地功耗藍(lán)牙模塊,擁有極低的運(yùn)行和待機(jī)功耗,使用一顆紐扣電池可連續(xù)工作數(shù)年之久。
藍(lán)牙音頻模塊:音頻需要大碼流的數(shù)據(jù)傳輸更適合使用BT經(jīng)典藍(lán)牙模塊。

3、按照濕度等級(jí)分為:

工業(yè)級(jí):溫度范圍-40攝氏度~85攝氏度,能夠在室外、干擾大等惡劣環(huán)境下正常運(yùn)行
商業(yè)級(jí):溫度范圍為0攝氏度~70攝氏度,一般應(yīng)用在普通民用產(chǎn)品中。

四、集成

iOS對(duì)藍(lán)牙庫(kù)進(jìn)行了封裝,封裝在CoreBluetooth庫(kù)

import CoreBluetooth

接下來(lái)是對(duì)一些代碼中需要使用到的名詞的備注

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

注:一個(gè)CBPeripheral可以包含多個(gè)CBService,而一個(gè)CBService也可以包含多個(gè)CBCharacteristic

接下來(lái)介紹藍(lán)牙從打開(kāi)到連接到發(fā)送數(shù)據(jù)到接收數(shù)據(jù)的一整個(gè)流程

1、設(shè)置權(quán)限,在info.plist里面加入
Privacy - Bluetooth Peripheral Usage Description
2、開(kāi)發(fā)流程

 /******藍(lán)牙******/
var bleManager: CBCentralManager? //系統(tǒng)藍(lán)牙設(shè)備管理對(duì)象
var peripheral: CBPeripheral? //外圍設(shè)備
var writeChar: CBCharacteristic? // 寫服務(wù)的特征值

override func viewDidLoad() {
        super.viewDidLoad()
        // MARK: - 可以不用系統(tǒng)的藍(lán)牙提示框,使用自定義的藍(lán)牙提示框
        self.bleManager = CBCentralManager.init(delegate: self, queue: nil, options: nil)
       //MARK:-使用系統(tǒng)的藍(lán)牙提示框
//        self.bleManager = CBCentralManager.init(delegate: self, queue: nil)
        // 設(shè)置代理
        self.bleManager?.delegate = nil
}
  • 判斷藍(lán)牙狀態(tài)

// 判斷藍(lán)牙狀態(tài),通過(guò)CBCentralManager的state來(lái)獲取
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
           // 確認(rèn)藍(lán)牙打開(kāi)狀態(tài),然后進(jìn)行一系列的操作
            // MARK: - 根據(jù)UUID篩選
//            let uuid = CBUUID(string: "0000180A-0000-1000-8000-00805F9B34FB")
//            self.bleManager?.scanForPeripherals(withServices: [uuid], options: nil)
            // MARK: - 根據(jù)名字篩選
            self.blueToothStatus = true
           self.bleManager?.scanForPeripherals(withServices: nil, options: nil)
            GMLog("藍(lán)牙打開(kāi),正在掃描")
        case .unknown:
            GMAlert.gm_alert(title: "", message: "藍(lán)牙權(quán)限不清楚", cancelBtnTitle: "設(shè)置", okBtnTitle: "好") { (alertIndex) in
                
            }
        case .poweredOff:
                GMAlert.gm_alert(title: "", message: "打開(kāi)藍(lán)牙來(lái)允許\"****\"連接到配件", cancelBtnTitle: "設(shè)置", okBtnTitle: "好") {[weak self] (alertIndex) in
                    if alertIndex == .ok {
                        GMLog("ok")
                        GMAlert.gm_alertSingle(title: "提示", message: "手機(jī)藍(lán)牙未開(kāi)啟\n請(qǐng)?jiān)谠O(shè)置中開(kāi)啟藍(lán)牙", okBtnTitle: "確定", handler: { (alertIndex) in
                            if #available(iOS 10.0, *) {
                                self?.settingAuthority()
                            } else {
                                // Fallback on earlier versions
                            }
                        })
                    }else {
                        GMLog("設(shè)置")
                        
                        if #available(iOS 10.0, *) {
                            self?.settingAuthority()
                        } else {
                            // Fallback on earlier versions
                            GMLog("版本低于10")
                        }
                    }
                    
                }
        default:
            GMLog("----藍(lán)牙關(guān)閉")
        }
    }
  • 掃描到外設(shè)
    // 掃描到外設(shè)
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        GMLog("----scanPeripheral: \(peripheral)") // , \(peripheral.identifier.uuidString), \(peripheral.identifier.uuid)
        GMLog("*********adver:\(advertisementData)")
        //        NSData *data = [advertisementData objectForKey:@"kCBAdvDataManufacturerData"];
        let data = advertisementData["kCBAdvDataManufacturerData"]
        if data != nil {
            let dataString = dataToString(data: data as! Data)          
        }
        // 連接設(shè)備
        self.bleManager?.connect(peripheral, options: nil)
    }

連接成功&失敗

    // 連接成功
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        GMLog("----連接成功")
//        GMLog("\(self.peripheral)")
        // 停止掃描
        self.bleManager?.stopScan()
        self.peripheral = peripheral
        peripheral.delegate = self
        self.peripheral?.delegate = self
        peripheral.discoverServices(nil)
//        let uuid = "9000"
//        peripheral.discoverServices([CBUUID.init(string: uuid)])
    }
    
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        GMLog("----連接失敗")
    }
  • 斷開(kāi)設(shè)備連接
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        GMLog("----斷開(kāi)連接, \(self.isBindState), \(String(describing: self.peripheral))")
        self.peripheral = nil
        self.writeChar = nil
// 如果需要重新掃描
        //        self.bleManager?.scanForPeripherals(withServices: nil, options: nil)
    }
  • 搜索到服務(wù)
    //一旦我們讀取到外設(shè)的相關(guān)服務(wù)UUID就會(huì)回調(diào)下面的方法
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        
        for aService in peripheral.services! {
            GMLog("aaaaa-----\(aService.uuid.uuidString)")
//            let uuid = "9000"
            peripheral.discoverCharacteristics(nil, for: aService)
//            peripheral.discoverCharacteristics([CBUUID.init(string: uuid)], for: aService)
            
            // 過(guò)濾條件
            //            if aService.uuid == CBUUID.init(string: "0xFFF0") {
            //                peripheral.discoverCharacteristics(nil, for: aService)
            //            }
        }
    }
  • 搜索到特征
    // 根據(jù)特征值去判斷讀操作還是寫操作
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
//        GMLog("!!!!!!!!!!!!!!!!\(service.characteristics)")
        for aChar in service.characteristics! {
            GMLog("#########\(aChar.uuid.uuidString)")
            self.writeChar = aChar
//            self.sendValue1()
            
            // 過(guò)濾條件
            //            if aChar.uuid == CBUUID.init(string: "0xFFF1") { // 0xFFF1 寫數(shù)據(jù)
            //
            //                self.writeChar = aChar
            //            }
            //                // 0xFFF4 讀數(shù)據(jù)
            //            else if aChar.uuid == CBUUID.init(string: "0xFFF4") {
            //
            //                peripheral.setNotifyValue(true, for: aChar)
            //            }
            
            if aChar.uuid == CBUUID.init(string: "9999") {
                peripheral.setNotifyValue(true, for: aChar)
            }
        }
    }
  • 收到外設(shè)消息更新
    // 發(fā)送
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        self.sendValue1()
//        if characteristic.isNotifying {
//            self.sendValue1()
//        }
    }
    
    
    // 接收
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        
        guard let data = characteristic.value else {
            return
        }
        GMLog("\(characteristic)")
        GMLog("----接收數(shù)據(jù):\(self.dataToString(data: data))")
        let dataString = self.dataToString(data: data)
        let resultString = String(dataString.suffix(dataString.count - 12))
//        let acceptString = string(from: self.dataTo(from: resultString))
        self.deviceId = string(from: self.dataTo(from: resultString))
        GMLog("\(string(from: self.dataTo(from: resultString)))")
        
        // didDisconnectPeripheral
        
        // MARK: - 如果接收到deviceId 斷開(kāi)藍(lán)牙;否則繼續(xù)接收
        if self.deviceId != nil {
            if let peripheral = self.peripheral {
                self.bleManager?.cancelPeripheralConnection(peripheral)
            }
        }
        // didDisconnectPeripheral
//        self.bleManager?.cancelPeripheralConnection(self.peripheral!)
        // FIXME: - 接收到數(shù)據(jù)后請(qǐng)求后臺(tái)接口
        self.newAnlanyData(data: data)
    }
    
    //向peripheral中寫入數(shù)據(jù)后的回調(diào)函數(shù)
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
        GMLog("寫入成功")
    }
    
    
    /// 新的計(jì)算方法
    private func newAnlanyData(data: Data) {
        if data[0] == 0xCF {
            GMLog("進(jìn)這里")
        } else if data[0] == 0x00 && data.count == 2 {
            self.sendDeviceOff()
        }
    }
}

說(shuō)明

  • 藍(lán)牙的流程:搜索-連接-連接成功/失?。ㄔO(shè)置外設(shè)代理,搜索服務(wù))-搜索到服務(wù)(搜索特征)-搜索到特征-監(jiān)聽(tīng)需要的特征(讀寫、讀、寫等根據(jù)情況來(lái)確定)-通過(guò)外設(shè)讀寫特征寫入指令-收到設(shè)備返回信息-斷開(kāi)連接
  • CBCentralManagerDelegate:中心管理者代理,負(fù)責(zé)搜索,設(shè)備狀態(tài)的一些回調(diào)
    CBPeripheralDelegate:外設(shè)代理,負(fù)責(zé)對(duì)外設(shè)的一些操作,特征的訂閱,以及設(shè)備信息和消息的更新回調(diào)

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()
    }
}

OTA升級(jí)

OTA是DFU(Device Firmware Update)的一種類型,準(zhǔn)確說(shuō),OTA的全稱應(yīng)該是OTA DFU,就是設(shè)備固件升級(jí)的意思。只不過(guò)大家為了方便起見(jiàn),直接用OTA來(lái)指代固件空中升級(jí)(有時(shí)候大家也將OTA稱為FOTA)。
OTA升級(jí)并不復(fù)雜,只需要按照硬件定制的協(xié)議,把數(shù)據(jù)按照正常的寫入方式發(fā)送給硬件即可(注意查看硬件是否規(guī)定數(shù)據(jù)的大小端)

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS開(kāi)發(fā)藍(lán)牙4.0初識(shí)轉(zhuǎn)載 2015-09-20 15:26:44標(biāo)簽:ios開(kāi)發(fā)藍(lán)牙ios開(kāi)發(fā)藍(lán)牙4.0ios...
    Jany_4a9a閱讀 3,010評(píng)論 0 3
  • 公司項(xiàng)目需要用到BLE以CBCentralManager的身份和硬件交互,開(kāi)發(fā)過(guò)程中解決了一些遇到的問(wèn)題和一些處理...
    daihz閱讀 5,495評(píng)論 3 32
  • 前言 很久沒(méi)有更新技術(shù)博客了。最近一年都在做低功耗藍(lán)牙和物聯(lián)網(wǎng)等相關(guān)的事情,想把一些經(jīng)驗(yàn)和心得寫成博客分享出來(lái),也...
    熊小宇閱讀 7,450評(píng)論 2 8
  • 背景 藍(lán)牙歷史說(shuō)到藍(lán)牙,就不得不說(shuō)下藍(lán)牙技術(shù)聯(lián)盟(Bluetooth SIG),它負(fù)責(zé)藍(lán)牙規(guī)范制定和推廣的國(guó)際組織...
    徐正峰閱讀 13,030評(píng)論 6 33
  • 追女秘籍一:極力贊美女人,讓她慢慢融化。 女人,都喜歡被人夸獎(jiǎng)和贊美,即使說(shuō)的不是實(shí)話,聽(tīng)后心里也很開(kāi)心。楊玉環(huán)在...
    紅豆印跡閱讀 968評(píng)論 0 1

友情鏈接更多精彩內(nèi)容