The BLE user device continues to appear in the list of paired devices and continues to try to connect

We are implementing a BLE user device that communicates after connecting using our own iOS application.

We exchange data by reading and writing the characteristics of the BLE device

userInfo.peripheral.writeValue(segments[currentSegment], for: userInfo.characteristic, type: .withoutResponse)

After the first successful connection and data exchange using our iOS application, we experience this behavior.

This happens on some iOS devices, and we did not find any similarities for devices that seem to have a problem (for example, iPhone 6s iOS 11.2.1 and iPhone 5s 10.2.2)

When this behavior occurs (the BLE device continues to appear in the list of paired devices and continues to try to connect) affects the flow and reliability of our iOS application.

All connections go through this manager.

import Foundation
import CoreBluetooth





enum BluetoothManagerResponse {
    case success(result:String)
    case fail(error:String)
    case cancelled
}



enum SendEncryption {
    case encrypted(publicKey:String)
    case notEncrypted
}


enum DeviceType {
    case newDevice
    case connectedDevices(device:BluetoothManagerConnectedDevice)
}

typealias BluetoothManagerComlpetion = ((_ response:BluetoothManagerResponse,_ connectedDevice:BluetoothManagerConnectedDevice)->Void)?
typealias BluetoothManagerConnectedDevice = (peripheral:CBPeripheral,characteristic:CBCharacteristic)?
class BluetoothManager:NSObject {


    var manager: CBCentralManager?
    var peripherals = [CBPeripheral]()
    var connectedDevice:BluetoothManagerConnectedDevice = nil
    static let shared = BluetoothManager()

    fileprivate var stx:String {
        return  String(Character.init(UnicodeScalar(0002)))
    }


    fileprivate var etx:String {
        return  String(Character.init(UnicodeScalar(0003)))
    }



    var minimumDistance:Int?
    var currentSegment = 0
    var segments:[Data]?  {
        didSet {
            currentSegment = 0
        }
    }



    var completion:BluetoothManagerComlpetion = nil


    private func encrypt(data:String , withPublicKey key:String) -> String? {
        ...

    }


    var rssiUpdate:((_ rssi:Int) -> Void)?

    func startGettingRSSIUpdate(callback:@escaping (_ rssi:Int) -> Void) {

        self.rssiUpdate = callback
    }



    func send(data:String?,to deviceType:DeviceType,withEncryption type:SendEncryption = .notEncrypted,andMinimumDistance distance:Int? = nil, and completion:BluetoothManagerComlpetion) {
        self.minimumDistance = distance
        hasCanceled = false
        let selector = #selector(deviceNotFound(sender:))
        self.notFoundTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: selector, userInfo: nil, repeats: false)
        self.completion = completion



        if let data = data {
            switch type {
            case .encrypted(let publicKey):
                let encrypted = self.encrypt(data: data, withPublicKey: publicKey)
                self.segments = encrypted?.hexadecimalString?.components(withLength: 20).flatMap({$0.hexadecimalData}) ?? []
            case .notEncrypted:
                self.segments = data.hexadecimalString?.components(withLength: 20).flatMap({$0.hexadecimalData}) ?? []
            }
        }
        else {
             self.segments = data?.hexadecimalString?.components(withLength: 20).flatMap({$0.hexadecimalData}) ?? []
        }


        switch deviceType {
        case .newDevice:
             connectedDevice = nil
             manager = CBCentralManager(delegate: self, queue: nil)
        case .connectedDevices(let device):


            guard let device = device else {
                completeWithResponse(response: .fail(error: Messages.Bluetooth.deviceDisconnected))
                return
            }
            switch  device.peripheral.state {
            case .connected:
                device.peripheral.setNotifyValue(true, for: device.characteristic)

            default:
                completeWithResponse(response: .fail(error: Messages.Bluetooth.deviceDisconnected))
            }
        }

    }

    var hasCanceled:Bool = false


    func disconnect() {
        completion = nil
        notFoundTimer?.invalidate()
        notFoundTimer = nil
        manager?.stopScan()
        guard let connectedDevice = self.connectedDevice else { return }
        manager?.cancelPeripheralConnection(connectedDevice.peripheral)

        connectedDevice.peripheral.setNotifyValue(false, for: connectedDevice.characteristic)
        self.connectedDevice = nil

    }

    func cancel() {
        hasCanceled = true
        completeWithResponse(response: .cancelled)
        completion = nil
    }


    func completeWithResponse(response:BluetoothManagerResponse) {
        notFoundTimer?.invalidate()
        notFoundTimer = nil
        manager?.stopScan()
        self.completion?(response,connectedDevice)


    }



    var notFoundTimer:Timer?


    func deviceNotFound(sender:Timer) {
        completeWithResponse(response: .fail(error: Messages.Bluetooth.notFound))

    }


    var rssiValues = [Int]()
    var resetRssiValues = true


    var receivedData:String? {
        didSet {


            guard let receivedData = receivedData,receivedData.contains("\u{03}") else {
                return
            }

            let responseString = String(receivedData.dropLast().dropFirst())
            self.completeWithResponse(response: .success(result: responseString))
        }
    }


    let easedProximity = EasedValue()

    fileprivate func convertRSSItoProximity(rssi:Int) -> Int? {
        let absRssi = Swift.abs(rssi)
        easedProximity.setValue(value: CGFloat(absRssi))
        easedProximity.update()
        if let result = easedProximity.value {
            return Int(result * -1.0)
        }
        return nil
    }

}


