I have an iOS application. I can successfully connect to my paired EAAccessory (Bluetooth Classic). I am able to pull information off of the device that is exposed through the EAAccessory object. One thing I noticed is that the name of the device that is paired (in my Settings -> Bluetooth -> My Devices list) does not match the name of the device that my EAAccessory object exposes. I find this very odd.
Is there any way to get the actual name of the device (the one from the Settings page) through my iOS app?
You didn't mention if this is Bluetooth Classic or BLE ?, My answer below is for bluetooth Classic, I recall I've seen something like that before, here's my findings so far:
Take a look at the Accessory Design Guidelines, sections 2.1.5 and 2.1.8 specifically.
2.1.5:
During the Bluetooth discovery process, the Apple product prefers to
display the Friendly Name of discovered accessories. Before the 2.1
version of the Bluetooth specification the Apple product would have to
set up a connection to the accessory and do a Remote Name Request,
which takes power, antenna time, and user's time. The Extended Inquiry
Response feature, introduced in Bluetooth 2.1, lets an accessory send
its Local Name and other information as part of the Inquiry Response
and thereby increase the speed and efficiency of the discovery
process. The Local Name should match the accessory's markings and
packaging and not contain ':' or ';'.
Also review the Class of device section
2.1.8:
Every accessory that is compatible with an Apple product must
accurately set its Class of Device using the Bluetooth SIG defined
Major Device Class and Minor Device Class. See Volume 3, Part C,
Section 3.2.4 in the Bluetooth Core Specification , Version 5.0. For
example, an audio/video accessory intended to operate in a vehicle
should set Major Device Class to audio/video and Minor Device Class to
car-audio .
Your case might be just the fact that the accessory has a friendly name, and iOS did not clear the cache for the name at that point for the settings, or it could be a faulty implementation on the firmware side.
If this doesn't answer your question, please let me know what names do you see on settings and within your app, and what type of accessory this is with FW version if applicable and I'll try to take a further look into it.
It is possible to get the list of paired/connected devices if you have their advertisement UUID.
var centralQueu = DispatchQueue(label: "A_NAME")
centralManager = CBCentralManager(delegate: self, queue: centralQueu, options: [CBCentralManagerOptionRestoreIdentifierKey: "RESTORE_KEY", CBCentralManagerOptionShowPowerAlertKey: true])
ServiceUUIDs = [CBUUID(string: "THE_UUID")]
//the array of CBUUIDs which you are looking for
You need to find the service UUID in which you are interested:
var options = [
CBCentralManagerScanOptionAllowDuplicatesKey : (1)
]
centralManager.scanForPeripherals(withServices: [CBUUID(string: SERVICE_UUID)], options: options)
centralManager.retrieveConnectedPeripherals(withServices: ServiceUUIDs)
handle didDiscoverperipheral in this way:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
discoveredPeripheral = peripheral
if !mRemoteDevices.contains(discoveredPeripheral) {
let peripherels = centralManager.retrievePeripherals(withIdentifiers: [discoveredPeripheral.identifier])
mRemoteDevices.append(peripherels[0])
}
}
this code is converted from objective-C to swift if you are familiar with objective C then the original code is here.
Related
I have an app demanding to scan BLE devices around in background mode when app is not active in foreground.
I have implemented such functionality using CoreBluetooth framework. This is code, I am using to scan device. First I have all device in DB, fetch and creating array.
for item in self.allItems ?? [] {
let uuid = UUID(uuidString: item.identifier)!
let id = CBUUID(nsuuid: uuid)
self.allServiceIds.append(id)
}
And when start scanning, passing same array in method.
self.centralManager?.scanForPeripherals(withServices: self.allServiceIds, options: [CBCentralManagerScanOptionAllowDuplicatesKey:true])
Also I have tried to pass service ids in array as I read lots of articles suggesting in background mode it is required.
Also inside Capabilities, I have checked required options. But still it is not scanning in when app is in background.
Any ideas are welcome.
A few tips:
Get your app working in the foreground first. Only once this is working should you try to get it working in the background.
Make sure that bluetooth is turned on in phone settings.
Make sure you have obtained Bluetooth permission from the user. An iPhone will send a dialog to prompt you for this permission the first time an app runs (it is triggered by CBCentralManager usage.) If you deny this permission, you won't see the dialog again and you must go to Settings -> Your App -> Permissions -> Bluetooth to enable it manually.
Take care that you have set the CBCentralManagerDelegate properly and that you are getting callbacks. In particular, log the callback to centralManagerDidUpdateState(_ central: CBCentralManager) and make sure that you see central.state transition to .poweredOn. Only once you reach the poweredOn state should you start scanning. See: https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate/1518888-centralmanagerdidupdatestate
Start off testing in the foreground without filtering self.centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey:true]) (note the withServices: nil). Only once you get callbacks detecting peripherals should you start filtering for specific service UUIDs. This will help you eliminate all other problems before filtering on the service.
If you can get all of the above working, there are tricks you can do to get duplicate detections even in the background.
If you cannot get the above working, please described the results of the above, and show more of your code including where you instantiate the CBCentralManager, where you set the delegate, showing the delegate callback methods, and describing which delegate callbacks get called and when.
Yes, I know that there is no way to retrieve the MAC address of a BLE peripheral found by using the CoreBluetooth package, as discussed in other questions like here.
The company I'm working for has a test setup where we have different types of iPhones which should connect to different peripherals under test. For each peripheral under test, we know the MAC address. For our tests we need a specific iPhone to connect to a specific peripheral. The peripherals do differ only in their MAC address.
Since it is possible to have consistent peripheral ids on a single iOS device by using
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
let id = peripheral.identifier
}
we create a mapping table for each peripheral and each iPhone. Thus, we have a function similar to this one: (we don't use this one, but you get the idea)
func getMAC(peripheralId: String, iPhoneID: String) -> String {
if iPhoneID == "iPhoneFoo" && peripheralId == "uuid-peripheralFoo-iPhoneFoo" {
return "MAC-peripheralFoo"
}
else if iPhoneID == "iPhoneFoo" && peripheralId == "uuid-peripheralBar-iPhoneFoo" {
return "MAC-peripheralBar"
}
else if iPhoneID == "iPhoneBar" && peripheralId == "uuid-peripheralFoo-iPhoneBar" {
return "MAC-peripheralFoo"
}
else if iPhoneID == "iPhoneBar" && peripheralId == "uuid-peripheralBar-iPhoneBar" {
return "MAC-peripheralBar"
}
else {
return "unkown"
}
}
We use the output of this function to check if a found peripheral matches the MAC address it should search for.
As you can guess, it is very tedious to make this mapping function, especially, if new iPhones or new peripherals enter the test setup.
My question is now: Is there a way to "calculate" the peripheral Id of a device if you know the MAC address beforehand?
So something like a function, where you provide the MAC address and retrieve the uuid, as if a device was actually found by performing a BLE scan?
Thanks in advance!
No, there isn't. The Bluetooth device address can't be used alone to get the uuid device identifier. In fact, the identifier will be different depending on which phone you use to scan.
As Emil notes, the identifier will be different on different phones. It can even change on the same phone. Peripheral IDs are not promised to be stable and they do change from time to time. There is no way to uniquely identify an arbitrary device when you don't control the firmware. (And if you find a way, expect Apple to break it eventually. They hide this stuff on purpose to prevent user tracking.)
If you do control the firmware, I recommend putting identifying information in the advertising data. I personally generally put identifiers in the manufacturers data, but for your situation the Local Name is probably the most convenient (and may even be settable for firmware you don't control). There are tons of other solutions. I even advertised per-device service UUIDs in one project (this is nice because it means you can easily limit your scanForPeripherals call to look for specific devices in the background).
If you go down this road (assuming you control your firmware), be very thoughtful about user privacy if you store an identifier in the advertising packet. Anything that doesn't change can allow your users to be tracked. There are various good solutions to this problem. I generally base mine on the IRK system that BLE uses to hide MAC addresses. (This may not impact you because this seems to be for internal testing, but I always want to warn people about the privacy concerns of long-lived identifiers.)
I want to create a BLE peripheral as an app on iOS, and implement the Firmware Version String characteristic in Device Information Service.
When I do:
deviceInformationService = CBMutableService(type: CBUUID(string: "0x180A"), primary: false)
deviceInformationService.characteristics = [
manufacturerNameString,
modelNumberString,
firmwareRevisionString
]
peripheral.add(deviceInformationService)
The func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) callback is called with an error:
Error Domain=CBErrorDomain Code=8 "The specified UUID is not allowed for this operation." UserInfo={NSLocalizedDescription=The specified UUID is not allowed for this operation.}
When I connect to the iPhone from Mac using Bluetooth Explorer and read Device Information, it shows
Manufacturer Name String = Apple Inc.
Model Number String = iPhone10,3
which is NOT what I'm setting in the CBMutableCharacteristic. And also the Firmware Revision String is not there.
Is there a way to implement Firmware Revision String ?
The Device Information Service belongs to the device, not your app. Your app shares the BLE stack with all other apps and the OS, so you can't override system-wide characteristics like this.
I have two BLE circuit boards (custom built by my company) that are advertising. When I scan, they are identified as the same CBPeripheral despite having different names and identifiers. Here's my console readout of the delegate call:
centralManager didDiscoverPeripheral <CBPeripheral: 0x144d26c70, identifier = E0FE60A5-FC4A-A6C5-3868-7D0EEAA580CD, name = Board A3D43, state = disconnected>
centralManager didDiscoverPeripheral <CBPeripheral: 0x144d26c70, identifier = 2209CC24-05E8-8455-3A40-3F27AE9078DB, name = Board 51EB5E, state = disconnected>
My question is how are CBPeripherals differentiated, if not by name or identifier? Our firmware uses Bluetopia under the hood, perhaps there's some incomplete serialization in there?
As Paul mentioned in his comment, the memory addresses didn't end up being relevant to this problem. The core problem was that the peripherals were generating identical IRKs, which caused iOS to mistakenly think they were all the same peripheral. The error stemmed from a false assumption that the key diversifying function (d1 in core BT spec, vol 3 part H 5.2.2.1) had a randomness component, when in fact it's just a hashing function.
We were able to verify success on this by looking at the Settings app - before the change, when we connected a new peripheral, it overwrote the previous pairing in Bluetooth settings. Once we made the IRKs unique we saw multiple devices listed in the Settings app.
I am currently working on an iOS application based on bluetooth low energy devices. In order to get a unique identifier to compare the peripherals got, I have to get the MAC address of the peripherals.
It is observed that the UUID property of a peripheral device varies across iOS devices and also for the peripheral device to get a UUID, it will have to get connected to a master device at least once. Since I have to deal with check-in's I don't want to establish a connection. As I went through the bluetooth services portal, I found that the device information itself is a service, which couldn't be retrieved unless a connection has been established between the master iOS device and the peripheral bluetooth low energy device.
I found that in Android we get the entire information of the device, including its MAC address (using getAddress()) when we get the response from the device on scanning itself.
I didn't find any properties in CBPeripheral class related to the device address.
Another way to get a unique parameter would be to customize the advertisement data to send additional information regarding the device, which requires more work on firmware side.
So is there any way in iOS that I could get the MAC address of the bluetooth low energy peripheral without establishing a connection?
Any help would be greatly appreciated.
CBPeripheral's identifier property will serve your purpose, available from a still-unconnected device in CBCentralManager's didDiscoverPeripheral delegate method:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
CBPeripheral *peripheral ...
NSUUID* serverId = [peripheral identifier];
I have a half dozen LE devices I'm experimenting with, including multiple sets of identical devices. I just confirmed that across two iOS devices, the identifiers for all of these LE widgets were different, but for EACH iOS device, the identifier for each widget was retained across application launches and even across app deletes and reinstalls. I would say this definitively proves that the OS is storing enough info internally that for a given iThing, you'll be able to distinguish between, and re-identify, all the peripherals your app encounters, without actually connecting to them.
Also note that the advertisementData, also available before connecting, is full of useful info like CBAdvertisementDataLocalNameKey, CBAdvertisementDataManufacturerDataKey, CBAdvertisementDataServiceUUIDsKey, CBAdvertisementDataSolicitedServiceUUIDsKey, and others, although none as certain to uniquely identify the device as [peripheral identifier] is.
I didn't try doing a device backup and restore to prove the UUIDs were retained, but I'd wager they are, and if they're not, it's something Apple would consider a bug.
Updated Answer :-
After iOS 12 we can get UDID
print(UIDevice.current.identifierForVendor)
print(UIDevice.current.identifierForVendor?.uuidString)
Before iOS 12**
There is no public API to get this information.
If this is an internal or jailbreak application you can get the value of the kLockdownBluetoothAddressKey key via liblockdown.dylib
Low energy peripherals may use privacy feature which hides the MAC address, so it is not necessarily even possible to get the address before connection or bonding. If you somehow get the MAC address which goes over the air, you need to handle privacy or you have interoperability problems.
Apple uses UUIDs to abstract these privacy features out so users do not need to worry about those.
Correct way to do that like you wrote is to either add some vendor specific data to advertisement packet or use the Device Information service.
On-behalf of the discussion of the other professionals I've found some facts which says -
“iOS hides the MAC address of the device and generates a UUID. The UUID on iOS is generated by the iOS device. Different iOS devices will get different UUIDs for the same peripheral. The MAC address is usually based on the hardware. If we both have iPhones and scan the same peripheral, we'll see different UUIDs. iOS generates the UUID on the device and hides the MAC address.
Summary - iOS doesn't let you get the MAC address of a device, it gives you a random UUID instead.“
Source - https://github.com/don/cordova-plugin-ble-central/issues/77
As per above study I've found that there’s not such a unique way to get connect to the board so far, Every board has a MAC address, which Doesn’t changes and easy to access in (only) Android, while iOS doesn’t allow to access MAC Address of the peripheral, however iOS use this MAC address to create a peripheral identifier (UUID), which is unique on (unique) device only. The peripheral identifier for a single Board is different for different iPhones devices (but unique on single device).
However we can connect to a board by searching with Peripheral's Bluetooth Service UUID, but this service UUID is same for all the boards of a kind say- “Adafruit Feather M0”. It means the App will look around the BLE boards of the same type (“Adafruit Feather M0”) and will get connect to ANY one of them. In order to connect to a particular user to a specific Board doesn’t seems to be possible so far due to the inaccessibility of MAC and giving the random UUID in iOS.
You can access to the MAC ADDRESS without problem in iOS 12.
To get the mac address you have to follow the next steps.
Parse the Data received by the BLE device to String.
extension Data{
func hexEncodedString() -> String {
let hexDigits = Array("0123456789abcdef".utf16)
var hexChars = [UTF16.CodeUnit]()
hexChars.reserveCapacity(count * 2)
for byte in self {
let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
hexChars.insert(hexDigits[index2], at: 0)
hexChars.insert(hexDigits[index1], at: 0)
}
return String(utf16CodeUnits: hexChars, count: hexChars.count)
}
}
Add a separator ":" to the address.
extension String {
func separate(every stride: Int = 4, with separator: Character = " ") -> String {
return String(enumerated().map { $0 > 0 && $0 % stride == 0 ? [separator, $1] : [$1]}.joined())
}
}
In didReadValueForCharacteristic( characteristic: CBCharacteritic) you can use the previous 2 functions to get the mac address.
func didReadValueForCharacteristic(_ characteristic: CBCharacteristic) {
if characteristic.uuid == BleDeviceProfile.MAC_ADDRESS, let mac_address = characteristic.value?.hexEncodedString().uppercased(){
let macAddress = mac_address.separate(every: 2, with: ":")
print("MAC_ADDRESS: \(macAddress)")
}
}
enjoy your mac address:
"MAC_ADDRESS: 00:0A:57:4E:86:F2"