SwiftUI / BlueJay unexpectedPeripheral Error

Viewed 104

This post is regarding an error I am getting from the Bluetooth API BlueJay. I have this error that I was not able to find documentation.

"CBCentralManager initialized."
"Bluejay with UUID: 6A7273D6-66D7-4C36-B6A4-E59DC84422F7 started."
2021-07-19 13:24:33.079775-0400 Argon18 UAT[9780:4330125] [connection] nw_endpoint_handler_set_adaptive_read_handler [C1.1 172.217.13.170:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for read_timeout failed
2021-07-19 13:24:33.080006-0400 Argon18 UAT[9780:4330125] [connection] nw_endpoint_handler_set_adaptive_write_handler [C1.1 172.217.13.170:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for write_timeout failed
"Error: unexpectedPeripheral(Bluejay.PeripheralIdentifier(uuid: 0BC9C8FE-74FB-A237-D61B-F28748FE7EC5, name: \"ARGON04280CEC\"))"
"Central manager state updated: Powered On"

I get this after the app starts the first thing it does on showing the main view is to try and connect to the default BLE device.

To do this I fetch from DB the entity for it and then using the store peripheral ID and peripheral name I create a PeripheralIdentifier struct and then past to the BluJay API.

this fails as per the error above, because of this I set up a button to manually retry this code and this does work.

I have the feeling that BlueJay is taking a long time to finish setting up and that I am sending the request too quickly after starting up BueJay.

this is the code to setup BluJay

self.bluejay.register(logObserver: self)
bluejay.registerDisconnectHandler(handler: self)
bluejay.register(connectionObserver: self)
bluejay.register(serviceObserver: self)
self.bluejay.start()

This code is set in a Singleton object this is called just before calling the connection. (I did test to call the initialiser at app start but I am getting the same problem)

This is the code called to get the BLE device info that is stored in the DB.

.onAppear(perform: {
            let bleManager = A18BLEManager.shared
            if bleManager.model == nil {
                if let bike = A18DataStore.shared.getDefautBike(){
                    bleManager.connect(bikeID: bike.bleID!, name: bike.peripheralName!)
                }
            }
        })

func getDefautBike() -> Bike?
    {
        var bike: Bike? //Bike is a NSManagedObject
        let fetchRequest = Bike.fetchRequest() as NSFetchRequest<Bike>
        
        if let idString = UserDefaults.standard.object(forKey: "defaultBikeUUID") as? String, let bleID = NSUUID(uuidString: idString)
        {
            fetchRequest.predicate = NSPredicate(format:"%K == %@", #keyPath(Bike.bleID), bleID)
        }
                
        do{
            bike = try viewContext.fetch(fetchRequest).first
        }
        catch let error {
            debugPrint("Fetch Error: \(error)")
        }
        return bike //does return a valid record with valid UUID and name
        
    }

func connect(bikeID id: UUID, name: String) {
        self.connect(PeripheralIdentifier(uuid: id, name: name))
    }

func connect(_ peripheral: PeripheralIdentifier) {
    self.bluejay.connect(peripheral, timeout: .seconds(15)) { [unowned self] connectionResult in
        switch connectionResult {
        case .success:
            readBikeInfo()
            debugPrint("Connection attempt to: \(peripheral.description) is successful")
        case .failure(let error):
            debugPrint("Error: \(error)")
        }
    }
}

the function getDefautBike() does return an object and the and the info is valide.

To test this I have a button setup in a different location in the app that calls this.

A18BLEManager.shared.connect(bikeID: UUID(uuidString: "0BC9C8FE-74FB-A237-D61B-F28748FE7EC5")!, name: "ARGON04280CEC")

And this call will work, it works only in the other view, if I use this on the main call site in the main view this will get the same error.

EDIT: Ok so after writing this. I tried to have the call set 5 seconds after the main view is presented. And indeed the connections works. So in the end the real question is how long does BlueJay takes to set up ?

1 Answers

There's no specific time when Bluetooth will be available. The OS will tell you by calling centralManagerDidUpdateState() on the delegate. Bluejay exposes this through its ConnectionObserver, which will call bluetoothAvailable() with true when ready. You need to watch for this to determine when Bluetooth is available. In practice this is generally within a few hundred milliseconds, though it can be much faster than that (close to, but not quite, instantaneous) if the antenna is already powered up.

In your logs you're seeing this transition as "Central manager state updated: Powered On." I strongly expect you're trying to access the Bluetooth stack before seeing that.

Related