5

I am hoping to port some of my CoreBluetooth code from iOS to OS X. I've set up a shared set of CoreBluetooth wrappers which are consumed by both an iOS app and an OS X app in exactly the same manner with the same BLE devices.

Scanning for peripherals:

override init() {
    super.init()
    let queue = DispatchQueue.global(qos: .background)
    centralManager = CBCentralManager(delegate: self, queue: queue)
}

func startScanning() {
    let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: true]
    let deviceUUID = CBUUID(string: Project.Service.Device)
    let recoveryUUID = CBUUID(string: Project.Service.DFURecovery)
    centralManager?.scanForPeripherals(withServices: [deviceUUID, recoveryUUID], options: options)
}

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber){
    // Inspect advertisementData here to decipher what kind of device
}

On my iOS app, didDiscoverPeripheral is fired. Then when I inspect the advertisement data I get all the keys/values that I am expecting:

{
    kCBAdvDataIsConnectable = 1;
    kCBAdvDataLocalName = "My Device";
    kCBAdvDataManufacturerData = <34045254 5877f283 43fdd12d ff530978 45000000 000050c2 6500>;
    kCBAdvDataServiceData =     {
        Battery = <64>;
    };
    kCBAdvDataServiceUUIDs =     (
        "My Inforamtion"
    );
}

However when this same code is run (scanning for the same devices) from an OS X app, the advertisement data is missing some of the fields.

{
    kCBAdvDataIsConnectable = 1;
    kCBAdvDataManufacturerData = <34045254 5877f36e 43fdd12d ff530978 45000000 000050c2 6500>;
}

The following key/value pairs are missing from advertisedData.

kCBAdvDataLocalName
kCBAdvDataServiceData
kCBAdvDataServiceUUIDs

I've tried adding those keys to the scanForPeripherals call like so:

    let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: true,
                                  CBAdvertisementDataLocalNameKey: true,
                                  CBAdvertisementDataServiceDataKey: true,
                                  CBAdvertisementDataServiceUUIDsKey: true]
    let deviceUUID = CBUUID(string: Nightlight.Service.Device)
    let recoveryUUID = CBUUID(string: Nightlight.Service.DFURecovery)
    centralManager?.scanForPeripherals(withServices: [deviceUUID, recoveryUUID], options: options)

With no effect.

VaporwareWolf
  • 10,143
  • 10
  • 54
  • 80

2 Answers2

6

OSX may call didDiscoverPeripheral multiple times per device, each call with different advertisementData. The solution I came up with is to write a cache.

import Foundation
import CoreBluetooth
    
class AdvertisementDataCache {
    /// A dictionary of advertised data to peripheral.uuid
    private var cache: [UUID: [String: Any]] = [:]
    
    /// Appends advertisementData to our cache
    /// for each unique `peripheral.uuid`.
    func append(
        advertisementData: [String: Any],
        to peripheral: CBPeripheral
    ) -> [String: Any] {
        // Join our cached adverts (our `cache` ivar)
        // with new adverts (`advertisementData`)
        let joined = advertisementData.reduce(
            cache[peripheral.identifier] ?? [:]
        ) {
            var all = $0
            all[$1.key] = $1.value
            return all
        }
        
        // write back to our private iVar
        cache[peripheral.identifier] = joined
        
        // Return all cached averts for this peripheral
        return joined
    }
    
    /// Purges all cached avertisements for all peripherals
    func clear() {
        cache.removeAll()
    }
}

And then in didDiscoverPeripheral:

private var advertisementDataCache = AdvertisementDataCache()

public func centralManager(
    _ central: CBCentralManager,
    didDiscover peripheral: CBPeripheral,
    advertisementData adPart: [String: Any],
    rssi RSSI: NSNumber
) {
    let advertisementData = advertisementDataCache.append(
        advertisementData: adPart,
        to: peripheral
    )
    
    // advertisementData now contains all data contained in multiple callbacks
}
VaporwareWolf
  • 10,143
  • 10
  • 54
  • 80
1

Update. I am using UIKitForMac Beta 2 (now called Catalyst) and am seeing the same issue. The solution listed above (caching/aggregating the adverstisment data) applies in this situation as well.

VaporwareWolf
  • 10,143
  • 10
  • 54
  • 80