小米手環(huán)iOS開發(fā)實戰(zhàn)(二):開發(fā)Demo讓你的手環(huán)振動起來

小米手環(huán)iOS開發(fā)實戰(zhàn)(二):開發(fā)Demo讓你的手環(huán)振動起來

上一節(jié)講了CoreBluetooth的使用,理論知識很枯燥,那么現(xiàn)在先利用上一節(jié)講的內(nèi)容,做一個簡易手環(huán)應(yīng)用,實現(xiàn)連接/斷開手環(huán),查看手環(huán)UUID、查看電量信息,并讓振動的功能。
本節(jié)知識默認(rèn)大家掌握iOS的基礎(chǔ)控件,掌握通過storyboard或代碼搭建界面UI,能夠利用Swift或Objective-C編寫程序。文章會盡量詳細(xì)講解這些過程,當(dāng)然如果你是大??梢苑判奶x。


章節(jié)目錄

  • 藍(lán)牙連接所涉及到的類
  • 小米手環(huán)Demo應(yīng)用的開發(fā)
  • 一些功能優(yōu)化

藍(lán)牙連接所涉及到的類

上一節(jié)講了怎么用CoreBluetooth,本節(jié)講一下所涉及到的類,及常用的成員函數(shù)和成員變量,其他方法請見蘋果開發(fā)文檔。

CBCentralManager
此類為中心設(shè)備類,用于控制作為中心設(shè)備時的行為

  • state:獲取當(dāng)前中心設(shè)備狀態(tài)
  • isScanning:當(dāng)前中心設(shè)備是否在掃描外圍設(shè)備
  • stopScan():停止掃描外圍設(shè)備
  • scanForPeripherals(...):掃描外圍設(shè)備(請確保藍(lán)牙開啟)
  • connect(...):連接外圍設(shè)備(需要先掃描到外圍設(shè)備)
  • cancelPeripheralConnection(...):斷開外圍設(shè)備

CBPeripheral
此類為外圍設(shè)備類,用于對外圍設(shè)備進(jìn)行管理

  • name:獲取外圍設(shè)備的名稱
  • rssi:獲取當(dāng)前外圍設(shè)備的信號強(qiáng)度
  • state:獲取外圍設(shè)備的狀態(tài)(disconnected/connecting/connected)
  • services:獲取外圍設(shè)備所提供的服務(wù)(需要先掃描到服務(wù))
  • discoverServices(...):掃描設(shè)備所提供的服務(wù)
  • discoverCharacteristics(...):掃描特征值(需要先獲取服務(wù))
  • readValue(...):讀取特征值所對應(yīng)的值(需要先獲取到特征值,同時要注意此方法不反回值,要用協(xié)議的didUpdateValueFor characteristic方法處理)

是不是已經(jīng)懵了?在此做一個圖大致描述一下流程,其實這些方法的調(diào)用還是很有規(guī)律的。


CoreBluetooth調(diào)用流程

CBCharacteristic
外圍設(shè)備服務(wù)的特征值

  • Value:獲取特征值對應(yīng)的值


</br>

小米手環(huán)Demo應(yīng)用的開發(fā)

