I am writing an iOS application which will communicate to a device using Bluetooth Low Energy (BLE).
When my connected device is out of range, my application is getting disconnect event.
But I am not getting any connect event when the device comes back to the range.
Please suggest any approach to detect when the device comes back to the range.
when you get the event that the device is no reachable anymore, you can launch a method which continuously checks if the device is still not reachable.
For example: while(isConnected==false)...
You say that you get a notification if the device disconnects, so you can use that event to launch such a method
When you receive the disconnect event, just restart the scan function:scanForPeripheralsWithServices or you can setup the scan mode to accept duplicate key by [_manager scanForPeripheralsWithServices:self.targetDeviceServiceIDs options:#{CBCentralManagerScanOptionAllowDuplicatesKey:#YES}]. which means the same device would be discovered for many time until you stop scanning.
Just reconnect in your disconnect method if the connection times out. There's no need to start a scan. It will automatically try to reconnect until it gets in range.
When your device get disconnected or goto out of range of Bluetooth then you don't need to scan for Peripheral devices because it already scanned for the BLE devices.
Call function [centralObj connectPeripheral:peripheral options:nil]; into didDisconnectPeripheral delegate method when BLE device disconnected.
Related
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.
I've noticed that when you disconnect for a bluetooth device in an application the iOS device will continue to hold that connection for around 10 seconds. I've attempted to get around this by writing to a characteristic that causes the bluetooth module to cancel the connection with the iOS device instead but that isn't working (mostly because I changed the module and the iOS doesn't see the change because I assume the device is cached somewhere). Is there a way to make it disconnect instantly in code? I am using the swift command
manager.cancelPeripheralConnection(peripheral) currently.
Unfortunately, there isn't. The only way to tell the system to disconnect a peripheral is via the cancelPeripheralConnection method that you are already using. Yet, if you call this method it doesn't necessarily mean that the peripheral will be disconnect.
Background
On iOS the whole BLE connection management is maintained by the operating system. That means that no single application "owns" a connection. All BLE functionality is multiplexed to allow more than one application to gain access to centrals and peripherals.
For example, if you have installed a fitness application that tracks data from your heart rate sensor in the background then you can also "connect" to the heart rate sensor in your app but you will be unable to trigger a real disconnect as long as the fitness app maintains a connection.
The disconnect delay you have noticed is basically an optimization of the operating system. If no application holds a connection to the peripheral anymore it will wait for some time (to avoid unnecessary connect/disconnect cycles) and then trigger the disconnect on the bluetooth chip.
Hope that helps.
In iOS when you call the CBCentralManager method cancelPeripheralConnection(CBPeripheral), it does not always immediately terminate the connection. As Apple's documentation states:
Because other apps may still have a connection to the peripheral, canceling
a local connection does not guarantee that the underlying physical link is
immediately disconnected. From the app’s perspective, however, the
peripheral is considered disconnected, and the central manager object calls
the centralManager:didDisconnectPeripheral:error: method of its delegate
object.
If you have a need to immediately terminate a connection programmatically, say to free up the peripheral to be connected from another central device or to reset one's own security layer, then you need to follow the procedure described in the following StackOverflow thread:
iOS 6 - Bluetooth LE disconnect
which is for the app to send your own proprietary command to the peripheral that tells the peripheral to disconnect through normal means (e.g. "GAPRole_TerminateConnection" or "GAP_TerminateLinkReq" or "LL_Disconnect" or "HCI_Disconnect[_*]" with reason HCI_DISCONNECT_REMOTE_USER_TERM). This always works and is not delayed by the connection supervision timeout because it is a formal disconnection notifying the central device (i.e. iOS). The supervision timeout (up to 6 seconds in iOS; on Android the default is 20 seconds) only comes into play if the disconnection is unplanned as with going out of range or if the peripheral does a disconnect without notifying the remote device as with "LL_EXT_DisconnectImmed" (only available in some BLE peripheral implementations).
I have a use case where my objective-c application needs to immediately use iBeacon after it's been terminated in order to wake the application up from a terminated state, connect to BLE and send a command to the device. I have a larger longer running post found here that you can check out for my code if need be.
The Problem
The problem so far, happens when I run the application, search for previously paired devices and/or scan for peripherals, find my BLE device and connect. Once connected the user pairs the BLE connection so that they can send encrypted characteristic data over BLE connection. Without pairing (aka auth/bond in the device's naming conventions) the user cannot send the data to the device at all. It never makes it there. As soon as you pair you can send the command...
When I terminate the app, in the applicationWillTerminate method, I run through this code...
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(#"*** Application Will Terminate.");
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSNumber *mode = [userDefaults objectForKey:#"deviceConnectedMode"];
if([mode intValue] == CONNECTED_MODE_INDICATOR) {
[self.bluetoothManager sendCodeToBTDevice:#"magiccommand1"
characteristic:self.bluetoothManager.wipeCharacteristic];
//I have been turning this command on and off in testing to see if I can get it to work better while disconnecting in the device rather than in the app...
//The command magiccommand2 wipes the auth/bond inside of the device
// [self.bluetoothManager sendCodeToBTDevice:#"magiccommand2"
// characteristic:self.bluetoothManager.disconnectCharacteristic];
//Place where I attempt to stop being connected to BT
[self.bluetoothManager disconnectDevice];
[self.beaconManager startMonitoring];
NSLog(#"*** Application terminated from connected mode!");
} else {
NSLog(#"*** DriveCare terminated without violation!");
}
}
What I am trying to accomplish
The magiccommand1 and magiccommand2 commands are just silly test strings (for now, 128 bit tokens later) that the device is listening for over the serial port. Once they receive the commands they react by attempting to wipe the auth/bond on the device and also disconnecting from BLE in the device.
So the only way I can seem to get the app to wake up from a terminated state is with iBeacon. So I am having to do a bunch of seemingly dirty stuff here just to get this round peg in a square hole. In the app's lifecycle it connects and pairs and when I terminate I want it to completely be removed as a connected device from BLE. My hope is that iBeacon will wake up the app, connect back to BLE, shut off iBeacon monitoring and then send a command to the BLE device from a terminated state. This turning on/off or connecting/disconnecting from iBeacon to BLE and back would most likely cause the user to have to re-pair and I don't want this.
More Problems
When I call [self.centralManager cancelPeripheralConnection:self.thePeripheral]; the iOS system level BT manager seems to auto reconnect almost instantly (because of the pairing) so there is no time for the connection to be severed and iBeacon to be picked up again. If I attempt to disconnect from my centralManager instance before sending the disconnect command to my box (as seen above in code commented out) they obviously wont send either. If I just use only that CBCentralManager disconnect method it isn't enough for iBeacon to begin being detected as the iOS system is still paired with the device. Lastly, if I then go into my iOS system BT manager and choose "Forget this device", the iBeacon gets picked up again and my didEnterRegion method fires!
This is a lot of back and forth between iBeacon and BLE and I just wish I didn't even need iBeacon to wake up the app. I have all info.plist background BLE and iBeacon services turned on. If I don't connect to BLE at all and never pair once and connect my device, the local app notifications slide in without problem letting me know that the iBeacon didEnterRegion and didExitRegion methods are being fired without trouble.
What am I doing wrong here?
I figured out this answer! I was sort of on the right path... It's not a great idea to try and disconnect from BT pairing in order to try to get iBeacon delegate methods to fire. It causes a lot of weird dirty code, tightly coupled things that shouldn't be and worse... the dreaded spaghetti code.
Instead my solution was to build a board with 2 Bluetooth chips in it. One as a dedicated iBeacon chip and the other as a permanent connected/paired/auth and bonded chip. This way there does not need to be any timed connection and disconnection events to be monitored (or worse... resolved race conditions). Waiting for a device to disconnect in iOS is not a true disconnect, it is instead when the iOS system thinks it's disconnected (in the phone's side of things). The things going on inside of the peripheral system paint a very different picture...
This 2 chip approach made life so much easier on the development side because now anytime you need the application to wake up to handle something, you can power cycle the iBeacon device and receive the appropriate event.
Side note: When using iBeacon the didEnterRegion method fires almost instantly where as the didExitRegion can take 30 or more seconds sometimes to fire. Therefore if you need immediate beacon detection resolution you should try to make sure the iBeacon is being powered on and not off for this... if even possible.
In Core Bluetooth, after connecting to a device, I turn off the device and the device is disconnected. But when I turn on the device again, there is no didDiscoverPeripheral called again. How can I reconnect to the device again?
When you disconnect a device with cancelPeripheralConnection the didDisconnectPeripheral delegate method will be invoked. However from iOS 6.0 the device remains connected for about 40-50 seconds (or more), so no didDiscoverPeripheral will be invoked in that timeframe. If you want to "discover" it again just call the retrieveConnectedPeripherals method and you will get the reference in didRetrieveConnectedPeripherals.
However, the best solution is to save the device's UUID and use that to reconnect with the retrievePeripherals method. This will invoke didRetrievePeripherals and you can reconnect with connectPeripheral. This is the fastest way to reconnect to a device, no scanning is required in this case.
When you do a scan with scanForPeripheralsWithServices, it will normally only notify you once for a particular device address. You can change this to report duplicates by specifying the option CBCentralManagerScanOptionAllowDuplicatesKey. Or you can have your app detect that the other device disconnected using a timeout, and restart your scan.
#Andras gave me the right path, but his answer is not complete anymore since iOS7.
The best way to reconnect to a previous device is to use the retrievePeripherals(withIdentifiers:) method.
This method does not call a delegate, but directly returns you a list of Peripherals, corresponding to the list of UUID passed in parameters.
if let peripheral = self.centralManager.retrievePeripherals(withIdentifiers: [uuid]).first {
self.peripheral = peripheral // <-- super important
self.centralManager.connect(peripheral, options: nil)
}
Please check the "super important" line of the above code: The method connect(_:option:) does not retain the peripheral, and if you don't do it yourself, the connection will always fail without any callback since the peripheral objet will be destroyed.
In CoreBluetooth all management is done by application layer.
In your case, what I would do it is to listen for disconnect event than in same event, reconnect the peripheral.
The connection method is an inexpensive one and assure you to reconnect to your device when it is back in range.
Note that if you explicitly disconnect the device, you received the same disconnect event, but you haven't to call the reconnect method.
I've got an iPhone, I've got a bluetooth low energy (aka Bluetooth 4.o) device. The two have connected in the past. But now they're apart. Something happened, words were said that couldn't be taken back, and now they've disconnected.
Now they're in the same room again. The BTLE device wants to connect again. It doesn't want to wait for the iPhone to call it. It's taking the initiative. It wants to talk to the iPhone, and get that app they used to share launched again.
How do I set up the iPhone code to support this?
(Wow, corny.)
You have to leave the app connecting to the device, so when the device becomes available the app will connect to it (so long as the app is alive). Just call [centralManager connectPeripheral] on the CMPeripheral, so the phone will keep listening for the device and connect immediately when it sees it. That doesn't time out, so you can just leave it running forever.
If no app is to connect to the peripheral, there's nothing the peripheral can do to change that.
This is a little late, but here's what I would do to accomplish this:
When your device wants to connect, have it advertise a special service.
Assuming the phone is already scanning for devices, it will see the service and you can auto-connect to it.
Now you can have the periphal trigger whatever you like on the central by updating a characteristic, etc.
Of course, this relies on the phone scanning at the time you need the action to trigger, but that is the nature of the beast.