如何阻止 ios core-bluetooth CBCentralManagerDelegate 在 ios13 中断开连接

How to stop ios core-bluetooth CBCentralManagerDelegate from disconnecting in ios13

问题

我正在使用核心蓝牙以 6 Hz 的速率将传感器数据(64-128 字节的数组)传输到 iPad(第 6 代)。它在 ios 12 中工作正常。在我更新到 ios 13 后,蓝牙连接变得非常不可靠。该连接一次只能传输 20 个字节,并且会频繁断开连接(调用 centralManager(_ central: CBCentralManager, didDisconnectPeripheral...)并出现错误 Code=0 "Unknown error." UserInfo={NSLocalizedDescription=Unknown error.}.

在调试过程中查看 iPad 屏幕,我注意到每次中央连接并发现我的服务时,都会在断开连接前半秒弹出安全提示“Bluetooth Pairing Request: "<device-name>" would like to pair with your iPad. [Cancel] [Pair]”如上所述。

所以似乎出于某种原因,核心蓝牙正在触发安全提示并且(我猜)延迟导致连接失败?奇怪的是这个提示发生了 19/20 次,但偶尔连接会在没有触发提示的情况下通过,并且在断开连接之前会收到几个缓冲区。

相关问题

another post类似的内容:ios13中的如何防止权限请求,但根据报错信息看来这更多是外设的问题。

a post 详细说明了 CBCentralManager didDiscover peripheral 字典中的一个缺失值,但我并没有使用那个值。

我确实存储了对外围设备的硬引用,所以这不是这里的问题。我还打开了其他应用程序,可以让你浏览bluetooth4.0外设gatt服务器。 LightBlue、BLE Scanner 和 BLE Hero 在我的 iPad 6th gen.

上都显示蓝牙连接提示,然后与外围设备断开连接

我的实现

这个项目是学校的一部分 class。我在 gitlab 上拥有所有代码。 The critical ios bluetooth code is below, and the project is on gitlab.


import Foundation
import CoreBluetooth

struct Peripheral {
    var uuid: String
    var name: String
    var rssi: Double
    var peripheral: CBPeripheral
}

class SensorsComputer: BLECentral {
    // just a rename of below...
}

class BLECentral: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    var manager: CBCentralManager!
    var availablePeripherals: [String: Peripheral]
    var sensorsConnected: Bool
    var ref: CBPeripheral?
    var sensorServiceUUIDs: [String]
    var bleSensorComputerUUID: String
    var data: [String:[UInt8]]
    var dataNames: [String:[String:String]]

    override init() {
        data = [:]
        availablePeripherals = [:]
        sensorsConnected = false
        ref = nil
        sensorServiceUUIDs = ["5CA4"]
        //bleSensorComputerUUID = "096A1CFA-C6BC-A00C-5A68-3227F2C58C06" // builtin is shit
        bleSensorComputerUUID = "33548EF4-EDDE-6E6D-002F-DEEFC0A7AF99" // usb dongle

        dataNames = [
            // service uuids
            "5CA4": [
                // characteristic uuids
                "0000": "LidarRanges",
            ],
        ]

        super.init()
        manager = CBCentralManager(delegate: self, queue: nil)
        self.connect(uuid:bleSensorComputerUUID)
    }

    func scan(){
        let services = dataNames.map { CBUUID(string:[=12=].key) }
        manager.scanForPeripherals(withServices: services, options: nil)
    }

    func connect_internal_(uuid: String) -> Bool{ // TODO known bt device uuids.
        if self.sensorsConnected {
            // do not try to connect if already connected
            return true
        } else {
            print(self.availablePeripherals.count)
            if let found = self.availablePeripherals[uuid] {
                manager.stopScan()
                manager.connect(found.peripheral, options: nil)
                return true
            } else {
                if availablePeripherals.count == 0 {
                    scan()
                }
                print("Error! no peripheral with \(uuid) found!")
                return false
            }
        }
    }

    func connect(uuid: String){
        let queue = OperationQueue()
        queue.addOperation() {
            // do something in the background
            while true {
                //usleep(100000)
                sleep(1)
                OperationQueue.main.addOperation {
                    self.connect_internal_(uuid: self.bleSensorComputerUUID)
                }
            }
        }
    }

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            print("The central is powered on!")
            scan() // automatically start scanning for BLE devices
        default:
            print("The centraol is NOT powered on (state is \(central.state))")
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        var name = ""
        if let name_ = peripheral.name {
            name = name_
        }

        let uuid = peripheral.identifier.uuidString
        let rssi = Double(truncating: RSSI)
        availablePeripherals[peripheral.identifier.uuidString] = Peripheral(uuid: uuid, name: name, rssi: rssi, peripheral: peripheral)
        print(uuid, rssi)
    }

    func getSorted(uuids:Bool = false) -> [Peripheral] {
        let peripherals = Array(availablePeripherals.values)
        return peripherals.sorted(by:) {[=12=].rssi >= .rssi}
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("Central connected!")
        sensorsConnected = true
        peripheral.delegate = self
        var cbuuids:[CBUUID] = []
        for id in sensorServiceUUIDs {
            cbuuids.append(CBUUID(string: id))
        }
        peripheral.discoverServices(cbuuids) // TODO store service uuids somewhere nicer
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        if let e = error {
            print("Central disconnected because \(e)")
        } else {
            print("Central disconnected! (no error)")
        }
        sensorsConnected = false
        availablePeripherals = [:]
    }

    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print("Central failed to connect...")
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let error = error {
            print("Peripheral could not discover services! Because: \(error.localizedDescription)")
        } else {
            peripheral.services?.forEach({(service) in
                print("Service discovered \(service)")
                // TODO characteristics UUIDs known
                peripheral.discoverCharacteristics(nil, for: service)
            })
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let error = error {
            print("Could not discover characteristic because error: \(error.localizedDescription)")
        } else {
            service.characteristics?.forEach({ (characteristic) in
                print("Characteristic: \(characteristic)")
                if characteristic.properties.contains(.notify){
                    peripheral.setNotifyValue(true, for: characteristic)
                }
                if characteristic.properties.contains(.read){
                    peripheral.readValue(for: characteristic)
                }
                if characteristic.properties.contains(.write){
                    peripheral.writeValue(Data([1]), for: characteristic, type: .withResponse)
                }
            })

        }
    }


    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print("error after didUpdateValueFor characteristic \(characteristic): \(error.localizedDescription)")
        } else {
            let sname = characteristic.service.uuid.uuidString
            let cname = characteristic.uuid.uuidString
            guard let _dname = dataNames[sname], let dataName = _dname[cname] else {
                return
            }
            //print("value of characteristic \(cname) in service \(sname) was updated.")
            if let value = characteristic.value {
                let value_arr = Array(value)
                //print("    The new data value is \(value_arr.count) bytes long")
                //print("dataname is \(dataName)")
                self.data[dataName] = value_arr
            }
        }
    }
}

