小米手環(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)然如果你是大旁醮叮可以放心跳讀谭企。


章節(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è)備的信號強度
  • 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)版本限制的問題,也是普遍采用的方法鲫售。

補充:
小米手環(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)系作者
  • 序言:七十年代末敛腌,一起剝皮案震驚了整個濱河市卧土,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌像樊,老刑警劉巖夸溶,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凶硅,居然都是意外死亡,警方通過查閱死者的電腦和手機扫皱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進(jìn)店門足绅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人韩脑,你說我怎么就攤上這事氢妈。” “怎么了段多?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵首量,是天一觀的道長。 經(jīng)常有香客問我进苍,道長加缘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任觉啊,我火速辦了婚禮拣宏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杠人。我一直安慰自己勋乾,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布嗡善。 她就那樣靜靜地躺著辑莫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪罩引。 梳的紋絲不亂的頭發(fā)上各吨,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天,我揣著相機與錄音蜒程,去河邊找鬼绅你。 笑死伺帘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的忌锯。 我是一名探鬼主播伪嫁,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼偶垮!你這毒婦竟也來了张咳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤似舵,失蹤者是張志新(化名)和其女友劉穎脚猾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砚哗,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡龙助,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛛芥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片提鸟。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖仅淑,靈堂內(nèi)的尸體忽然破棺而出称勋,到底是詐尸還是另有隱情,我是刑警寧澤涯竟,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布赡鲜,位于F島的核電站,受9級特大地震影響庐船,放射性物質(zhì)發(fā)生泄漏银酬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一筐钟、第九天 我趴在偏房一處隱蔽的房頂上張望捡硅。 院中可真熱鬧,春花似錦盗棵、人聲如沸壮韭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喷屋。三九已至,卻和暖如春瞭恰,著一層夾襖步出監(jiān)牢的瞬間屯曹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留恶耽,地道東北人密任。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像偷俭,于是被迫代替她去往敵國和親浪讳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,937評論 2 361

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