I have a mac set up as a bluetooth accessory using CoreBluetooth as a CBPeripheralManager
. The Mac is advertising on a set characteristic CBUUID
, and once it has a subscriber, I click a button to stream a UTF-8-encoded time stamp every half second.
I have an iPhone set up as a CBCentralManager
subscribing to the appropriate characteristic. I update the iPhone's UI with the decoded timestamp every time it receives the data and the app is active. The bluetooth-central
background mode is set in the .plist
file.
The iPhone continues to debug print and update the UI for the timestamps for about 25 seconds, then just stops. This happens whether the app is in the foreground or the background. EDIT: The CBPeripheralManager
receives an didUnsubscribeFrom characteristic
callback at this time. I don't see any reason didUnsubscribeFrom
would be called, no idea why it's always after 25 seconds.
The CBPeripheralManager
continues to merrily send its time stamps. The return value from the CBPeripheralManager
's updateData(_:for:onSubscribedCentrals:)
call is always true, indicating that the queue never is full.
There's a lot of code involved here, showing the most relevant.
From the CBCentralManager
app:
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print(central.state.rawValue)
centralManager.scanForPeripherals(withServices: [timeUUID], options: nil)
print("scanning")
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices(nil)
print("connected")
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral.name as Any)
print(advertisementData[CBAdvertisementDataServiceUUIDsKey] as! Array<CBUUID>)
self.peripheralController = peripheral
self.centralManager.connect(peripheral, options: nil)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if service.uuid.uuidString == timeUUID.uuidString {
peripheralController.setNotifyValue(true, for: service.characteristics!.first!)
peripheralController.readValue(for: service.characteristics!.first!) // EDIT: This was the offending line
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid.uuidString == timeUUID.uuidString {
if let valueFrom = characteristic.value {
if let this = String(data: valueFrom, encoding: .utf8) {
if UIApplication.shared.applicationState == .active {
label.text = this
print("ACTIVE \(this)")
} else if UIApplication.shared.applicationState == .background {
print("BACKGROUND \(this)")
} else if UIApplication.shared.applicationState == .inactive {
print("INACTIVE \(this)")
}
}
}
}
}
From the CBPeripheralManager
app:
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
myCharacteristic = CBMutableCharacteristic(type: myServiceUUID, properties: [CBCharacteristicProperties.read, CBCharacteristicProperties.notify], value: nil, permissions: CBAttributePermissions.readable)
let myService = CBMutableService(type: myServiceUUID, primary: true)
myService.characteristics = [myCharacteristic]
bluetoothController.add(myService)
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
print(characteristic.uuid)
subscriber = central
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
print(characteristic)
print("unsubscribe")
}
func repeatAdvertisement() {
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [unowned self] (timerRef) in
guard let maybeTimer = self.timer, maybeTimer.isValid else { return }
let datum = Date()
let stringFromDate = self.dateFormatter.string(from: datum)
let data = stringFromDate.data(using: .utf8)
print(data!.count)
// myCharacteristic.value = data
let myService = CBMutableService(type: self.myServiceUUID, primary: true)
myService.characteristics = [self.myCharacteristic]
let did = self.bluetoothController.updateValue(data!, for: self.myCharacteristic as! CBMutableCharacteristic, onSubscribedCentrals: [self.subscriber])
print("timed \(stringFromDate) \(did)")
}
}
func advertise() {
if timer == nil {
repeatAdvertisement()
} else {
timer?.invalidate()
timer = nil
}
}
Let me know anything else you need.
Okay, for heaven's sake. The issue was the line peripheralController.readValue(for: service.characteristics!.first!)
I had that line in the app based on some sample code and, well, it was unnecessary.
Apparently the call to readValue(for:)
causes some sort of timeout. I edited that line out of the app and it happily updates on and on.
Leaving the question up and adding this answer in case anyone ends up facing the same thing someday.