Swift - BLE AllowDuplicatesKey behavior - ios

I have a question for people who have knowledge of BLE.
I have 2 ble devices (they are the same, so same primary service)
When I start the scan with the option CBCentralManagerScanOptionAllowDuplicatesKey = false and I put the 2 devices in advertisment, there is only one which is discovered. Why is the second one not discovered?
On what basis the parameter CBCentralManagerScanOptionAllowDuplicatesKey? Does it only based on the primary service to decide that a device is duplicate, or it is based on the device identifier (mac address)?
For the moment, according to my tests, I have the impression which is based only on the primary service, am I right?
If this is the case I think it is an error which is not based on the identifier (mac address) of the peripheral to decide if it is duplicate
EDIT
Code for start scanning is very straightforward:
let options = [CBCentralManagerScanOptionAllowDuplicatesKey: false]
let devicesUUID = devices.compactMap { $0?.cbUUID }
centralManager.scanForPeripherals(withServices: devicesUUID, options: options)
My delegate:
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
logger?.log(emoji: "💪️", message: "Found \(getPeripheralName(from: peripheral)) - \(peripheral.identifier)")
}

Related

Scan for peripherals with services returns nothing

I'm trying to scan for a specific device by it's service uuid - in this case JBL speaker, in future cases, I would like to scan for devices inside a car.
At first, I scanned for nearby peripherals with nil.
self.centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey:true])
Once I found my speaker (this is the data I get):
peripheral identifier = 0BD95D98-D979-67DA-F3AA-C6C03781E70B
discovered peripheral = <CBPeripheral: 0x283ed4000, identifier = 0BD95D98-D979-67DA-F3AA-C6C03781E70B, name = JBL Flip 4, state = disconnected>
advertisement data = ["kCBAdvDataRxSecondaryPHY": 0, "kCBAdvDataTimestamp": 643124133.584141, "kCBAdvDataIsConnectable": 1, "kCBAdvDataManufacturerData": <5700d11e 0100ff58>, "kCBAdvDataRxPrimaryPHY": 0, "kCBAdvDataLocalName": JBL Flip 4]
kCBAdvDataManufacturerData = {length = 8, bytes = 0x5700d11e0100ff58}
I connected to the speaker, and when I got the didConnect callback I called for discover services:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
self.connectedPeripheral = peripheral
peripheral.delegate = self
peripheral.discoverServices(nil)
}
When didDiscoverServices services returned I saw the following output:
services = Optional([<CBService: 0x281adc700, isPrimary = YES, UUID = 65786365-6C70-6F69-6E74-2E636F6D0000>, <CBService: 0x281adcb40, isPrimary = YES, UUID = FE8F>])
At this point, I wanted to use the service uuids that I got, and to scan for the JBL speaker.
So instead of called for scanForPeripherals with nil, I used an array with the services like this:
self.centralManager?.scanForPeripherals(withServices: [CBUUID(string: "FE8F"), CBUUID(string: "65786365-6C70-6F69-6E74-2E636F6D0000")], options: [CBCentralManagerScanOptionAllowDuplicatesKey:true])
Then I relaunched the app, but this time I didn't find any device.
Why is this so?
See the doc of scanForPeripherals(withServices:options:):
You can provide an array of CBUUID objects — representing service UUIDs — in the serviceUUIDs parameter. When you do, the central manager returns only peripherals that advertise the services you specify
The service you got 65786365-6C70-6F69-6E74-2E636F6D0000, you got it AFTER you made before a connection to it (didConnect), and after "scanning more infos on the device" (discoverServices).
But it was not advertising it, it was not telling everyone that it has a service with that UUID. It keeps it secret. So you can't scan for it.

How to read characteristics in background BLE using CoreBluetooth

I'm developing app which reads specific characteristics in the background mode. Can this be achieved in the background with selecting the background mode as "Uses Bluetooth LE Accessories" in the plist. Is there any chances of rejecting the app in background if we read particular characteristics of a known service? Reading the characteristics should happen continuously. If we setNotify to "True" will this work in the background. Please provide some valuable suggestions/work around if anyone is aware of it. Thanks in advance.
Can this be achieved in the background with selecting the background mode as "Uses Bluetooth LE Accessories" in the plist.
Yes you can definitely read ble characteristics when your app is in background mode setting the “Uses Bluetooth LE Accessories” background mode in the capabilities tab
Is there any chances of rejecting the app in background if we read particular characteristics of a known service?
If your app needs ble background mode and correctly declares it in the capabilities tab, the app won’t be rejected
Reading the characteristics should happen continuously. If we setNotify to "True" will this work in the background
In order to get notified each time the ble characteristic of your ble peripheral changes its value, you can correctly setNotify to true on the desired characteristic. Each time the characteristic changes its value the peripheral(_:didUpdateStateFor:Error) callback will be triggered and you can get the updated value. Setting the background mode correctly, this callback will be triggered even if your application is in background and your phone is locked.
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
error: Error?)
{
print("characteristic: ", characteristic)
guard let string = String(bytes: characteristic.value!, encoding: .utf8) else {
return
}
}
Capabilities -> On Background Modes -> Select External accessory communication & Uses Bluetooth LE accessories
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService,
error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
print(characteristic)
if characteristic.properties.contains(.read) {
print("\(characteristic.uuid): properties contains .read")
peripheral.readValue(for: characteristic)
}
if characteristic.properties.contains(.notify) {
print("\(characteristic.uuid): properties contains .notify")
}
}
}

