iOS|iOS App 通过CoreBluetooth(Swift 蓝牙)和Android(低功耗蓝牙BLE)交互。

> 概念

如果你是进来找代码的,那么直接拉到最后!!!
本文概念参考的了Pein_Ju的文章BLE蓝牙开发—Swift版 本文更像是是偏向于在工作中记录和实践性,大佬请随意鄙视。我的代码连接放在最后。
  1. 现在iOS BLE开发一般调用的是CoreBluetooth系统原生库开发的蓝牙4.0以上的低功耗版本,其他连接方式和版本的暂不讨论。
  2. 蓝牙术语:
    * CBCentralManager //系统蓝牙设备管理对象 * CBPeripheral //外围设备 * CBService //外围设备的服务或者服务中包含的服务 * CBCharacteristic //服务的特性 * CBDescriptor //特性的描述符

    关系图如下:

    iOS|iOS App 通过CoreBluetooth(Swift 蓝牙)和Android(低功耗蓝牙BLE)交互。
    文章图片
    关系图
    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
      • 【iOS|iOS App 通过CoreBluetooth(Swift 蓝牙)和Android(低功耗蓝牙BLE)交互。】外设模式 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") { //排除 xxbprint("peripheral.name = \(peripheral.name!)") print("central = \(central)") print("peripheral = \(peripheral)") print("RSSI = \(RSSI)") print("advertisementData = https://www.it610.com/(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 = https://www.it610.com/article/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版

    推荐阅读