jordanebelanger / SwiftyBluetooth

Closures based APIs for CoreBluetooth
MIT License
209 stars 66 forks source link

alternative connection to peripheral with identifier and services #7

Closed jakerockland closed 7 years ago

jakerockland commented 7 years ago

I was looking for a way to connect to a SwiftyBluetooth Peripheral given its UUID and service identifiers, and it didn't seem like this was currently possible. This is important for times where one is storing the UUID of the peripheral for faster reconnection at a later time. Thoughts @tehjord ?

jordanebelanger commented 7 years ago

I don't think you're supposed to do that because the UUID of peripherals, unless I'm mistaken, changes every time you power cycle Bluetooth on your iOS device.

I believe what you are actually looking for we're you to do things properly would be to use the CBCentralManagerDelegate state restoration function, see:

https://developer.apple.com/reference/corebluetooth/cbcentralmanagerdelegate/1518819-centralmanager

and

https://developer.apple.com/reference/corebluetooth/cbcentralmanagerdelegate/1667432-central_manager_state_restoratio

Edit: Once the Central proxy implements this delegate you could create a new Central function that returns an array of Peripheral instance for every CBPeripheral returned by the bluetooth state restoration delegate call. You can then manually look at the peripherals and find the peripheral with the UUID you're looking for manually.

jakerockland commented 7 years ago

@tehjord I believe that is only the case if the peripheral is another iOS device, in which you're definitely right that the UUID is not guaranteed to be consistent across power cycles. However, if the peripheral is a beacon or other Bluetooth device such as speakers or wearable devices, it is dependent on the hardware of the given peripheral whether or not the UUID will change.

Relevant excerpt from Apple Documentation:

The first time you discover a peripheral, the system generates an identifier (a UUID, represented by an NSUUID object) to identify the peripheral. You can then store this identifier (using, for instance, the resources of the NSUserDefaults class), and later use it to try to reconnect to the peripheral using the retrievePeripheralsWithIdentifiers: method of the CBCentralManager class. The following describes one way to use this method to reconnect to a peripheral you’ve previously connected to.

jordanebelanger commented 7 years ago

In the documentation Apple mentions that you still need to rediscover these peripherals with the scanning function.

What I suggest you do, is to create a Central class extension that implements this behavior by wrapping the Central scanning function instead.

jakerockland commented 7 years ago

@tehjord I don't think that is true, if the peripheral is already known, there is no reason to scan again. Apple advises to scan as little as possible as it is very bad for battery life. In the situation where the peripheral is already known, there is no reason to scan again. The documentation only says to scan again if both connecting to known peripherals and connecting to already connected peripherals both fail.

jakerockland commented 7 years ago

Rescanning should only be done in the case that the peripheral identifier has changed:

If you cannot reconnect to the peripheral because its address has changed, you must rediscover it using the scanForPeripheralsWithServices:options: method.

However, in many cases (such as the ones working with custom hardware that I described) it can be guaranteed that the identifier will never change, in which case the framework does not currently offer good support for reconnection.

jordanebelanger commented 7 years ago

I notice your connect with UUID function use the central retrieveperipheral functions, can't you just create a Central function that wraps these functions and return an array of Peripheral objects? You can then connect to them by UUID. That is how I would do it. To go further, if you really want a connect to peripheral with UUID function, you can create your own extension that calls these retrieve peripherals function on the central, find the one with the UUID you want if possible and connect to it.

jakerockland commented 7 years ago

I had thought about creating such a wrapper function for Central to retrieve the Peripheral and then using the already defined .connect() method; however this results in retrievePeripherals() being called twice every time that a known peripheral is reconnected to, which is a bit inefficient. As the framework is currently, it doesn't seem like there is a way to create such an extension that doesn't rely on the existing .connect() method, which makes it seem that the double call to retrievePeripherals() is unavoidable with the current implementation. Also I think it is useful for this library to support reconnection to known peripherals as Apple specifies, no?

jordanebelanger commented 7 years ago

I just added these retrieve peripherals wrapper functions in:

https://github.com/tehjord/SwiftyBluetooth/commit/7bc7bf883ea2cc4f425c05da366e1d26a691ac0f

I want to keep the library small and simply focus on offering block based wrappers around CBCentralManager/CBPeripheral functions. If all the functions are exposed, you can then create your own utility extensions.

jakerockland commented 7 years ago

@tehjord Just saw those additions, which look good to me! 😄 However, I'm still a bit confused, even if one uses the wrapper you wrote, it will still require a double call to the underlying retrievePeripherals() CoreBluetooth function, once in the wrapper function you wrote and then again when your .connect function is called?