2

For now I run everything in main thread, so far I noticed only once out of many times that UI gets a bit laggy.

I wonder what is the general practice of utilizint CoreBluetooth library regarding concurrency?

Could you provide some examples what exactly should be run in other queue, if anything?

My usage of bluetooth:

I scan for two peripheral devices, control them by sending approapriate value as CBPeripheralManager in order to make them start sending data from IMU (50Hz/100Hz depending on value).

I synchronize and normalize data from tags and write them into file using streamer.

After transmission is done, I send the data file manually by trigerring relevant action from button.

My code

class BluetoothTagController: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, CBPeripheralManagerDelegate{
static let sharedInstance = BluetoothTagController()
var transferCharacteristic:CBMutableCharacteristic!
var centralManager : CBCentralManager!
var sensorTagPeripheral : CBPeripheral!
var synchronizer:DataSynchronizer!
var sensorTagPeripheralArray : [CBPeripheral] = []
var peripheralManager: CBPeripheralManager!
var bluetoothIsON:Bool = false

var tag1Updating:Bool = false
var tag2Updating:Bool = false
var tag1Changed:Bool = false
var tag2Changed:Bool = false
var tagsIds:[String] = []
var peripheralCounter:Int = 0
var peripheralCounter2:Int = 0
var writeCounter:Int = 0
var timerSet:Bool = false
var haveBeenStarted:Bool = false


override init()
{
    super.init()

    centralManager = CBCentralManager(delegate: self, queue: nil)
    peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
    self.synchronizer = DataSynchronizer(frequency: 1)



}


func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {

    print("WATCH OUT")
    //        print(service)
}
func setHaveBeenStarted( haveBeen: Bool) {
    haveBeenStarted = haveBeen
}

func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {

    print("subscription started")
    var intVal: NSInteger = 0
    if haveBeenStarted == true {
        intVal = 2
    }
    let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))



    var didSend:Bool = self.peripheralManager.updateValue(valueData, for: self.transferCharacteristic, onSubscribedCentrals: nil)
    print(didSend)

}

func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
    if peripheral.state != .poweredOn
    {
        print("no power")
        self.bluetoothIsON = false
        return
    }
    self.bluetoothIsON = true
    print("powered on")
    let serviceCBUUID = CBUUID(string: "5DC90000-8F79-462B-98D7-C1F8C766FA47")
    let transferService:CBMutableService = CBMutableService(type: serviceCBUUID, primary: true)
    let characteristicBUUID = CBUUID(string: "5DC90001-8F79-462B-98D7-C1F8C766FA47")
    var intVal: NSInteger = 2
    let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))
    let transferCharacteristic = CBMutableCharacteristic(type: characteristicBUUID, properties: .notify, value: nil, permissions: .readable)
    self.transferCharacteristic = transferCharacteristic

    transferService.characteristics = [transferCharacteristic as CBCharacteristic]
    self.peripheralManager.add(transferService)
    self.peripheralManager.startAdvertising(nil)


}


func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {

    print("didReceiveReadRequest")
    //
}

func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
    print("Unsubscribed")

    //        var intVal: NSInteger = 0
    //        let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))
    //        self.peripheralManager.updateValue(valueData, for: self.transferCharacteristic, onSubscribedCentrals: nil)

}


/******* CBCentralManagerDelegate *******/

// Check status of BLE hardware
func centralManagerDidUpdateState(_ central: CBCentralManager) {

    if central.state == .poweredOn {
        // Scan for peripherals if BLE is turned on
        central.scanForPeripherals(withServices: nil, options: nil)

    }
    else {
        // Can have different conditions for all states if needed - show generic alert for now

    }
}



