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.
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
}