manolofdez / AsyncBluetooth

A small library that adds concurrency to CoreBluetooth APIs.
MIT License
168 stars 31 forks source link

Unable to connect to X because a connection attempt is already in progress after turning off/on bluetooth #28

Closed theedov closed 1 year ago

theedov commented 1 year ago

Hello,

I'm facing this weird issue. When a peripheral is in state where it's trying to connect, I disable bluetooth and enable it back(basically interrupting the process). The connection is then stuck and this message printed every time I manually try to reconnect:

[centralManager] Unable to connect to 86B377B8-97B1-F21A-7BB4-50C5D7F8943C because a connection attempt is already in progress

The problem is, it's not connecting even after 10 min. The only way to get the device reconnected is to kill the app.

Here is my reconnection code:

        switch state {
        ....
        case .poweredOn:
            try await reconnect(identifier: <ID_FROM_USERDEFAULTS>)
        @unknown default: break
        }

    @discardableResult
    func reconnect(withIdentifier identifier: UUID) async throws -> Peripheral {
        let restoredPeripheral = try await retrievePeripheral(withIdentifier: identifier)
        try await manager.connect(restoredPeripheral)

        return restoredPeripheral
    }

    func retrievePeripheral(withIdentifier identifier: UUID) async throws -> Peripheral {
        try await manager.waitUntilReady()

        var restoredPeripheral: Peripheral

        if let peripheral = manager.retrieveConnectedPeripherals(withServices: [.init(nsuuid: identifier)]).first {
            restoredPeripheral = peripheral
        } else if let peripheral = manager.retrievePeripherals(withIdentifiers: [identifier]).first {
            restoredPeripheral = peripheral
        } else {
            throw DeviceManagerError.unableToRetrievePeripheral(identifier: identifier)
        }

        return restoredPeripheral
    }

I'm not sure if it's a bug, or if I'm doing something wrong. Can't seem to find a way to cancel any pending connections.

manolofdez commented 1 year ago

Hi! You can wrap the call to try await manager.connect(restoredPeripheral) in a Task, store it, and then cancel it before attempting to reconnect again. This is a bit of a pain though. I'm looking at other options. Something I've been thinking is exposing a flush method that clients can call when, for example, bluetooth is turned off. Anyway, please let me know if this works.

manolofdez commented 1 year ago

There's now a cancelAllOperations call you can make to flush all awaits. There's an example in the README, but tl;dr; you can call manager.cancelAllOperations() when the state switches to .poweredOff.