5

I am building an iOS app with Xamarin, with this BLE plugin:

https://github.com/aritchie/bluetoothle

I'm just broadcasting a UUID via BLE, and it works. Here is my code:

var data = new Plugin.BluetoothLE.Server.AdvertisementData
            {
                LocalName = "MyServer",
            };

data.ServiceUuids.Add(new Guid("MY_UUID_HERE"));

await this.server.Start(data);

The only problem is that it stops broadcasting once I put the app in the background. And resumes again when I open the app again.

How can I let it continue to broadcast once it's in the background? I read the documentation here:

https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html

And it says that I have to use the CBCentralManager class to obtain the preservation and restoration feature (so I can keep broadcasting the UUID at all times), but I'm having a hard time translating this to Xamarin/C#.

EDIT

After researching some more, I read that I need to create an instance of CBCentralManager and implement WillRestoreState in the delegate. I did this in the AppDelegate:

[Register("AppDelegate")]
public class AppDelegate : MvxApplicationDelegate, ICBCentralManagerDelegate
{
    private IGattServer server = CrossBleAdapter.Current.CreateGattServer();
    private CBCentralManager cbCentralManager;

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        // irrelevant code...

        this.Ble();

        return true;
    }

    private async Task Ble()
    {
        try
        {
            await Task.Delay(5000); // wait for it to finish initializing so I can access BLE (it crashes otherwise)

            var options = new CBCentralInitOptions();
            options.RestoreIdentifier = "myRestoreIndentifier";
            this.cbCentralManager = new CBCentralManager(this,null,options);

            var data = new Plugin.BluetoothLE.Server.AdvertisementData
            {
                LocalName = "MyServer",
            };

            data.ServiceUuids.Add(new Guid("MY_UUID_HERE"));

            await this.server.Start(data);
        }
        catch (Exception e)
        {

        }
    }

    public void UpdatedState(CBCentralManager central)
    {
        //throw new NotImplementedException();
    }

    [Export("centralManager:willRestoreState:")]
    public void WillRestoreState(CBCentralManager central, NSDictionary dict)
    {
        //never gets called
    }

But it didn't make a difference for me. And the WillRestoreState method never gets called... I don't mind using a different plugin/library if I have to at this point...

EDIT 2

I just realized that the app is still broadcasting while it is in the background, I just don't see the service UUID anymore (in the web portal of the beacon that I'm testing with), I only see the phone's identifier.

