SJ cartoon avatar

Embedded BGScript Pairing - A Less Hellish Experience

I’ve done a fair chunk of BGScript work over the last 2-3 months, but I feel like I haven’t written about my current favourite BLE modules in a while… Time to remedy that problem with a quick dive into my most hated BLE problem… Pearing… Err… Pairing!

I’ve said for a long time that I really like the Bluetooth specification. It’s pretty well defined, it continually improves, and generally, it makes sense. Bluetooth Low Energy is no exception - as each BLE version that has come out adds something new and needed to the spec. Also, Bluetooth/BLE spec adherence is pretty good across mobile phones.

The downside? Bluetooth Classic pairing is SUCH a pain… BLE Pairing? Doubly, if not triply-so.

BLE Pairing - A Less Ranty Experience

I’ve just deleted a huge section on why I think BLE pairing is so bad, mostly because it became a 350 word rant - so let me back up and say that it’s actually the un-pairing and then re-pairing that is the real problem. Typically, a first-time pairing on mobile devices is pretty seamless, but as soon as you want to do something ‘exceptional’ - such as unilaterally un-pair from the phone or peripheral, you’re probably in for a bit of trouble.

And to anyone who says “just have the user un-pair from the Settings page” … Umm… Have you met users before?

What Can We Do?

After a lot of trial and error, and having perfectly working iOS solutions, which failed on Android - and perfectly working Android solutions, which failed on iOS - I think I’ve hit a fair middle ground in the BLE pairing/un-pairing/re-pairing experience. It’s not perfect, but it’s simple, and relatively consistent.

I’m using the BLE121LR for testing, but this code is just as applicable to the BLE112, BLE113, and probably the BGM111 (with some slight mods). I also tested the scenarios of pairing to the device, and then un-pairing when both devices were on and connected, or one device was off and I unilaterally unpaired from the other (‘forgot’ a pairing connection, or deleted all bonds off the Bluegiga module).

Thanks go out to Jeff Rowberg for his whitelisting post, which I used as the basis of my pairing code.

Also, this post builds upon my comment in the Silicon Labs Community forum, and I’ve also updated my GitHub repo.

On Connection

When connecting to a device, you need to keep track of its bond handle, in case you need to delete it later on down the road (in a failed pairing situation).

Otherwise, we just need to request encryption if we’re not already encrypted (or in the case of a smart phone, wait to let it request encryption - and follow up a second later, if we don’t see any requests).

dim current_bond_handle
const ENCRYPTION_TIMEOUT_SECONDS = 1
const ENCRYPTION_TIMER_HANDLE = 0
const TICKS_PER_SECOND = 32760

# Connection event listener
event connection_status(connection, flags, address, address_type, conn_interval, timeout, latency, bonding)

    # Set up a connection interval of between 6*1.25ms to 10*1.25ms (7.5-12.5ms)
    # Android and iOS respond to connection interval updates, but cannot set them (they also have minimums)
    if is_connected = 0 then
        call connection_update(connection, 6, 10, latency, timeout)
    end if
    is_connected = 1

    # Use whatever bonding handle we're given (used in case of bond failure)
    current_bond_handle = bonding

    # If we're not yet encrypted, request encryption
    if (flags & $02) != $02 then
         # Request encryption if not encrypted already
        if bonding = $ff then
            call sm_encrypt_start(connection, 1)
        else
            # Start one-shot encryption attempt timer in case the remote side doesn't attempt an encrypted link within 1 second
            # (this might happen if you bond with a smartphone and then remove the pairing info from the phone side, but not the local module side)
            # Only seems to work on iOS - Android rejects the pairing a few times - works eventually...
            call hardware_set_soft_timer(ENCRYPTION_TIMEOUT_SECONDS * TICKS_PER_SECOND, ENCRYPTION_TIMER_HANDLE, 1)
        end if
    end if
end

Timer Handler

The timer handler just covers the case in which we’re waiting a second before we try to start encryption.

event hardware_soft_timer(handle)

    # Other side hasn't tried an encrypted link yet even though we are
    # bonded as far as we know, so try to start it from this end
    if handle = ENCRYPTION_TIMER_HANDLE then
        call sm_encrypt_start(0, 1)
    end if

    ...
