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.
Related
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 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.
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"
I have an app that simulates heart rate monitor peripheral (The peripheral app).
I also have an app that receives the data and present it (The central app).
The central app decided to connect to the discovered peripheral based on its name.
The problem is that both app work perfectly good, except that the name is always "iPhone".
The advertising is done this way:
- (IBAction)switchChanged:(id)sender
{
if (self.advertisingSwitch.on) {
NSDictionary *advData =
#{CBAdvertisementDataLocalNameKey:#"Custom Name",
CBAdvertisementDataServiceUUIDsKey:#[[CBUUID UUIDWithString:#"180D"]]};
[self.peripheralManager startAdvertising:advData];
NSLog(#"Advertising");
}
else {
[self.peripheralManager stopAdvertising];
[[self timerInterval] invalidate];
NSLog(#"Stopped advertising");
}
}
But on the central side, inside
- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)aPeripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
The name property never changed.
Is there anything that should be done?
What I have observed with CBPeripheral.name is that the device will, in fact, set the name to the name you select with CBAdvertisementDataLocalNameKey. This name is not persistent, though. If you disconnect the master and reconnect,the name will generally have been switched to "iPhone". If the peripheral disconnects due to an error, I have seen it reconnect with the correct peripheral name, but a new UUID.
There may be other situations where the name also switches to iPhone.
This appears to be a bug in iOS. I'm looking for confirmation before reporting it.
CBAdvertisementDataLocalNameKey only change kCBAdvDataLocalName in advertisementData.
When you nslog advertisementData, you will see some data like this:
{
kCBAdvDataIsConnectable = 1;
kCBAdvDataLocalName = Custom Name;
kCBAdvDataServiceUUIDs = (
"FB694B90-F49E-4597-8306-171BBA78F846"
);
}
Unfortunately, there is no other way to set the peripheral name. iPhone will always have the name: iPhone.
The advertisement is probably correctly seen on the central side. You may check by NSLogging the advertisementData. However, if you rely on the peripheral.name property, then that will either be empty (if you connect first) or contain the "iPhone" string.
I remember it is used to happen to me and I figured it was something to do with the way Core Bluetooth handles caching and service discovery. What Happened to me was that at first I received a default name like iPhone, iPad or nothing at all. But then after discovering services or trying to establish a connection the key magically changes to the value I had set on the other end.
Moreover, it seems it only happens the first time, afterwards, even between launches and subsequent runs of the app Core Bluetooth will try its best to return those values to you on the advertisement stage even on first discovery, but those might as well be outdated values.
my current implementation looks as follows:
NSString * baconName = [[UIDevice currentDevice] name];
NSDictionary *advertisementData = #{CBAdvertisementDataServiceUUIDsKey:#[[CBUUIDUUIDWithString:BACON_SERVICE_UUID]],
CBAdvertisementDataLocalNameKey:baconName};
And It just works for me, iPhones love Bacon, everybody does ;).
Hence, the best way to ensure you get any data you want, is to create another characteristic to transmit your flag and do constant discoveries of services and characteristics for Peripherals you are discovering, and accordingly minimise the discovery of existing or cached peripherals by caching or keeping a reference to them, CB is supposed to do this for ya, and they do their best effort but only you know the business logic of your app and what is important to you. I am overly paranoid and keep references to the discovered peripherals I am interested all the time. That is just me: it ensures I have the right information, and that I minimise scanning and constant re discovery of services and characteristics.
I Hope this helps.
In most such applications instead of identifying the peripheral by name, the client app should be identifying it by a service ID, and the server (peripheral), should be providing a either a standard service ID, as defined at bluetooth.org, or a proprietary service ID/name.
I have the same problem. I argee with Mike, this really seems like bug in IOS. If you discover your peripheral with TI multitool (for example) first, then your device will be discovered as you setuped in CBAdvertisementDataLocalNameKey.
to Dan1one:
you should use [[UIDevice currentDevice] model], not name, to get the string same to default.
I'm trying to connect a BTLE peripheral to my iOS app but the CBConcretePeripheral object that is sent to didDiscoverPeripheral has a null UUID.
This is the info from the CBConcretePeripheral Object retrieved on an iPhone 5.
<CBConcretePeripheral: 0x20043eb0 UUID = (null), Name = "Wahoo HRM V1.7", IsConnected = NO>
This works for some devices but not others.
This is the info from the CBConcretePeripheral Object retrieved on an iPhone 4s.
<CBConcretePeripheral: 0x1dde9720 UUID = <CFUUID 0x1dde9400> 5147BE71-E894-0152-5669-D640CA5E321C, Name = "Wahoo HRM V1.7", IsConnected = NO>
Apple have chopped and changed a bit with this, it depends on the version of iOS. I believe it relates to a privacy leak.
Some versions of iOS don't provide a UUID until after you connect to it, at which point you discover the same peripheral for a second time. The CBPeripheral object you are passed remains the same, so you can use that to determine if you are having your information updated about an existing peripheral or if it's a new peripheral. The documentation isn't great for CoreBluetooth, but there is additional information to be gained by reading the framework headers, and if I recall correctly, the iOS 7 documentation on CoreBluetooth is improved.