BLE背景扫描iOS

时间:2016-10-20 08:00:07

标签: ios background bluetooth-lowenergy scanning

我开始开发可以连接到外围设备的应用程序。我现在遇到的一个问题是蓝牙的背景扫描。当我查看苹果开发者网站时,他们会给出一些如何在后台运行的说明,但我不确定在哪里可以将这些代码放到我的项目中。

应用程序应在外围设备发出命令时收到弹出通知。截至目前,它正在前台工作。

我把背景扫描的代码放在AppDelegate

//
//  AppDelegate.m
//  WITLock
//
//  Created by NTekSystem on 6/21/16.
//  Copyright © 2016 Ntek. All rights reserved.
//

#import "AppDelegate.h"
#import "ControlViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import "TransferService.h"
//#import "DeviceListViewController.h"
#import <AVFoundation/AVAudioPlayer.h>

@interface AppDelegate ()<CBCentralManagerDelegate, CBPeripheralDelegate>
@property (strong, nonatomic) CBCentralManager      *centralManager;
//@property (strong, nonatomic) CBPeripheral          *discoveredPeripheral;
@property (strong, nonatomic) NSMutableData         *data;
@property (strong, nonatomic) AVAudioPlayer *audioPlayer;


@end

@implementation AppDelegate
{
    #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]

}

NSString *characteristicVal;

NSString *tenchar;
NSString *hexAppDel = @"0";
NSUInteger hexAsIntAppDel;

//Binary to String Conversion
-(NSString *)toBinary:(NSUInteger)input
{
    NSString *binaryString;
    if (input == 1 || input == 0)
    {
        return [NSString stringWithFormat:@"%lu", (unsigned long)input];
    }
    else
    {

        binaryString = [NSString stringWithFormat:@"%@%lu", [self toBinary:input / 2], input % 2];
    }
    return binaryString;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {


    //[self scan];


    [[UINavigationBar appearance] setBarTintColor:UIColorFromRGB(0xFE5E00)]; //navigation bar
    //[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"bg_teal_gradient.png"] forBarMetrics:UIBarMetricsDefault];

    NSShadow *shadow = [[NSShadow alloc] init];
    shadow.shadowColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8];
    shadow.shadowOffset = CGSizeMake(0, 1);
    [[UINavigationBar appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys:
                                                           [UIColor colorWithRed:245.0/255.0 green:245.0/255.0 blue:245.0/255.0 alpha:1.0], NSForegroundColorAttributeName,
                                                           shadow, NSShadowAttributeName,
                                                           [UIFont fontWithName:@"HelveticaNeue-CondensedBlack" size:18.0], NSFontAttributeName, nil]];
    [[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];



    UITabBarController *tabBar = (UITabBarController *)self.window.rootViewController;
    [tabBar setDelegate:self];

     //Slide Menu
    ControlViewController *frontViewController = [[ControlViewController alloc] init];

    UINavigationController *frontNavigationController = [[UINavigationController alloc] initWithRootViewController:frontViewController];

    //_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    _centralManager =
    [[CBCentralManager alloc] initWithDelegate:self queue:nil
                                       options:@{ CBCentralManagerOptionRestoreIdentifierKey:
                                                      @"myCentralManagerIdentifier" }];

    NSArray *centralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];



    // And somewhere to store the incoming data
    _data = [[NSMutableData alloc] init];

       // Override point for customization after application launch.

    return YES;

}

- (void)centralManager:(CBPeripheralManager *)central
         willRestoreState:(NSDictionary *)dict
{

    NSArray *services = dict[CBCentralManagerRestoredStateScanServicesKey];
    NSLog(@"NSLog services %@ ", services);
    kill(getpid(), SIGKILL);

}

#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

{
//    CBCentralManagerOptionRestoreIdentifierKey:@"YourUniqueIdentifier"}

    NSArray *service_uuid = @[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]];