end

Error Handling

There are a LOT of error handling scenarios, but as per the Bluegiga suggestions, we only handle the most common.

A peripheral can only store 8 bonds (a pretty crappy limit actually), and there probably aren’t many automated things you can do about that. You need to notify the user if there is an action to take. One recommendation could be to clear all the bonds, but you basically punish the first people to setup their device - which doesn’t seem very smart.

Otherwise, I’ve handled two of the cases just by re-attempting encryption - which seems to work surprisingly reliably, and in the remaining case - I try to delete the local bond, then I disconnect the peripheral - so that it can re-connect and go through the normal pairing process.

This last scenario seems to be the least reliable in Android - however, it works eventually (‘eventually consistent’?). The reason I went with this route is that another workaround I put in to make sure Android worked reliably, made it so that iOS worked rarely/never. That was a pretty bad use case - since an Android developer can just re-try connections behind the scenes and manipulate almost anything they want to (hiding some of the failures).

event sm_bonding_fail(handle, result)

    # If bonding fails, handle it gracefully based on the following possible results:
    # - 0x018B - Out of bonds (no space left, all 8 bonding slots taken)
    # - 0x0205 - Authentication failure (shouldn't happen with "just works" mode, but might otherwise)
    # - 0x0206 - Pin or key missing (probably local or remote device is missing the key, but not both)
    # - 0x0301 - Passkey entry failed (also shouldn't happen in "just works" mode unless bonding is cancelled)
    # - 0x0302 - OOB data not available (only occurs if OOB is required and not supported on both ends)
    # - 0x0303 - Authentication requirements (I/O capabilities required but not supported)
    # - 0x0304 - Confirm value failed (PIN entry/comparison attempted but failed)
    # - 0x0305 - Pairing not supported (also occurs if bond info removed from remote device but not local module)
    # - 0x0306 - Encryption key size (key size insufficient to meet security requirements)
    # - 0x0307 - Command not supported (SMP command is not supported on this device)
    # - 0x0308 - Unspecified reason (may occur if bond info is present remotely but not locally)
    # - 0x0309 - Repeated attempts (too little time has elapsed since last pairing/security request)
    # - 0x030A - Invalid parameters (bad parameters sent during pairing/bonding process)

    # NOTE: The most common cases:
    # - 0x018B, which means you ran out of space and must remove at least one bond in order to bond again
    # - 0x0206, which typically means the pairing info was removed on the remote device but not locally
    # - 0x0301, which typically means the user cancelled the pairing request or entered the wrong passkey
    # - 0x0305, which is like 0x0206 but is often generated instead if the remote device is a smartphone
    # - 0x0308, which typically means the pairing info was removed on the local device but not remotely
    if result = $018b then
        # Only solved by removing bonds - requires the user to reset the bonds...
    end if

    if result = $0301 then
        # Usually solved simply by trying again
        # Seems to solve most problems on iOS
        # On Android, pairing rejected a few times if Android deleted pairing without informing device
        call sm_encrypt_start(0, 1)
    end if

    if result = $0305 || result = $0206 then
        # Remove local bonding info first, then the remote device needs to reconnect
        # If current_bond_handle is $ff, that means we don't have a bonding handle - so not much we can do
        if current_bond_handle != $ff then
            call sm_delete_bonding(current_bond_handle)
        end if

        # Sometimes takes a few tries
        call connection_disconnect(0)
    end if

    if result = $0308 then
        # Remove remote bonding info first, then the remote device needs to reconnect
        # Android can recover automatically, iOS cannot
        # Instead of disconnecting, just force a re-encryption... Usually works
        call sm_encrypt_start(0, 1)
    end if
end

Check Out The Code!

Here again is the link to my GitHub repo with a bunch of my BGScript code (it’s technically a BLE113 repo - but if you move some pins around, it’s perfectly fine with the BLE121LR - and if you change some API calls, it’ll basically work with the BGM111).

Feature Photo credit: Turinboy / Foter / CC BY