bluetooth-lowenergycore-bluetooth

Can I connect to multiple peripherals while still scanning?


I'm trying to build a feature on iOS with Bluetooth Low energy which allows:

  1. Peripheral send some info (up to 1024 bytes) to central
  2. Central receives data from the peripherals, and display the connected peripherals as a list
  3. When user tap on any of the peripheral from the list, central send some info back.

My question is:

If I have many peripherals nearby, I'll need to connect to all of them for a round-trip data transfer, but does BLE allow (or recommend) connecting to peripherals while the central is still scanning?

The reason I'm asking is, for most of the examples I saw online, peripherals are broadcasting data to central BEFORE establishing connection by using func startAdvertising(_ advertisementData: [String : Any]?); However, this advertisementData has a MTU as 31 bytes, which is NOT enough for my step 1.

In order to achieve this, I need to make sure to connect to the discovered peripherals before sending over the data because the data i want to send through BLE is larger than advertisementData MTU while peripheral starts broadcasting, which means I can't put the 1024 bytes data into peripheralManager.startAdvertising(advertisementData) because the advertisementData has a size limit of 31.

This is my current approach:

Peripheral:

public func startAdvertising() {
    peripheralManager.startAdvertising(advertisementData)
    delegate?.didStartAdvertising()
}

func viewDidLoad() {
    startAdvertising()
}

public func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
    peripheralManager.updateValue(dataFromPeripheralThatIsLargerThan1024Bytes, for: peripheralSendingCharacteristic, onSubscribedCentrals: nil)
}

Central

func viewDidLoad() {
    centralManager.scanForPeripherals(
        withServices: [TransferService.serviceUUID],
             options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
        )
}

public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
    centralManager.connect(peripheral, options: nil)
}

public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    peripheral.delegate = self
    peripheral.discoverServices([TransferService.serviceUUID])
    delegate?.didConnectPeripheral(central: central, peripheral: peripheral)
}

// CBPeripheralDelegate
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: (any Error)?) {
    guard let peripheralServices = peripheral.services else {return}
for service in peripheralServices {
peripheral.discoverCharacteristics([TransferService.peripheralSendingCharacteristicUUID, TransferService.centralSendingCharacteristicUUID], for: service)
}

public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: (any Error)?) {
    peripheral.setNotifyValue(true, for: characteristic)
}
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: (any Error)?) {
    peripheral.writeValue(centralResponseDataInStep3, for: characteristic, type: .withResponse)
}

My current code only works for 1:1 discovery & data transfer.
In order to make it 1 Central : multi Peripheral, I'll need to build a table view and a discovered peripheral array in the central.
However, in this case, I'll probably need to make connect while scanning, is that allowed in BLE? If not, any recommendations? (Let's assume BLE is the ONLY approach I can go with for transferring data.)


Solution

  • If I have many peripherals nearby, I'll need to connect to all of them for a round-trip data transfer, but does BLE allow (or recommend) connecting to peripherals while the central is still scanning?

    Yeah, that's fine. Just note that it can cause your scanning to take longer to discover new devices because they all compete for time on the antenna.

    You haven't said what scale "many" is, and I'm going to assume you mean half a dozen at most. If you're talking about dozens of devices, you can overwhelm Bluetooth. Bluetooth is not designed for large mesh networks.

    The reason I'm asking is, for most of the examples I saw online, peripherals are broadcasting data to central BEFORE establishing connection by using func startAdvertising(_ advertisementData: [String : Any]?); However, this advertisementData has a MTU as 31 bytes, which is NOT enough for my step 1.

    If your devices support Bluetooth 5, iOS will accept up to 124 bytes of advertising payload using extended advertising. It sounds like your peripheral devices are other iPhones, so they will generally all support extended advertising. You can check by querying the extendedScanAndConnect feature on CBCentralManager using supports(_ features :).

    But noted; this is still well below 1KB, so probably doesn't help you a lot.

    I'll need to build a table view and a discovered peripheral array in the central. However, in this case, I'll probably need to make connect while scanning, is that allowed in BLE?

    Yes. It's not even unusual.

    But if your use case is entirely iPhones talking to other iPhones, consider using Multipeer Connectivity instead of rolling your own solution. Doing this stuff really well can be challenging, and Apple will do a lot of the work for you if you're entirely within the Apple ecosystem.