I am attempting to scan for a bluetooth device using the CoreBluetooth framework. The device is a prototype provided by a client.
I believe my implementation of the CBCentralManagerDelegate is correct, but I am never receiving the centralManager: didDiscoverPeripheral: advertisementData: RSSI: callback. Even if I register for nil service UUIDs, which should fire discovery callbacks for all devices, nothing happens.
I would consider whether the bluetooth device might be defective, except here's the wrinkle... when I fire up the Bluetooth debugging application LightBlue on my iPad, the prototype device is listed, along with some services. In fact, as soon as I use that app's 'clone' feature to reproduce the service from the iPad directly, my delegate callback fires.
I can post some sample code but it really is boiler plate stuff (I believe). Bluetooth experts: are there any obvious causes for the behavior I am experiencing?
Some points to check: make sure you have set the delegate of your CCBCentralManager correctly. If your device is bonded to iOS device I suppose didDiscoverPeripheralshould not be fired, but in this case LightBlue app would not show any service at "Scanning for peripherals..." screen.
Good luck
Related
I'm trying to use the iOS 13 introduced capability in CoreBluetooth to do GATT over BR/EDR.
The accessory I'm using obviously implements GATT over BR/EDR (it even publishes its services using SDP).
According to the session WWDC 2019 What's New in Core Bluetooth session, we have to use the registerForConnectionEvents API on a CBCentralManager instance, providing services UUIDs (CBUUIDs) that will be available to use on the BR/EDR accessory.
In the session, they explicitly say:
Your app will have instantiated a CBCentralManager, passed us a known
service UID, and in the case of a BR/EDR or classic device, your user
will go to the Bluetooth settings and search for the device, in this
case let's say it's a headset running heart rate.
They'll discover the device, find it, and attempt to connect.
Pairing will be triggered, and then afterwards when we're connected,
we'll run a service discovery of the GATT services.
If we find a service that you want, then you'll get the delegate
callback.
So when doing a manual connection in the Settings app to the accessory, iOS does a GATT service discovery and if it matches, we receive an associated connectionEventDidOccur delegate callback with the event (peerConnected) and the associated CBPeripheral.
However, this delegate callback never happens.
I'm using the UsingCoreBluetoothClassic sample from Apple, with custom service CBUUIDs implemented by my accessory.
I did multiple attempts: providing only one service CBUUID, severals CBUUIDs, none, generic ones, very specific ones... It never fires.
Note that on Android, it seems to work: connection is possible using the MAC address directly, and a service discovery using SDP lists the accessory services.
Also note that the event fires when I'm scanning using scanForPeripherals and connect to my accessory (because my accessory also does GATT over BLE, but for specific reasons we explicitly want to perform GATT over BR/EDR).
Is there something I have to do on the accessory that I probably missed ?
Is it possible to be notified when a Bluetooth Device is connected or disconnected from iOS even when my app is in background ?
On Android, I use the ACTION_ACL_CONNECTED and ACTION_ACL_DISCONNECTED events. But I cannot find equivalents for iOS.
I found the CBCentralManager that can be used to monitor Bluetooth events, but my functions aren't called when a bluetooth device is connected/disconnected, only when I enable/disable the bluetooth. Is it an error on my side or is it normal ?
I also found the doc about Audio Route changes, that can also be an idea to detect the bluetooth connections/disconnections. Just check the kind of new route and detect the connected bluetooth devices at that time.
In the doc, I also found NSNotification types like IOBluetoothHostControllerXXX but nothing is explained about them. Did someone already used them ?
Is there something better or am I missing something ?
You cannot receive notifications about the connection and disconnection of Bluetooth peripherals generally. You can get connection and disconnection events for BLE peripherals that your app connects to.
For example, if your app initiates a connection to a heart rate sensor then you will get a call to your CBCentralManagerDelegate connection function when the connection succeeds. If that device subsequently is switched off or goes out of range then you will get a call to the disconnection delegate method.
If some other app initiates and makes the connection then you will not get a callback.
As you mentioned, you can monitor audio route changes to infer that a Bluetooth audio device has been connected/disconnected, but this will also fire when headphones are plugged in.
Successfully connected my BLE device using corebluetooth library and set notify values for certain service from it on my ios app. Works fine in foreground and background modes.
I currently save the identifiers of the device to UserDefaults and need the ios device to be always connected to the device at all times.
Question: What are the best practices for corebluetooth implementation with the states of the ios app? Specifically:
What should be implemented when user opens the app? (currently: I use the saved identifiers to reconnect to the ble device.. but is this necessary as corebluetooth should automatically stay connected to the device)
What should be implemented when the user backgrounds the app?
What should be implemented when the user reopens the app from background? (should i reconnect to the device?)
What should be implemented when the user kills the app? Does the corebluetooth connection get disconnected?
What should be implemented when battery is low and bluetooth is powered down or turned off by the device?
and lastly:
Is it necessary to implement the corebluetooth library in a singleton class to ensure that only one bluetooth manager is used by the app to connect to the devices?
Here are some advises and best (as I can see them) practices. These are not craved in stone:
When the user opens the app, it doesn't necessarily reconnects to the device. So your approach is good. However, you should check if the app wants to reconnect.
When the user backgrounds the app and the device is connected, the device stays connected. So nothing to implement here unless some special cases. We will get to them later.
When the user brings the app to the foreground (reopens it from background) and the device was connected before the app was sent to background, it still remains connected. So nothing to implement here.
When user kills the app, the device is disconnected, and there's nothing you can do. You can try to reconnect to the device when the user opens the app again.
I would monitor the device battery (check it once in a while) and when the battery reaches the critical level, let's say 5%, disconnect from the device.
And lastly: it is not necessary to implement it as a singleton. It is very convenient though when you have only one object that handles all the bluetooth connection. So, I'd say, singleton is a good choice.
In general you have to take into account two cases:
The app is killed by the system (probably because of memory pressure or crash). When the app is killed while in background, the system will relaunch it and the func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) function will be called. In this function you should try to reconnect to the bluetooth device. When the app is killed by the system while in foreground, sometimes (from my experience) the same function will be called. So you should do the same thing. When it's not called - see the following paragraph.
The app is killed by the user. In this case you cannot relaunch the app. But when the user opens it again you should check the latest connection status, and try to reconnect to the device if needed. Since you save the connected device in UserDefaults (which is the right thing), you should have all the needed properties.
For any other cases - disconnections because the Bluetooth turns off (on the device or on the phone) or because the device is out of range, the system handles the reconnection. So basically, nothing to do here.
Once again, all this is not carved in stone. That's how I implemented it, and it works fine.
I want my users to be able to track their heart-rate with my app. So I use CBCentralManager for that. Everything works fine if no other app is connected to the heart-rate sensor yet. The problem I have is if I start f.e. Strava or Endomondo first. Then I just can't find any devices any more. The other way round everything works fine, so I guess I am missing an options somewhere?
What I currently do:
I instantiate my CBCentralManager like so
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
which will cause the delegate method for connection to be triggered
func centralManagerDidUpdateState(_ central: CBCentralManager) {
let heartRateServiceUUID = CBUUID(string: "180D")
let services = [heartRateServiceUUID]
switch central.state {
case .poweredOn:
centralManager.scanForPeripherals(withServices: services, options: nil)
and from there on no peripheral are found.
But again, when I force quit other apps like Endomondo or Strava and then start my app, everything works fine.
Ok, I just found the solution to my main problem myself. So anybody who might have the same issue:
Once you are connected to a device remember it's UUID
When you want to reconnect to it you don't need to scan, just use centralManager.retrievePeripherals(withIdentifiers: [the id's of the devices you know])
This will give you back a list of CBPeripherals and you simple connect to them like you would have after searching for them.
The only thing that I still don't know:
Even when i uninstall Endomondo, start my app first, they still can discover my heartrate monitor... And I suppose even if they remember the UUID it will be in UserDefaults i suppose, so they can't reconnect using centralManager.retrievePeripherals(...). So I still like to know what I'd need to change in order for that to work...
See Best Practices For Interacting With A Remote Peripheral Device for full details on how to connect to devices you already know about.
The main issue you're encountering is that most BLE devices stop advertising once they have a connection. Since they aren't advertising, you can't see them in a scan. In this particular case, the BLE device is connected to the iPhone you're running on, but that doesn't change anything. It's still not advertising.
To deal with this, you want to ask the iPhone for connected devices that have the service you want, using retrieveConnectedPeripherals(withServices:). This is a very fast, synchronous call, and you generally should do it before calling scanForPeripherals(withServices:options:).
There are several other steps that you generally should do. The precise order and logic depends a little on your situation, but the linked flowchart above walks you through one approach. Basically it will look something like this:
Call retrievePeripherals(withIdentifiers:) to find a peripheral you already know the identifier for. Note that this just tells you the system knows about the peripheral; it doesn't mean it's currently nearby. Calling connect on it may never succeed.
Call retrieveConnectedPeripherals(withServices:) to find a peripheral that is already connected to this iPhone and advertises your service. You still need to call connect on it for your process, but it should succeed.
If all the rest fails, then call scanForPeripherals(withServices:options:).
Update:
It appears that BLE4.0 chipset did only support one connection so when a centralmanager connected to a BLE4.0 device it stopped advertising and established a connected layer.
With BLE4.1 chipset it added support for multi-role connection. It is possible that the heart-rate sensor use newer BLE technique that support more than one connection.
https://e2e.ti.com/blogs_/b/connecting_wirelessly/archive/2016/11/30/bluetooth-low-energy-multi-role-demystified
It is still unclear why your app wont connect if the other apps are connected. Can you post all your code?
My old answer:
"Core bluetooth and Bluetooth Low Energy can only handle one connection for each peripheral which means that if your app(CBCentralManager) establish a connection with a peripheral(UUID) it establish a message layer between the central and peripheral so other apps cant interfere the connection to that peripheral unless you disconnect the first app. This is how Bluetooth Low Energy works."
Apple Guides and Samples:
https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/AboutCoreBluetooth/Introduction.html#//apple_ref/doc/uid/TP40013257-CH1-SW1
Apple Core Bluetooth API:
https://developer.apple.com/reference/corebluetooth
I have a Bluetooth 4.0 (BLE) device using the CC2541 chipset which I am interfacing with via the iOS Core Bluetooth Framework.
I can successfully make a connection to the device using Core Bluetooth when the device is in a discoverable/advertising mode and transfer data to and from the device without any problem.
I maintain a collection of device UUIDs that I have connected with and I am now attempting to connect to one of these devices again using:
CBCentralManager
- (void)retrievePeripherals:(NSArray *)peripheralUUIDs
Calling this function appears to work and I receive a callback to my implementation of the the following function:
CBCentralManagerDelegate
- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals
The device I'm attempting to connect with is the one and only peripheral listed in the peripherals array that is passed to this function.
When I then attempt to connect to this device using my connect function (shown below) the connection will not initiate and I get no callbacks at all on either the CBCentralManagerDelegate or the CBPeripheralDelegate.
- (void) connectPeripheral:(CBPeripheral *)peripheral {
NSLog(#"Connecting to peripheral with UUID : %s\r\n",[self UUIDToString:peripheral.UUID]);
activePeripheral = peripheral;
activePeripheral.delegate = self;
[CM connectPeripheral:activePeripheral options:nil];
}
I can make the connection go through successfully, as described above, if I first place the device into discoverable/advertising mode, but this is not a workable solution. The device must allow reconnection without it being placed into discoverable mode.
I did note the answer given to this question CoreBluetooth: What is the lifetime of unique UUIDs suggests I need to pair/bond the BLE device with the iOS device but that this may be dependent on the BLE chipset, the device I'm using is the CC2541. Any advice on how to go about pairing with the device would be most useful, or indeed whether this is a necessary step. I have attempted to watch WWDC 2012: Advanced Core Bluetooth which might give me some assistance, but since Apple were hacked on Thursday I'm not able to access my acccount to watch the video.
Please let me know if any more details are required. The Bluetooth device is stable, but it is being developed internally. If changes may be required to the firmware to assist with the pairing process, I can pass this information along to the electronics team.
Why dont you try this?
It specifies first you need to send a request to a GATT characteristic that requires GATT_AUTHEN_READ permission. Then your CC2541 will respond with INVALID AUTHENTICATION. This will trigger Apple's internal Bonding mechanism and ask for a key. You can enter the passkey and then if the device and CC2541 are successfully paired, it will read the characteristic value and enter your callback.
NOTE: I'm not an iOS developer, but I have worked with CC2541. If you are using the default simplePEripheral example, then a characteristic in simpleProfile characteristic5 requires authentication to read. Take a look at profiles/SimpleProfile/simpleGATTprofile.c