understanding corebluetooth didDiscover peripheral

Hello,


I am trying to get my head around the CoreBluetooth framework. From different code samples I found here and there I managed to write a very simple script that scans for Bluethooth peripherals but I a m a bit puzzled by the result.

Why do I get duplicates in the list of peripherals ? (typically my iPad almost always appears twice)

Why do I sometime get a "nil" peripheral ?


<CBPeripheral: 0x1c011a040, identifier = A02449FF-A70C-92FC-1950-9DDFC0709126, name = Adafruit, state = disconnected>


what is 0x1c011a040 ?



Thanks !


//
//  ViewController.swift
//  BLE2
//
//  Created by UoC on 15/10/2018.
//  Copyright © 2018 Matt. All rights reserved.
//

import UIKit
import CoreBluetooth

let myCBUUID = CBUUID(string: "0x1c01135f0")

class ViewController: UIViewController {

    // Variables
    var myCentralManager: CBCentralManager!
    var myPeripherals =  Array<CBPeripheral>()
    
    // IBOutlets
    @IBOutlet weak var myTableView: UITableView!
    
    // Functions
    override func viewDidLoad() {
        super.viewDidLoad()
        myCentralManager = CBCentralManager(delegate: self, queue: nil)
        myTableView.dataSource = self
    }
}


// Class extensions
extension ViewController:UITableViewDataSource{
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myPeripherals.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let myCell = myTableView.dequeueReusableCell(withIdentifier: "myReusableIdentifier")!
        let myPeripheral = myPeripherals[indexPath.row]
        myCell.textLabel?.text = myPeripheral.name
        return myCell
    }
}

extension ViewController: CBCentralManagerDelegate{
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            print("powered on")
            myCentralManager.scanForPeripherals(withServices: nil, options: nil)  // withServices: nil or [myCBUUID]
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        print(peripheral.name as Any)
        //print(RSSI)
        //print(UUID.self)
        //print(myPeripherals)
        myPeripherals.append(peripheral)
        myTableView.reloadData()
    }
}

Accepted Reply

It's been like this for years, the documentation is neither correct nor does it give any details on how this works. But there are several things that affect the number of scan results that your application receives:

  • Is your app running in the foreground or the background?
  • Is the screen on or off?
  • Do you have other applications on the phone that use bluetooth?
  • Is your app doing other BLE related things, e.g. are you monitoring/ranging iBeacons at the same time as you're scanning for peripherals?


The limitations described in the documentation may be "worst case scenario", meaning that if you are running your application in the background with the screen off, while no other application is doing anything bluetooth related, and there are no iBeacon monitoring/ranging going on, you may in some instances only receive one advertisement. In my experience though, you almost always receive more that one advertisement.


Also note that iOS will not differentiate between advertisements and scan responses, meaning that the call to didDiscoverPeripheral will first get called when the phone sees an advertisement, and phone will then send a scan request, and the scan response will also generate a call to didDiscoverPeripheral. The difference between these two calls is the amount of information available, so make sure that you never assume that the peripheral information is fully formed.


There's also a lot of cacheing going on, and state restoration also affects the timing of when advertisements are seen.

Replies

Found in bluetooth best practice (https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/BluetoothBestPractices.html) :


"Minimize Processing of Duplicate Device Discoveries

Remote peripheral devices may send out multiple advertising packets per second to announce their presence to listening apps. By default, these packets are combined into a single event and delivered to your app once per peripheral. You should avoid changing this behavior—don’t specify the CBCentralManagerScanOptionAllowDuplicatesKey constant as a scan option when calling the scanForPeripheralsWithServices:options: method. Doing so results in excess events that can drain battery life."


It shouldn't be the case I call: scanForPeripherals(withServices: nil, options: nil). I don't understand why I still get some duplicates...



As for the second question, actually it is not a nil peripheral but a peripheral with name=nil... Again, why am I picking this up ?



Anyone ?


Thanks !

It's been like this for years, the documentation is neither correct nor does it give any details on how this works. But there are several things that affect the number of scan results that your application receives:

  • Is your app running in the foreground or the background?
  • Is the screen on or off?
  • Do you have other applications on the phone that use bluetooth?
  • Is your app doing other BLE related things, e.g. are you monitoring/ranging iBeacons at the same time as you're scanning for peripherals?


The limitations described in the documentation may be "worst case scenario", meaning that if you are running your application in the background with the screen off, while no other application is doing anything bluetooth related, and there are no iBeacon monitoring/ranging going on, you may in some instances only receive one advertisement. In my experience though, you almost always receive more that one advertisement.


Also note that iOS will not differentiate between advertisements and scan responses, meaning that the call to didDiscoverPeripheral will first get called when the phone sees an advertisement, and phone will then send a scan request, and the scan response will also generate a call to didDiscoverPeripheral. The difference between these two calls is the amount of information available, so make sure that you never assume that the peripheral information is fully formed.


There's also a lot of cacheing going on, and state restoration also affects the timing of when advertisements are seen.