Skip to content

Basic bluetooth linking implementation#1

Merged
marcprux merged 22 commits intoskiptools:mainfrom
bburgess19:main
Aug 31, 2024
Merged

Basic bluetooth linking implementation#1
marcprux merged 22 commits intoskiptools:mainfrom
bburgess19:main

Conversation

@bburgess19
Copy link
Contributor

@bburgess19 bburgess19 commented Aug 30, 2024

Overview

This PR contains a limited but functional implementation Apple's CoreBluetooth of Bluetooth Low Energy (BLE) for Android.

Because CoreBluetooth's implementation is limited in many ways, i.e. not allowing certain advertisement data, this implementation also disables those features to emulate an experience as similar as possible.

Features

With these changes, it is possible to perform critical functions of BLE:

Peripheral

  • Configuring services and characteristics
  • Adding services to GATT database (CBPeripheralManager.add())
  • Advertising and stopping advertising
  • Receiving (un)subscription notifications
  • Receiving reads/writes and responding to them
  • Updating characteristic values (whose changes propagate to subscribed centrals)

Central

  • Scanning and stopping scanning for peripherals
    • Some limited configuration for this is possible
  • Connecting to peripherals
  • Discovering services/characteristics
  • Reading/writing to characteristics
  • Subscribing to characteristics
  • Cancelling peripheral connections

Next Steps

Ancillary API Delegate Implementations

There are a number of API calls which aren't strictly necessary in BLE communication which aren't implemented. As of the time of this PR, these include:

CBPeripheralDelegate

func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: (any Error)?)
func peripheralDidDiscoverIncludedServicesFor(_ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: (any Error)?)
func peripheralDidDiscoverDescriptorsFor(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: (any Error)?)
func peripheralDidUpdateValueFor(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: (any Error)?)
func peripheralDidWriteValueFor(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: (any Error)?)
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral)
func peripheral(_ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: (any Error)?)

CBPeripheralManagerDelegate

func peripheralManager(_ peripheral: CBPeripheralManager, willRestoreState dict: [String : Any])
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: (any Error)?)

CBCentralManagerDelegate

func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any])

There are a smattering of other fields and properties which are also unimplemented, but they are notated with @available(*, unavailable) which will signal compile-time failures.

Better advertisement cleanup logic

Apple's CoreBluetooth implementation ostensibly cleans up all GATT database values after both invoking CBPeripheralManager.stopAdvertising() and the instance is de-initialized, but since we cannot reliably hook into the finalize lifecycle in Skip, there is no guarantee the equivalent teardown function BluetoothGattServer.cleanup() function would be called if it was placed in there. There may be some clever solution here to guarantee the cleanup() function is called at a similar point in the lifecycle, but I am not aware of one.

Testing

Testing these APIs is quite challenging because their functionality requires hardware, and I am unfamiliar with testing to this end. This is a rich area for development going forward.

@cla-bot
Copy link

cla-bot bot commented Aug 30, 2024

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Ben Burgess.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@cla-bot
Copy link

cla-bot bot commented Aug 30, 2024

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Ben Burgess.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

Copy link
Contributor

@aabewhite aabewhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing! I left just a few comments

scanner?.stopScan(scanDelegate)
}

#if !SKIP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a number of #if !SKIP blocks, but the whole thing is surrounded by #if SKIP, so they aren't functional. This pattern is repeated in other files. Is this your way of excluding things that aren't implemented? If so, why not use @available(*, unavailable) for these? Do the signatures use types that aren't implemented or similar?

Sometimes in those cases we change the unimplemented types to Any (i.e. func f(x: Any /* OriginalType /) and mark @available(, unavailable) to hopefully give a better error message on attempted use. But fine either way - just checking that I understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are remnants from my initial implementation. I've replaced these sites with @available(*, unavailable) references instead. Sorry for the confusion here.

@@ -0,0 +1,12 @@
#if SKIP
import SkipFoundation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import Foundation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed all other references as well.

/// This process is handled automatically by CoreBluetooth for Swift when you instantiate either
/// `CBCentralManager` or `CBPeripheralManager`, but Android requires the request
/// to occur within an activity.
@Composable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only reason the package relies on SkipUI?

If so, I think it's worth moving this to documentation and dropping the dependency. What do you think?

Come to think of it, we should probably add a general permission check utility to SkipUI itself... but that's another topic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that dependency was vestigial! It's removed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it does look like we need to depend on SkipUI, since that brings in the compose dependencies we need for androidx.activity.result.contract.ActivityResultContracts and other things that are being used. That seems to be the only thing stopping CI from passing (https://github.com/skiptools/skip-bluetooth/actions/runs/10640472849), so if you add back the SkipUI dependency, then we should get a clean CI run.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not include the permission prompt util and not depend on SkipUI. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, if askForBluetoothPermissions() is removed, then it looks like the androidx.activity.compose and other imports can be removed, and it can just depend on skip-foundation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing that one function works for me. It's changed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you just need to remove the import androidx.core… and import androidx.activity.compose… from CBCentralManager.swift, and it looks like the CI run should pass (see the failure at https://github.com/skiptools/skip-bluetooth/actions/runs/10641143687).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see. I wasn't checking the CI output -- fixed. Not sure what's causing the cla-bot failures though... Thank you for the help thus far!

@@ -0,0 +1,42 @@
import Foundation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add the LGPL license header to this and the other files so they all have the same header?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, completed.

@marcprux
Copy link
Member

This looks great! We've added a couple of comments, but overall it seems like a really solid PR.

Also, the weird "cla-bot" errors are indicating that it wants you to add your username to https://github.com/skiptools/clabot-config

Once those are in place, we'll be ready to merge and we can cut a new release. Thanks!

@cla-bot
Copy link

cla-bot bot commented Aug 30, 2024

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Ben Burgess.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@cla-bot
Copy link

cla-bot bot commented Aug 30, 2024

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Ben Burgess.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@cla-bot
Copy link

cla-bot bot commented Aug 30, 2024

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Ben Burgess.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@cla-bot
Copy link

cla-bot bot commented Aug 31, 2024

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Ben Burgess.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@cla-bot
Copy link

cla-bot bot commented Aug 31, 2024

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Ben Burgess.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@cla-bot
Copy link

cla-bot bot commented Aug 31, 2024

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Ben Burgess.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@marcprux
Copy link
Member

CI passed, looks good! I'll go ahead and merge and create a tagged release.

FTR, not entirely sure what the cla-bot error is about (since you added your name there) – it looks like it might be happening from a lack of e-mail address, either configured in GitHub (https://github.com/settings/emails) or from your local git config (git config --global user.email email@example.com). In any case, it isn't a blocker for this, but it might be an ongoing annoyance if you make future PRs.

Thanks again for the contribution!

@marcprux marcprux merged commit 3c1ff7b into skiptools:main Aug 31, 2024
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

Successfully merging this pull request may close these issues.

3 participants