I am developing an app which is capable of doing something like this.
My app should connects with BLE peripheral and i want to keep monitoring the connection. when disconnects the connection it should take the locatoin and update the map. i need to track the disconnection even if the peripheral's switch off event in my App.
i have two View Controllers. One is for update my Google map. other one is a tableViewController which is listed available device list. i have implemented a BLE Manager using Singolton pattern.
class BleManager: NSObject , CBCentralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager?
var peripherals = Array<CBPeripheral>()
var periparal : CBPeripheral!
let dataSaver = DataSaver.sharedInstance
let locationMgr = LocationInfo.sharedInstance
var viewController : CarList!
static let shared = BleManager()
//MARK:- Initialiser
private override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}
init(vc : CarList) {
super.init()
viewController = vc
//centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}
//MARK:- BLE Activities
func startScanForDevices() {
peripherals = []
//self.centralManager?.scanForPeripherals(withServices: [CBUUID(string: "DFB0")], options: nil)
self.centralManager?.scanForPeripherals(withServices: nil, options: nil)
//self.centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey : true])
}
func stopScanForDevices() {
if centralManager != nil {
centralManager?.stopScan()
}
}
func connectToADevice(pheriperal : CBPeripheral){
periparal = pheriperal
// saving Connected Car in App LifeSycle
connectedCar = pheriperal
centralManager?.connect(pheriperal, options: nil)
(viewController != nil) ? viewController.devTable.reloadData() : print("")
}
func disconnectFromApp(pheriperal : CBPeripheral){
centralManager?.cancelPeripheralConnection(pheriperal)
}
//MARK:- BLE Central Delegates
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if (central.state == .poweredOn){
startScanForDevices()
}
else if (central.state == .poweredOff){
print("Powered Off")
centralManager?.cancelPeripheralConnection(periparal)
}
else if (central.state == .resetting){
print("resetting")
}
else if (central.state == .unauthorized){
print("unauthorized")
}
else if (central.state == .unsupported){
print("unsupported")
}else{
print("Unknown")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
//peripherals.append(peripheral)
//print("DEVICES LIST : \(peripherals)")
updatePheriperals(device: peripheral)
if viewController != nil {
viewController.devTable.reloadData()
if dataSaver.isAnyCarHasBeenSaved() == true && dataSaver.getMyCarIdentification() == peripheral.identifier.uuidString && peripheral.state == .disconnected{
connectToADevice(pheriperal: peripheral)
viewController.devTable.reloadData()
}
}else{
if dataSaver.isAnyCarHasBeenSaved() == true && dataSaver.getMyCarIdentification() == peripheral.identifier.uuidString{
connectToADevice(pheriperal: peripheral)
}
}
}
func updatePheriperals(device : CBPeripheral) {
if peripherals.count != 0 {
for dev in peripherals {
if device.identifier != dev.identifier {
peripherals.append(device)
}
}
}else{
peripherals.append(device)
}
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("\(ConnectionState(rawValue: peripheral.state.rawValue)?.name()) \(peripheral.name) from App.. Taking Geo cordinates")
locationMgr.didFindLocation = false
locationMgr.getCurrentLocation()
if viewController != nil {
viewController.devTable.reloadData()
}else{
print("Just Updating Locations")
}
showNotification(message: peripheral.name! + " has been Disconnected from Find My Car" )
startScanForDevices()
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("\(ConnectionState(rawValue: peripheral.state.rawValue)?.name()) \(peripheral.name) to App.. Removeing previous location")
dataSaver.removeLocation()
dataSaver.saveMyCarIdentification(myCar: peripheral.identifier.uuidString)
(viewController != nil) ? viewController.devTable.reloadData() : print("Just Updating Locations")
//print("Service UUIDs : \(peripheral)")
NotificationCenter.default.post(name: NSNotification.Name(rawValue: AppStrings.updateMapString), object: nil)
showNotification(message: peripheral.name! + " has been connected to Find My Car" )
}
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
print("Already Connected Pheriperals : \(dict[CBCentralManagerRestoredStatePeripheralsKey])")
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("Failed To Connect with \(peripheral.name)")
centralManager?.cancelPeripheralConnection(peripheral)
}
In my MapViewController i use sharedInstance and start scanning
even in my TableViewController i do the same.
but sometimes when i try to switch off the peripheral device, my BLE Central does not called did disconnect peripheral delegate.
and i also wanna know how can i handle background scanning. with state prevention and restoration (I dont know anything about it. i just want to waork my app in background As same way in foreground)
Please help
Related
I am trying to disconnect from a BLE device (raspberry-pi3), the delegates for disconnection are being triggered and upon checking explicitly if the peripheral is disconnected it shows that the peripheral is indeed disconnected. But, when I see in the settings app, I still see the device is connected.
Following is what I am trying:
Setup CBCentralManager as I come to the page.
func setupBLE() {
lblStatus.text = "Establising connection to the Scrubport"
self.manager = CBCentralManager(delegate: self, queue: .main)
}
Handle any state change if any
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
self.dismissAlert()
self.showLoader()
self.timer = Timer.scheduledTimer(withTimeInterval: 30, repeats: false, block: { _ in
// No device found. Stop scanning.
})
self.manager?.scanForPeripherals(withServices: [SERVICE_UUID])
return
}
else if central.state == .unauthorized {
// Permission handlers here:
return
}
// Handle all other cases.
}
Upon find the device try connecting
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
self.peripheral = peripheral
connect()
}
func connect() {
guard let peripheral = peripheral else {
// Making sure we got the peripheral.
return
}
lblStatus.text = "Connecting..."
self.manager?.connect(peripheral)
}
Upon connection discover the service
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
lblStatus.text = "Connected, preparing device for transfer..."
self.timer.invalidate()
central.stopScan()
peripheral.delegate = self
peripheral.discoverServices([SERVICE_UUID])
}
Find the characteristics next
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let services = peripheral.services {
for service in services {
if service.uuid == SERVICE_UUID {
peripheral.discoverCharacteristics([CHARACTERISTICS_UUID], for: service)
}
}
}
}
Request notification
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let characteristics = service.characteristics {
for characteristic in characteristics {
if characteristic.uuid == self.CHARACTERISTICS_UUID {
self.characteristic = characteristic
peripheral.setNotifyValue(true, for: characteristic)
}
}
}
}
Upon getting an update for notification change I do the write and it is all successful.
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.isNotifying {
self.stopLoader()
lblStatus.text = "Notifying now.."
} else {
print("Un-notified")
print("Trying disconnection")
manager?.cancelPeripheralConnection(peripheral)
}
}
At the end I want to disconnect, or handle if app is closed or pressed back mid way
Trying clean up using following code:
fileprivate func cleanup() {
print("CLEANING")
guard let manager = manager else {
return
}
print("Check scan.")
if manager.isScanning {
print("Stopping scan.")
manager.stopScan()
}
// Don't do anything if we're not connected
// self.discoveredPeripheral.isConnected is deprecated
print("Checking peripheral connection")
guard peripheral?.state == .connected else {
print("No peripheral connected.")
return
}
// See if we are subscribed to a characteristic on the peripheral
print("Checking services")
guard let services = peripheral?.services else {
print("No services connection found.")
cancelPeripheralConnection()
return
}
print("Looping services")
for service in services {
print("Checking characteristics")
guard let characteristics = service.characteristics else {
print("No characteristics")
continue
}
print("Looping characteristics")
for characteristic in characteristics {
print("Comparing characteristics UUID is notifying")
if characteristic.uuid.isEqual(CHARACTERISTICS_UUID) && characteristic.isNotifying {
print("Un-notifying")
peripheral?.setNotifyValue(false, for: characteristic)
} else {
print("Nope not the one.", characteristic.isNotifying)
}
}
}
}
fileprivate func cancelPeripheralConnection() {
print("Remove peripheral connection")
guard let manager = manager, let peripheral = peripheral else {
print("Manager or peripheral not found!")
return
}
print("Cancelling peripheral connection.")
// If we've got this far, we're connected, but we're not subscribed, so we just disconnect
manager.cancelPeripheralConnection(peripheral)
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("SERVICE INFO: didDisconnectPeripheral", peripheral.name ?? "Unknown")
print("Was there an error?", error ?? "No error")
self.peripheral = nil
let peripherals = central.retrieveConnectedPeripherals(withServices: [])
let _peri = central.retrievePeripherals(withIdentifiers: [peripheral.identifier])
_peri.forEach { per in
print(per.state == .connected, "connected")
print(per.state == .connecting, "connecting")
print(per.state == .disconnected, "disconnected")
print(per.state == .disconnecting, "disconnecting")
}
print(peripherals)
}
Following are the logs that get printed on cleanup():
CLEANING
Check scan.
Checking peripheral connection
Checking services
Looping services
Checking characteristics
Looping characteristics
Comparing characteristics UUID is notifying
Un-notifying
Un-notified
Trying disconnection
SERVICE INFO: didDisconnectPeripheral raspberrypi-cm3
Was there an error? No error
false connected
false connecting
true disconnected
false disconnecting
[]
But this what I see in the settings app after clean up.
I have an iOS app that uses Core Bluetooth to connect to a V.BTTN smart button. Everything worked perfectly fine in iOS 10, but since iOS 11 has come out the restoration process seems to break.
When I launch my app, with a previously paired button, the OS does in fact call the centralManager:willRestoreState, and the accompanying dictionary includes a pointer to a CBPeripheral object that has a status of connected. Just as it did back in iOS 10. However, the problem I am running into is when I call the discoverServices on the peripheral I am never returned any services in the peripheral:didDiscoverServices method. In fact that method, nor any other method, is called at all.
I have been searching all of the internet and have found people having similar issues with button pairing an connections, but those have generally been issues with improper lifecycle management of the peripheral object. I believe I have everything setup correct and at a loss. Does anyone have any idea what could be going on here?
import Foundation
import CoreBluetooth
class SmartButtonManager: NSObject {
//MARK: - Singleton
static let sharedManager = SmartButtonManager()
//MARK: - Properties
var discoveredDevices: [CBPeripheral] {
get {
return perfs
}
}
fileprivate(set) var isBluetoothOn = false
//MARK: - Private Constants & Variables
fileprivate var connectedButtonUUID: String?
fileprivate let queue = DispatchQueue(label: "V.BTTN", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
fileprivate var manager: CBCentralManager?
fileprivate var perfs = [CBPeripheral]()
fileprivate var connectedPerf: CBPeripheral?
fileprivate var isButtonReady = false
//MARK: - Initialization
override init() {
super.init()
}
//MARK: - Configure
func configure(withLaunchOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) {
// create new central manager
manager = CBCentralManager(delegate: self, queue: queue, options: [CBCentralManagerOptionRestoreIdentifierKey: managerIdentifier])
}
}
//MARK: - V.BTTN
extension SmartButtonManager: CBCentralManagerDelegate, CBPeripheralDelegate {
func scanForVbttn() {
perfs.removeAll()
manager?.scanForPeripherals(withServices: services, options: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOff:
print("[centralManagerDidUpdateState] CB BLE hardware is powered off")
perfs.removeAll()
isBluetoothOn = false
case .poweredOn:
print("[centralManagerDidUpdateState] CB BLE hardware is powered on. Start scanning for peripherals")
isBluetoothOn = true
case .unauthorized:
print("[centralManagerDidUpdateState] CB BLE hardware is not authorized")
case .unsupported:
print("[centralManagerDidUpdateState] CB BLE hardware is not supported")
isBluetoothOn = false
default:
print("[centralManagerDidUpdateState] CB BLE hardware state is unknown")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("[centralManager:didDiscover peripheral:advertisementData] CB BLE did discover peripheral with advertisement data: \(advertisementData)")
guard let perfName = advertisementData[CBAdvertisementDataLocalNameKey] as? String else {
print("[centralManager:didDiscover peripheral:advertisementData] peripheral name is unknown")
return
}
if perfName.contains("V.ALRT") {
peripheral.delegate = self
perfs.append(peripheral)
if connectedButtonUUID == perfName {
connect(peripheral: peripheral)
}
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("[central:didConnect peripheral:] CB BLE hardware did connect")
handleDidConnect(toPeripheral: peripheral)
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("[central:didFailToConnect error:] CB BLE peripheral did failed to connect with error: \(String(describing: error))")
connectedPerf = nil
isButtonReady = false
connectedButtonUUID = nil
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("[central:didDisconnectPeripheral peripheral:] CB BLE peripheral did disconnect")
connectedPerf = nil
isButtonReady = false
connectedButtonUUID = nil
}
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
print("[central:willRestoreState dict:] CB BLE hardware will restore state")
print("\(dict)")
guard let ps = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else {
print("[central:willRestoreState dict:] No perfs to restore")
return
}
print("[central:willRestoreState dict:] Will restore perfs")
perfs = ps
print("[central:willRestoreState dict:] Attempt to reconnect to V.BTTN")
print("[central:willRestoreState dict:] perfs \(perfs)")
for p in perfs {
if p.name == connectedButtonUUID {
print("[central:willRestoreState dict:] Connect to perf \(p)")
handleDidConnect(toPeripheral: p)
break
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else {
print("[peripheral:didDiscoverServices error:] CB BLE peripheral unable to discover services")
return
}
print("[peripheral:didDiscoverServices error:] BLE peripheral did discover services")
for s in services {
print("[peripheral:didDiscoverServices error:] CB BLE Service \(s.description)")
peripheral.discoverCharacteristics(nil, for: s)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
print("[peripheral:didDiscoverCharacteristicsFor service:] CB BLE did discover characteristics for service \(service.uuid.description)")
if compareCBUUID(uuid1: service.uuid, uuid2: CBUUID(string: BLE_VSN_GATT_SERVICE_UUID)) {
guard let characteristics = service.characteristics else {
return
}
for aChar in characteristics {
// write the verification key
if aChar.uuid.isEqual(CBUUID(string: BLE_VERIFICATION_SERVICE_UUID)) {
self.writeVerificationKey(forPeripheral: peripheral, characteristic: aChar)
self.enable(peripheral: peripheral)
break
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
//NOTE: This code has been omitted as there is little need to see this
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print("[peripheral:didWriteValueFor characteristic:] characteristic: \(characteristic.uuid)")
if let e = error {
print("[peripheral:didWriteValueFor characteristic:] error: \(e)")
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if !characteristic.isNotifying {
print("[peripheral:didUpdateNotificationStateFor:][Characteristic is not notifiying so cancel the connection]")
manager?.cancelPeripheralConnection(peripheral)
}
}
//MARK: - Helpers
fileprivate func writeVerificationKey(forPeripheral peripheral: CBPeripheral, characteristic: CBCharacteristic) {
print("[peripheral:didDiscoverCharacteristicsFor service:] Write verification key")
let data = NSData(bytes: [0x80,0xBE,0xF5,0xAC,0xFF] as [UInt8], length: 5)
peripheral.writeValue(data as Data, for: characteristic, type: CBCharacteristicWriteType.withResponse)
}
fileprivate func handleDidConnect(toPeripheral peripheral: CBPeripheral) {
if let n = peripheral.name {
connectedButtonUUID = n
}
connectedPerf = peripheral
peripheral.delegate = self
peripheral.discoverServices(nil)
isButtonReady = true
}
fileprivate func enable(peripheral: CBPeripheral) {
guard let services = peripheral.services else {
return
}
for service: CBService in services {
if compareCBUUID(uuid1: service.uuid, uuid2: CBUUID(string: BLE_VSN_GATT_SERVICE_UUID)) {
guard let characteristics = service.characteristics else {
return
}
for aChar in characteristics {
if aChar.uuid.isEqual(CBUUID(string: BLE_KEYPRESS_DETECTION_UUID)) {
// enable button
print("[peripheral:didDiscoverCharacteristicsFor service:] Enable short press")
let data = NSData(bytes: [0x02] as [UInt8], length: 1)
peripheral.writeValue(data as Data, for: aChar, type: CBCharacteristicWriteType.withResponse);
} else if aChar.uuid.isEqual(CBUUID(string: BLE_SILENT_NORMAL_MODE)) {
// enable button
print("[peripheral:didDiscoverCharacteristicsFor service:] Enable normal mode")
let data = NSData(bytes: [0x00] as [UInt8], length: 1)
peripheral.writeValue(data as Data, for: aChar, type: CBCharacteristicWriteType.withResponse);
} else if aChar.uuid.isEqual(CBUUID(string: BLE_FALL_KEYPRESS_DETECTION_UUID)) {
// enable fall detection
print("[peripheral:didDiscoverCharacteristicsFor service:] Enable fall detection")
peripheral.setNotifyValue(true, for: aChar)
}
}
}
}
checkBatteryLevel()
}
func disconnectVbttn() {
guard let peripheral = connectedPerf else {
return
}
setVbttnToStealthMode()
manager?.cancelPeripheralConnection(peripheral)
isButtonReady = false
}
fileprivate func compareCBUUID(uuid1: CBUUID, uuid2: CBUUID) -> Bool {
if (uuid1.data as NSData).isEqual(to: uuid2.data) {
return true
}
return false
}
func stopScanning() {
manager?.stopScan()
}
}
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.
For now I run everything in main thread, so far I noticed only once out of many times that UI gets a bit laggy.
I wonder what is the general practice of utilizint CoreBluetooth library regarding concurrency?
Could you provide some examples what exactly should be run in other queue, if anything?
My usage of bluetooth:
I scan for two peripheral devices, control them by sending approapriate value as CBPeripheralManager in order to make them start sending data from IMU (50Hz/100Hz depending on value).
I synchronize and normalize data from tags and write them into file using streamer.
After transmission is done, I send the data file manually by trigerring relevant action from button.
My code
class BluetoothTagController: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, CBPeripheralManagerDelegate{
static let sharedInstance = BluetoothTagController()
var transferCharacteristic:CBMutableCharacteristic!
var centralManager : CBCentralManager!
var sensorTagPeripheral : CBPeripheral!
var synchronizer:DataSynchronizer!
var sensorTagPeripheralArray : [CBPeripheral] = []
var peripheralManager: CBPeripheralManager!
var bluetoothIsON:Bool = false
var tag1Updating:Bool = false
var tag2Updating:Bool = false
var tag1Changed:Bool = false
var tag2Changed:Bool = false
var tagsIds:[String] = []
var peripheralCounter:Int = 0
var peripheralCounter2:Int = 0
var writeCounter:Int = 0
var timerSet:Bool = false
var haveBeenStarted:Bool = false
override init()
{
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
self.synchronizer = DataSynchronizer(frequency: 1)
}
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
print("WATCH OUT")
// print(service)
}
func setHaveBeenStarted( haveBeen: Bool) {
haveBeenStarted = haveBeen
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
print("subscription started")
var intVal: NSInteger = 0
if haveBeenStarted == true {
intVal = 2
}
let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))
var didSend:Bool = self.peripheralManager.updateValue(valueData, for: self.transferCharacteristic, onSubscribedCentrals: nil)
print(didSend)
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state != .poweredOn
{
print("no power")
self.bluetoothIsON = false
return
}
self.bluetoothIsON = true
print("powered on")
let serviceCBUUID = CBUUID(string: "5DC90000-8F79-462B-98D7-C1F8C766FA47")
let transferService:CBMutableService = CBMutableService(type: serviceCBUUID, primary: true)
let characteristicBUUID = CBUUID(string: "5DC90001-8F79-462B-98D7-C1F8C766FA47")
var intVal: NSInteger = 2
let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))
let transferCharacteristic = CBMutableCharacteristic(type: characteristicBUUID, properties: .notify, value: nil, permissions: .readable)
self.transferCharacteristic = transferCharacteristic
transferService.characteristics = [transferCharacteristic as CBCharacteristic]
self.peripheralManager.add(transferService)
self.peripheralManager.startAdvertising(nil)
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
print("didReceiveReadRequest")
//
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
print("Unsubscribed")
// var intVal: NSInteger = 0
// let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))
// self.peripheralManager.updateValue(valueData, for: self.transferCharacteristic, onSubscribedCentrals: nil)
}
/******* CBCentralManagerDelegate *******/
// Check status of BLE hardware
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
// Scan for peripherals if BLE is turned on
central.scanForPeripherals(withServices: nil, options: nil)
}
else {
// Can have different conditions for all states if needed - show generic alert for now
}
}
// Check out the discovered peripherals to find Sensor Tag
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("array2 contains" + "\(self.sensorTagPeripheralArray.count)")
if SensorTag.sensorTagFound(advertisementData) == true {
// Update Status Label'
self.sensorTagPeripheral = peripheral
self.sensorTagPeripheral.delegate = self
self.centralManager.connect(peripheral, options: nil)
if !self.sensorTagPeripheralArray.contains(peripheral)
{
self.sensorTagPeripheralArray.append(peripheral)
self.tagsIds.append("\(peripheral.identifier)")
// self.centralManager.connectPeripheral(peripheral, options: nil)
}
else {
//showAlertWithText(header: "Warning", message: "SensorTag Not Found")
}
}
}
// Discover services of the peripheral
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("connected " + "\(peripheral.identifier)")
print("array contains" + "\(self.sensorTagPeripheralArray.count)")
numberOfTagsSending = numberOfTagsSending + 1
peripheral.discoverServices(nil)
}
// If disconnected, start searching again
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
// print("error")
// print(error)
// self.sensorTagPeripheralArray.arrayRemovingObject(peripheral)
// print(sensorTagPeripheralArray)
numberOfTagsSending = numberOfTagsSending - 1
print("removed")
synchronizer.alreadySynced = false
central.scanForPeripherals(withServices: nil, options: nil)
}
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
print("ciekawe")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("looking for p services")
print("discovered services " + "\(peripheral.identifier)")
for service in peripheral.services! {
let thisService = service as CBService
if SensorTag.validService(thisService) {
// Discover characteristics of all valid services
peripheral.discoverCharacteristics(nil, for: thisService)
}
}
}
// Enable notification and sensor for each characteristic of valid service
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
// print("discovered characteristic " + "\(peripheral.identifier)")
var enableValue = 1
let enablyBytes = Data(buffer: UnsafeBufferPointer(start: &enableValue, count: 1))
// print("\n")
for charateristic in service.characteristics! {
print(charateristic.uuid)
let thisCharacteristic = charateristic as CBCharacteristic
if SensorTag.validDataCharacteristic(thisCharacteristic) {
// Enable Sensor Notification
print( "valid char")
// print(thisCharacteristic)
peripheral.setNotifyValue(true, for: thisCharacteristic)
if thisCharacteristic.uuid == MagnetometerDataUUID{
peripheral.readValue(for: thisCharacteristic)
}
print("after notify set")
// print(self.sensorTagPeripheral.services)
}
if SensorTag.validConfigCharacteristic(thisCharacteristic) {
// Enable Sensor
print("more valid")
// print(thisCharacteristic)
// for peripheral in self.sensorTagPeripheralArray{
peripheral.writeValue(enablyBytes, for: thisCharacteristic, type: CBCharacteristicWriteType.withResponse)
}
}
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print(error)
}
// var streamerTag1 = MyStreamer(fileString: "tag1.txt")
// var streamerTag2 = MyStreamer(fileString: "tag2.txt")
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print(characteristic.value!)
if "\(peripheral.identifier)" == self.tagsIds[0]
{
switch characteristic.uuid
{
case MagnetometerDataUUID:
tag1Compensator.getTrimRegisterData(characteristic.value!)
case IRTemperatureDataUUID:
tag1Temperature = Double(UInt16(littleEndian: (characteristic.value! as NSData).bytes.bindMemory(to: UInt16.self, capacity: characteristic.value!.count).pointee))
case IMUDataUUID:
synchronizer.fillTagArray(characteristic.value!, tag: .first)
default:
return
}
}
else if (self.tagsIds.count > 1) && ("\(peripheral.identifier)" == self.tagsIds[1])
{
switch characteristic.uuid
{
case MagnetometerDataUUID:
tag2Compensator.getTrimRegisterData(characteristic.value!)
case IRTemperatureDataUUID:
tag2Temperature = Double(UInt16(littleEndian: (characteristic.value! as NSData).bytes.bindMemory(to: UInt16.self, capacity: characteristic.value!.count).pointee))
case IMUDataUUID:
synchronizer.fillTagArray(characteristic.value!, tag: .second)
default:
return
}
}
}
}
I'm always running bluetooth activities on a background thread since there is a certain probability that some of bluetooth API calls are blocking.
I guess that main candidates for moving to background are scanning and discovering methods because that's a place where a real hardware operations are performed.
For my tasks using Grand Central Dispatch is enough.
EDIT: the simplest example of GCD usage:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
DispatchQueue.main.async {
valueLabel.text = peripheral.value.map { String(data: $0, encoding: NSUTF8StringEncoding) }
}
}
Try to create blutoothOperation class , which is subclass of Oeration(NSOperation).
Sample reference for operation classes and there utilisation.
https://developer.apple.com/videos/play/wwdc2015/226/
Download sample project from the above link.
I've recently bought a Bluno and am trying to create an iPhone app to talk to it. The Bluno makers include source code but it's in objective-c and I'm trying to port it to swift. Currently I can discover the Bluno, connect to it and see it's services. However I seem to see no characteristics, and get a nil when I print them. I've setup the Bluno so that it flashes once I send it the character "5" but I can't seem to find the correct characteristic in order to do so. Any help would be much appreciated. Here is my current code, the Obj-C code can be found here:
//
// ViewController.swift
// Bluetooth-Interaction
//
// Created by Frederik Lohner on 26/Oct/15.
// Copyright © 2015 JeongGroup. All rights reserved.
//
import UIKit
import CoreBluetooth
import SnapKit
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
let backgroundView = UIView()
let scanButton = UIButton()
var DFRobotServiceId = "0000dfb0-0000-1000-8000-00805f9b34fb"
var kBlunoService = "DFB1"
var kBlunoDataCharacteristic = "dfb1"
var DFRobotCharacteristicsNameSerialPortId = "0000dfb1-0000-1000-8000-00805f9b34fb"
var NameCommandId = "0000dfb2-0000-1000-8000-00805f9b34fb"
// BLE
var centralManager: CBCentralManager!
var sensorTagPeripheral: CBPeripheral!
override func viewDidLoad() {
scanButton.setTitle("Scan", forState: UIControlState.Normal)
scanButton.addTarget(self, action: "startScanning", forControlEvents: UIControlEvents.TouchUpInside)
scanButton.backgroundColor = UIColor.blackColor()
backgroundView.addSubview(scanButton)
self.view.addSubview(backgroundView)
backgroundView.snp_makeConstraints { (make) -> Void in
make.left.right.top.bottom.equalTo(self.view)
}
scanButton.snp_makeConstraints { (make) -> Void in
make.left.bottom.equalTo(backgroundView)
make.width.height.equalTo(60)
}
// Initialize central manager on load
centralManager = CBCentralManager(delegate: self, queue: nil)
// self.centralManager.stopScan()
}
func startScanning() {
// print("Started Scanning!")
// //Could add service UUID here to scan for only relevant services
// self.centralManager.scanForPeripheralsWithServices(nil, options: nil)
let one = "1"
let data = one.dataUsingEncoding(NSUTF8StringEncoding)
// self.sensorTagPeripheral.writeValue(data!, forCharacteristic: , type: .WithoutResponse)
// self.sensorTagPeripheral.writeValue(data!, forCharacteristic: CBCharacteristic., type: .WithoutResponse)
// self.centralManager.
}
// Check status of BLE hardware
func centralManagerDidUpdateState(central: CBCentralManager) {
if central.state == CBCentralManagerState.PoweredOn {
print("Bluetooth is ON")
central.scanForPeripheralsWithServices(nil, options: nil)
} else if central.state == CBCentralManagerState.Resetting {
print("RESETTING")
} else if central.state == CBCentralManagerState.Unauthorized {
print("Not Authorized")
} else {
print("Bluetooth switched off or not initialized")
}
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
// print(peripheral)
let deviceName = "Bluno"
if let nameOfDeviceFound = peripheral.name {
if (nameOfDeviceFound == deviceName) {
print("Name was found")
print("")
print("")
print(peripheral)
// for (key, value) in advertisementData {
// print("\(key) -> \(value)")
// }
// Stop scanning
self.centralManager.stopScan()
print("Stopped Scanning")
// Set as the peripheral to use and establish connection
self.sensorTagPeripheral = peripheral
self.sensorTagPeripheral.delegate = self
self.centralManager.connectPeripheral(peripheral, options: nil)
print("")
print("")
print("Connected")
print("")
}
else {
print("NOPE.EXE")
}
}
}
// // Check if the service discovered is a valid IR Temperature Service
func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
if(error != nil) {
print(error?.description)
}
for service in peripheral.services! {
let thisService = service as CBService
print("Discovered Service: \(thisService.description)")
print("Discovered Characteristic: \(thisService.characteristics)")
}
}
func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
if(error != nil) {
print(error?.description)
}
for characteristic in service.characteristics! {
print("Characteristic found: \(characteristic)")
let one = "1"
let data = one.dataUsingEncoding(NSUTF8StringEncoding)
peripheral.writeValue(data!, forCharacteristic: characteristic, type: .WithoutResponse)
if(String(characteristic.UUID) == kBlunoService) {
print("Found")
}
}
}
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
print("Did connect to peripheral.", terminator:"")
peripheral.delegate = self
peripheral.discoverServices(nil)
print(peripheral)
}
func centralManager(central: CBCentralManager, didFailToConnectPeripheral peripheral: CBPeripheral, error: NSError?) {
print("Failed to connect to peripheral.")
}
// func centralManager(central: CBCentralManager, didFailToConnectPeripheral peripheral: CBPeripheral, error: NSError?) {
// print("CONNECTION FAILED")
// }
func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
print("CONNECTION WAS DISCONNECTED")
}
}
Managed to get this to work. Code can be found here.