Background BLE operation with iOS - ios

i’m trying to perform the following operations with the Core Bluetooth Framework of iOS:
1) Connection to peripheral
2) Services Discovery
3) Characteristics Discovery
4) Start Notify on a particular characteristic
These operations have to be triggered in background, when a callback not related to Core Bluetooth (Core Location Framework) fires up.
I have correctly set the Bluetooth Background settings for the app and when the application is put in background through the central button, all is ok: core location callback fires up and the core bluetooth steps are executed correctly.
The problem comes when the application is killed by swiping up with the task manager: in this case when the core location callback fires up the phone is able to connect to the peripheral (1) but it does not discover services.
I’m using iPhone 6 with iOS 10.3.1 and micro nrf51822.
Here is my code:
CBCentralManager initialization (viewDidLoad)
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:#{ CBCentralManagerOptionRestoreIdentifierKey:#“myCentralManagerIdentifier”}];
Core Location Callback:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
NSLog(#“*** enter region”);
dispatch_async(dispatch_get_main_queue(), ^{
NSString *savedUuid = [[NSUserDefaults standardUserDefaults]stringForKey:#“preferenceUuid”];
if (savedUuid!=NULL){
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:savedUuid];
NSArray *arrayUuid = [NSArray arrayWithObjects:uuid, nil];
_knownPeripheral = [[self.centralManager retrievePeripheralsWithIdentifiers:arrayUuid] objectAtIndex:0];
}
if (_knownPeripheral!=NULL){
[self.centralManager connectPeripheral:_knownPeripheral options:nil];
}
});
}
CoreBluetooth Callbacks:
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral(CBPeripheral *)peripheral {
[self.knownPeripheral discoverServices:nil];
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
//Core Bluetooth creates an array of CBService objects —- one for each service that is discovered on the peripheral.
for (CBService *service in peripheral.services) {
if (([service.UUID isEqual:[CBUUID UUIDWithString:K_BT_SERVICE_BATTERY]])) {
[peripheral discoverCharacteristics:nil forService:service];
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:K_BT_CHARACTERISTIC_BATTERY]]) {
[self.sensorTag setNotifyValue:YES forCharacteristic:characteristic];
}
}
}
How can i perform correctly the steps from 1) to 4) when the application is awaked from a killed state?
Thank you

Related

didDisconnectPeripheral: not getting called

