Why does iOS bluetooth discover the same device more than once? - ios

I'm trying to discover a heart rate monitor with this statement:
[self.centralManager scanForPeripheralsWithServices:#[[CBUUID UUIDWithString:SERVICE_HR]] options:#{#"CBCentralManagerScanOptionAllowDuplicatesKey":#NO} ];
SERVICE_HR is defined to be the heart rate service number.
As I read the documentation the option I gave means to coalesce all discoveries of the same device into a single discovery event. I'm scanning for 2.2 seconds with only one heart rate monitor available and getting 2 or 3 discoveries. Since the HR monitor advertises once per second that makes sense if every advertisement is "discovered" separately. I get the following callback 2 or 3 times:
(void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
I save the data from each callback into a mutable array. Here is the array with two discoveries:
(lldb) po [RCLBTLE sharedBTLE].discoveredPeripherals
<__NSArrayM 0x17404bc70>(
<CBPeripheral: 0x1700f8180, identifier = 1DC9167F-6DB8-4216-5217-B1E8B2F3FB90, name = Polar H7 3F1DE71C, state = disconnected>,
<CBPeripheral: 0x1700f8180, identifier = 1DC9167F-6DB8-4216-5217-B1E8B2F3FB90, name = Polar H7 3F1DE71C, state = disconnected>
)
As you can see the same device was discovered twice but I think it should have been discovered only once. What am I doing wrong? Or what am I misunderstanding?

When specifying the scan use the following code
[self.centralManager scanForPeripheralsWithServices:#[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] options: #{CBCentralManagerScanOptionAllowDuplicatesKey : #NO }];
If you specify the above key as #YES then it will detect the device when ever it is seen.

Related

Bluetooth peripheral not found until another app connects to it

I have a fitness app that can connect to Bluetooth LE heart rate monitors. I've had no problem with it for the last few years. Now I'm updating it for iOS 10, and something strange is happening. If I scan for peripherals like this...
- (id)init {
self.heartRateService = #"180D";
self.heartRateKey = #"2A37";
self.allServices = [NSArray arrayWithObjects:[CBUUID UUIDWithString:self.heartRateService], nil];
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
- (void)startScanning {
if (self.centralManager.state == CBCentralManagerStatePoweredOn) {
[self.centralManager scanForPeripheralsWithServices:self.allServices options:nil];
}
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
DLog(#"found peripheral: %#", peripheral);
DLog(#"advertisementData: %#", advertisementData);
DLog(#"RSSI: %#", RSSI);
}
...it can't find the heart rate monitor -- the scan starts but didDiscoverPeripheral is never called. If I try competitor app #1, it finds and displays data from the HRM, but if I quit it and return to my app, my app still can't find the HRM. If I try competitor app #2, it finds and displays data from the HRM, and if I quit it and return to my app, then my app connects immediately and works as expected. It seems like competitor app #1 has a different way to find the HRM, while competitor app #2 does something to not only find the HRM, but let my app find it afterwards. Although this scenario seems strange, it is 100% reproducible.
I can make two guesses about what's happening:
1) Competitor app #2 enables some setting on the HRM that allows it to be found. But I don't think this is possible because I think communication with a BTLE accessory is one way.
2) iOS 10 has some kind of privacy setting that is disabled by default, but competitor app #2 enables it, allowing my app to see the HRM afterwards.
Does this make any sense, or does anyone have a theory about what's wrong?

iOS Core bluetooth get all features description from device

How to can I get all available features from my device using Core Bluetooth.
I figured that before get any information we need to observe all devices via low energy bluetooth.
So the first step is scan for all peripheral devices scanForPeripheralsWithServices via CBCentralManager. In the delegate callback:
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
we can save peripheral identifier and save peripheral to handle it in future like change some characteristics, switch off/on and etc.
But my question how to get description of this function, for example I got some characteristic, but I don't know how to use. Where to find info about this future.
Let me describe situation. For example I have sound player with some options where I can handle volume of sound via bluetooth.
So I need to get peripheral first, then detect service and then discover characteristic to find volume "property", but how can I find it, how should I understand which is min/max amount of volume where to find this information. For example we can pass 0 as min volume amount and 1 as a max. But it also could be in range from 0 to 1000 or any other. How to detect this information?
You can't be sure to find the "documentation" for each characteristics, like what are the possible values, etc.
If the Services & the Characteristics follow the documentation of Bluetooth, and are per se "documented", it just follow the doc, it's here. In theory, theses "well known" services and characters should have an UUID like "0x0000".
Example: Battery service is 0x180F, Battery Level is 0x2A19 and the possible values have a defined protocol.
For other custom services/characteristics, it's more difficult. Each one has their own documentation, and the UUID is longer (if it's developed respecting the rules).
If set, you can read the CBDescriptor to get more info.
In all case, you have to either refer to the Bluetooth Low Energy Documentation, or if it's custom to the manufacturer. Either wise, it's all about reverse-engeenering.
You can refer this Demo Project here.
With the following snippet you can get all characteristics, services associated with any iOS supported peripheral.
BabyBluetooth *objBluetooth = [BabyBluetooth shareBabyBluetooth];
objBluetooth.scanForPeripherals().begin();
You can set Delegate call-backs in this manner.
-(void)babyDelegate{
//If any peripheral discovered
[objBluetooth setBlockOnDiscoverToPeripherals:^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) {
NSLog(#"搜索到了设备:%#",peripheral.name);
}];
//Set the filtration criteria for the bluetooth peripherals
[objBluetooth setFilterOnDiscoverPeripherals:^BOOL(NSString *peripheralName, NSDictionary *advertisementData, NSNumber *RSSI) {
//if ([peripheralName hasPrefix:#"Pxxxx"] ) {
// return YES;
//}
//return NO;
if (peripheralName.length >1) {
return YES;
}
return NO;
}];
//and so on.
}
Once peripheral filtered , you can get it from the array and then use this:
objBluetooth.having(peripheralFromArray).then.connectToPeripherals().discoverServices().discoverCharacteristics().begin();
[_channelForaD40 setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error) {
//get your characteristics description here
}];
[_channelForaD40 setBlockOnReadValueForCharacteristic:^(CBPeripheral *peripheral, CBCharacteristic *characteristics, NSError *error) {
//Read the values of your characteristics here
}];
then, you can finally compare your characteristics with Bluetooth standards.

How get the list of paired bluetooth devices in swift?

I need to get the list of paired bluetooth devices(iOS Devices) as same as the list in 'Bluetooth' section in iOS settings as shown in below picture.
Is it possible?
Have you seen any apps doing this type of functionality?
I have tried the following:
link1, link2, link3, link4, link5, link6
But nothing helped me clearly to get the exact list. I hope there should be a way to achieve this. Please help me by sharing your experience.
Thank you.
It's not possible to retrieve list of paired peripherals from iOS. Neither it's possible to check if specific peripheral is paired.
Retrieving peripheral which is paired
There are two cases which you need to consider:
Peripheral may be already connected in the system (iOS connects automatically with some peripherals in order to for example display battery level). In this case peripheral won't be broadcasting and detection using scanForPeripherals won't work.
Peripheral is paired, but disconnected. In this case retrieveConnectedPeripherals(withServices:) won't work.
Therefore to retrieve your peripheral you need to combine both things. First you need to check if it's in peripherals returned from retrieveConnectedPeripherals(withServices:). If not you should scanForPeripherals.
If you want to retrieve peripheral which is out of range, you can try to use retrievePeripherals(withIdentifiers:), however it may return also not paired devices and it relies on peripheral's UUID which you have to save after pairing.
Detecting if peripheral is paired
There is one way to detect if the specific peripheral is paired. You need to try to read from protected characteristic (which requires encryption - bonding). If you receive expected data, it means that user accepted pairing request. Otherwise you will receive empty response or none.
References
You can read more about Bluetooth in my articles:
Original answer
Swift – Bluetooth Low Energy communication using Flow Controllers
How to communicate with Bluetooth Low Energy devices on iOS
Bluetooth Low Energy – background mode on iOS
You can use ShowBluetoothAccessoryPicker if you are using the Classic Bluetooh into an MFi device.
https://developer.apple.com/documentation/externalaccessory/eaaccessorymanager/1613913-showbluetoothaccessorypicker
The code below is a C# but you can easily convert it into the IOS (object c your swift)
EAAccessoryManager.SharedAccessoryManager.ShowBluetoothAccessoryPicker(null, completion: ((Foundation.NSError error) => {
Console.WriteLine("My callback");
if (error != null && (uint)error.Code != (uint)EABluetoothAccessoryPickerError.AlreadyConnected)
{
Console.WriteLine(String.Format("Error code: {0} Desc: {1}", error.Code, error.DebugDescription));
Console.WriteLine("Failed? " + EABluetoothAccessoryPickerError.Failed.ToString());
Console.WriteLine("Failed? " + Convert.ToInt64(EABluetoothAccessoryPickerError.Failed));
}
}));
You need to find the service UUID in which you are interested, in my case it works perfectly,
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[self.centralManager scanForPeripheralsWithServices:#[[CBUUID UUIDWithString:SERVICE_UUID]]
options:options];
and when it will find any device which advertise same service UUID, then it will appear in the screen which you have pointed above.
handle didDiscoverperipherel in this way:
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
_discoveredPeripheral = peripheral;
if(![self.mRemoteDevices containsObject:_discoveredPeripheral])
{
NSArray *peripherels = [self.centralManager retrievePeripheralsWithIdentifiers:#[_discoveredPeripheral.identifier]];
[self.mRemoteDevices addObject:[peripherels objectAtIndex:0]];
}
}

Refreshing RSSI value of many bluetooth peripherals

I'm trying to mesure RSSI indicator on iOS (6 with BLE) from several bluetooth peripheral.
I can get RSSI with scanForPeripheral :
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[_manager scanForPeripheralsWithServices:nil
options:options];
coupled with:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
this works but I have no control on the rate of packets receive and the result seems uncertain.
I've read : https://stackoverflow.com/a/12486927/270209 but my rate is not close to 100ms at all (more 1~2 seconds)
If I'm connected to the device the result with readRSSI seems more reliable.
I'm looking for a way to "stimulate" peripherals for more frequents updates in scan mode or a way to connect to more than one peripheral at a time.
Thanks
Edit : I've also tried to start / stop scan quickly, it seems that at start scan detects more devices and updates are more frequent
I'm sure you've already figured this out but just in case someone comes across this (like I just did) looking for other info, if you're not using other iOS devices with coreLocation you need to call the peripheralDidUpdateRSSI: delegate using [self.myPeripheral readRSSI];.
You can call for RSSI data updates in didUpdateValueForCharacteristic: as often as you like once in the updateValue delegate.
You don't need this in viewDidLoad:
NSDictionary *options = etc... This NSDictionary is created in the didDiscoverPeripheral: delegate.
So the overall flow would be:
Check that you are receiving RSSI in the NSLog where you obtained the RSSI data...
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
if ([localName length] > 0){
NSLog(#"Discovered: %# RSSI: %#", peripheral.name, RSSI);
// your other needs ...
}
Call peripheralDidUpdateRSSI: here since this is where updates will occur continuously if you've set notify value to YES [peripheral setNotifyValue:YES forCharacteristic:characteristic];
- (void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//Call the peripheralDidUpdateRSSI delegate ..
[self.myPeripheral readRSSI];
// do all of your other characteristic value updates here
}
readRSSI will call the delegate where you perform your RSSI updates on your UILabel (or whatever you're using) every time you update the previous characteristics values:
- (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(NSError *)error;
{
_rssiLabel.text = [self.myPeripheral.RSSI stringValue];
NSLog(#"RSSI Method”);
}
If you don't need characteristic values for your app just run a loop in there with whatever timing you need the RSSI value to refresh.
(Assuming you're on an iOS device:) CoreBluetooth is likely deliberately limiting the rate at which it's active on the antenna. Bluetooth LE, Bluetooth Classic, and Wi-Fi are all on the same antenna on iOS devices, so the radios try to keep unnecessary chatter to a minimum. You could try filing a bug on CoreBluetooth to add an option or way to control the update frequency, but I don't imagine they'll implement it, as their primary design goals are to: 1. Not drain the device's battery unnecessarily and 2. Co-exist with the other antenna's radios. (They've also said in posts on the Apple devforums that, for example, if you want a higher data rate than the iOS BTLE implementation, you should use Bluetooth Classic or something that's been designed for it. BTLE is meant to be low-power first and foremost, and they've taken that to its logical end.)
Indeed, the delay between peripheral.readRSSI() and peripheralDidUpdateRSSI is like a second!
I think the best way is to read the RSSI value on the BLE device itself and send it to the iOS device.
The peripheral advertises based on the firmware of the peripheral. This can be set from as low at 100ms to almost as high as 2 seconds.
When using an iOS device as a Bluetooth Peripheral, I have found no way of changing it.
Here is Apples documentation: Look in section 3.5...
https://developer.apple.com/hardwaredrivers/BluetoothDesignGuidelines.pdf

Continuous scanning for iOS CoreBluetooth Central Manager?

The Low Energy Bluetooth spec does not say much about whether peripherals can connect to more than one central at a time, but my experience testing tells me that they cannot.
Because my application requires a non-possessive relationship with peripherals (i.e. no connections, which would block others), and needs to constantly update their RSSI values, I am seeking a way to continuously scan for peripherals and capture their RSSI values.
The scanForPeripheralsWithServices method appears to scan for a certain interval and then stops. I believe my best bet is to scan for 3 seconds at a time, stopScan, wait (several seconds) and then reinitiate a scan. Repeat.
Can anyone point to a better way of doing it? For example, configuring a peripheral to connect to more than one Central?
A peripheral cannot connect to more than one central. But if you need to just capture the RSSI then you don't even need connecting. Scanning for devices can retrieve the RSSI using this function:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
As for the previous answer, if you are interested only in RSSI you can simply get it into the delegate method:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
BTW, by default the CBCentralManager will only call this method once. If you need this callback to be called every time the CBCentralManager receives an advertisement packet you need to initiate the scan with the option CBCentralManagerScanOptionAllowDuplicatesKey set to YES:
NSDictionary *scanningOptions = #{CBCentralManagerScanOptionAllowDuplicatesKey: #YES};
[centralManager scanForPeripheralsWithServices:nil options:scanningOptions];
Beware that Apple discourage the usage of this option if not strictly necessary.
See: iOS Developer Library -Best Practices for Interacting with a Remote Peripheral Device
I solved this type of issue with this code, basically just restarting the scanning every time an advertisement is processed. I was facing the same issue where CBCentralManager instance would stop listening to a peripheral.
(Setting CBCentralManagerScanOptionAllowDuplicatesKey to #YES did not fully solve the issue for me.)
Assuming the class implements CBCentralManagerDelegate:
- (id) init {
self.central = [[CBCentralManager alloc]initWithDelegate:self queue:nil];
[self initScan];
}
- (void) initScan {
[self.central stopScan];
[self.central scanForPeripheralsWithServices:nil
options:[NSDictionary dictionaryWithObjectsAndKeys:#NO, CBCentralManagerScanOptionAllowDuplicatesKey, nil]];
}
- (void) centralManager:(CBCentralManager*)central didDiscoverPeripheral:(CBPeripheral*)peripheral advertisementData:(NSDictionary*)advertisementData RSSI:(NSNumber*)RSSI {
//
// Do stuff here
//
[self initScan];
}

Resources