iOS 11 Core Bluetooth restoration not working

Viewed 1288

I have an iOS app that uses Core Bluetooth to connect to a V.BTTN smart button. Everything worked perfectly fine in iOS 10, but since iOS 11 has come out the restoration process seems to break.

When I launch my app, with a previously paired button, the OS does in fact call the centralManager:willRestoreState, and the accompanying dictionary includes a pointer to a CBPeripheral object that has a status of connected. Just as it did back in iOS 10. However, the problem I am running into is when I call the discoverServices on the peripheral I am never returned any services in the peripheral:didDiscoverServices method. In fact that method, nor any other method, is called at all.

I have been searching all of the internet and have found people having similar issues with button pairing an connections, but those have generally been issues with improper lifecycle management of the peripheral object. I believe I have everything setup correct and at a loss. Does anyone have any idea what could be going on here?

import Foundation
import CoreBluetooth

class SmartButtonManager: NSObject {

    //MARK: - Singleton

    static let sharedManager = SmartButtonManager()

    //MARK: - Properties

    var discoveredDevices: [CBPeripheral] {
        get {
            return perfs
        }
    }

    fileprivate(set) var isBluetoothOn = false

    //MARK: - Private Constants & Variables

    fileprivate var connectedButtonUUID: String?

    fileprivate let queue = DispatchQueue(label: "V.BTTN", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)

    fileprivate var manager: CBCentralManager?

    fileprivate var perfs = [CBPeripheral]()
    fileprivate var connectedPerf: CBPeripheral?

    fileprivate var isButtonReady = false

    //MARK: - Initialization

    override init() {
        super.init()
    }

    //MARK: - Configure

    func configure(withLaunchOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) {

        // create new central manager
        manager = CBCentralManager(delegate: self, queue: queue, options: [CBCentralManagerOptionRestoreIdentifierKey: managerIdentifier])
    }
}

//MARK: - V.BTTN

extension SmartButtonManager: CBCentralManagerDelegate, CBPeripheralDelegate {

    func scanForVbttn() {

        perfs.removeAll()
        manager?.scanForPeripherals(withServices: services, options: nil)
    }