BLE外设代码是一个基于bleno和rosnodejs的传感器数据节点项目。 The peripheral code is also on gitlab.

我已经回滚到 git 中几个已知的工作项目版本,更新到 ios 13 后似乎没有任何工作。我将尝试 运行 corebluetooth mac 项目上的代码以测试它是否可以在那里工作。

问题

所以,综上所述,从ios12.4更新到ios13后,当连接到BLE外围设备时,即使通信是蓝牙4.0 / BLE,也会打开蓝牙连接提示。随着这个提示,连接一启动就断开,有时给我一些部分数据缓冲区,有时什么都不给我。

BLE 连接未正确启动,如果连接提示正常工作,它会干扰我在没有用户输入的情况下自动连接到设备(机器人传感器)的能力。

如果有人知道 info.plist 中的某些选项可以防止出现此中断连接提示,那就太好了。我在这里不知所措,有没有简单的方法可以回滚 ios 版本?或者有什么方法可以让 Apple INC 发布紧急补丁,防止核心蓝牙低功耗的弹出窗口?或者有什么方法可以解决弹出窗口并允许用户在不破坏 BLE 协议的情况下正确进行身份验证?

感谢任何看过这篇文章的人。

我可能没有完整的解决方案,但这应该有助于指明正确的方向。

我假设代码在 Info.plist 中设置了 NSBluetoothAlwaysUsageDescription,现在 iOS 13 中需要它。在 iOS 13.

之前需要 NSBluetoothPeripheralUsageDescription

您在消失前看到的提示是由配对过程引起的,可能由两件事触发:

  1. 外设尝试启动配对过程(discouraged by Apple in 25.10 of Accessory Design Guidelines)

  2. 该应用试图访问已加密但已被拒绝的特征。这会触发配对过程,从而触发弹出的警报。

在点击“配对”按钮之前,有一些情况可能会导致连接断开。

  • 该应用程序未保留 CBPeripheral 引用(您表示它是),如果是这种情况,您会看到 API 误用日志消息。

  • 在您有机会响应之前,外围设备正在断开连接。这更有可能,您可以嗅探 BLE 数据包以确定。更多详情如下。

  • 可能存在计时问题,应用程序在配对过程完成之前尝试继续请求,这可能会触发断开连接。要测试这种可能性,请在服务和特征发现完成后添加延迟,然后再发出其他 read/write/notify 请求。

外围设备很可能正在断开连接,这可能发生在密钥交换过程中,如果密钥不匹配,则在使用安全特性时发生。如果密钥因某种原因发生变化(我通常会在固件更新时看到它,也许外围设备端发生了某些变化,可能 iOS 更新为 13),则可能会发生此行为。如果您转到蓝牙设置并忘记了设备,密钥将被丢弃,因此密钥交换过程可能会再次开始工作。如果是这样,则有过时的密钥,很可能在必须重新协商的 iOS 端。

最后的选择是暂时确保服务和特性不受保护。我没有在你的代码中看到 secure: [...] ,所以我不确定。

如果 none 的简单选项修复了它,我的下一步将是嗅探数据包。