小米手环iOS开发实战(二):开发Demo让你的手环振动起来

小米手环iOS开发实战(二):开发Demo让你的手环振动起来

上一节讲了CoreBluetooth的使用,理论知识很枯燥,那么现在先利用上一节讲的内容,做一个简易手环应用,实现连接/断开手环,查看手环UUID、查看电量信息,并让振动的功能。
本节知识默认大家掌握iOS的基础控件,掌握通过storyboard或代码搭建界面UI,能够利用Swift或Objective-C编写程序。文章会尽量详细讲解这些过程,当然如果你是大牛可以放心跳读。


章节目录

  • 蓝牙连接所涉及到的类
  • 小米手环Demo应用的开发
  • 一些功能优化

蓝牙连接所涉及到的类

上一节讲了怎么用CoreBluetooth,本节讲一下所涉及到的类,及常用的成员函数和成员变量,其他方法请见苹果开发文档。

CBCentralManager
此类为中心设备类,用于控制作为中心设备时的行为

  • state:获取当前中心设备状态
  • isScanning:当前中心设备是否在扫描外围设备
  • stopScan():停止扫描外围设备
  • scanForPeripherals(...):扫描外围设备(请确保蓝牙开启)
  • connect(...):连接外围设备(需要先扫描到外围设备)
  • cancelPeripheralConnection(...):断开外围设备

CBPeripheral
此类为外围设备类,用于对外围设备进行管理

  • name:获取外围设备的名称
  • rssi:获取当前外围设备的信号强度
  • state:获取外围设备的状态(disconnected/connecting/connected)
  • services:获取外围设备所提供的服务(需要先扫描到服务)
  • discoverServices(...):扫描设备所提供的服务
  • discoverCharacteristics(...):扫描特征值(需要先获取服务)
  • readValue(...):读取特征值所对应的值(需要先获取到特征值,同时要注意此方法不反回值,要用协议的didUpdateValueFor characteristic方法处理)

是不是已经懵了?在此做一个图大致描述一下流程,其实这些方法的调用还是很有规律的。


CoreBluetooth调用流程

CBCharacteristic
外围设备服务的特征值

  • Value:获取特征值对应的值


</br>

小米手环Demo应用的开发

本Demo是对上一节所讲CoreBluetooth的操作复习,每个方法的实现已经有所解释,故在此不再赘述。如果有疑问,欢迎在评论区提问及讨论。
该Demo所要实现的功能:练习连接设备、断开设备、读取手环信息、让手环振动。具体涉及到的知识点为连接和断开设备、获取设备服务和特征值、获取特征值对应的信息以及对其写入。

  • 界面搭建
    方便起见,该项目直接采用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!
  • 设置蓝牙操作过程所需对象
    涉及到的类在第一讲已经讲解,如果有不明白的,可以查阅前面的讲解。
    var theManager: CBCentralManager!
    var thePerpher: CBPeripheral!
    var theVibrator: CBCharacteristic!
  • CoreBluetooth协议方法的实现
    本部分内容在第一讲已经涉及,如果有不明白的,可以查阅前面的讲解。
    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 = "设备已断开"
            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
        }
    }
    
    
    // 处理当前蓝牙主设备状态
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            statusLabel.text = "蓝牙已开启"
            self.scanButton.isEnabled = true
        default:
            statusLabel.text = "蓝牙未开启!"
            self.loadingInd.stopAnimating()
        }
    }
    
    // 扫描到设备
    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: "发现手环\n名称:%@\nUUID:%@\n", peripheral.name!, peripheral.identifier.uuidString)
    }
    
    // 成功连接到设备
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        statusLabel.text = "连接成功,正在扫描信息..."
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }
    
    // 连接到设备失败
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        loadingInd.stopAnimating()
        statusLabel.text = "连接设备失败"
        scanButton.isEnabled = true
    }
    
    // 扫描服务
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if ((error) != nil) {
            statusLabel.text = "查找服务失败"
            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 = "查找服务失败"
            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
                }
            }
        }
    }
    
    // 扫描到具体设备
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if ((error) != nil) {
            statusLabel.text = "从设备获取值失败"
            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)
            }
        }
    }
    
    // 与设备断开连接
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        statusLabel.text = "设备已断开"
        scanButton.isEnabled = true
        if(!isDisconnected) {
            theManager.scanForPeripherals(withServices: nil, options: nil)
        }
    }

再次重提一下我在解决关于CBCentralManager的State属性遇到的问题:
CBCentralManager的State属性在之前是CBCentralManagerState,但是现在变成了CBManagerState,而后者需要iOS10以上才支持。查了StackoverFlow发现很多人也遇到了同样的问题,也是苹果很矛盾的一个用发。通过测试发现用switch语句对state属性判断可以解决系统版本限制的问题,也是普遍采用的方法。

补充:
小米手环振动的UUID是2A06,0代表不振,1为短振,2为长振。
其他UUID也均有相关文章有写,太多就不一一列举,可以直接Google之。如果需要的人比较多,我可以稍后撰写一份对照表。

接下来,部署->调试即可。功能运行正常。

</br>

一些功能改进

前一部分改进已经放到了上述代码中,若后期有改进将更新此处。


</br>


至此已经完成了对第一讲知识的复习,接下来我们将讲解对小米手环其他功能的开发。最终截稿时完成仿小米手环APP,并实现各种创意功能。

PS:现在开发小米手环可能都是出于情怀了吧?还有没有必要继续做下去呢。如果想要二次开发的人比较多,可以尝试做一套SDK方便开发。

写文章不易,如果觉得满意,欢迎大家粉一下我的GitHub,以及动动手指Star一下我的项目,持续更新需要你的支持!
本人GitHub:https://github.com/Minecodecraft
本项目链接:https://github.com/Minecodecraft/MiBandDemo

“小米手环iOS开发实战”系列
小米手环iOS开发实战(一):iOS蓝牙框架CoreBluetooth
小米手环iOS开发实战(二):开发Demo让你的手环振动起来

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容