// Check out the discovered peripherals to find Sensor Tag
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
    print("array2 contains" + "\(self.sensorTagPeripheralArray.count)")

    if SensorTag.sensorTagFound(advertisementData) == true {

        // Update Status Label'
        self.sensorTagPeripheral = peripheral
        self.sensorTagPeripheral.delegate = self
        self.centralManager.connect(peripheral, options: nil)
        if  !self.sensorTagPeripheralArray.contains(peripheral)
        {
            self.sensorTagPeripheralArray.append(peripheral)
            self.tagsIds.append("\(peripheral.identifier)")
            //                self.centralManager.connectPeripheral(peripheral, options: nil)
        }

        else {

            //showAlertWithText(header: "Warning", message: "SensorTag Not Found")
        }
    }
}

// Discover services of the peripheral
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {

    print("connected " + "\(peripheral.identifier)")
    print("array contains" + "\(self.sensorTagPeripheralArray.count)")

    numberOfTagsSending = numberOfTagsSending + 1
    peripheral.discoverServices(nil)
}


// If disconnected, start searching again
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
    //        print("error")
    //        print(error)

    //        self.sensorTagPeripheralArray.arrayRemovingObject(peripheral)
    //        print(sensorTagPeripheralArray)
    numberOfTagsSending = numberOfTagsSending - 1
    print("removed")

    synchronizer.alreadySynced = false


    central.scanForPeripherals(withServices: nil, options: nil)
}

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

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

    print("looking for p services")
    print("discovered services " + "\(peripheral.identifier)")
    for service in peripheral.services! {

        let thisService = service as CBService
        if SensorTag.validService(thisService) {
            // Discover characteristics of all valid services
            peripheral.discoverCharacteristics(nil, for: thisService)
        }
    }
}


// Enable notification and sensor for each characteristic of valid service
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {

    //        print("discovered characteristic " + "\(peripheral.identifier)")



    var enableValue = 1
    let enablyBytes = Data(buffer: UnsafeBufferPointer(start: &enableValue, count: 1))
    //        print("\n")
    for charateristic in service.characteristics! {
        print(charateristic.uuid)
        let thisCharacteristic = charateristic as CBCharacteristic
        if SensorTag.validDataCharacteristic(thisCharacteristic) {
            // Enable Sensor Notification
            print( "valid char")
            //                print(thisCharacteristic)

            peripheral.setNotifyValue(true, for: thisCharacteristic)
            if thisCharacteristic.uuid == MagnetometerDataUUID{
                peripheral.readValue(for: thisCharacteristic)


            }
            print("after notify set")
            //                print(self.sensorTagPeripheral.services)
        }
        if SensorTag.validConfigCharacteristic(thisCharacteristic) {
            // Enable Sensor
            print("more valid")
            //                print(thisCharacteristic)
            //                for peripheral in self.sensorTagPeripheralArray{
            peripheral.writeValue(enablyBytes, for: thisCharacteristic, type: CBCharacteristicWriteType.withResponse)

        }
    }

}




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

    print(error)

}
//    var streamerTag1 = MyStreamer(fileString: "tag1.txt")
//    var streamerTag2 = MyStreamer(fileString: "tag2.txt")


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

    print(characteristic.value!)




    if "\(peripheral.identifier)" == self.tagsIds[0]

    {
        switch characteristic.uuid
        {
        case MagnetometerDataUUID:
            tag1Compensator.getTrimRegisterData(characteristic.value!)

        case IRTemperatureDataUUID:
            tag1Temperature = Double(UInt16(littleEndian: (characteristic.value! as NSData).bytes.bindMemory(to: UInt16.self, capacity: characteristic.value!.count).pointee))

        case IMUDataUUID:
            synchronizer.fillTagArray(characteristic.value!, tag: .first)

        default:
            return

        }
    }
    else if (self.tagsIds.count > 1) && ("\(peripheral.identifier)" == self.tagsIds[1])
    {
        switch characteristic.uuid
        {
        case MagnetometerDataUUID:
            tag2Compensator.getTrimRegisterData(characteristic.value!)

        case IRTemperatureDataUUID:
            tag2Temperature = Double(UInt16(littleEndian: (characteristic.value! as NSData).bytes.bindMemory(to: UInt16.self, capacity: characteristic.value!.count).pointee))

        case IMUDataUUID:
            synchronizer.fillTagArray(characteristic.value!, tag: .second)

        default:
            return

        }

    }
}

}
theDC
  • 6,364
  • 10
  • 56
  • 98
  • It is preferred to use a dedicated queue for core bluetooth managers. The main queue is suspended when the application goes to background so your app will stop responding to BT callbacks if backgrounded. There are also some other factors that you can read about in many SO core-bluetooth questions. – allprog Oct 06 '16 at 10:59
  • 2
    My app has enabled background mode and works fine in this mode, could you provide some resources? – theDC Oct 06 '16 at 11:03

