Как остановить отключение ios core-bluetooth CBCentralManagerDelegate в ios13 - PullRequest
0 голосов
/ 31 октября 2019

Проблема

Я использую Core-Bluetooth для передачи данных датчика (массив 64-128 байт) на iPad (6-го поколения) с частотой 6 Гц. В ios 12. все работало нормально. После обновления до ios 13 соединение Bluetooth стало невероятно ненадежным. Соединение будет передавать только 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]" до отключения, описанного выше.

Так что может показаться, что по какой-то причине ядро ​​Bluetooth вызывает запрос безопасности и(я предполагаю) задержка вызывает сбой соединения? Странно то, что это приглашение происходит как 19/20 раз, но иногда соединение проходит без запуска запроса, и перед отключением принимается пара буферов.

Связанные проблемы

Существует другой пост о чем-то похожем: как предотвратить запрос разрешения в ios 13, но похоже, что это больше связано с периферийным устройством, основанным на сообщении об ошибке.

Есть пост детализация пропущенного значения в словаре CBCentralManager didDiscover peripheral, но я все равно не использую это значение.

Я определенно храню жесткую ссылку на периферию, так что здесь проблема не в этом. Я также открыл другие приложения, которые позволяют просматривать периферийные серверы Bluetooth 4.0. LightBlue, BLE Scanner и BLE Hero - все показывают сообщение о подключении по Bluetooth, за которым следует отключение от периферийных устройств на моем iPad 6-го поколения.

Моя реализация

Этот проект является частью школьного класса. У меня есть весь код на gitlab. Ниже приведен критический код Bluetooth ios, и проект находится на 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:$0.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:) {$0.rssi >= $1.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 для данных датчика. Периферийный код также находится на gitlab .

Я откатился на несколько известных рабочих версий проекта в git, и после обновления до ios 13 ничего не работает. Я собираюсь попробоватьзапустите код corebluetooth в проекте Mac, чтобы проверить, будет ли он там работать.

Вопрос

Итак, после обновления до ios 13 с ios 12.4 при подключении к периферийному устройству BLE,открывается запрос на соединение Bluetooth, хотя связь Bluetooth 4.0 / BLE. Наряду с этим приглашением соединение прерывается, как только оно запускается, иногда давая мне несколько частичных буферов данных, а иногда ничего не давая.

Соединение BLE не правильно инициировано, и если приглашение для соединения работало правильно, это помешало бы моей способности автоматически подключаться к устройству (датчикам роботов) без ввода данных пользователем.

Если кто-нибудь знает какой-либо параметр в info.plist, чтобы предотвратить появление этого разрываемого соединения, это было бы здорово,Я в недоумении, есть ли простой способ откатить версии IOS? Или каким-то образом заставить Apple INC выпустить аварийный патч, предотвращающий всплывающее окно с низким энергопотреблением core-bluetooth? Или каким-то образом обойти всплывающее окно и позволить пользователю правильно проходить аутентификацию, не нарушая протокол BLE?

Спасибо всем, кто взглянул на это.

1 Ответ

1 голос
/ 02 ноября 2019

У меня может не быть полного решения, но это должно помочь указать правильное направление.

Я предполагаю, что для кода установлен NSBluetoothAlwaysUsageDescription в Info.plist, который теперь требуется в iOS 13. Требуется NSBluetoothPeripheralUsageDescriptionдо iOS 13.

Приглашение, которое вы видите перед тем, как оно исчезнет, ​​вызвано процессом сопряжения и может быть вызвано двумя причинами:

  1. Периферийное устройствопытается инициировать процесс сопряжения ( не рекомендуется Apple в 25.10 Руководства по разработке аксессуаров )

  2. Приложение попыталось получить доступ к характеристике, которая была зашифрована и была отклонена,Это запускает процесс сопряжения и, следовательно, всплывающее предупреждение.

Есть несколько вещей, которые могут привести к разрыву соединения, прежде чем вы сможете нажать кнопку сопряжения.

  • Приложение не держит ссылку CBPeripheral (вы указываете, что это так), и вы бы увидели сообщение журнала неправильного использования API, если бы это было так.

  • Периферийное устройство отключается, прежде чем вы сможете ответить. Это более вероятно, и вы можете прослушать пакеты BLE, чтобы убедиться в этом. Подробнее см. Ниже.

  • Может быть проблема с синхронизацией, когда приложение пытается обработать запросы до завершения процесса сопряжения, что может вызвать отключение. Чтобы проверить эту возможность, добавьте задержку после того, как обслуживание и обнаружение признаков будут завершены перед выполнением дополнительных запросов на чтение / запись / уведомление.

Скорее всего, периферийное устройство отключается, и это может произойти во времяпроцесс обмена ключами, который происходит при работе с защищенными характеристиками в случае несоответствия ключей. Если по какой-то причине ключи меняются (я обычно вижу это с обновлением прошивки, возможно, что-то на периферийной стороне изменилось, возможно, обновление iOS до 13), то такое поведение может произойти. Если вы перейдете в настройки Bluetooth и забудете устройство, ключи будут отброшены, поэтому процесс обмена ключами может снова начать работать. Если это так, то были устаревшие ключи, скорее всего, на стороне iOS, с которыми пришлось пересмотреть условия.

Последний вариант - временно убедиться, что услуги и характеристики не защищены. Я не видел в вашем коде secure: [...], так что я не уверен в этом.

Если ни один из простых вариантов не исправит это, моим следующим шагом будет прослушивание пакетов.

...