//    [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@(YES)}];


    [self.centralManager scanForPeripheralsWithServices:service_uuid options:nil];

    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 < -68) {
        return;
    }

    //getting all advertisment data//
    NSLog(@"Advertisement Data %@", advertisementData);

    //getting service data//
    NSData *serviceData = [advertisementData objectForKey:CBAdvertisementDataServiceDataKey];
    NSLog(@"Service Data %@ ",serviceData);

    //nsdata convert to string //
    NSString* serviceDataStr = [NSString stringWithFormat:@"%@",serviceData];
    NSLog(@"Service String %@ ",serviceDataStr);

    //remove all spaces//
    NSString *serviceDataNoSpace = [ serviceDataStr stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"No space %@", serviceDataNoSpace);

    NSString *pername =[advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
    NSLog(@"Pername %@ ", pername);

    if([pername isEqualToString:@"WL-00000003"])
    {

    if (serviceDataNoSpace.length != 0 && ![serviceDataNoSpace isEqualToString:@"(null)"] )
    {
        NSLog(@"Length Characteristic Value %lu", (unsigned long)[serviceDataNoSpace length]);

        int indexten = 10;


        tenchar = [NSString stringWithFormat:@"%c", [serviceDataNoSpace characterAtIndex:indexten-1]];

        [[NSScanner scannerWithString:tenchar] scanHexInt:&hexAsIntAppDel];
        [[NSScanner scannerWithString:tenchar] scanHexInt:&hexAsIntAppDel];

        NSString *binary = [NSString stringWithFormat:@"%@", [self toBinary:hexAsIntAppDel]];


        if (binary.length == 3)
        {
            binary = [@"0" stringByAppendingString:binary];
        }
        else if (binary.length == 2)
        {
            binary = [@"00" stringByAppendingString:binary];
        }
        else if (binary.length == 1)
        {
            binary = [@"000" stringByAppendingString:binary];
        }
        NSLog(@"Binary %@", binary);



        NSLog(@"Value of Tenth Character %@", tenchar);


        NSString *Port1 = [NSString stringWithFormat:@"%c", [binary characterAtIndex:0]];
        NSString *Port2 = [NSString stringWithFormat:@"%c", [binary characterAtIndex:1]];
        NSString *Port3 = [NSString stringWithFormat:@"%c", [binary characterAtIndex:2]];
        NSString *Port4 = [NSString stringWithFormat:@"%c", [binary characterAtIndex:3]];
        NSLog(@"Log ng mga Port %@ , %@ ,%@ ,%@ ", Port1, Port2,Port3, Port4);


        if ([Port1 isEqualToString:@"0"])
        {
            NSLog(@"Inactive Background");
        }
        else if ([Port1 isEqualToString:@"1"])
        {
            NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/pipipipick.mp3", [[NSBundle mainBundle] resourcePath]]];

            NSError *error;

            _audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
            _audioPlayer.numberOfLoops = -1;

            NSLog(@"Active Background");
            UIAlertController *alertController = [UIAlertController
                                                              alertControllerWithTitle:@"Pipipipick"
                                                              message:@""
                                                              preferredStyle:UIAlertControllerStyleAlert];


            UIAlertAction *cancelAction = [UIAlertAction
                                                       actionWithTitle:NSLocalizedString(@"Cancel", @"OK action")
                                                       style:UIAlertActionStyleDefault
                                                       handler:^(UIAlertAction *action)
                                                       {
                                                           [_audioPlayer stop];
                                                       }];

            [alertController addAction:cancelAction];
            [_audioPlayer play];

        [self.window.rootViewController presentViewController:alertController animated:YES completion:nil];


        }


        if ([Port2 isEqualToString:@"0"])
        {
           NSLog(@"Inactive Background");
        }
        else if ([Port2 isEqualToString:@"1"])
        {
            NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/pipipipick.mp3", [[NSBundle mainBundle] resourcePath]]];

            NSError *error;

            _audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
            _audioPlayer.numberOfLoops = -1;

            NSLog(@"Active Background");
            UIAlertController *alertController = [UIAlertController
                                                  alertControllerWithTitle:@"Pipipipick"
                                                  message:@""
                                                  preferredStyle:UIAlertControllerStyleAlert];


            UIAlertAction *cancelAction = [UIAlertAction
                                           actionWithTitle:NSLocalizedString(@"Cancel", @"OK action")
                                           style:UIAlertActionStyleDefault
                                           handler:^(UIAlertAction *action)
                                           {
                                               UITextField *login = alertController.textFields.firstObject;

                                               [_audioPlayer stop];
                                           }];

            [alertController addAction:cancelAction];
            [_audioPlayer play];
            [self.window.rootViewController presentViewController:alertController animated:YES completion:nil];
        }

        if ([Port3 isEqualToString:@"0"])
        {
            NSLog(@"Inactive Background");
        }

        else if ([Port3 isEqualToString:@"1"])
        {
            NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/pipipipick.mp3", [[NSBundle mainBundle] resourcePath]]];

            NSError *error;

            _audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
            _audioPlayer.numberOfLoops = -1;

            NSLog(@"Active Background");
            UIAlertController *alertController = [UIAlertController
                                                  alertControllerWithTitle:@"Pipipipick"
                                                  message:@""
                                                  preferredStyle:UIAlertControllerStyleAlert];


            UIAlertAction *cancelAction = [UIAlertAction
                                           actionWithTitle:NSLocalizedString(@"Cancel", @"OK action")
                                           style:UIAlertActionStyleDefault
                                           handler:^(UIAlertAction *action)
                                           {
                                               UITextField *login = alertController.textFields.firstObject;

                                               [_audioPlayer stop];
                                           }];

            [alertController addAction:cancelAction];
            [_audioPlayer play];
            [self.window.rootViewController presentViewController:alertController animated:YES completion:nil];

        }

        if ([Port4 isEqualToString:@"0"])
        {
            NSLog(@"Inactive Background");
        }
        else if ([Port4 isEqualToString:@"1"])
        {
            NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/pipipipick.mp3", [[NSBundle mainBundle] resourcePath]]];

            NSError *error;

            _audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
            _audioPlayer.numberOfLoops = -1;

            NSLog(@"Active Background");
            UIAlertController *alertController = [UIAlertController
                                                  alertControllerWithTitle:@"Pipipipick"
                                                  message:@""
                                                  preferredStyle:UIAlertControllerStyleAlert];


            UIAlertAction *cancelAction = [UIAlertAction
                                           actionWithTitle:NSLocalizedString(@"Cancel", @"OK action")
                                           style:UIAlertActionStyleDefault
                                           handler:^(UIAlertAction *action)
                                           {
                                               UITextField *login = alertController.textFields.firstObject;

                                               [_audioPlayer stop];
                                           }];

            [alertController addAction:cancelAction];
            [_audioPlayer play];
            [self.window.rootViewController presentViewController:alertController animated:YES completion:nil];

        }

    }
}
    NSLog(@"Discovered %@ at %@", peripheral.name, RSSI);
    NSLog(@"Logging %@ ", peripheral);

    // Ok, it's in range - have we already seen it?
   // if([peripheral.name isEqual:@"SW_TEST_4"])
    //{
        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:nil options:nil];
        }
    //}

    //<be38fad4 7c0092d2>;
   }


