Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

DennisBlah

macrumors 6502
Original poster
Dec 5, 2013
485
2
The Netherlands
Hi all,

Is there anyone who can show me how to create an app that advertises itself over bluetooth, while the app is in background? Which I will pick up with centralManager on a mac
Code:
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {

Thanks in advance!
 
Have you come across Core Bluetooth Background Processing for iOS Apps, yet?

It sounds like what you're describing is a BT peripheral role task, which (down the page) gives you a few examples and an elaboration:
In addition to allowing your app to be woken up to handle read, write, and subscription requests from connected centrals, the Core Bluetooth framework allows your app to advertise while in the background state.

There's some goodies in there that I suggest you look into, but that should guide you to what you need unless you have any other questions.
 
If you want a practice tutorial that might give you a starting point:

http://www.raywenderlich.com/14618/...multiplayer-and-bluetooth-intro-and-challenge

It's not PC to device, but it could give you a starting point.

Hi Karl,

I can send data from a iPhone to iPhone. I had a very nice sample. (added below)
The code works on both sides.
But from iPhone to iPhone works straight forward, but from iPhone to Mac it doesnt want to connect.
Commented the line; // if (_discoveredPeripheral != peripheral) { on the central, to see whats going on...
The status stays 'Connecting'
Hopefully someone could help me out :)

As I read that once a peripheral has been connected before, the peripheral can be in background...


Central (OSX)
ViewController.h
Code:
#import <Cocoa/Cocoa.h>
#import <IOBluetooth/IOBluetooth.h>
@interface ViewController : NSViewController <CBCentralManagerDelegate, CBPeripheralDelegate>
@end
ViewController.m
Code:
#import "ViewController.h"

#define TRANSFER_SERVICE_UUID           @"9275FAEC-DCC5-4806-84B6-94C056D28F8E"
#define TRANSFER_CHARACTERISTIC_UUID    @"F47E85EE-2C2D-4D79-B77A-2BE3F617635E"

@implementation ViewController
CBCentralManager      *_centralManager;
CBPeripheral          *_discoveredPeripheral;
NSMutableData         *_data;
NSApplicationPresentationOptions *presentationOptions;

- (void)viewDidLoad {
    [superviewDidLoad];
    _centralManager = [[CBCentralManageralloc] initWithDelegate:selfqueue:nil];
    _discoveredPeripheral = nil;
    // And somewhere to store the incoming data
    _data = [[NSMutableDataalloc] init];
    /*    NSApplicationPresentationOptions presentationOptions = (NSApplicationPresentationHideDock |
    NSApplicationPresentationHideMenuBar |
    NSApplicationPresentationDisableAppleMenu |
    NSApplicationPresentationDisableProcessSwitching |
    NSApplicationPresentationDisableSessionTermination |
    NSApplicationPresentationDisableForceQuit |
    NSApplicationPresentationDisableHideApplication);
    NSDictionary *fullScreenOptions = @{NSFullScreenModeApplicationPresentationOptions: @(presentationOptions)};
    [self.view enterFullScreenMode: [NSScreen mainScreen] withOptions:fullScreenOptions]; */
    // Do any additional setup after loading the view.
}

- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];
    // Update the view, if already loaded.
}

- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    // In a real app, you'd deal with all the states correctly
    if (central.state != CBCentralManagerStatePoweredOn)
        return;

    // The state must be CBCentralManagerStatePoweredOn...
    // ... so start scanning
    [self scan];
}

/** Scan for peripherals - specifically for our service's 128bit CBUUID  */

