iOS低功耗蓝牙(Corebluetooth)开发(swift版)

现在公司手头的项目基本上是基于蓝牙的开发,所以对低功耗蓝牙还是有一定的了解,今天特意把我写的蓝牙模块儿整理出来,供大家参考。


一 : 初识低功耗蓝牙

1, 中心(central)和外设(peripheral)

刚接触蓝牙开发的时候,找了许多专业的资料,讲到蓝牙开发分为两种模式,中心模式(central),和外设模式(peripheral)。云里雾里,以至于搞了两三天都不知道自己做的东西到底是做为中心还是做为外设。其实远远没有那么复杂,一般来讲,我们做的需要在软件内连接硬件,通过连接硬件给硬件发送指令以完成一些动作的蓝牙开发都是基于中心模式(central)模式的开发,也就是说我们开发的app是中心,我们要连接的硬件是外设。外设有个特点是它会一直广播自己,告诉中心我在这里,我在这里,快来连我---,这个时候呢,我们的中心就可以通过扫描发现并且点击连接了。

2,服务(service)和特征(character)

我们通过蓝牙连接了硬件,无非就是想通过连接硬件的读取一些信息,例如硬件通过温度传感器获取的环境温度,或者湿度传感器获取的环境湿度,又或者是硬件自己的电量等等。或者是给这个设备发送一些指令以达到控制硬件行为的目的,例如我们的app连接了空调,想发送指令过去改变空调的预设温度,或者发送指令给共享单车,让单车的锁解开。我们的app中心与设备之间的通信需要一个桥梁,这个桥梁就是服务与特征。一个设备中可能包含很多服务,而一个服务可能又包含几个特征。比方说,标准蓝牙模块会有几个内置的服务与特征如温度,心跳,等等。


蓝牙标准的服务.png

这些服务与特征都是硬件工程师可以直接拿来用的。如果有其他特殊的需求,硬件工程师也可以再重新自己定义服务与特征。


外设服务与特征的关系.png
3,读(read) , 写(write), 订阅(notify)

我们的目的是读取设备中的数据(read) , 或者给设备写入一定的数据(write)。有时候我们还想设备的数据变化的时候不需要我们手动去读取这个值,需要设备自动通知我们它的值变化了,值是多少。把值告诉app,这个时候就需要订阅这个特征了(notify)


二 : app做为中心(central)编码

鉴于大部分需求都是以app做为中心,硬件设备做为外设,所以这里只针对中心模式做详细介绍。
一般来讲,都要求我们的app时刻与硬件保持连接,当然了,这里的时刻保持连接指的是物理上的,我们的app能看到的是当前设备处于连接状态。低功耗蓝牙内部的实现绝对不是时刻保持连接,否则也不能称之为低功耗蓝牙了。Corebluetooth框架内部app做为中心的情况下蓝牙管理有一个中心类,叫CBCentralManager,我们叫它中心管理类。我们手机的蓝牙开启状态,手机与蓝牙设备等连接,断开连接,连接失败等等都由这个中心管理类来控制。为了这个中心管理类在app的生命周期都存在。我们一般把它进行封装,做成一个单例。这样app整个生命周期中这个单例时刻存在。下面是封装的蓝牙单例代码。

import Foundation
import CoreBluetooth

//用于看发送数据是否成功!
class LLBlueTooth:NSObject {
    
    //单例对象
    internal static let instance = LLBlueTooth()
    
    //中心对象
    var central : CBCentralManager?
    
    //中心扫描到的设备都可以保存起来,
    //扫描到新设备后可以通过通知的方式发送出去,连接设备界面可以接收通知,实时刷新设备列表
    var deviceList: NSMutableArray?
    
    // 当前连接的设备
    var peripheral:CBPeripheral!
    
    //发送数据特征(连接到设备之后可以把需要用到的特征保存起来,方便使用)
    var sendCharacteristic:CBCharacteristic?
    
    
    override init() {
        
        super.init()
        
        self.central = CBCentralManager.init(delegate:self, queue:nil, options:[CBCentralManagerOptionShowPowerAlertKey:false])
        
        self.deviceList = NSMutableArray()
        
    }

    
    // MARK: 扫描设备的方法
    func scanForPeripheralsWithServices(_ serviceUUIDS:[CBUUID]?, options:[String: AnyObject]?){
        
        self.central?.scanForPeripherals(withServices: serviceUUIDS, options: options)
        
    }
    
    
    // MARK: 停止扫描
    func stopScan() {
        
        self.central?.stopScan()
        
    }
    
