1
votes

Our system consists of a main device with a remote control unit. The main device is the GAP Central and the GATT Server. The remote control unit advertises and connects over BLE where the remote is the GAP Peripheral and GATT Client. When buttons are pressed on the remote control, it writes to the button status characteristics to the GATT Server.

We want to write an iOS app to function as and replace the remote control unit. Is iOS CoreBluetooth able operate as a GAP Peripheral while also being the GATT Client? The app will have to advertise as the peripheral, and then do the service discovery once connected. The app buttons will perform write requests to characteristics on the main device's GAP Central GATT Server.

Our configuration is a little bit different from the standard BLE model where the GATT DB usually sits on the GAP Peripheral. But in our case it didn't make sense logically to have the main device's settings being stored in the remote control unit. Is iOS flexible enough to support this configuration?

2

2 Answers

0
votes

Just for clarification: You are talking about BLE service solicitation. Unfortunately, CoreBluetooth only supports service solicitation in one direction, namely the iOS device being the GAP Central and another GAP Peripheral can discover the GATT server on the iOS device. That's basically what accessories such as Pebble use to access media control (Apple Media Service) and notifications (Apple Notification Center Service). You will find the special dictionary key CBCentralManagerScanOptionSolicitedServiceUUIDsKey on the CBCentralManager to support the aforementioned scenario but there is no equivalent on the CBPeripheralManager to support your scenario.

Hope that helps.

0
votes

It looks like CoreBluetooth couples GAP and GATT roles, despite many combinations being valid. So even though your iOS app will advertise itself as a BLE peripheral, you can use CBCentralManager to connect back to the central device. Instead of scanning like you would do in a central app, you can use retrieveConnectedPeripherals(withServices:) or retrievePeripherals(withIdentifiers:) to find the central device.

Unfortunately there isn't a didConnect method in CBPeripheralManagerDelegate. If you're going to use retrievePeripherals(withIdentifiers:) you'll need to add a dummy service and characteristic and have your central device access that characteristic after connecting. When you receive a didReceiveRead or didSubscribeTo event, you can connect back to request.central.identifier. Using retrieveConnectedPeripherals(withServices:) is simpler, but it didn't consistently return the central device in my testing.

retrieveConnectedPeripherals(withServices:) example

import CoreBluetooth

// A GATT client for an iOS App that operates as a BLE Peripheral. CoreBluetooth requires using a CBCentralManager
// to use remote GATT services, but we don't have to actually scan for the remote device.
class GattClient: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    // Replace with your service UUIDs
    let remoteServiceUUIDs = [CBUUID(string: "FACE")]
    var centralManager: CBCentralManager!
    var remoteDevice: CBPeripheral? = nil
    var timer: Timer? = nil

    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if (central.state == .poweredOn) {
            // There's no `didConnect` event we can listen for, so poll for connected devices.
            timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: findConnectedDevice)
        }
    }

    func findConnectedDevice(timer: Timer) {
        // Don't scan, just connect to the device already connected to us offering the right services.
        print("Searching for connected devices...")
        let connectedPeripherals = centralManager.retrieveConnectedPeripherals(withServices: remoteServiceUUIDs)
        print("Devices found: \(connectedPeripherals.count)")

        if (connectedPeripherals.count == 1) {
            remoteDevice = connectedPeripherals[0]
            print("Connecting...")
            centralManager.connect(remoteDevice!, options: nil)

            timer.invalidate()
        }
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("Discovering services...")
        peripheral.delegate = self
        peripheral.discoverServices(remoteServiceUUIDs)
    }

    ...
}