- (void)scan {    [_centralManagerscanForPeripheralsWithServices:@[[CBUUIDUUIDWithString:TRANSFER_SERVICE_UUID]] 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 {
    // 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 < -50)
       return;

    NSLog(@"Discovered %@ at %@", peripheral.name, RSSI);
    // Ok, it's in range - have we already seen it?
  if (_discoveredPeripheral != peripheral) {
        // Save a local copy of the peripheral, so CoreBluetooth doesn't get rid of it
        _discoveredPeripheral = peripheral;
        // And connect
        NSLog(@"Connecting to peripheral %@", peripheral);
        [_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
    [_centralManagerstopScan];
    NSLog(@"Scanning stopped");
    // Clear the data that we may already have
    [_data setLength:0];
    // Make sure we get the discovery callbacks
    peripheral.delegate = self;
    // Search only for services that match our UUID
    [peripheral discoverServices:@[[CBUUIDUUIDWithString:TRANSFER_SERVICE_UUID]]];
}

/** The Transfer Service was discovered*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    NSLog(@"didDiscoverServices...");
    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) {
        NSLog(@"Check services...");
        [peripheral discoverCharacteristics:@[[CBUUIDUUIDWithString: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 {
    NSLog(@"Did discover characteristics");
    // Deal with errors (if any)
    if (error) {
        NSLog(@"Error discovering characteristics: %@", [error localizedDescription]);
        [self cleanup];
        return;
    }
    NSLog(@"No error...");
    // Again, we loop through the array, just in case.
    for (CBCharacteristic *characteristic in service.characteristics) {
        NSLog(@"%@", characteristic.UUID);
        // 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 = [[NSStringalloc] initWithData:characteristic.valueencoding:NSUTF8StringEncoding];
    // Have we got everything we need?
    if ([stringFromData isEqualToString:@"EOM"]) {
        // We have, so show the data,
        NSLog(@"%@", [[NSStringalloc] initWithData:_dataencoding:NSUTF8StringEncoding]);
        // Cancel our subscription to the characteristic
        [peripheral setNotifyValue:NO forCharacteristic:characteristic];
        // and disconnect from the peripehral
        [_centralManagercancelPeripheralConnection:peripheral];
    }
    // Otherwise, just add the data on to what we already have
    [_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]]) {
        NSLog(@"Exit...");
        return;
    }
    // Notification has started
    if (characteristic.isNotifying) {
        NSLog(@"Notification began on %@", characteristic);
    } else { // Notification has stopped
        // so disconnect from the peripheral
        NSLog(@"Notification stopped on %@.  Disconnecting", characteristic);
        [_centralManagercancelPeripheralConnection: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");
    _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

    // See if we are subscribed to a characteristic on the peripheral
    if (_discoveredPeripheral.services != nil) {
        for (CBService *service in _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
                            [_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
    [_centralManagercancelPeripheralConnection:_discoveredPeripheral];
}
@end


Peripheral (iOS)
ViewController.h
Code:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <CoreBluetooth/CoreBluetooth.h>

@interface ViewController : UIViewController <CBPeripheralDelegate>
@property (strong, nonatomic) CBCentralManager      *centralManager;
@property (strong, nonatomic) CBPeripheral          *discoveredPeripheral;
@property (strong, nonatomic) NSMutableData         *data;
@end
ViewController.m
Code:
#import "ViewController.h"

#define TRANSFER_SERVICE_UUID           @"9275FAEC-DCC5-4806-84B6-94C056D28F8E"
#define TRANSFER_CHARACTERISTIC_UUID    @"F47E85EE-2C2D-4D79-B77A-2BE3F617635E"
#define NOTIFY_MTU      20

@interfaceViewController ()
@property (strong, nonatomic) IBOutletUITextView       *textView;
@property (strong, nonatomic) IBOutlet UISwitch         *advertisingSwitch;
@property (strong, nonatomic) CBPeripheralManager       *peripheralManager;
@property (strong, nonatomic) CBMutableCharacteristic   *transferCharacteristic;
@property (strong, nonatomic) NSData                    *dataToSend;
@property (nonatomic, readwrite) NSInteger              sendDataIndex;
@end

@implementation ViewController
- (void)viewDidLoad {
    [superviewDidLoad];
    // Start up the CBPeripheralManager
    _peripheralManager = [[CBPeripheralManageralloc] initWithDelegate:selfqueue:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    // Don't keep it going while we're not showing.
    [self.peripheralManagerstopAdvertising];
    [super viewWillDisappear:animated];
}

#pragma mark - Peripheral Methods
/** Required protocol method.  A full app should take care of all the possible states,
*  but we're just waiting for  to know when the CBPeripheralManager is ready
*/

- (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 = [[CBMutableCharacteristicalloc] initWithType:[CBUUIDUUIDWithString:TRANSFER_CHARACTERISTIC_UUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

    // Then the service
    CBMutableService *transferService = [[CBMutableServicealloc] initWithType:[CBUUIDUUIDWithString: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];
    if(![self.peripheralManagerisAdvertising])
        [self.peripheralManagerstartAdvertising: @{
                                                    CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]],
                                                    CBAdvertisementDataLocalNameKey: @"someNameHere"}];
}

/** Catch when someone subscribes to our characteristic, then start sending them data */
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
    NSLog(@"Central subscribed to characteristic");
    // Get the data
    self.dataToSend = [@"ASDASDSAD BLAH"dataUsingEncoding:NSUTF8StringEncoding];
    // Reset the index
    self.sendDataIndex = 0;
    // Start sending
    [self sendData];
}

/** Recognise when the central unsubscribes */
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic {
    NSLog(@"Central unsubscribed from characteristic");
}

/** Sends the next amount of data to the connected central  */
- (void)sendData {
    // First up, check if we're meant to be sending an EOM
    static BOOL sendingEOM = NO;
    if (sendingEOM) {
        // send it
        BOOL didSend = [self.peripheralManagerupdateValue:[@"EOM"dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.transferCharacteristiconSubscribedCentrals:nil];

        // Did it send?
        if (didSend) {
            // It did, so mark it as sent
            sendingEOM = NO;
            NSLog(@"Sent: EOM");
        }
        // It didn't send, so we'll exit and wait for peripheralManagerIsReadyToUpdateSubscribers to call sendData again
        return;
    }

    // We're not sending an EOM, so we're sending data
    // Is there any left to send?
    // No data left.  Do nothing
    if (self.sendDataIndex >= self.dataToSend.length)
        return;


    // There's data left, so send until the callback fails, or we're done.
    BOOL didSend = YES;

    while (didSend) {
        // Make the next chunk
        // Work out how big it should be
        NSInteger amountToSend = self.dataToSend.length - self.sendDataIndex;
        // Can't be longer than 20 bytes
        if (amountToSend > NOTIFY_MTU)
           amountToSend = NOTIFY_MTU;

        // Copy out the data we want
        NSData *chunk = [NSData dataWithBytes:self.dataToSend.bytes+self.sendDataIndex length:amountToSend];

        // Send it
        didSend = [self.peripheralManagerupdateValue:chunk forCharacteristic:self.transferCharacteristiconSubscribedCentrals:nil];

        // If it didn't work, drop out and wait for the callback
        if (!didSend)
            return;

        NSString *stringFromData = [[NSStringalloc] initWithData:chunk encoding:NSUTF8StringEncoding];
        NSLog(@"Sent: %@", stringFromData);
        // It did send, so update our index
        self.sendDataIndex += amountToSend;
        // Was it the last one?
        if (self.sendDataIndex >= self.dataToSend.length) {
            // It was - send an EOM
            // Set this so if the send fails, we'll send it next time
            sendingEOM = YES;

            // Send it
            BOOL eomSent = [self.peripheralManagerupdateValue:[@"EOM"dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.transferCharacteristiconSubscribedCentrals:nil];
            if (eomSent) {
                // It sent, we're all done
                sendingEOM = NO;
                NSLog(@"Sent: EOM");
            }
            return;
        }
    }
}

/** This callback comes in when the PeripheralManager is ready to send the next chunk of data.
*  This is to ensure that packets will arrive in the order they are sent
*/
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral {
    // Start sending again
    [self sendData];
}
@end
 
Last edited:
I don't know much about Mac programming, but I hear it's very much the same as iOS. If you know how to do phone to phone, I'd look into the mac programming. Maybe jump over to that forum.
I think Big Nerd Ranch has a book on that, but I'd try the other forum 1st.

I'd guess there is some sample code out there, maybe on GitHub?
 
I don't know much about Mac programming, but I hear it's very much the same as iOS. If you know how to do phone to phone, I'd look into the mac programming. Maybe jump over to that forum.
I think Big Nerd Ranch has a book on that, but I'd try the other forum 1st.

I'd guess there is some sample code out there, maybe on GitHub?
You are right they are 'equal' and the above code works
but it won't connect

it discovers, sees the name, uuid, rssi etc but wont connect and therefor it wont receive the advertisement data

Once its connected once, the app should be able to go to background and reconnect when its near again.
 
Ok so you did get it to connect once.

Does it see the other every time?

Id look at all the parameters and see if this is an issue of using a different typing system.

Look at the long, int, etc and see if this is an issue of 32 bit vs 64 bit. Then look at the outcome of any methods (functions) and the parameters to them as well as the output.

Then look for something that gets converted blindly. That's a type that is converted to another type for compatibility reasons without you knowing it's been converted.

Also, look the making sure everything in on the same version. Is the machine using the same version of BT?
 
Ok so you did get it to connect once.

Does it see the other every time?

Id look at all the parameters and see if this is an issue of using a different typing system.

Look at the long, int, etc and see if this is an issue of 32 bit vs 64 bit. Then look at the outcome of any methods (functions) and the parameters to them as well as the output.

Then look for something that gets converted blindly. That's a type that is converted to another type for compatibility reasons without you knowing it's been converted.

Also, look the making sure everything in on the same version. Is the machine using the same version of BT?

I don't really understand your questions.

I reviewed and cleaned up my previous message with a full copy-paste solution.
No visual buttons or textviews w.e

The Central OSX uses IOBluetooth, and Peripheral iOS the CoreBluetooth
If I put the central to an iOS app, using the CoreBluetooth and iOS peripheral, it works fine.

Please advice :)
 
I don't really understand your questions.

I reviewed and cleaned up my previous message with a full copy-paste solution.
No visual buttons or textviews w.e

The Central OSX uses IOBluetooth, and Peripheral iOS the CoreBluetooth
If I put the central to an iOS app, using the CoreBluetooth and iOS peripheral, it works fine.

Please advice :)

Ok is there no configuration in BT? I didn't see anything about BT version or settings in there, did I miss something?

How do you know that your PC has the same version of BT as the phone?

Where is it getting to, you have a bunch of NSLogs, at what point is the program failing to work properly?

You mentioned it sees the other device and gets the ID, but doesn't connect, is that correct?

Does BT have setting in Xcode where you set flags and stuff?

Is there some kind of "handshake" that happens?
 
Ok is there no configuration in BT? I didn't see anything about BT version or settings in there, did I miss something?

How do you know that your PC has the same version of BT as the phone?

Where is it getting to, you have a bunch of NSLogs, at what point is the program failing to work properly?

You mentioned it sees the other device and gets the ID, but doesn't connect, is that correct?

Does BT have setting in Xcode where you set flags and stuff?

Is there some kind of "handshake" that happens?


I just put in a bunch of NSLogs to quickly see whats going on, and I know it's trying to connect and subscribe for the iOS service to receive feedback.
Code:
NSLog(@"Connecting to peripheral %@", peripheral);

But it never gets connected, or subscribing (characteristic) on the iOS device:
Code:
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
    NSLog(@"Central subscribed to characteristic");

Hopefully someone can help out here, I just need to connect, so then the iOS app can rest in the background and the mac is able to find it when I'm near again.

I want to use this as being a 'key' for my mac, when I walk off that my screen will be locked, and when I'm back it will be unlocked again.
 
I just put in a bunch of NSLogs to quickly see whats going on, and I know it's trying to connect and subscribe for the iOS service to receive feedback.
Code:
NSLog(@"Connecting to peripheral %@", peripheral);

But it never gets connected, or subscribing (characteristic) on the iOS device:
Code:
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
    NSLog(@"Central subscribed to characteristic");

Hopefully someone can help out here, I just need to connect, so then the iOS app can rest in the background and the mac is able to find it when I'm near again.

I want to use this as being a 'key' for my mac, when I walk off that my screen will be locked, and when I'm back it will be unlocked again.
Ok, you have to slow down a second. You didn't answer all the questions.

So at this point the PC is trying to connect to the phone. Does the phone see it or not?

I need to know where in the code each program gets stuck.

The PC gets to the point when it is connecting and then it waits?

The phone doesn't see the PC or it does see the PC?

List out what EACH device is doing when it gets stuck.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.