    func centralManagerDidUpdateState(_ central: CBCentralManager) {

        switch central.state {

        case .poweredOff:
            print("[centralManagerDidUpdateState] CB BLE hardware is powered off")
            perfs.removeAll()
            isBluetoothOn = false

        case .poweredOn:
            print("[centralManagerDidUpdateState] CB BLE hardware is powered on. Start scanning for peripherals")
            isBluetoothOn = true

        case .unauthorized:
            print("[centralManagerDidUpdateState] CB BLE hardware is not authorized")

        case .unsupported:
            print("[centralManagerDidUpdateState] CB BLE hardware is not supported")
            isBluetoothOn = false

        default:
            print("[centralManagerDidUpdateState] CB BLE hardware state is unknown")
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {

        print("[centralManager:didDiscover peripheral:advertisementData] CB BLE did discover peripheral with advertisement data: \(advertisementData)")

        guard let perfName = advertisementData[CBAdvertisementDataLocalNameKey] as? String else {
            print("[centralManager:didDiscover peripheral:advertisementData] peripheral name is unknown")
            return
        }

        if perfName.contains("V.ALRT") {

            peripheral.delegate = self
            perfs.append(peripheral)

            if connectedButtonUUID == perfName {
                connect(peripheral: peripheral)
            }
        }
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {

        print("[central:didConnect peripheral:] CB BLE hardware did connect")
        handleDidConnect(toPeripheral: peripheral)
    }

    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {

        print("[central:didFailToConnect error:] CB BLE peripheral did failed to connect with error: \(String(describing: error))")
        connectedPerf = nil
        isButtonReady = false
        connectedButtonUUID = nil
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {

        print("[central:didDisconnectPeripheral peripheral:] CB BLE peripheral did disconnect")
        connectedPerf = nil
        isButtonReady = false
        connectedButtonUUID = nil
    }

    func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {

        print("[central:willRestoreState dict:] CB BLE hardware will restore state")
        print("\(dict)")

        guard let ps = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else {
            print("[central:willRestoreState dict:] No perfs to restore")
            return
        }

        print("[central:willRestoreState dict:] Will restore perfs")

        perfs = ps

        print("[central:willRestoreState dict:] Attempt to reconnect to V.BTTN")
        print("[central:willRestoreState dict:] perfs \(perfs)")

        for p in perfs {
            if p.name == connectedButtonUUID {

                print("[central:willRestoreState dict:] Connect to perf \(p)")
                handleDidConnect(toPeripheral: p)
                break
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {

        guard let services = peripheral.services else {
            print("[peripheral:didDiscoverServices error:] CB BLE peripheral unable to discover services")
            return
        }

        print("[peripheral:didDiscoverServices error:] BLE peripheral did discover services")

        for s in services {
            print("[peripheral:didDiscoverServices error:] CB BLE Service \(s.description)")
            peripheral.discoverCharacteristics(nil, for: s)
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {

        print("[peripheral:didDiscoverCharacteristicsFor service:] CB BLE did discover characteristics for service \(service.uuid.description)")

        if compareCBUUID(uuid1: service.uuid, uuid2: CBUUID(string: BLE_VSN_GATT_SERVICE_UUID)) {

            guard let characteristics = service.characteristics else {
                return
            }

            for aChar in characteristics {

                // write the verification key
                if aChar.uuid.isEqual(CBUUID(string: BLE_VERIFICATION_SERVICE_UUID)) {

                    self.writeVerificationKey(forPeripheral: peripheral, characteristic: aChar)
                    self.enable(peripheral: peripheral)

                    break
                }
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {

        //NOTE: This code has been omitted as there is little need to see this
    }

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {

        print("[peripheral:didWriteValueFor characteristic:] characteristic: \(characteristic.uuid)")

        if let e = error {
            print("[peripheral:didWriteValueFor characteristic:] error: \(e)")
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {

        if !characteristic.isNotifying {
            print("[peripheral:didUpdateNotificationStateFor:][Characteristic is not notifiying so cancel the connection]")
            manager?.cancelPeripheralConnection(peripheral)
        }
    }

    //MARK: - Helpers

    fileprivate func writeVerificationKey(forPeripheral peripheral: CBPeripheral, characteristic: CBCharacteristic) {

        print("[peripheral:didDiscoverCharacteristicsFor service:] Write verification key")
        let data = NSData(bytes: [0x80,0xBE,0xF5,0xAC,0xFF] as [UInt8], length: 5)
        peripheral.writeValue(data as Data, for: characteristic, type: CBCharacteristicWriteType.withResponse)
    }

    fileprivate func handleDidConnect(toPeripheral peripheral: CBPeripheral) {

        if let n = peripheral.name {
            connectedButtonUUID = n
        }

        connectedPerf = peripheral

        peripheral.delegate = self
        peripheral.discoverServices(nil)
        isButtonReady = true
    }

    fileprivate func enable(peripheral: CBPeripheral) {

        guard let services = peripheral.services else {
            return
        }

        for service: CBService in services {

            if compareCBUUID(uuid1: service.uuid, uuid2: CBUUID(string: BLE_VSN_GATT_SERVICE_UUID)) {

                guard let characteristics = service.characteristics else {
                    return
                }

                for aChar in characteristics {

                    if aChar.uuid.isEqual(CBUUID(string: BLE_KEYPRESS_DETECTION_UUID)) {

                        // enable button
                        print("[peripheral:didDiscoverCharacteristicsFor service:] Enable short press")

                        let data = NSData(bytes: [0x02] as [UInt8], length: 1)
                        peripheral.writeValue(data as Data, for: aChar, type: CBCharacteristicWriteType.withResponse);

                    } else if aChar.uuid.isEqual(CBUUID(string: BLE_SILENT_NORMAL_MODE)) {

                        // enable button
                        print("[peripheral:didDiscoverCharacteristicsFor service:] Enable normal mode")

                        let data = NSData(bytes: [0x00] as [UInt8], length: 1)
                        peripheral.writeValue(data as Data, for: aChar, type: CBCharacteristicWriteType.withResponse);

                    } else if aChar.uuid.isEqual(CBUUID(string: BLE_FALL_KEYPRESS_DETECTION_UUID)) {

                        // enable fall detection
                        print("[peripheral:didDiscoverCharacteristicsFor service:] Enable fall detection")
                        peripheral.setNotifyValue(true, for: aChar)
                    }
                }
            }
        }

        checkBatteryLevel()
    }

    func disconnectVbttn() {

        guard let peripheral = connectedPerf else {
            return
        }

        setVbttnToStealthMode()
        manager?.cancelPeripheralConnection(peripheral)
        isButtonReady = false
    }

    fileprivate func compareCBUUID(uuid1: CBUUID, uuid2: CBUUID) -> Bool {

        if (uuid1.data as NSData).isEqual(to: uuid2.data) {
            return true
        }

        return false
    }

    func stopScanning() {
        manager?.stopScan()
    }
}
0 Answers
Related