    // MARK: 写数据
    func writeToPeripheral(_ data: Data) {
        peripheral.writeValue(data , for: sendCharacteristic!, type: CBCharacteristicWriteType.withResponse)
    }
    
    
    // MARK: 连接某个设备的方法
    /*
     *  设备有几个状态
     @available(iOS 7.0, *)
     public enum CBPeripheralState : Int {
         case disconnected
         
         case connecting
         
         case connected
         
         @available(iOS 9.0, *)
         case disconnecting
     }
     */
    func requestConnectPeripheral(_ model:CBPeripheral) {
        
        if (model.state != CBPeripheralState.connected) {
            
            central?.connect(model , options: nil)
            
        }
        
    }
    
}


//MARK: -- 中心管理器的代理
extension LLBlueTooth : CBCentralManagerDelegate{
    
    // MARK: 检查运行这个App的设备是不是支持BLE。
    func centralManagerDidUpdateState(_ central: CBCentralManager){
        
        if #available(iOS 10.0, *) {
            switch central.state {
                
            case CBManagerState.poweredOn:
                print("蓝牙打开")
                
            case CBManagerState.unauthorized:
                print("没有蓝牙功能")
                
            case CBManagerState.poweredOff:
                print("蓝牙关闭")
                
            default:
                print("未知状态")
            }
        }
        // 手机蓝牙状态发生变化,可以发送通知出去。提示用户
        
    }
    
    
    // 开始扫描之后会扫描到蓝牙设备,扫描到之后走到这个代理方法
    // MARK: 中心管理器扫描到了设备
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        
        //  在这个地方可以判读是不是自己本公司的设备,这个是根据设备的名称过滤的
        guard peripheral.name != nil , peripheral.name!.contains("*****") else {
            return
        }
        
        //  这里判断重复,加到devielist中。发出通知。
        
    }
    
    
    // MARK: 连接外设成功,开始发现服务
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral){
        
        // 设置代理
        peripheral.delegate = self
        
        // 开始发现服务
        peripheral.discoverServices(nil)
        
        // 保存当前连接设备
        self.peripheral = peripheral
        
        // 这里可以发通知出去告诉设备连接界面连接成功
        
    }
    
    // MARK: 连接外设失败
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        
        // 这里可以发通知出去告诉设备连接界面连接失败
        
    }
    
    // MARK: 连接丢失
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        NotificationCenter.default.post(name: Notification.Name(rawValue: "DidDisConnectPeriphernalNotification"), object: nil, userInfo: ["deviceList": self.deviceList as AnyObject])

        // 这里可以发通知出去告诉设备连接界面连接丢失
        
    }
    
}


// 外设的代理
extension LLBlueTooth : CBPeripheralDelegate {
    
    //MARK: - 匹配对应服务UUID
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?){
        
        if error != nil {
            return
        }
        
        for service in peripheral.services! {
            peripheral.discoverCharacteristics(nil, for: service )
        }
        
    }
    
    //MARK: - 服务下的特征
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?){
        
        if (error != nil){
            return
        }
        
        for  characteristic in service.characteristics! {
            
            switch characteristic.uuid.description {
                
            case "A28DA977":
                // 订阅特征值,订阅成功后后续所有的值变化都会自动通知
                peripheral.setNotifyValue(true, for: characteristic)
            case "******":
                // 读区特征值,只能读到一次
                peripheral.readValue(for:characteristic)
            default:
                print("扫描到其他特征")
            }
            
        }
        
    }
    
    //MARK: - 特征的订阅状体发生变化
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?){
        
        guard error == nil  else {
            return
        }
        
    }
    
    // MARK: - 获取外设发来的数据
    // 注意,所有的,不管是 read , notify 的特征的值都是在这里读取
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)-> (){
        
        if(error != nil){
            return
        }
        
        switch characteristic.uuid.uuidString {
            
        case "***************":
            print("接收到了设备的温度特征的值的变化")
        default:
            print("收到了其他数据特征数据: \(characteristic.uuid.uuidString)")
        }
        
    }
    
    
    
    //MARK: - 检测中心向外设写数据是否成功
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        
        if(error != nil){
            print("发送数据失败!error信息:\(String(describing: error))")
        }
        
    }
    
}

如果有没有写清楚的地方,欢迎大家提问和补充。

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

推荐阅读更多精彩内容