美文网首页人猿星球
iOS App 通过CoreBluetooth(Swift 蓝牙

iOS App 通过CoreBluetooth(Swift 蓝牙

作者: 韩大熊宝要姓张 | 来源:发表于2018-01-18 16:56 被阅读129次

    > 概念

    如果你是进来找代码的,那么直接拉到最后!!!
    本文概念参考的了Pein_Ju的文章BLE蓝牙开发—Swift版 本文更像是是偏向于在工作中记录和实践性,大佬请随意鄙视😅。我的代码连接放在最后。

    1. 现在iOS BLE开发一般调用的是CoreBluetooth系统原生库开发的蓝牙4.0以上的低功耗版本,其他连接方式和版本的暂不讨论。

    2. 蓝牙术语:

      * CBCentralManager //系统蓝牙设备管理对象
      * CBPeripheral //外围设备
      * CBService //外围设备的服务或者服务中包含的服务
      * CBCharacteristic //服务的特性
      * CBDescriptor //特性的描述符
      

      关系图如下:


      关系图
      1. 模式 & 步骤
        • 中心模式 Client

          1. 建立中心角色 CBCentralManager
          2. 扫描外设 cancelPeripheralConnection
          3. 发现外设 didDiscoverPeripheral
          4. 连接外设 connectPeripheral
          5. 扫描外设中的服务 discoverServices
          6. 发现并获取外设中的服务 didDiscoverServices
          7. 扫描外设对应服务的特征 discoverCharacteristics
          8. 发现并获取外设对应服务的特征 didDiscoverCharacteristicsForService
          9. 给对应特征写数据 writeValue:forCharacteristic:type:
          10. 订阅特征的通知 setNotifyValue:forCharacteristic:
          11. 根据特征读取数据 didUpdateValueForCharacteristic
        • 外设模式 Server --->

          1. 建立外设角色
          2. 设置本地外设的服务和特征
          3. 发布外设和特征
          4. 广播服务
          5. 响应中心的读写请求
          6. 发送更新的特征值,订阅中心
          • Android提供服务参考这里
          • iOS也可以作为外设(Server)参考这里

    > Tips

    • 用上面的方式进行扫描后能获得的设备是正在广播的设备。这就可能和系统的列表不一样,连接的时候需要Android作为Server。
    • 需要注意外设,服务,特征之间的uuid,断线重连是用的Peripherals的uuid不要弄混了😂

    > 具体连接步骤

    方式1 原生连接
    1.实现代理及代理方法 CBCentralManagerDelegate,CBPeripheralDelegate
    2.在代理方法 centralManagerDidUpdateState 中检测到蓝牙设备的状态是poweredOn 才能开始扫描设备,要不然找不到~

        func centralManagerDidUpdateState(_ central: CBCentralManager) {
            
            tempInputView.text = "初始化对象后,来到centralManagerDidUpdateState"
            
            switch central.state {
            case .unknown:
                print("CBCentralManager state:", "unknown")
                break
            case .resetting:
                print("CBCentralManager state:", "resetting")
                break
            case .unsupported:
                print("CBCentralManager state:", "unsupported")
                break
            case .unauthorized:
                print("CBCentralManager state:", "unauthorized")
                break
            case .poweredOff:
                print("CBCentralManager state:", "poweredOff")
                break
            case .poweredOn:
                print("CBCentralManager state:", "poweredOn")
                //MARK: -3.扫描周围外设(支持蓝牙)
                // 第一个参数,传外设uuid,传nil,代表扫描所有外设
                self.addInputString(str: "开始扫描设备")
                central.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber.init(value: false)])
            }
        }
    
    

    3.发现设备回调的方法是:

        func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
            
            print("=============================start")
            
            if (peripheral.name != nil && peripheral.name! != "xxb") { //排除 xxb
                
                print("peripheral.name = \(peripheral.name!)")
                print("central = \(central)")
                print("peripheral = \(peripheral)")
                print("RSSI = \(RSSI)")
                print("advertisementData = \(advertisementData)")
                
                deviceList.append(peripheral)
                
                tableView.reloadData()
            }
            print("=============================end")
    
        }
    

    注意:这里面是每寻找到一个设备就会回调一次这个方法。

    4.选中列表中其中一个点击进行连接:

    self.addInputString(str: "链接设备")
                central.stopScan()
                central.cancelPeripheralConnection(p)
                central.connect(p, options: nil)
    

    5.连接成功,连接失败的回调,其中连接成功了会记录对应的外设并且开始寻找服务

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
            //设备链接成功
            self.addInputString(str: "链接成功=====>\(peripheral.name ?? "~~")")
            peripheralSelected = peripheral
            peripheralSelected!.delegate = self
            peripheralSelected!.discoverServices(nil) // 开始寻找Services。传入nil是寻找所有Services
        }
        
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
            //设备链接失败
            self.addInputString(str: "链接失败=====>\(peripheral.name ?? "~~")")
            
        }
    

    6.寻找到对应的服务特征会回调到peripheral didDiscoverServices 如果这里有和后台商量好的对一个的service可以做判断,当前demo是传入了nil,发现所有service,并且利用前面保存好的外设去调用发现特征,这里传入nil,和前文的意思相同。

        //请求周边去寻找他的服务特征
        func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
            
            if error != nil {
                self.addInputString(str: "didDiscoverServices error ====> \(error.debugDescription) ")
                return
            }
        
            guard let serArr = peripheral.services else {
                self.addInputString(str: "Peripheral services is nil ")
                return
            }
            
          
            for ser in serArr {
                
                self.addInputString(str: "服务的UUID \(ser.uuid)")
                self.peripheralSelected!.discoverCharacteristics(nil, for: ser)
            }
    
            self.addInputString(str: "Peripheral 开始寻找特征 ")
            
        }
    

    7.peripheral外设搜索服务后对应的特征回调信息方法是:peripheral didDiscoverCharacteristicsFor

     //找特征的回调
        func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
            
            if error != nil { self.addInputString(str: "服务的回调error \(error.debugDescription)");return}
            
            guard let serviceCharacters = service.characteristics else {
                self.addInputString(str: "service.characteristics 为空")
                return
            }
            
            for characteristic in serviceCharacters {
                self.addInputString(str: "--------------------------characteristic")
                self.addInputString(str: "特征UUID \(characteristic.uuid)")
                self.addInputString(str: "uuidString \(characteristic.uuid.uuidString)")
                peripheralSelected!.setNotifyValue(true, for: characteristic) //接受通知
                //判断类型 <=========> 有问题的。
                /*
                 CBCharacteristicPropertyBroadcast                                                = 0x01,
                 CBCharacteristicPropertyRead                                                    = 0x02,
                 CBCharacteristicPropertyWriteWithoutResponse                                    = 0x04,
                 CBCharacteristicPropertyWrite                                                    = 0x08,
                 CBCharacteristicPropertyNotify                                                    = 0x10,
                 CBCharacteristicPropertyIndicate                                                = 0x20,
                 CBCharacteristicPropertyAuthenticatedSignedWrites                                = 0x40,
                 CBCharacteristicPropertyExtendedProperties                                        = 0x80,
                 CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0)    = 0x100,
                 CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0)    = 0x200
                */
                self.addInputString(str: "characteristic.properties --> \(characteristic.properties)")
                
                switch characteristic.properties {
    
                case CBCharacteristicProperties.write:
                    self.addInputString(str: "characteristic ===> write")
                    writeValue(characteristic) //写入数据
                    tempCBCharacteristic = characteristic //给个全局的点,
                    continue
                case CBCharacteristicProperties.writeWithoutResponse:
                    self.addInputString(str: "characteristic ===> writeWithoutResponse")
                    continue
                case CBCharacteristicProperties.read:
                    self.addInputString(str: "characteristic ===> read")
                    continue
                case CBCharacteristicProperties.notify:
                    self.addInputString(str: "characteristic ===> notify")
                    continue
                case CBCharacteristicProperties.indicate:
                    self.addInputString(str: "characteristic ===> indicate") //获取本身的权限
                    /*
                    let f = UInt8(characteristic.properties.rawValue) & UInt8(CBCharacteristicProperties.write.rawValue)
                    if f == CBCharacteristicProperties.write.rawValue { //判断本身有没有写的权限
                        self.addInputString(str: "characteristic ===> in indicate test write")
                        writeValue(characteristic) //写入数据
                        tempCBCharacteristic = characteristic //给个全局的点,
                    }
                    */
                    continue
                case CBCharacteristicProperties.authenticatedSignedWrites:
                    self.addInputString(str: "characteristic ===> authenticatedSignedWrites")
                    continue
                case CBCharacteristicProperties.extendedProperties:
                    self.addInputString(str: "characteristic ===> extendedProperties")
                    continue
                case CBCharacteristicProperties.notifyEncryptionRequired:
                    self.addInputString(str: "characteristic ===> notifyEncryptionRequired")
                    continue
                case CBCharacteristicProperties.indicateEncryptionRequired:
                    self.addInputString(str: "characteristic ===> indicateEncryptionRequired")
                    
                default:
                    self.addInputString(str: "characteristic ===> default")
                    let f = UInt8(characteristic.properties.rawValue) & UInt8(CBCharacteristicProperties.write.rawValue)
                
                    if f == CBCharacteristicProperties.write.rawValue { //判断本身有没有写的权限 这个可能是综合的 ---> 注意 16进制的转换问题~
                        self.addInputString(str: "characteristic ===> default --test-- write")
                        
                        tempCBCharacteristic = characteristic //给个全局的点,
                        self.addInputString(str: "连接成功,设置全局characteristic设置成功,可以发送数据")
                    }
                }
            }
        }
        
    

    ps: 这个里面的代码主要是做了一个判断,因为是demo,我全部都写上了,可以根据实际情况进行筛选,比方说只需要可以读的就显示一个.read就可以了~

    注意:这里面的default操作,回调的characteristic.properties可能是一个复合信息,以位运算的形式返回,这里面使用了位操作判断是否支持读写。找到后保存了一个全局的characteristic。 最好将characteristic设置成接收notify,这样后面能接收到发送数据的回调信息。代码:peripheralSelected!.setNotifyValue(true, for: characteristic)

    8.有了全局的characteristic 就可以发送信息了。

      func writeValue(_ Characteristic: CBCharacteristic) {
            
            let string = inputTextField.text ?? "~测试数据"
            let data = string.data(using: .utf8)
            self.addInputString(str: "写入测试数据 ==> ")
            peripheralSelected!.writeValue(data!, for: Characteristic, type: CBCharacteristicWriteType.withResponse)
        }
    

    9.接收Notification 和 服务器回传的数据 :

    // 获取外设发来的数据
        func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
            print("接到服务端发送的数据")
    
            if (characteristic.value != nil) {
                print("开始解析数据")
                let str = String.init(data: characteristic.value!, encoding: .utf8)
                print(str)
                receiveMessage.text = receiveMessage.text + "\n" + (str ?? "~")
            }
        }
        //接收characteristic信息
        func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
            print("接收characteristic信息")
        }
    

    > 另一种连接方式:第三方库,这个库是OC写的。利用点语法,挺方便。

    BabyBluetooth
    SimpleCoreBluetooth (自己用swift封装的。)

    如果作为服务端Server请参考
    Android
    iOS

    本文代码

    参考资料:
    Swift语言iOS8的蓝牙Bluetooth解析
    Pein_Ju的文章BLE蓝牙开发—Swift版

    相关文章

      网友评论

      本文标题:iOS App 通过CoreBluetooth(Swift 蓝牙

      本文链接:https://www.haomeiwen.com/subject/uvnsoxtx.html