CBCentralManager -- willRestore -- CBCentralManagerRestoredStateScanServicesKey

When restoring state with CBCentralManager the dictionary sometimes contains a scanned peripheral's UUID
internal func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
let scannedPeripherals = dict[CBCentralManagerRestoredStateScanServicesKey] as? [CBUUID]
}
In contrast to willRestoreState, didDiscoverPeripheral provides CBPeripheral objects that the Central Manager can connect with.
My question is, what can I do with the UUID? The Central Manager can't connect or detect the device when this happens.
Since the peripheral has already been discovered you don't need to scan for it.
You can pass the identifier to retrievePeripherals(withIdentifiers): in order to obtain a CBPeripheral instance that you can connect to.

sending data to bluetooth module to be read by arduino rom iOS swift 3

If i want to send data to a bluetooth module, which is connected to the Arduino, what code lines are specifically the ones I need to take notice of.
I want to send something like, the number '75' to the bluetooth module and the Arduino will read it
thanks
Bluetooth LE is very long and cumbersome with many delegates in between. The minimum path to write your data is:
Make sure you have bluetooth permission: CBCentralManagerDelegate.centralManagerDidUpdateStateand if so start scanning with scanForPeripherals
CBCentralManagerDelegate.didDiscover If this is the peripheral you want then set yourself as its delegate
CBPeripheralDelegate.peripheral:didDiscoverServices: If this is the service you want then stop scanning and discoverCharacteristics:for: service
CBPeripheralDelegate.peripheral:didDiscoverCharacteristicsFor: service If a characteristic in the array of characteristics is the one you want then:
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else {
return
}
for characteristic in characteristics {
if characteristic.uuid == CBUUID(string: characteristicIdentifier) {
let value: UInt8 = 75
let data = Data(bytes: [value])
peripheral.writeValue(data, for: characteristic, type: .withResponse)
}
}
}

Scanning for Peripherals offering specific services, But doesn't find Services on discovered peripheral

I'm experimenting with the CoreBluetooth Framework for iOS and I'm facing an issue that I find very Strange.
Once I have a Connected Peripheral, I want to discover it's services - but it fails. Despite searching for and finding Peripherals which offer a specific service. See the code below:
func centralManagerDidUpdateState(central: CBCentralManager) {
if(central.state != .PoweredOn) {
print("Returning due to wrong state: ", central.state)
return;
}
print("Starting peripheral scan")
manager.scanForPeripheralsWithServices(serviceUUID, options: nil)
}
Peripheral Scanning is started and looking for specified serviceUUIDs. In this case: serviceUUIDs = [CBUUID(string: "13333333-3333-3333-3333-333333333337")]. If I change the String, No service is discovered at all (as expected).
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
print("Discovered peripheral: ", peripheral.name)
if(peripheral.name == "PizzaSquat") {
print("Advertising data: ", advertisementData)
print("RSSI: ", RSSI)
activePeripheral = peripheral
print("Connecting to peripheral")
manager.connectPeripheral(peripheral, options: nil)
}
}
On a Linux computer, I have a Bleno instance running which is displaying as a Peripheral with the name "PizzaSquat" (And obviously offers a Service, since it's discovered at all. So when this Peripheral is discovered, I connect to it.
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
print("Connected to Peripheral: ", peripheral.name)
if(peripheral.services == nil) {
print("Starting service discovery")
peripheral.delegate = self
peripheral.discoverServices(serviceUUIDs)
} else {
print("Already has services: ")
for service: CBService in peripheral.services! {
print(service)
}
}
}
Once connected (to "PizzaSquat"), I check if it already has services (It doesn't) and start Service discovery. Everything is correct up to this point - But no Services are ever discovered. I expect the below function to be called:
func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
print("Discovered new Service")
for service: CBService in peripheral.services! {
print(service)
}
}
with at least one service, Since the Peripheral shouldn't be discovered at all if it didn't have the specified Service!
I have also tried to call peripheral.discoverServices(nil) to discovery any Service, but still no response.
Could someone help me find the problem?
When you scan for CBPeripheral objects, the peripheral service UUID is obtained from the advertisement packet. The peripheral is basically saying, "Oh yeah, I have this service UUID!" iOS picks that up and passes the CBPeripheral to you.
But the peripheral might not be synced with what is in the advertisement packet. It's a non-binding agreement that the advertisement packet matches what's actually in the peripheral's service/characteristic hierarchy.
So what this means is that it's possible for a peripheral to advertise one service UUID, but actually contain another (or none).
Check out LightBlue on iOS or nRF Control Panel on Android to see if the service/characteristic hierarchy matches what you think it does. In LightBlue you should be able to view the advertisement data and compare it to the services on the peripheral you're connected to.

Resources