iosswiftbluetooth-lowenergypairing

iOS AccessorySetupKit gets stuck while Pairing


Currently, I am writing an application in Swift to establish Bluetooth Low Energy (BLE) connections with devices and facilitate pairing and configuration. The pairing process I am employing involves the ‘Passkey Entry: Responder Displays, Initiator Inputs’ method.

To handle all of this I am using the AccessorySetupKit because it gives me the ability to retrieve the devices that I already paired, which is crucial for my project

However, I am encountering an issue during the pairing process: After inserting the PIN, my iPhone becomes stuck in the ‘Connecting’ state, despite the fact that both devices have successfully paired (as indicated by the other device’s screen), only to timeout after some time.

I tried this with two different iPhones ( 12 Pro and 13 Pro Max with different iOS 18 versions ), and multiple 'to-pair' devices ( all had different firmwares )

Here how I initialized the ASPickerDisplayItem:

internal struct MyAccessory
{
    private static let serviceUuid: String = "180A"
    
    public static let name: String = "MyDevice"
    
    public static let image: UIImage = UIImage( named: "MyImage" )!
    
    public static let services: CBUUID = CBUUID( string: serviceUuid )
    
    public static let pickerDisplayItem: ASPickerDisplayItem =
    {
        let descriptor: ASDiscoveryDescriptor = ASDiscoveryDescriptor()

        descriptor.bluetoothServiceUUID = services
        
        let pickerDisplayItem = ASPickerDisplayItem(
            name: name,
            productImage: image,
            descriptor: descriptor
        )
        
        // For Passkey Entry '.confirmAuthorization' is needed
        pickerDisplayItem.setupOptions = [.confirmAuthorization, .finishInApp]
        
        return pickerDisplayItem
    }()
}

And a quick sample on how the App works ( which is similar to this example given by Apple:

public class AccessoriesManager
{
    private var accessorySession: ASAccessorySession = ASAccessorySession()

    init()
    {
        accessorySession.activate( on: DispatchQueue.main, eventHandler: handleSessionEvent )
    }

    public func startScan()
    {
        accessorySession.showPicker( for: [
            MyAccessory.pickerDisplayItem
            ]
        )
        { error in
            if let error
            {
                print( "Failed to show picker due to: \( error.localizedDescription )" )
            }
        }
    }

    private func handleSessionEvent( event: ASAccessoryEvent )
    {
        switch event.eventType
        {
        case .accessoryAdded:
            /*
             Continue the configuration
             */
            
            break
        default:
            break
        }
    }
}

I anticipate the pairing process to conclude as it does when I perform it without utilizing the AccessorySetupKit (invoking the traditional operating system pop-up menu to insert the PIN using classic Bluetooth handling).


Solution

  • Use ASDiscoveryDescriptor's ASAccessorySupportOptions of .bluetoothPairingLE to tell the system your accessory supports bonding and it will show the right UI for pairing. If you want to customize the setup flow then you should choose .confirmAuthorization added as one of the setupOptions of your ASPickerDisplayItem. Then your app needs to handle connection to your accessory and look for protected characteristic to trigger pairing sequence in the picker, once your app verifies setup is good then call finishAuthorization(for:settings:completionHandler:) for success or failAuthorization(for:completionHandler:) if your app finds an issue with the setup flow.

    Hope this helps! I call it ASK in short ;)