Creating a Contact Tracing Scanner using Swift for macOS
Following on from my previous experiement which highlighted what an Exposure Notification-enabled device actually emits using Web Bluetooth, I decided to explore how I could do the same using Swift and Interface Builder for macOS.
The latest application release can be downloaded from GitHub, and simply unzipped/executed without any additional dependencies.
Using Core Bluetooth I was able to scan for devices which were advertising the registered service UUID 0xfd6f
- and in-turn parse the service data.
The process overall, was very similair to the one used when building the Web Bluetooth solution.
// ...
let exposureNotificationServiceUuid = CBUUID(string: "FD6F")
open func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
central.scanForPeripherals(withServices: [exposureNotificationServiceUuid], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
case .poweredOff:
central.stopScan()
default:
break
}
}
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if let serviceAdvertisementData = advertisementData[CBAdvertisementDataServiceDataKey] as? NSDictionary {
if let exposureNotificationServiceData = serviceAdvertisementData.object(forKey: exposureNotificationServiceUuid) as? Data {
let hex = exposureNotificationServiceData.map { String(format: "%02hhx", $0) }.joined()
updateDevice(Device(uuid: peripheral.identifier.uuidString, rollingProximityId: "\(hex.prefix(32))", metadata: "\(hex.suffix(8))", rssi: "\(RSSI)", lastSeen: Date()))
}
}
}
// ...
To clean-up the listing and ensure that only active devices where present, I used a scheduled timer which would remove any old devices every 5 seconds.
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
self.removeDevicesNotSeenInLastSeconds(10)
self.tableView?.reloadData()
}
private func removeDevicesNotSeenInLastSeconds(_ seconds: Double) {
devices = devices.filter({ Date().timeIntervalSince1970 - $0.lastSeen.timeIntervalSince1970 < seconds })
}
For a deeper understanding of how the Exposure Notification system works, please visit my previous post.