//MARK: CBCentralManagerDelegate
extension BluetoothManager: CBCentralManagerDelegate {

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch  central.state {
        case .poweredOn:
            central.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey:true])

        case .poweredOff:
            self.completeWithResponse(response:.fail(error: Messages.Bluetooth.poweredOff))
        case .resetting:
            self.completeWithResponse(response:.fail(error: Messages.Bluetooth.resetting))
        case .unauthorized:
            self.completeWithResponse(response:.fail(error: Messages.Bluetooth.unauthorized))
        case .unknown:
            self.completeWithResponse(response:.fail(error: Messages.Bluetooth.unknown))
        case .unsupported:
            self.completeWithResponse(response:.fail(error: Messages.Bluetooth.unsupported))
        }
    }

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

        peripheral.delegate = self
        peripheral.discoverServices(nil)


    }

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

        guard let error = error else {
            self.completeWithResponse(response:.fail(error:  Messages.Bluetooth.unknown))
            return
        }

        self.completeWithResponse(response:.fail(error: error.localizedDescription))

    }



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


        func connect(peripheral: CBPeripheral) {

            print("Should connect")
            peripherals.append(peripheral)
            central.connect(peripheral, options: nil)
            central.stopScan()
            easedProximity.reset()
        }


        guard  let name = peripheral.name , name.isMod10 else {return}

        print(name)
        if let distance = self.minimumDistance {


            let rssi = (RSSI as? Int) ?? 0
            let value = self.convertRSSItoProximity(rssi: rssi)

            if let value = value {
                self.rssiUpdate?(value)
            }


            guard let _value = value,_value >= distance else{ return }
        }


         connect(peripheral: peripheral)


    }




}


//MARK: CBPeripheralDelegate
extension BluetoothManager:CBPeripheralDelegate {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {

        guard let error = error else {
            guard let service = peripheral.services?.first else {
                self.completeWithResponse(response:.fail(error: Messages.Bluetooth.noService))
                return
            }
            peripheral.discoverCharacteristics(nil, for: service)
            return
        }

        self.completeWithResponse(response:.fail(error: error.localizedDescription))

    }


    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        print("OS Disconect : \(String(describing: error?.localizedDescription))")
    }


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

        guard let error = error else {
            guard let characteristic = service.characteristics?.first else {
                self.completeWithResponse(response:.fail(error: Messages.Bluetooth.noCharacteristic))
                return

            }
            connectedDevice = (peripheral:peripheral,characteristic:characteristic)
            peripheral.setNotifyValue(true, for: characteristic)
            return
        }

        self.completeWithResponse(response:.fail(error: error.localizedDescription))

    }


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

        guard characteristic.isNotifying  else {
            manager?.cancelPeripheralConnection(peripheral)
            return

        }
        guard let error = error else {
            guard let segments = segments , segments.count > 0 else {
                 return
            }


            let selector = #selector(send(sender:))
            Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: selector, userInfo: (peripheral:peripheral,characteristic:characteristic), repeats: true)

            return
        }

        self.completeWithResponse(response:.fail(error: error.localizedDescription))
    }





    internal func send(sender:Timer) {

        if let userInfo = sender.userInfo as? (peripheral:CBPeripheral,characteristic:CBCharacteristic),
           let segments = segments,
           currentSegment >= 0 && currentSegment < segments.count && segments.count > 0
        {

            userInfo.peripheral.writeValue(segments[currentSegment], for: userInfo.characteristic, type: .withoutResponse)
        }
        else {
            sender.invalidate()
        }


        currentSegment = currentSegment + 1

    }



    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        //guard characteristic.isNotifying  else {return}
        //peripheral.setNotifyValue(false, for:characteristic)
        guard let error = error else {
            guard let receivedData = characteristic.value?.toString else {
                self.completeWithResponse(response:.fail(error: Messages.Bluetooth.invalidReadData))
                return
            }



            if receivedData.first == "\u{02}"  {
                self.receivedData = nil
            }

            self.receivedData = (self.receivedData ?? "")  +  receivedData
            return
        }


        self.completeWithResponse(response:.fail(error: error.localizedDescription))


    }


}
+4
source share

Source: https://habr.com/ru/post/1692306/


All Articles