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.
Related
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 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.
What I am trying to do
I am trying to connect my app to a Bluetooth LE device which needs to be paired.
Current behaviour
There is no problem without pairing the device and my iPhone application. I am able to connect, reconnect and read/write characteristics without any problem.
But, if the device need to be paired, I am only able to read/write characteristics the first time, right after the pairing popup confirmation. The next time, I discover and connect the app to my device, but I don't have the rights to read/write characteristics data because (I guess) I am not using the pairing information.
Finally...
After spending few hours searching around the web with no luck here are my questions :
How can I connect my app to a Bluetooth LE device from my iPhone app using the pairing data stored in my phone? Am I missing something?
Is it possible that it is not an IOS problem because if pairing data are present in the phone for the connecting device, it is automatically used?
Is there someone with experience with Bluetooth LE and IOS to help me?
Update 2013-10-27
I have discovered that you can't read a protected characteristic by pairing authentication right after that the characteristic has been discovered if a pairing exists (no confirmation popup). No problem with non-protected characteristic! I don't know exactly why is happening, but the behavior is that the IOS app never receive answers from the device.
So if the first reading is done after, it doesn't cause problem. Here is the code I am using to discover characteristics with the data reading in comment.
- (void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error;
{
NSArray *characteristics = [service characteristics];
CBCharacteristic *characteristic;
if (peripheral != servicePeripheral) {
NSLog(#"Wrong Peripheral.\n");
return ;
}
if (service != batteryService) {
NSLog(#"Wrong Service.\n");
return ;
}
if (error != nil) {
NSLog(#"Error %#\n", error);
return ;
}
for (characteristic in characteristics) {
NSLog(#"discovered characteristic %#", [characteristic UUID]);
if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:kBatteryCharacteristicUUIDString]]) { // Bat
NSLog(#"Discovered Bat Characteristic");
batteryCharacteristic = [characteristic retain];
//--> generate problem when pairing exists between IOS app and device
//[peripheral readValueForCharacteristic:batteryCharacteristic];
}
}
}
You don't have to do anything in your app for pairing management.
If your app runs in LE Central mode, and the peripheral sends an Insufficient Authentication error code in response to a read / write request, iOS will automatically pair with your device and will retry the request.
If you disconnect from the device, and later reconnect again, the peripheral needs to send the Insufficient Authentication error code again for the iPhone to restart encryption. Again, you don't have to do anything special in your app here.
If your app runs in LE Peripheral mode, things are a bit different. When you set up your GATT database, make sure to set correct flags for both the CBAttributePermissions and CBCharacteristicProperties. This will tell iOS that it should send the Insufficient Authentication error code itself, if it is not paired. It is then the responsibility of the central device to start the encryption process.
In the Bluetooth Accessory Design Guidelines for Apple Products, further restrictions are described.
Your accessory needs the capability to resolve private Bluetooth addresses. The iPhone will change its public Bluetooth address every now and then, and only paired devices will have the correct key to resolve that public address and recognize the iPhone.
"Section 3.9 Pairing" is also interesting.
Note that if you pair without man-in-the-middle (MITM) protection, your peripheral can use the resulting key to resolve the private Bluetooth address of the iPhone. However, you won't be able to encrypt the channel.
Pairing with MITM protection on iOS involves entering a PIN code that is displayed by the remote device. Out-of-band (OOB) pairing where you send pairing data over an external channel is not supported by iOS as far as I know (at least there's no public APIs to set OOB data).
Long story short: if you have only a "Pair" / "Cancel" pairing, you cannot encrypt the LE channel but only recognize the iPhone in future connections. The nice thing is that you can still recognize the iPhone even if you unpair it on the iPhone side, and even after restoring the iPhone firmware ;-).
Regarding LE encryption in general: it's not secure anyways (see http://eprint.iacr.org/2013/309).
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 just wrote a simple app where I scan and connect to peripheral (which is also an IOS device). However the CBPeripheral object I get back from ConnectPeripheral function does not have the device UUID and it's always null. Now I am trying to understand where do I set it so that it gets passed.
Here is what I am doing.
To advertise my service I am doing
NSDictionary *advertisingDict=[NSDictionary dictionaryWithObject: services forKey:CBAdvertisementDataServiceUUIDsKey]
[manager startAdvertising:advertisingDicts];
(From the framework I understand that I cannot pass the device UUID in the advertising packet. Correct me if I am wrong here)
My client scan for the service and gets into the function
centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
And as expected the peripheral.uuid is null. If I call connectPeripheral on this peripheral it works fine too. I am not sure how it understands which device to connect when the uuid is null. Also what would I need to do If I want to reconnect later. How do I fill this uuid?
It has a work around. You can pass it instead of the local name of the device like this:
[self.peripheralManager startAdvertising:#{CBAdvertisementDataLocalNameKey : *the UUID*}];
And in the other device in the didDiscoverPeripheral function you can get it like this:
[advertisementData objectForKey:#"kCBAdvDataLocalName"]