本Demo是對上一節(jié)所講CoreBluetooth的操作復(fù)習(xí),每個方法的實現(xiàn)已經(jīng)有所解釋,故在此不再贅述。如果有疑問,歡迎在評論區(qū)提問及討論。
該Demo所要實現(xiàn)的功能:練習(xí)連接設(shè)備、斷開設(shè)備、讀取手環(huán)信息、讓手環(huán)振動。具體涉及到的知識點為連接和斷開設(shè)備、獲取設(shè)備服務(wù)和特征值、獲取特征值對應(yīng)的信息以及對其寫入。

  • 界面搭建
    方便起見,該項目直接采用storyboard搭建,如果不會可以看項目Demo
    界面搭建
    @IBOutlet weak var scanButton: UIButton!
    @IBOutlet weak var stopButton: UIButton!
    @IBOutlet weak var vibrateButton: UIButton!
    @IBOutlet weak var stopVibrateButton: UIButton!
    @IBOutlet weak var loadingInd: UIActivityIndicatorView!
    @IBOutlet weak var statusLabel: UILabel!
    @IBOutlet weak var resultField: UITextView!
    @IBOutlet weak var vibrateLevel: UISegmentedControl!
  • 設(shè)置藍(lán)牙操作過程所需對象
    涉及到的類在第一講已經(jīng)講解,如果有不明白的,可以查閱前面的講解。
    var theManager: CBCentralManager!
    var thePerpher: CBPeripheral!
    var theVibrator: CBCharacteristic!
  • CoreBluetooth協(xié)議方法的實現(xiàn)
    本部分內(nèi)容在第一講已經(jīng)涉及,如果有不明白的,可以查閱前面的講解。
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        theManager = CBCentralManager.init(delegate: self as? CBCentralManagerDelegate, queue: nil)
        self.scanButton.isEnabled = false
        statusLabel.text = ""
        loadingInd.isHidden = true
    }
    
    // 掃描并連接
    @IBAction func startConnectAction(_ sender: UIButton) {
        switch theManager.state {
        case .poweredOn:
            statusLabel.text = "正在掃描…"
            theManager.scanForPeripherals(withServices: nil, options: nil)
            self.loadingInd.startAnimating()
            self.scanButton.isEnabled = false
            self.isDisconnected = false
        default:
            break
        }
    }
    
    @IBAction func disconnectAction(_ sender: UIButton) {
        if ((thePerpher) != nil) {
            theManager.cancelPeripheralConnection(thePerpher)
            thePerpher = nil
            theVibrator = nil
            statusLabel.text = "設(shè)備已斷開"
            scanButton.isEnabled = true
            isDisconnected = true
            isVibrating = false
        }
    }
    
    @IBAction func vibrateAction(_ sender: Any) {
        if ((thePerpher != nil) && (theVibrator != nil)) {
            let data: [UInt8] = [UInt8.init(vibrateLevel.selectedSegmentIndex+1)];
            let theData: Data = Data.init(bytes: data)
            thePerpher.writeValue(theData, for: theVibrator, type: CBCharacteristicWriteType.withoutResponse)
        }
    }
    
    @IBAction func stopVibrateAction(_ sender: UIButton) {
        if ((thePerpher != nil) && (theVibrator != nil)) {
            let data: [UInt8] = [UInt8.init(0)];
            let theData: Data = Data.init(bytes: data)
            thePerpher.writeValue(theData, for: theVibrator, type: CBCharacteristicWriteType.withoutResponse)
            isVibrating = false
        }
    }
    
    
    // 處理當(dāng)前藍(lán)牙主設(shè)備狀態(tài)
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            statusLabel.text = "藍(lán)牙已開啟"
            self.scanButton.isEnabled = true
        default:
            statusLabel.text = "藍(lán)牙未開啟!"
            self.loadingInd.stopAnimating()
        }
    }
    
    // 掃描到設(shè)備
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if (peripheral.name?.hasSuffix("MI"))! {
            thePerpher = peripheral
            central.stopScan()
            central.connect(peripheral, options: nil)
            statusLabel.text = "搜索成功,開始連接"
            
        }
        // 特征值匹配請用 peripheral.identifier.uuidString
        resultField.text = String.init(format: "發(fā)現(xiàn)手環(huán)\n名稱:%@\nUUID:%@\n", peripheral.name!, peripheral.identifier.uuidString)
    }
    
    // 成功連接到設(shè)備
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        statusLabel.text = "連接成功,正在掃描信息..."
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }
    
    // 連接到設(shè)備失敗
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        loadingInd.stopAnimating()
        statusLabel.text = "連接設(shè)備失敗"
        scanButton.isEnabled = true
    }
    
    // 掃描服務(wù)
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if ((error) != nil) {
            statusLabel.text = "查找服務(wù)失敗"
            loadingInd.stopAnimating()
            scanButton.isEnabled = true
            return
        }
        else {
            for service in peripheral.services! {
                peripheral.discoverCharacteristics(nil, for: service)
            }
        }
    }
    
    // 掃描到特征值
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if ((error) != nil) {
            statusLabel.text = "查找服務(wù)失敗"
            loadingInd.stopAnimating()
            scanButton.isEnabled = true
            return
        }
        else {
            for characteristic in service.characteristics! {
                peripheral.setNotifyValue(true, for: characteristic)
                
                if (characteristic.uuid.uuidString == BATTERY) {
                    peripheral.readValue(for: characteristic)
                }
                else if (characteristic.uuid.uuidString == DEVICE) {
                    peripheral.readValue(for: characteristic)
                }
                else if (characteristic.uuid.uuidString == VIBRATE) {
                    theVibrator = characteristic
                }
            }
        }
    }
    
    // 掃描到具體設(shè)備
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if ((error) != nil) {
            statusLabel.text = "從設(shè)備獲取值失敗"
            return
        }
        else {
            if(characteristic.uuid.uuidString == BATTERY) {
                var batteryBytes = [UInt8](characteristic.value!)
                var batteryVal:Int = Int.init(batteryBytes[0])
                self.resultField.text = String.init(format: "%@電量:%d%%\n", resultField.text, batteryVal)
            }
            loadingInd.stopAnimating()
            scanButton.isEnabled = true
            statusLabel.text = "信息掃描完成!"
            if (isVibrating) {
                vibrateAction(Any)
            }
        }
    }
    
    // 與設(shè)備斷開連接
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        statusLabel.text = "設(shè)備已斷開"
        scanButton.isEnabled = true
        if(!isDisconnected) {
            theManager.scanForPeripherals(withServices: nil, options: nil)
        }
    }