Drake
  • 2,679
  • 4
  • 45
  • 88
  • as I understand you cannot just keep broadcasting. You can specify to be in 2 possible modes bluetooth-central or bluetooth-peripheral. You should specify that in plist. – Yuri S Jun 20 '17 at 23:27
  • There's a section called "State Preservation and Restoration" in the document that I linked that says that you can. It quotes: `Because state preservation and restoration is built in to Core Bluetooth, your app can opt in to this feature to ask the system to preserve the state of your app’s central and peripheral managers and to continue performing certain Bluetooth-related tasks on their behalf, even when your app is no longer running.` – Drake Jun 21 '17 at 00:55
  • "to continue performing certain Bluetooth-related tasks on their behalf" what tasks? Not just any task you want but certain tasks depending on mode you are in, right? – Yuri S Jun 21 '17 at 00:57
  • There are two Core Bluetooth background execution modes that an app may declare—one for apps implementing the central role, and another for apps implementing the peripheral role. If your app implements both roles, it may declare that it supports both background execution modes. The Core Bluetooth background execution modes are declared by adding the UIBackgroundModes key to your Info.plist file and setting the key’s value to an array containing one of the following strings: bluetooth-central bluetooth-peripheral – Yuri S Jun 21 '17 at 00:59
  • Advertising the UUID of a service is the simplest thing you can do with BLE... it also quotes an example: `Some apps may need to use the Core Bluetooth framework to perform long-term actions in the background. As an example, imagine you are developing a home security app for an iOS device that communicates with a door lock (equipped with Bluetooth low energy technology). The app and the lock interact to automatically lock the door when the user leaves home and unlock the door when the user returns—all while the app is in the background.` – Drake Jun 21 '17 at 00:59
  • Have you tried to add background modes "UIBackgroundModes" to pinfo.list as described? – Yuri S Jun 21 '17 at 01:00
  • Yes I did add all those. – Drake Jun 21 '17 at 01:01
  • The plugin says "Manages iOS backgrounding by allowing hooks to WhenWillRestoreState" Have you tried to hook to that? – Yuri S Jun 21 '17 at 01:03
  • Also Q&A: Q. I cannot see the device name when scanning in the background on iOS This is the work of iOS. The library cannot fix this. You should scan by service UUIDs instead – Yuri S Jun 21 '17 at 01:04
  • I looked into `WhenWillRestoreState` and only found `WillRestoreState` (I think it's a typo in their documentation). I don't understand what to do with it... – Drake Jun 22 '17 at 14:40
  • how do you specify for how long you will transmit your UUID and how you verify that you stop transimtting when app is in the background? – Yuri S Jun 22 '17 at 18:37
  • I don't specify for how long I transmit my UUID, it continues to advertise the UUID for as long as the app is running. I know that it stops transmitting the UUID when it's in the background because I'm testing it with a beacon I have here that reads all BLE transmissions in the vicinity (it has a web interface that I can look at). – Drake Jun 22 '17 at 18:40
  • what is server here "this.server.Start"? – Yuri S Jun 22 '17 at 19:50
  • I declared it at the top, it's an `IGattServer` – Drake Jun 22 '17 at 19:51
  • ```All service UUIDs contained in the value of the CBAdvertisementDataServiceUUIDsKey advertisement key are placed in a special “overflow” area; they can be discovered only by an iOS device that is explicitly scanning for them.``` How do you scan for the peripheral/services while your app is in a background? Don't expect to see the service in some of the app store BLE utility applications... – Darko Djuric Jun 22 '17 at 20:31
  • I guess I can't rely on the service UUID if I wanted a UUID to be broadcasted at all times...I don't know how else to do it? – Drake Jun 22 '17 at 20:45
  • I am not sure if Apple fixed it. But in iOS10 there was a bug related to background advertisement https://forums.developer.apple.com/thread/51309 and https://stackoverflow.com/questions/39624045/corebluetooth-advertising-in-background-on-ios-10 your best choice would be to scan for known service UUID (if that works either). – Darko Djuric Jun 22 '17 at 21:01
  • I am getting invalid state Uknown as you described here https://stackoverflow.com/questions/44634242/acr-reactive-bluetoothle-plugin-invalid-state-when-starting-server but the solution there doesn't work for me. May be my iPad doesn't support LE? – Yuri S Jun 22 '17 at 21:12
  • hmm try putting `await Task.Delay(5000);` before doing BLE stuff – Drake Jun 22 '17 at 21:16
  • did, as in your code – Yuri S Jun 22 '17 at 21:19
  • Did you also wrap it in a function (and not await it) so that it doesn't block `FinishedLaunching` from finishing? (or wherever you're calling it from) – Drake Jun 22 '17 at 21:20
  • yes, copied your code using Ble asyn function. ok, I am out as I cannot get it working on my iPad but please look at the follow link https://github.com/aritchie/bluetoothle/blob/master/docs/ios.md#setup That might give you an idea about your backgrounding. That's why I usually don't use plugins – Yuri S Jun 22 '17 at 21:23
  • Can you try that // in your iOS application AppDelegate, do the following: CrossBleAdapter.Init(BleAdapterConfiguration.DefaultBackgroudingConfig); // then in your shared code (somewhere near your initialization) CrossBleAdapter.Current.WhenDeviceStateRestored().Subscribe(device => { // will return the device(s) that are reconnecting }); – Yuri S Jun 22 '17 at 21:30
  • That didn't fix it, but I just noticed something...the phone is still broadcasting, because I can still see the phone's identifier in the web portal of my beacon when I put the app in the background, I just don't see the service UUID anymore.. – Drake Jun 22 '17 at 21:47

2 Answers2

2

After doing tons of research, I found that it is simply an iOS restriction - you can not broadcast the UUID of a BLE service while your app is in the background. Background work is very restrictive in iOS.

EDIT to include Paulw11 comment (which is true): You can advertise a service, but it is advertised in a way that only another iOS device that is specifically scanning for that service UUID can see.

Drake
  • 2,679
  • 4
  • 45
  • 88
  • 1
    Well, you can advertise a service, but it is advertised in a way that only another iOS device that is specifically scanning for that service uuid can see. – Paulw11 Jun 28 '17 at 22:03
0

Although you can not broadcast the UUID of a BLE service while your iOS app is in the background, for anyone trying to do something similar, you should look into iBeacon. It's Apple's protocol for letting iOS apps do bluetooth stuff while it's in the background.

Drake
  • 2,679
  • 4
  • 45
  • 88