Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start scanning every time Bluetooth gets .poweredOn #283

Closed
troupmar opened this issue Sep 4, 2018 · 5 comments
Closed

Start scanning every time Bluetooth gets .poweredOn #283

troupmar opened this issue Sep 4, 2018 · 5 comments

Comments

@troupmar
Copy link

troupmar commented Sep 4, 2018

Hello there,

I need to be scanning for peripherals all the time when Bluetooth is .poweredOn. Thus I only need to cancel scanning when Bluetooth gets turned off (or when the app is quit by a user).

Firstly I used Core Bluetooth only to achieve this and all worked as expected:

extension BleManager: CBCentralManagerDelegate {
	public func centralManagerDidUpdateState(_ central: CBCentralManager) {
		switch central.state {
		case .poweredOn:
			manager.scanForPeripherals(withServices: [BleServices.myModule.uuid], options: nil)
		default:
                       ()
		}
	}

	public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
		print("didDiscoverPeripheral: \(peripheral)")
	}
}

Then I wanted to make it more reactive thus I used this framework trying to achieve the same behavior with following code:

centralManager.observeState()
   .filter { $0 == .poweredOn }
   .flatMap { _ -> Observable<ScannedPeripheral> in
      return self.centralManager.scanForPeripherals(withServices: [BleServices.myModule.uuid], options: nil)
   }
   .subscribe { event in
      print(event)
   }

Let's assume I start the app with Bluetooth turned on. Scanning works as expected. Then I turn the Bluetooth off and I get following message from Core Bluetooth:

[CoreBluetooth] API MISUSE: <CBCentralManager: 0x1c0263d40> can only accept this command while in the powered on state

After that, I turn the Bluetooth back on and the scanning does not work anymore (I mean new scanning should be started). I do not even receive that Bluetooth state changed (via centralManager.observeState()). As I understand, when I turn the bluetooth off, I receive an error via the scanning observable, which should cancel the observable. I can see in code that when disposing the observable, stopScan() is called. So I assume scanning should be stopped automatically when bluetooth is turned off.

I believe the code snippets using Core Bluetooth vs. using RxBluetoothKit are similar and should have similar behavior (am I missing something?). I am new to this so I am sorry if it is something trivial I should know how to use. However, I followed the documentation when writing the code.

@pouljohn1
Copy link
Contributor

Hey @troupmar, thanks for using our library!

First [CoreBluetooth] API MISUSE: <CBCentralManager: 0x1c0263d40> can only accept this command while in the powered on state means that you are calling some method on central manager while is is not poweredOn. From the code that you have pasted it looks ok - do you have any other code that does something on centralManager?

When bluetooth will be turned on then scanForPeripherals observable finishes with error, so it won't wait for poweredOn state again. You need to handle such behaviour by yourself.

@troupmar
Copy link
Author

Hi @paweljaneczek,

firstly thank you for your comment. It helped me to clarify how the whole chain of commands work.

However, I haven't still solved the [CoreBluetooth] API MISUSE problem. I made a simple app to demonstrate what I am doing exactly:

import UIKit
import CoreBluetooth
import RxSwift
import RxBluetoothKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
	var window: UIWindow?

	private let bag = DisposeBag()

	// 1) - initialization of CentralManager
	private let centralManager: CentralManager = {
		let bluetoothCommunicationSerialQueue = DispatchQueue(label: "bluetoothCommunicationSerialQueue")
		let centralManagerOptions = [
				// The system uses this UID to identify a specific central manager.
				// As a result, the UID must remain the same for subsequent executions of
				// the app in order for the central manager to be successfully restored.
				CBCentralManagerOptionRestoreIdentifierKey: "com.quanti.BleFramework",
				// A Boolean value that specifies whether the system should display a warning dialog
				// to the user if Bluetooth is powered off when the central manager is instantiated.
				CBCentralManagerOptionShowPowerAlertKey: false
			] as [String: AnyObject]

		return CentralManager(queue: bluetoothCommunicationSerialQueue, options: centralManagerOptions)
	}()

	func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
		// 2) - scanning of peripherals
		centralManager.observeState()
			.do(onNext: { state in
			   print(state)
			})
			.filter { $0 == .poweredOn }
			.take(1)
			.flatMap { _ -> Observable<ScannedPeripheral> in
			   return self.centralManager.scanForPeripherals(withServices: [CBUUID(string: "a54e8025-b815-41ba-bf3f-cbf7d06de5b4")], options: nil)
			}
			.subscribe { event in
			   print(event)
			}
			.disposed(by: bag)


		window = UIWindow(frame: UIScreen.main.bounds)
		if let window = window {
			let viewController = ViewController()
			viewController.view.backgroundColor = .red
			window.rootViewController = viewController
			window.makeKeyAndVisible()
		}

		return true
	}
}

I start the app with bluetooth turned off. Then I turn it on. The scanning starts. Then I turn it off again. This is the console output:

poweredOn
next(RxBluetoothKit.ScannedPeripheral)
error(Bluetooth is powered off)
2018-09-10 11:32:24.759544-0400 RxBluetoothKitTest[6143:1878801] [CoreBluetooth] API MISUSE: <CBCentralManager: 0x1d4479400> can only accept this command while in the powered on state

I think the problem is that when I turn the bluetooth off, scanForPeripherals sequence emits an error (Bluetooth is powered off) and then it gets disposed. When its disposed you call stopScan method on the bluetooth manager, but by that time the bluetooth is already off. I assume I should call dismiss on scanForPeripherals manually while bluetooth is still on so it all works as expected. But that, in my case, is not possible. As I mentioned before, I need to be scanning for peripherals all the time when bluetooth is on. And as I never get any callback telling me that bluetooth will go off soon I cannot call stopScan (or dismiss in your case) on time.

Could you recommend any way I could achieve desired behavior without getting the API MISUSE? I am also wondering what happens with the original Core Bluetooth's scanForPeripherals method when somebody turns the bluetooth off. Do you think it stops scanning automatically?

Thank you so much for your reply, I really appreciate it.

@pouljohn1
Copy link
Contributor

It looks like it is a bug in our library. We shouldn't call stopScan when bluetooth is not poweredOn. Will fix that.
Regarding your question about scanForPeripheral, yes it stops scanning when sombebody turns bluetooth off.

Thanks for pointing out that issue!

@troupmar
Copy link
Author

Thank you for the explanation and the fix! Great job with the library by the way 👍

@bartoszstelmaszuk
Copy link
Contributor

Since it is resolved and fix was introduced I will close issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants