Bluetooth advertising in background

Discussion in 'iOS Programming' started by DennisBlah, Feb 25, 2016.

  1. DennisBlah macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #1
    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!
     
  2. Mascots macrumors 65816

    Mascots

    Joined:
    Sep 5, 2009
    #2
    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:
    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.
     
  3. 1458279 Suspended

    1458279

    Joined:
    May 1, 2010
    Location:
    California
    #3
  4. DennisBlah, Feb 25, 2016
    Last edited: Feb 26, 2016

    DennisBlah thread starter macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #4
    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
    
     
  5. 1458279 Suspended

    1458279

    Joined:
    May 1, 2010
    Location:
    California
    #5
    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?
     
  6. DennisBlah thread starter macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #6
    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.
     
  7. 1458279 Suspended

    1458279

    Joined:
    May 1, 2010
    Location:
    California
    #7
    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?
     
  8. DennisBlah thread starter macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #8
    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 :)
     
  9. 1458279 Suspended

    1458279

    Joined:
    May 1, 2010
    Location:
    California
    #9
    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?
     
  10. DennisBlah thread starter macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #10

    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.
     
  11. 1458279 Suspended

    1458279

    Joined:
    May 1, 2010
    Location:
    California
    #11
    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.
     

Share This Page