I am writing an iOS app to communicate with a BLE device. The device can change names between connections (not during the BLE connection), but iOS refuses to change the device name.
For example: I can connect to the device when its name is SadName. I disconnect it, shut down the app, etc. and change the device's name to HappyName. But, when I scan for devices iOS still shows the peripheral name as SadName.
If I debug the app and look at:
(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
the value of peripheral.name is SadName so I don't think that it is something that I am interpreting incorrectly in code. I should mention that when I scan for devices, my code is:
[self.CM scanForPeripheralsWithServices:nil options:0]; // Start scanning
I am guessing that it is simply because the devices UUID is the same so iOS is pulling it from its cached devices list, but I want to override that.
Thoughts? Sorry, I am new to iOS.
Cheers -
MSchmidtbauer
The CoreBluetooth API of iOS SDK does not provide a way to force refresh the peripheral name.
Currently it is not feasible to use peripheral.name in iOS when the device name in the BLEdevice changes.
Apple suggests to scan for a specific device by specifying a list of CBUUID objects (containing one or more service UUIDs) that you pass to scanForPeripheralsWithServices:
NSArray *services = #[[CBUUID UUIDWithString: #"2456e1b9-26e2-8f83-e744-f34f01e9d701"] ]; // change to your service UUID!
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:1] forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
[self.manager scanForPeripheralsWithServices:services options:dictionary];
This reduces the number of calls of didDiscoverPeripheral. Do not just pass nil to scanForPeripheralsWithServices. It also allows your app to scan for a peripheral when in background state.
If you are looking for a way to broadcast dynamic information that's available before a connection is established, you can use the Advertise or Scan Response Data. The peripheral can be configured to broadcast the entries called Local Name and Manufacturer Specific Data. This data is availabe in the didDiscoverPeripheral:
- (void)centralManager: (CBCentralManager *)central
didDiscoverPeripheral: (CBPeripheral *)peripheral
advertisementData: (NSDictionary *)advertisementData
RSSI: (NSNumber *)RSSI {
NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
NSData *manufacturerData = [advertisementData objectForKey:CBAdvertisementDataManufacturerDataKey];
NSLog(#"Local: name: %#", localName);
NSLog(#"Manufact. Data: %#", [manufacturerData description]);
}
Local Name is an NSString, so write only printable characters on the BLE device in this filed. Manufacturer Data is an NSData, this can contain any byte value, so you can even have binary data here.
Depending on the BLE device you use, the length of Local Name and Manufacturer Specific Data is limited.
On my BLE device,I can send the 128 Bit service UUID and a 8 char Local Name with the Advertise Data. The Manufacturer Specific Data goes into the Scan Response Data and can be 29 bytes long.
Good thing about using the Adv./Scan Response Data is, it can change on this BLE device without a power cycle.
Suggestion:
Use the service UUID to filter when scanning (UUID must be part of advertising data! I omitted it in the above description)
Use the Advertise/Scan Response Data for further filtering
Forget about peripheral.name as long as there is no deterministic refresh available
Your guessing is correct.
It is because of the core-blutetooth cache.
Generally changing name / services / characteristics on BLE devices are "not supported". All these parameters are getting cached.
There are two ways of solving this:
restart bluetooth adapter, so bluetooth cache gets cleared (I'm afraid there is no way to do this programatically, but i might be wrong)
your device BLE implements the GATT Service Changed characteristic: read about this here: core_v4.1.zip
Vol 3, Part G, 2.5.2, and Vol 3, Part G, 7.1.
Alternatively check the advertisement data of your BLE device. It might have a name property which should get refreshed every time the BLE device is advertising data (advertising data doesn't get cachced).
The CBPeripheralDelegate protocol contains a method...
- (void)peripheralDidUpdateName:(CBPeripheral *)peripheral NS_AVAILABLE(NA, 6_0);
... which is made for this purpose.
Edit - just realized that the second part of the accepted answer above has the same solution :-( I should have read more closely. I will leave this answer here anyway, since it includes RoboVM code.
I have found a solution to this problem. Adding the GATT Service Changed characteristic didn't work, nor did reading the device name directly from the Device Name characteristic 2A00 since iOS hides the Generic Access service. However, if the peripheral includes its local name in an advertising packet, it is available from the advertisement data dictionary provided on a scan result using the retrieval key CBAdvertisementDataLocalNameKey. I copy this into my BLE device wrapper and use it instead of the name available from the CBPeripheral. Example code in Java for RoboVM is below. The OBJC or Swift equivalent is straightforward.
#Override
public void didDiscoverPeripheral(CBCentralManager cbCentralManager, CBPeripheral cbPeripheral, CBAdvertisementData cbAdvertisementData, NSNumber rssi) {
NSData manufacturerData = cbAdvertisementData.getManufacturerData();
byte[] data = null;
if(manufacturerData != null)
data = manufacturerData.getBytes();
IosBleDevice bleDevice = new IosBleDevice(cbPeripheral);
String name = cbAdvertisementData.getLocalName();
if(name != null && !name.equals(cbPeripheral.getName())) {
CJLog.logMsg("Set local name to %s (was %s)", name, cbPeripheral.getName());
bleDevice.setName(name);
}
deviceList.put(bleDevice.getAddress(), bleDevice);
if(!iosBlueMaxService.getSubscriber().isDisposed()) {
BleScanResult bleScanResult = new IosBleScanResult(bleDevice,
cbAdvertisementData.isConnectable(),
data);
bleScanResult.setRssi(rssi.intValue());
iosBlueMaxService.getSubscriber().onNext(bleScanResult);
}
}
Related
My current project is a simple bicycle odometer app using a bluetooth speed sensor.
I have used 'Wahoo', Xoss', 'CycPlus' sensors (peripheral) for testing.
App is intended for ios - iPhones.
The app runs well - using CoreBluetooth - simplified flow
initialize central manager
scan for peripherals with services
using did discover peripheral > connect to peripheral
using did connect peripheral > discover services
using did discover services > discover characteristics
using did discover characteristics > set notifying to yes for peripheral
using did update value for characteristic > extract my data and calculate mileage
All works well - as long as the bicycle keeps moving. But when the peripheral (bt sensor) stops broadcasting during a several minute rest period, the peripheral disconnects - as it should.
The problem is that if another bluetooth equipped bicycle starts up in the vicinity, the app will attempt to connect to that other peripheral.
How can I -
Make sure to reconnect only to the original peripheral.
or
Make sure I never disconnect from the original peripheral.
Thanks
JDay
Paulw11
My apologies for posting comment as an answer - too much to say.
Thanks for the suggestions. I have used your key items, and flavored a bit for my unmentioned specifics. I believe I have a working solution based on your guidance.
Rather than retrievePeripheralWithIdentifiers, I just get and save the CBUUID on first connect.
Then when connecting, I filter the incoming peripherals to match the original CBUUID.
I beleieve I do need to use scan, rather than just connect, because I do not know when the rider
will begin moving again. I don't want to ask the rider to press a button. I need to keep scanning in order to detect his motion.
Here's the jist of my added code:
...in implementation
bool preferredPeripheralExists = false;
...in peripheral didDiscoverCharacteristicsForService
NSString *founduuid = [NSString stringWithFormat:#"%#", [peripheral.identifier UUIDString]] ;
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:founduuid forKey:#"rememberFoundUUID"];
[prefs synchronize];
preferredPeripheralExists = true ;
... in didDiscoverPeripheral
NSString *newuuid = [NSString stringWithFormat:#"%#", [peripheral.identifier UUIDString]] ;
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *olduuid = [prefs objectForKey:#"rememberFoundUUID"] ;
if ((!preferredPeripheralExists) || (preferredPeripheralExists && [newuuid isEqualToString:olduuid])){
if (self.discoveredPeripheral != peripheral) {
self.discoveredPeripheral = peripheral;
[self.centralManager connectPeripheral:peripheral options:nil];
} // if self
} // if !preferred
With regard to your comment about a setup screen, and user selection of the peripheral -
My flow scheme is based on the sample xcode project on the apple developer site. This makes no mention of a setup interface. Also the Ray Wenderlich bluetooth heart monitor project does not include ability for user to select a device.
Is it considered poor practice to not include a device selection screen?
Thanks
J.Day
I want to check which Bluetooth Devices my iPhone is connected to. In order to do that, I use CBCentralManager.retrieveConnectedPeripherals() like this:
let connectedPerphs = centralManager.retrieveConnectedPeripherals(withServices: []);
My problem is that even if my iPhone is connected to a BluetoothDongle (it explicitly says "connected" in the settings), the list that is returned by retriveConnectedPeripherals() is always empty. Am I using the method in a wrong way or can it not be used to detect a bluetooth connection such as the connection to to my dongle? If the latter is the case, how can I detect that connection?
Let me clear, centralManager.retrieveConnectedPeripherals always return empty or nil value, If you are not passing any value into serviceUUIDs
retrieveConnectedPeripherals(withServices:)
Returns a list of the peripherals (containing any of the specified
services) currently connected to the system.
serviceUUIDs:
A list of service UUIDs (represented by CBUUID objects).
Update:
Unfortunately this the long way to do it. You can create Array of CBUUID statically then you can pass it to the method. Please refer below code.
let aryUUID = ["1800","18811"]
var aryCBUUIDS = [CBUUID]()
for uuid in aryUUID{
let uuid = CBUUID(string: "1800")
aryCBUUIDS.append(uuid)
}
let connectedPerphs = centralManager.retrieveConnectedPeripherals(withServices: aryCBUUIDS)
List of available services
First, this works only with BLE devices, thus if your dongle is using a common BT you will not get it from here, but probably using EAAccessoryManager var connectedAccessories: [EAAccessory] method, but as far as I know your app must comply to MFI.
That is why is asking which service your devices are exposing as a filter.
I connect BLE with CoreBluetooth and paired.
Now when I back to my app screen, I want to make sure that BLE already paired with iOS device.
If I store value in defaults and remove app, this case will not work to fetch device.
If user remove paired bluetooth peripheral from Setting -> Bluetooth -> list of devices this case also not work to identify.
NSArray *ary = [self.bleMgr retrieveConnectedPeripheralsWithServices:#[[CBUUID UUIDWithString:#"180A"]]];
NSUUID *nsUUID = [[NSUUID alloc] initWithUUIDString:identifier];
NSArray *temp = [self.bleMgr retrievePeripheralsWithIdentifiers:#[nsUUID]];
Above both code lines not giving robust result.
How to get that paired BLE device in app?
The first time you discover a peripheral, the system generates an identifier (a UUID, represented by an NSUUID object) to identify the peripheral.
You can then store this identifier (using, for instance, the resources of the NSUserDefaults class), and later use it to try to reconnect to the peripheral using the retrievePeripheralsWithIdentifiers: method of the CBCentralManager class.
The following describes one way to use this method to reconnect to a peripheral you’ve previously connected to.
knownPeripherals = [myCentralManager retrievePeripheralsWithIdentifiers:savedIdentifiers];
The NSUUID class is not toll-free bridged with CoreFoundation’s
CFUUID. Use UUID strings to convert between CFUUIDRef and NSUUID, if
needed. Two NSUUID objects are not guaranteed to be comparable by
pointer value (as CFUUID is); use isEqual(_:) to compare two NSUUID
instances.
Refer this: https://developer.apple.com/documentation/foundation/nsuuid
Refer last two sections: https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/BestPracticesForInteractingWithARemotePeripheralDevice/BestPracticesForInteractingWithARemotePeripheralDevice.html#//apple_ref/doc/uid/TP40013257-CH6-SW1
I am modifying an app that discovers a device via Bluetooth BLE in order to plot the values into a graph. It all works fine (thanks to the help of one of you in the past week). The app was initially written by my husband a while ago and during the discovery of the device, he was using checking code like :
NSLog(#" Failed to Connect to Peripheral : %# with UUID: %# ", peripheral, peripheral.UUID);
or
NSLog(#" Connected to Peripheral : %# with UUID: %# ", peripheral, peripheral.UUID);
In front of each of these lines (and some more) I get the warning messages that UUID is deprecated: first deprecated in ios 7.0
Since quite a while, I try to find out by what it has been replaced, but on Google, it doesn't look like other people have the same problem and when I go to the Apple Documentation : here
there is no mention of it being deprecated.
I don't understand...
Could somebody please help ? Thanks
EDIT: ADDING SOME INFO
This is what I get on my computer on the console
Update
I have double checked and the UUID property on CBPeer is also deprecated.
From the docs in Xcode I found -
Deprecation Statement
Use the identifier property instead.
And if you use peripheral.identifier.UUIDString you don't get a deprecation warning.
So, just in case anybody reads this:
Here is the general way to find out what to use instead of the deprecated API call.
1) Mark the deprecated call
2) Right click "Jump to definition"
3) Read what it says there.
In the case of UUID which was asked here, the answer would be 2 lines below:
#property (readonly, nonatomic) NSUUID *identifier NS_AVAILABLE(NA, 7_0);
You can access service UUID by CBAdvertisementDataServiceUUIDsKey in advertisementData in
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
I am developing an open source block based wrapper for Core Bluetooth.
Currently I am developing the Peripheral Manager part.
I have it broadcasting and I can see the service and its characteristic. When looking at other services I can see they have been given meaningful names such as Battery and Current time.
When I try and provide a meaningful name for any service or characteristic I create it returns the error String Characteristic name does not represent a valid UUID.
My code so far is
[_peripheralManager startAdvertising:#{CBAdvertisementDataLocalNameKey : #"Peripheral name",
CBAdvertisementDataServiceUUIDsKey : [CBUUID UUIDWithNSUUID:[NSUUID UUID]]}];
CBMutableCharacteristic *transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:#"Characteristic name"]
properties:CBCharacteristicPropertyRead
value:[#"Test value" dataUsingEncoding:NSUTF8StringEncoding]
permissions:CBAttributePermissionsReadable];
CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:#"Service name"]
primary:YES];
[transferService setCharacteristics:#[transferCharacteristic]];
[_peripheralManager addService:transferService];
If I replace any initWithType arguments with [CBUUID UUIDWithNSUUID:[NSUUID UUID]] it works but is not nice to show to the user.
Is there a way to set the UUID of the CBService or CBCharacteristic to a meaningful string?
It is also worth noting that the value of the characteristic is null when viewed, bonus points for seeing why this is.
Thanks
The Bluetooth organisation has accepted some services and their characteristics as standards. These can be referred to by a 4 hex digit assigned number when using the Core Bluetooth library - See the CBUUID documentation
User defined UUIDs are not recognised by the Core Bluetooth library as they are not assigned numbers, so you can only display them with their full UUID.
The value of the characteristic will be null if you haven't issued a read or received a notify.
Use the terminal command uuidgen to create a user defined UUID.
As Paul mentioned, the value will be null until you read the value of the characteristic. It seems like a lot of work but after you discover a peripheral you connect to it (don't forget to retain the peripheral and set the peripheral's delegate property), then discover its services, then discover the characteristics for the services, then you may read the value of the characteristics like so:
[peripheral readValueForCharacteristic:characteristic]; //where peripheral is the connected CBPeripheral
In the CBCentralManagerDelegate method peripheral:didUpdateValueForCharacteristic:error: the value can be read:
NSLog(#"%#",[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]);
Based on the code you included, it is not necessary to include the advertisement data service UUID key in your options dictionary . If you would like to specify a data service UUID that is specific to your app, start advertisementing like this:
[_peripheralManager startAdvertising:#{CBAdvertisementDataLocalNameKey : #"Peripheral name",
CBAdvertisementDataServiceUUIDsKey : #[[CBUUID UUIDWithString:#"generated-string-using-uuidgen"]]}];
You can then set your CBCentralManager to scan for that specific data service uuid like this:
[_centralManager scanForPeripheralsWithServices:#[[CBUUID UUIDWithString:#"generated-string-using-uuidgen"]] options:nil];