再次重提一下我在解決關(guān)于CBCentralManager的State屬性遇到的問題:
CBCentralManager的State屬性在之前是CBCentralManagerState,但是現(xiàn)在變成了CBManagerState,而后者需要iOS10以上才支持。查了StackoverFlow發(fā)現(xiàn)很多人也遇到了同樣的問題,也是蘋果很矛盾的一個用發(fā)。通過測試發(fā)現(xiàn)用switch語句對state屬性判斷可以解決系統(tǒng)版本限制的問題,也是普遍采用的方法。

補(bǔ)充:
小米手環(huán)振動的UUID是2A06,0代表不振,1為短振,2為長振。
其他UUID也均有相關(guān)文章有寫,太多就不一一列舉,可以直接Google之。如果需要的人比較多,我可以稍后撰寫一份對照表。

接下來,部署->調(diào)試即可。功能運行正常。

</br>

一些功能改進(jìn)

前一部分改進(jìn)已經(jīng)放到了上述代碼中,若后期有改進(jìn)將更新此處。


</br>


至此已經(jīng)完成了對第一講知識的復(fù)習(xí),接下來我們將講解對小米手環(huán)其他功能的開發(fā)。最終截稿時完成仿小米手環(huán)APP,并實現(xiàn)各種創(chuàng)意功能。

PS:現(xiàn)在開發(fā)小米手環(huán)可能都是出于情懷了吧?還有沒有必要繼續(xù)做下去呢。如果想要二次開發(fā)的人比較多,可以嘗試做一套SDK方便開發(fā)。

寫文章不易,如果覺得滿意,歡迎大家粉一下我的GitHub,以及動動手指Star一下我的項目,持續(xù)更新需要你的支持!
本人GitHub:https://github.com/Minecodecraft
本項目鏈接:https://github.com/Minecodecraft/MiBandDemo

“小米手環(huán)iOS開發(fā)實戰(zhàn)”系列
小米手環(huán)iOS開發(fā)實戰(zhàn)(一):iOS藍(lán)牙框架CoreBluetooth
小米手環(huán)iOS開發(fā)實戰(zhàn)(二):開發(fā)Demo讓你的手環(huán)振動起來

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

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

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