I have a custom object BLEDevice which has a weak reference to a CBPeripheral object. I maintain a dictionary that keeps the associations between the peripherals and my custom objects:
- (void)setDeviceForPeripheral:(CBPeripheral *)peripheral {
// New device: sets a new 'BLEDevice' instance
BLEDevice *new = [[BLEDevice alloc] initWithPeripheral:peripheral];
new.name = peripheral.name;
new.peripheral.delegate = self;
[associations setObject:new forKey:peripheral];
}
When I discover a new peripheral in the nearbies, I perform the following operations:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
if (![associations objectForKey:peripheral]) {
NSLog(#"Found peripheral: %#", peripheral.name);
[self setDeviceForPeripheral:peripheral];
[[NSNotificationCenter defaultCenter] postNotificationName:#"BLEDeviceFound" object:nil];
}
}
At this point, I define a custom method to connect to my device:
- (void)connect:(BLEDevice *)device {
// Connects with the peripheral
[manager connectPeripheral:device.peripheral options:nil];
}
Here everything works just fine: my peripheral gets connected and I start performing operations on services and characteristics inside the delegate method centralManager:didConnectPeripheral:.
Now my troubles come. When I want to disconnect to my peripheral, I perform the following operations:
- (void)disconnect:(BLEDevice *)device {
// Unsubscribes from all the characteristics in services
for (CBService *service in device.peripheral.services) {
for (CBCharacteristic *characteristic in service.characteristics)
[device.peripheral setNotifyValue:NO forCharacteristic:characteristic];
}
[manager cancelPeripheralConnection:device.peripheral];
}
When I call this method, my peripheral device confirms me that the disconnection was successful. Anyway, the delegate method centralManager:didDisconnectPeripheral: is not getting called. Can someone please explain me why?
I discovered that the solution is really simple. I just called the wrong delegate method. The correct signature is
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error.
This shit has been driving me crazy since weeks!
The method is not called when you disconnect the peripherals as the name of it tells. What you want is the following.
- centralManager:didDisconnectPeripheral:error:
The document says:
Invoked when an existing connection with a peripheral is torn down.
The delegate method will be called when you disconnect the peripherals.

Core Bluetooth doesn't find peripherals when scanning for specific CBUUID

It seems this question was "answered" here, but without any code to show what they did differently I'm having to ask a new question.
I have my own code with the same behaviour, where scanning for specific CBUUIDs using Core Bluetooth's CBCentralManager on OS X doesn't discover an iOS device acting as a peripheral with CBPeripheralManager (unless it and its services have previously been discovered). To see if it's something wrong in my code, I downloaded Apple's sample code. Running the sample code on two iOS devices works as intended, however when copying the CBCentralManager code to an OS X app, it fails to find the iOS device.
I've uploaded an Xcode project for the OS X app, it's hosted on WikiUpload as that seems to be the least dodgy. There's also a copy on my hosting, if people prefer.
Here's the AppDelegate.m code in the OS X project also (the CoreBluetooth framework is linked in the project):
#import <CoreBluetooth/CoreBluetooth.h>
#interface AppDelegate () <CBCentralManagerDelegate, CBPeripheralDelegate>
#property (strong, nonatomic) CBCentralManager *centralManager;
#property (strong, nonatomic) CBPeripheral *discoveredPeripheral;
#property (strong, nonatomic) NSMutableData *data;
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
#synthesize centralManager = _centralManager, discoveredPeripheral = _discoveredPeripheral, data = _data;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
// Start up the CBCentralManager
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
// And somewhere to store the incoming data
_data = [[NSMutableData alloc] init];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#pragma mark - Central Methods
/** centralManagerDidUpdateState is a required protocol method.
* Usually, you'd check for other states to make sure the current device supports LE, is powered on, etc.
* In this instance, we're just using it to wait for CBCentralManagerStatePoweredOn, which indicates
* the Central is ready to be used.
*/
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
if (central.state != CBCentralManagerStatePoweredOn) {
// In a real app, you'd deal with all the states correctly
return;
}
// The state must be CBCentralManagerStatePoweredOn...
// ... so start scanning
[self scan];
}
/** Scan for peripherals - specifically for our service's 128bit CBUUID
*/
- (void)scan
{
// This brings up nothing, unlike on iOS where it finds the device straight away
[self.centralManager scanForPeripheralsWithServices:#[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]]
options:#{ CBCentralManagerScanOptionAllowDuplicatesKey : #YES }];
// [self.centralManager scanForPeripheralsWithServices:nil
// options:#{ CBCentralManagerScanOptionAllowDuplicatesKey : #YES }];
NSLog(#"Scanning started");
}
/** This callback comes whenever a peripheral that is advertising the TRANSFER_SERVICE_UUID is discovered.
* We check the RSSI, to make sure it's close enough that we're interested in it, and if it is,
* we start the connection process
*/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(#"Discovered %# at %#", peripheral.name, RSSI);
//Took out RSSI check
if (self.discoveredPeripheral != peripheral) {
// Save a local copy of the peripheral, so CoreBluetooth doesn't get rid of it
self.discoveredPeripheral = peripheral;
// And connect
NSLog(#"Connecting to peripheral %#", peripheral);
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
/** If the connection fails for whatever reason, we need to deal with it.
*/
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(#"Failed to connect to %#. (%#)", peripheral, [error localizedDescription]);
[self cleanup];
}
/** We've connected to the peripheral, now we need to discover the services and characteristics to find the 'transfer' characteristic.
*/
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(#"Peripheral Connected");
// Stop scanning
[self.centralManager stopScan];
NSLog(#"Scanning stopped");
// Clear the data that we may already have
[self.data setLength:0];
// Make sure we get the discovery callbacks
peripheral.delegate = self;
// Search only for services that match our UUID
[peripheral discoverServices:#[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]]];
}
/** The Transfer Service was discovered
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (error) {
NSLog(#"Error discovering services: %#", [error localizedDescription]);
[self cleanup];
return;
}
// Discover the characteristic we want...
// Loop through the newly filled peripheral.services array, just in case there's more than one.
for (CBService *service in peripheral.services) {
[peripheral discoverCharacteristics:#[[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]] forService:service];
}
}
/** The Transfer characteristic was discovered.
* Once this has been found, we want to subscribe to it, which lets the peripheral know we want the data it contains
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
// Deal with errors (if any)
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
[self cleanup];
return;
}
// Again, we loop through the array, just in case.
for (CBCharacteristic *characteristic in service.characteristics) {
// And check if it's the right one
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]]) {
// If it is, subscribe to it
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
// Once this is complete, we just need to wait for the data to come in.
}
/** This callback lets us know more data has arrived via notification on the characteristic
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
return;
}
NSString *stringFromData = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
// Have we got everything we need?
if ([stringFromData isEqualToString:#"EOM"]) {
// We have, so show the data,
//[self.textview setText:[[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding]];
NSLog(#"Text: %#", [[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding]);
// Cancel our subscription to the characteristic
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
// and disconnect from the peripehral
[self.centralManager cancelPeripheralConnection:peripheral];
}
// Otherwise, just add the data on to what we already have
[self.data appendData:characteristic.value];
// Log it
NSLog(#"Received: %#", stringFromData);
}
/** The peripheral letting us know whether our subscribe/unsubscribe happened or not
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
NSLog(#"Error changing notification state: %#", error.localizedDescription);
}
// Exit if it's not the transfer characteristic
if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]]) {
return;
}
// Notification has started
if (characteristic.isNotifying) {
NSLog(#"Notification began on %#", characteristic);
}
// Notification has stopped
else {
// so disconnect from the peripheral
NSLog(#"Notification stopped on %#. Disconnecting", characteristic);
[self.centralManager cancelPeripheralConnection:peripheral];
}
}
/** Once the disconnection happens, we need to clean up our local copy of the peripheral
*/
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(#"Peripheral Disconnected");
self.discoveredPeripheral = nil;
// We're disconnected, so start scanning again
[self scan];
}
/** Call this when things either go wrong, or you're done with the connection.
* This cancels any subscriptions if there are any, or straight disconnects if not.
* (didUpdateNotificationStateForCharacteristic will cancel the connection if a subscription is involved)
*/
- (void)cleanup
{
// Don't do anything if we're not connected
if (!self.discoveredPeripheral.isConnected) {
return;
}
// See if we are subscribed to a characteristic on the peripheral
if (self.discoveredPeripheral.services != nil) {
for (CBService *service in self.discoveredPeripheral.services) {
if (service.characteristics != nil) {
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]]) {
if (characteristic.isNotifying) {
// It is notifying, so unsubscribe
[self.discoveredPeripheral setNotifyValue:NO forCharacteristic:characteristic];
// And we're done.
return;
}
}
}
}
}
}
// If we've got this far, we're connected, but we're not subscribed, so we just disconnect
[self.centralManager cancelPeripheralConnection:self.discoveredPeripheral];
}
And in AppDelegate.h there's the UUID definitions:
#ifndef LE_Transfer_TransferService_h
#define LE_Transfer_TransferService_h
#define TRANSFER_SERVICE_UUID #"E20A39F4-73F5-4BC4-A12F-17D1AD07A961"
#define TRANSFER_CHARACTERISTIC_UUID #"08590F7E-DB05-467E-8757-72F6FAEB13D4"
#endif
What's the problem here? According to the above linked question the service has to be part of the advertising packet, but as far as I can see that's exactly what the iOS peripheral is doing with
[self.peripheralManager startAdvertising:#{ CBAdvertisementDataServiceUUIDsKey : #[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }];
CoreBluetooth can be very frustrating. Here are a couple things to try:
#1: A peripheral that has been connected to stops advertising. If you make a successful connection to the peripheral, you'll need to restart advertising.
#2: iOS caches discovered status and provided services. There's no programmatic way to refresh / clear the cache. Try disabling BT on the iOS device and on the Mac and re-enabling it. Then attempt another connection.
#3: There's a problem with your UUIDs. Try scanning for peripherals with the UUID parameter set to nil. You should then discover all peripherals in range.
#4: The BT connection on a Mac can be finicky if the Wifi is on. Try disabling Wifi on your Mac and try again. I've found BTLE completely unusable with Wifi enabled so I've had to use ethernet when doing any BTLE dev on my MacBook.

multiple BLE devices which have same services and characteristics in ios

I have multiple BLE devices which have same services and characteristics. I can scan and connect multiple devices. After connections, when I try to distinguish each one by sending command it doesn't work. It works perfectly with a single device. Is it something like socket connection ? Like A server spawns child threads and each client can maintain a connection through threads.
Please provide some tips on how to scan each device when other device is reading the data from the device.
-(void) scanDevice {
centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[centralManager scanForPeripheralsWithServices:nil options:0];
[AppDelegate app].cbCentral = centralManager;
}
-(void) stopScan {
[[AppDelegate app].cbCentral stopScan];
}
-(void)connectToDevice:(CBPeripheral *)peripheral{
[[AppDelegate app] cbCentral].delegate = self;
[[[AppDelegate app] cbCentral] connectPeripheral:peripheral options:nil];
}
-(void)calldiscoverServicesForPeripheral:(CBPeripheral *)peripheral{
[peripheral setDelegate:self];
[peripheral discoverServices:nil];
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(#"Connected PERIPHERAL %d",peripheral.state);
[delegate getConnectedPeripheral:peripheral];
NSLog(#"Connected peripheral %#",peripheral);
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
NSLog(#"Discovered servicea: %#", peripheral.services);
for (CBService *service in peripheral.services) {
NSLog(#"Discovered service: %#", [service.UUID data]);
[peripheral discoverCharacteristics:nil forService:service];
}
}
I will explain in detail,
I have table view, that contains BLE devices. For the first time it is empty, so i will search for the devices by calling a class "Scan Devices".
This "Scan devices" class contains all the Corebluetooth methods like CBCentralManager allocation, CBperipheral delegate methods.
After search, I will display the device in table view and connect to the BLE device. I am getting some data from "Scan Device" class.
Now, I want to search more devices to connect and get the data. For this, I will call [[CBCentralManager alloc] initWithDelegate:self] in "ScanDevices" class. At this time, for previous device(connected and reading) is showing the warning "is not a valid peripheral" and new device is connecting and reading data from the device.
But I want read the data from both devices at a time
Please help me...
Thanks
You shouldn't keep creating a new CBCentral - doing so will cause your previous CBCentral to be deallocated and therefore invalidate the existing peripherals.
You should activate your scanning once, say in viewWillAppear and deactivate it in viewWillDisappear.
Once you have initiated scanning and set your delegate in your ScanDevices class, it will call [delegate getConnectedPeripheral:] each time a new peripheral is found and connected.

How to detect nearby devices with Bluetooth LE in iOS 7.1 both in background and foreground?

I have an app that needs to detect a nearby (in range for Bluetooth LE) devices running the same application and iOS 7.1. I've considered two alternatives for the detection:
Having the devices act as iBeacons and detect iBeacons in range
Using CoreBluetooth (like in Vicinity implementation here) to create a BLE peripheral, advertise that and scan the peripherals
It seems that the option 1 is out of the question because:
It may take at least 15 minutes for iOS to detect entering a beacon region when the application is running background (iOS 7.1)
Option 2 seems the way to go, but there are some difficulties regarding the implementation:
iOS seems to change the peripheral UUID in advertisement packets after a certain period of time (around 15 minutes?). This means that it's not directly possible to identify the advertising device from the advertisement broadcast signal.
Regarding this, I have the following questions:
Are there any other methods of implementing the nearby device detection I haven't considered?
Is it possible to identify the device through advertising (or by some other means) so that option 2 would work?
I found a way to make this work Core Bluetooth (option 2), the procedure is roughly the following:
The application advertises itself with an encoded device unique identifier in CBAdvertisementDataLocalNameKey (when the broadcasting application runs foreground) and a characteristic that provides the device unique identifier through a Bluetooth LE service (when the broadcasting application runs background)
At the same time, the application scans other peripherals with the same service.
The advertising works as follows:
For the other devices to be able to identify this device, I use a per-device unique UUID (I'm using Urban Airship's [UAUtils deviceID], because it's the device identifier in other parts of the program, also - but you might as well use any unique ID implementation).
When the application is running foreground, I can pass the device unique ID directly in the advertisement packet by using CBAdvertisementDataLocalNameKey. The standard UUID representation is too long, so I use a shortened form of the UUID as follows:
+ (NSString *)shortenedDeviceID
{
NSString *deviceID = [UAUtils deviceID];
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:deviceID];
uuid_t uuidBytes;
[uuid getUUIDBytes:uuidBytes];
NSData *data = [NSData dataWithBytes:uuidBytes length:16];
NSString *base64 = [data base64EncodedStringWithOptions:0];
NSString *encoded = [[[base64
stringByReplacingOccurrencesOfString:#"/" withString:#"_"]
stringByReplacingOccurrencesOfString:#"+" withString:#"-"]
stringByReplacingOccurrencesOfString:#"=" withString:#""];
return encoded;
}
When the application is running background, the advertisement packet gets stripped and CBAdvertisementDataLocalNameKey is not passed along anymore. For this, the application needs to publish a characteristic that provides the unique device identifier:
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
{
if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
[self startAdvertising];
if (peripheralManager) {
CBUUID *serviceUUID = [CBUUID UUIDWithString:DEVICE_IDENTIFIER_SERVICE_UUID];
CBUUID *characteristicUUID = [CBUUID UUIDWithString:DEVICE_IDENTIFIER_CHARACTERISTIC_UUID];
CBMutableCharacteristic *characteristic =
[[CBMutableCharacteristic alloc] initWithType:characteristicUUID
properties:CBCharacteristicPropertyRead
value:[[MyUtils shortenedDeviceID] dataUsingEncoding:NSUTF8StringEncoding]
permissions:CBAttributePermissionsReadable];
CBMutableService *service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
service.characteristics = #[characteristic];
[peripheralManager addService:service];
}
}
}
The scanning works as follows:
You start to scan peripherals with the certain service UUID as follows (notice that you need to specify the service UUID, because otherwise background scan fails to find the device):
[self.centralManager scanForPeripheralsWithServices:#[[CBUUID UUIDWithString:DEVICE_IDENTIFIER_SERVICE_UUID]]
options:scanOptions];
When a device is discovered at - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI you check that if advertisementData[CBAdvertisementDataLocalNameKey] exists and try to convert it back to UUID form like this:
+ (NSString *)deviceIDfromShortenedDeviceID:(NSString *)shortenedDeviceID
{
if (!shortenedDeviceID)
return nil;
NSString *decoded = [[[shortenedDeviceID
stringByReplacingOccurrencesOfString:#"_" withString:#"/"]
stringByReplacingOccurrencesOfString:#"-" withString:#"+"]
stringByAppendingString:#"=="];
NSData *data = [[NSData alloc] initWithBase64EncodedString:decoded options:0];
if (!data)
return nil;
NSUUID *uuid = [[NSUUID alloc] initWithUUIDBytes:[data bytes]];
return uuid.UUIDString;
}
If the conversion fails you know the broadcasting device is in background, and you need to connect to the device to read the characteristic that provides the unique identifier. For this you need to use [self.central connectPeripheral:peripheral options:nil]; (with peripheral.delegate = self; and implement a chain of delegate methods as follows:
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
[peripheral discoverServices:#[[CBUUID UUIDWithString:DEVICE_IDENTIFIER_SERVICE_UUID]]];
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (!error) {
for (CBService *service in peripheral.services) {
if ([service.UUID.UUIDString isEqualToString:DEVICE_IDENTIFIER_SERVICE_UUID]) {
NSLog(#"Service found with UUID: %#", service.UUID);
[peripheral discoverCharacteristics:#[[CBUUID UUIDWithString:DEVICE_IDENTIFIER_CHARACTERISTIC_UUID]] forService:service];
}
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (!error) {
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:DEVICE_IDENTIFIER_CHARACTERISTIC_UUID]]) {
[peripheral readValueForCharacteristic:characteristic];
}
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (!error) {
NSString *shortenedDeviceID = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSString *deviceId = [MyUtils deviceIDfromShortenedDeviceID:shortenedDeviceID];
NSLog(#"Got device id: %#", deviceId);
}
}

Run iOS 6 device as a BLE peripheral

As we know, iOS 6 support running devices (iPhone 4s and above, and new iPad) as a BLE peripheral. There is a demo in WWDC 2012 Session 705 called "advanced core bluetooth". I asked for the source code from Apple. They sent me a modified version of source code (BTLE_Transfer_Draft). Then I:
Run the app in iPhone 5 (iOS 6) in "Peripheral Mode" and start "Advertising"
Run the app in new iPad (iOS 5.1.1) in "Central Mode"
The problem is that the peripheral is never been discovered at all. So I use other testing applications including some downloaded from App Store. All failed to discover peripherals. I think the problem should be in BTLE_Transfer_Draft. Because I'm not sure whether I'm allowed to present the whole source code. So I just show the "peripheral mode" part here:
- (void)viewDidLoad {
[super viewDidLoad];
// Start up the CBPeripheralManager
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
}
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
// Opt out from any other state
if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
return;
}
// We're in CBPeripheralManagerStatePoweredOn state...
NSLog(#"self.peripheralManager powered on.");
// ... so build our service.
// Start with the CBMutableCharacteristic
self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]
properties:CBCharacteristicPropertyNotify
value:nil
permissions:CBAttributePermissionsReadable];
// Then the service
CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]
primary:YES];
// Add the characteristic to the service
transferService.characteristics = #[self.transferCharacteristic];
// And add it to the peripheral manager
[self.peripheralManager addService:transferService];
}
/** Start advertising
*/
- (IBAction)switchChanged:(id)sender
{
if (self.advertisingSwitch.on) {
// All we advertise is our service's UUID
[self.peripheralManager startAdvertising:#{ CBAdvertisementDataServiceUUIDsKey : #[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }];
}
else {
[self.peripheralManager stopAdvertising];
}
}
The BLE is in powered on status and the startAdvertising is called. But the BLE central can never discover it.
Post updated:
According to mttrb's suggestion I added "CBAdvertisementDataLocalNameKey" when I startAdvertising. But my service is still can't be discovered by most of the apps including some apps from app store. The only one app can discover my service is an app from app store called "BLE scanner".
My question is: does this mean my application is working as a peripheral? But why my own code can't discover the service? How am I supposed to debug it ?
My code in Central Mode is like this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Start up the CBCentralManager
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
if (central.state != CBCentralManagerStatePoweredOn) {
return;
}
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
......
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (error) {
NSLog(#"Error discovering services: %#", [error localizedDescription]);
return;
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
// Deal with errors (if any)
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
return;
}
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(#"Peripheral Disconnected");
self.discoveredPeripheral = nil;
}
The didDiscoverPeripheral and didDiscoverServices are never called. What could be wrong? Any idea? Thanks
There is also a high quality free app called LightBlue that you can use to test your code with. It should be able to pick up all devices advertising in peripheral mode and it can even turn itself into an advertising peripheral if you want to make sure your device is working properly.
I would try moving the startAdvertising: method call up and into the end of your peripheralManagerDidUpdateState: delegate method and see if that helps.
I would also add a CBAdvertisementDataLocalNameKey key-value pair to your startAdvertising: method call. I found things were unreliable when the advertisement didn't have a name.
Finally, I would invest in the BLExplr app available in the App Store to help with scanning for your peripheral. It removes the assumption that your central is working correctly.
This Git hub project also throws some light on the CBPeripheralManager API. Called PeripheralModeTest.
This line is particularly useful for setting the advertising data
NSDictionary *advertisingData = #{CBAdvertisementDataLocalNameKey : #"Device Name", CBAdvertisementDataServiceUUIDsKey : #[[CBUUID UUIDWithString:CBUUIDGenericAccessProfileString]]};
Though I can't see any official documentation in the Apple iOS Developer Library yet. More specifically anything about setting the repeat period for the advertising.
The BTLE Transfer example has this piece of (odd) code, which may cause some troubles:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
// Reject any where the value is above reasonable range
if (RSSI.integerValue > -15) {
return;
}
// Reject if the signal strength is too low to be close enough (Close is around -22dB)
if (RSSI.integerValue < -35) {
return;
}
Just remove those two if-statements as it makes no sense!
I have made a simplified version available here that can be used to test high volumes of messages being sent from peripheral to central.
Please note that the CBPeripheralManager class was first introduced in iOS 6.0.
I do not know witch version of BTLE Central Peripheral Transfer you did actually test but current version has iOS 6 as requirement.
So I would suggest to test linkage against iOS 5.1 to see what compatibility issues it shows.

Resources