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.