简体   繁体   English

BLE背景扫描iOS

[英]BLE Background Scanning iOS

I starting to develop a app that can connect to peripheral devices. 我开始开发一个可以连接到外围设备的应用程序。 One problem that i encounter now is the background scanning of bluetooth. 我现在遇到的一个问题是蓝牙的背景扫描。 As i checked the apple developers site , they give some instructions how to run in background, but i not sure where can i put that codes to my project. 当我检查苹果开发者网站时,他们给出了一些如何在后台运行的说明,但我不确定在哪里可以将这些代码放到我的项目中。

The app should be receive a pop-up notification when the peripheral devices gives a command. 当外围设备发出命令时,应用程序应该会收到弹出通知。 As of now it is working in foreground. 截至目前,它正在前台工作。

i put the codes of background scanning in AppDelegate 我把背景扫描的代码放在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

When you are scanning for devices in the foreground you can scan for anything. 当您在前台扫描设备时,您可以扫描任何内容。 In the background you must specify the actual service UUID you are scanning for. 在后台,您必须指定要扫描的实际服务UUID。 Ok, this isn't actually a problem as you know the UUID you are looking for. 好吧,这实际上不是问题,因为你知道你正在寻找的UUID。 you need to add a UIBackgroundModes entry to your Info.plist in capabilities check use bluetooth LE accessories. 你需要在功能检查中使用蓝牙LE配件向你的Info.plist添加一个UIBackgroundModes条目。 add your Scanning part of code in applicationDidEnterBackground method. 在applicationDidEnterBackground方法中添加您的扫描部分代码。

I've been struggling with a similar issue. 我一直在努力解决类似的问题。

If didDiscoverPeripheral is fired immediately when the app returns from the background it might be related to how the bluetooth peripheral is advertising itself . 如果在应用程序从后台返回时立即触发didDiscoverPeripheral,则可能与蓝牙外围设备如何通告自身有关

The interval of the advertisements have to conform to the section 3.5 of Bluetooth Accessory Design Guidelines for Apple . 广告的间隔必须符合Apple蓝牙配件设计指南的第3.5节。

Basically your peripheral needs to advertise every 20th millisecond for at least the first 30 seconds , then continue with intervals specified in the documentation. 基本上,您的外围设备需要至少在前30秒内每20毫秒做一次广告 ,然后按照文档中指定的间隔继续。

See link below for details and answer from Apple Staff, especially referring to situation 3). 请参阅以下链接,了解Apple员工的详细信息和答案,特别是参考情况3)。

Apple forum answer Apple论坛答案

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM