Setup, uniqueness of the situation, and success scanning in foreground
I am trying to pick up bluetooth readings from a CBPeripheral device. What is interesting about this particular device is that it sends out UUIDs that are embedding information in them directly. (This seems strange, but I don't know enough about BLE to know whether or not that's typical. There is no actual connection occurring in the end - only advertisements are being sent out.)
In the foreground, I am able to identify which readings are mine because [peripheral name] remains constant. So I'm able to pick up the readings that are relevant by doing the following check inside of didDiscoverPeripheral:
if ([[peripheral name] isEqualToString:#"UNIQUE_IDENTIFIER"]) {
NSLog(#"*** Got a reading ***");
}
This is working nicely in the foreground - I am running
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
in a loop (details there seem irrelevant) and the code is able to print out all of the readings that I expect it to print.
Trouble scanning in background and attempts to solve
My app's Info.plist contains the following entry:
I have also check the (what I think is) the appropriate field in the Capabilities tab in XCode:
Neither of these things have turned out to be useful. didDiscoverPeripheral is called perfectly up until the point where I close my application, at which point it stops immediately.
One other thing that I read on the internet was that background processing still won't happen when scanForPeripheralsWithServices is called with nil, however if a non-empty array of services is passed instead, then it should succeed. I'm not sure if this is correct. The problem is, I have only been able to find examples of calling scanForPeripheralsWithServices where the services are identified by their UUIDs, for example
NSArray *services = #[[CBUUID UUIDWithString: #"2456f1b9-26e2-8f83-e744-f34f01e9d701"]];
[self.centralManager scanForPeripheralsWithServices:services options:nil];
but not their name property.
The question
So I guess my question, after all of this, would be: Would providing an array of name filters (instead of UUIDs) help the application to run in the background, and if so, how would I write that in code? If not, what am I missing such that my app still only works in the foreground?
EDIT: I had previously used the terminology "identifier" when I think I meant to say "name", so I went back and changed those. From the documentation, here is the name value that I would like to scan for in the background https://developer.apple.com/documentation/corebluetooth/cbperipheral/1519029-name?language=objc
In order to scan for new devices in the background you must specify the UUIDs of the service(s) that you are interested in. The documentation is quite clear on this.
The identifier for a device is a value locally determined by iOS, not the device itself and will vary for different iOS devices connecting to the same peripheral. If you know the identifier for a device that you have previously discovered then you can use the identifier to connect to the device but you can’t scan for an identifier.
It is possible for a peripheral to include data in its service advertisement and perhaps this is what your peripheral is doing. If so, then you will not be able to get readings in the background since duplicate device discovery events are not delivered when your app is in the background.
You can discover and connect to a new device in the background as long as you know the service that it is advertising but any subsequent data transfers will require the device to issue a notify on a changed characteristic. It cannot use the manufacturer data portion of the advertising frame.
Related
I am trying to connect BLE device using his hex number written on his peripheral name. but the peripheral name is different for some reason on different devices.
What could be the reason for that?
also i tried to find it inside advertisementData but without success.
thanks
Your device might have the shortened name in the advertisement data but have the longer one in the scan response.
If you're scanning while the app is in the foreground, you'll get the full scan response data, so it might not matter in your app. You just might have to ignore the first callback that doesn't have scan response data.
I'm writing a suite of Bluetooth Low-Energy app for iOS 7.
When the app launches, there's a fair chance the peripheral I'm interested in is still connected to the iOS device, and to avoid scanning for the peripheral it would be great to connect immediately. retrieveConnectedPeripheralsWithServices: looks perfect for this, and indeed seems to work as advertised: I get a list of CBPeripherals which are connected to the device via some backgrounded app.
So far so good, but here's the issue:
There's information in the AdvertisementData which I want, but I don't know how to get the AdvertisementData when going this route.
As far as I can see, advertisementData is only available as a result of scanning.
So my question is this:
* Given a CBPeripheral returned by retrieveConnectedPeripheralsWithServices:, how can I get the associated advertisementData for that peripheral ?
One workaround would take advantage of the fact that the device was likely connected to another of the apps in my own suite, so I could persist the advertisement data myself in storage shared between the apps, but this is neither clean nor ideal, because there's a chance the device was connected to someone else's app, and so I'd have no insight into the advertisement data in that scenario, and would need to resort to a scan.
Unfortunately, the advertisement data is available only if you scan. It is not possible to retrieve it from Core Bluetooth or any other framework in any other way. You should revise your design if the advertisement is so important and rather go along the scanning route.
Sorry for the long title, but we are having a pretty interesting issue with using corebluetooth for ios. We are issuing a call to retrievePeripherals in CBCentralManager and are able to find the previously paired device.
This happens though regardless if the device is on or off though. I can't find anything in apple's documentation as to why it's able to find the device when it is off though and it isn't showing up in Settings -> Bluetooth -> Devices. I'm suspecting that Apple is caching this information but can't find any documentation to confirm this. Also, when the device is off and we issue the connect call, the program continues to execute as normal but the delegate for didFailToConnect never gets called. When the device is turned on, it will connect immediately.
Is there a way to pass a timeout parameter when trying to connect to a device? If not, what would the best solution be to handling reconnecting to a previously used device for an application (we're storing the last connected device within the app).
Two points you need to know about retrievePeripherals: and connectPeripheral:
1.) retrievePeripherals: attempts to retrieve the CBPeripheral object associated with the uuid you supply. Even if the ble device is off (or on the other side of the country) retrievePeripherals: will still return an instance of CBPeripheral that you can call connectPeripheral: on. This is done intentionally so that you can issue a call to a peripheral that is not even around and still automatically connect to it when it comes back into range. It basically creates a marker inside the system bluetooth so that when the device is actually seen, it will know it should connect to it.
2.)connectPeripheral: will not time out unless the communication channel is broken with the actual device. If the iOS device has not seen the device, it will not fail and should not time out (unless some error occurs inside the system bluetooth).
And as for the timeout parameter, there is no documented way inside the CoreBluetooth framework. You can create your own implementation for it, however I believe you'd be better off keeping a list of which peripheral uuid's you've actually called connectPeripheral: on and then just pop them from the list when they connect. If you no longer want to connect to a peripheral in the list call cancelPeripheral: on that UUID, call connectPeripheral: on the other, and swap entries. Good to go.
I've looked everywhere and tried everything, but nothing seems to work :(
On iOS, I'm making an app (for iOS 6 and above) in which iOS devices need to exchange data. Therefore, both devices need to be peripheral and central at the same time. I've done exactly as specified in the WWDC video, but the devices can't connect successfully with each other.
When I make one device only central and the other only peripheral, the central connects seamlessly to the peripheral.
However, when both devices are peripheral and central at the same time, I get random errors: at any stage (discovering services/characteristics or setting notify value to YES) errors sometimes happen, and sometimes discoverServices doesn't even call didDiscoverServices
Is there something different I should be doing? I simply merged the peripheral and central code into one view controller. I've noticed that if device "a" connects to device "b", and then device "b" connects to device "a", it works more often than not. I manage this by using NSThread sleepForTimeInterval: manually for different amounts of time on each device, but how could I get one device to connect first (and then the other) in a reliable (and not manually pre-defined) way?
If I do get errors, usually they're simply Unknown error
Please let me know if you need any code or any other information :)
Yes, it can be in both roles at the same time. You just have to initialize a CBPeripheralManager and a CBCentralManager. As soon as the peripheral manager is initialized and you receive the POWER ON state the device starts acting as a peripheral. You can add your services at this point and receive connections from other devices. At the same time you can use the central manager to scan and initiate connections to other peripherals.
Note that you cannot connect to your own device even if it acts as a peripheral.
For your errors, I suggest:
Turn off scanning before initiating a connection. That is, scan, find peripheral, stop scan, connect. Connection and scanning do not like each other.
Use a dedicated queue for handling bluetooth events, not the main queue. [[CBCentralManager alloc] initWithDelegate:self queue:my_dedicated_bluetooth_q]
Unfortunately, the stack sometimes become unstable. Even restarts are possible. But this usually happens only under heavy loads or several simultaneous connections. Hopefully, this will be improved in iOS7.
The unfamous Unknown error started to appear for several developers recently. Judging from your description there are probably a number of reasons why your setup may fail and it would require much more info that what fits well into a SO question.
For more info I suggest you search the bluetooth-dev mailing list archives https://lists.apple.com/archives/Bluetooth-dev or send a mail Bluetooth-dev#lists.apple.com. The community provides great help if you approach with reasonable questions like this.
As per my understanding one device can work with one mode at a time . That is if the device is working in the peripheral mode then it you cant work it as a central mode .If you see some standard examples like BTLE transfer or lilke Light Blue those are working in one mode at a time .
Firstly, what do you mean "the same time"?
If you mean the device advertising to other devices while it scanning for other devices, it can not.
But you can create two threads which share same lock to advertising and scanning.
Before scanning, stop advertising, before advertising, stop scanning.
I tested on my iPhone 4s and iPad air, worked well.
I'm implementing CBPeripheralManager on my iPhone running iOS6 and I have created and added a custom service and characteristic. An external Bluetooth Device (the BLE112, if it makes a difference) acts as the Central and starts writing data to the characteristic I have implemented. I know it's being written because a test application on the App Store (light blue) sees it as being written, but I'm just not sure how to see it from the CBPeripheralManager. didReceiveWriteRequests is never called, and the "value" property on my characteristic variable is never called either. So, to sum up, my question is: From the CBPeripheralManager, how can I tell when updates to a characteristic have been written from a central?
Through much trial and error, I found that under the Service property for the iOS side, you need to create it with "CBCharacteristicPropertyWrite" instead of "CBCharacteristicPropertyWriteWithoutResponse" in order to get didReceiveWriteRequests. Seems obvious, but kept me scratching my head for a while.