3 Answers3

2

I'm always running bluetooth activities on a background thread since there is a certain probability that some of bluetooth API calls are blocking.

I guess that main candidates for moving to background are scanning and discovering methods because that's a place where a real hardware operations are performed.

For my tasks using Grand Central Dispatch is enough.

EDIT: the simplest example of GCD usage:

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
    DispatchQueue.main.async { 
        valueLabel.text = peripheral.value.map { String(data: $0, encoding: NSUTF8StringEncoding) }
    }
}
slashdot
  • 630
  • 5
  • 14
  • Hi thanks for your reply. What about tasks like listening for updates in `peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)` method? Could you provide a code snippet how you used GCD? – theDC Oct 06 '16 at 09:43
  • 1
    As far as I know, and this has been communicated by Apple's engineers too, CB APIs are designed to be asynchronous. This is why there are callbacks all over the place. I personally have never encountered any blocking calls. Can you, please, mention an example? – allprog Oct 06 '16 at 11:04
  • @dcdc I added an example. – slashdot Oct 06 '16 at 12:14
  • 1
    @allprog It's very possible that they are all asynchronous, but me personally haven't met any mentions about it in documentation, so I think it's better not to do this assumption. At least there can be some portions of synchronous code under the hood, so I prefer to move it all to background just to keep main thread as lean as possible. – slashdot Oct 06 '16 at 12:16
  • 1
    You can be sure that there are no blocking calls in CB. No need to increase the complexity of your apps by asynchronously calling those APIs. You can safely call the initializers, or write functions from the main queue. If you use a separate queue in the initializer, all callbacks will be executed there. – allprog Oct 06 '16 at 13:59
  • 1
    could you show where you enter the background queue? @allprog could you provide some resources about? It would be great if it works intrisically in asynchronous way. – theDC Oct 06 '16 at 14:00
  • The managers' init function accept a queue parameter. This is usually set to **nil** in examples which implicitly means the _main_ queue. However, if you set it to a queue you created, all callbacks will be dispatched there, instead of the main queue. The [documentation](https://developer.apple.com/reference/corebluetooth/cbcentralmanager/1518695-init) is a little short on this topic but you should get the gist. – allprog Oct 06 '16 at 14:27
  • @allprog I created separate question for that, have a look http://stackoverflow.com/questions/39899008/is-core-bluetooth-framework-intrinsically-asynchronous – theDC Oct 06 '16 at 14:45
0

Try to create blutoothOperation class , which is subclass of Oeration(NSOperation). Sample reference for operation classes and there utilisation.

https://developer.apple.com/videos/play/wwdc2015/226/

Download sample project from the above link.

user3608500
  • 835
  • 4
  • 10
0

I would recommend moving the BLE tasks to a background. Especially if you have heavy reading or writing tasks. By default BLE runs on the main queue. You can change that by providing a BG queue to the CBCentralManager init:

centralManager = CBCentralManager(
                    delegate: self,
                    queue: DispatchQueue.global(qos: .background)
                )

now all your CBCentralManagerDelegate and CBPeripheralDelegate methods will be called on the provided BG queue.