/** 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];
        NSLog(@"Discovered service %@", 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) {
        [peripheral readValueForCharacteristic:characteristic];
        // 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];
            NSLog(@"Discovered characteristic %@", 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
 */


/** 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 didUpdateNotifficationStateForCharacteristic");
        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
{
    NSLog(@"cleanup method");
    // 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
                            NSLog(@"It is notifying, so unsubscribe");
                            [self.discoveredPeripheral setNotifyValue:NO forCharacteristic:characteristic];
                            NSLog(@"Done unsubscribing");
                            // And we're done.
                            return;
                        }
                    }
                }
            }
        }
    }

    // If we've got this far, we're connected, but we're not subscribed, so we just disconnect
    NSLog(@"If we've got this far, we're connected, but we're not subscribed, so we just disconnect");
    [self.centralManager cancelPeripheralConnection:self.discoveredPeripheral];
}




- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    NSLog(@"Did Enter in Background");
    //[self scan];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    NSLog(@"Did Enter in Foreground");
    //[self scan];

}


- (void)applicationDidBecomeActive:(UIApplication *)application {
    NSLog(@"Did Enter in Active");
    //[self scan];

    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application {
    NSLog(@"Did Enter in Terminate");
    //[self scan];

    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

@end

2 个答案:

答案 0 :(得分:0)

当您在前台扫描设备时,您可以扫描任何内容。在后台,您必须指定要扫描的实际服务UUID。好的,这并不是一个问题,因为你知道你正在寻找的UUID。  你需要在功能检查中使用蓝牙LE配件向你的Info.plist添加一个UIBackgroundModes条目。 在applicationDidEnterBackground方法中添加您的扫描部分代码。

答案 1 :(得分:0)

我一直在努力解决类似的问题。

如果应用程序从后台返回时会立即触发didDiscoverPeripheral,则可能与蓝牙外围设备如何自行宣传有关。

广告的间隔必须符合Bluetooth Accessory Design Guidelines for Apple的第3.5节。

基本上,您的外围设备需要至少在前30秒内每20毫秒做一次广告,然后按照文档中指定的间隔继续。

请参阅以下链接,了解Apple员工的详细信息和答案,特别是参考情况3)。

Apple forum answer