Breadboard
docs
Example project · Board-to-board radio

Bluetooth LE: bond & reconnect

The pairing story taken to its real-world conclusion: the boards don’t just encrypt once, they BOND. The central pairs, both hosts persist the long-term key through the secret store, then the central hangs up and comes back. On the reconnect the second gap_pair exchanges no pairing traffic at all (check the Sniffer: after the first handshake not a single SMP frame crosses); the link re-encrypts straight from the stored key, exactly how a phone greets a smartwatch it already knows. One script on both boards; roles derive from each board’s MAC.

The Bluetooth LE: bond & reconnect circuit as rendered by the simulator

What's on the bench

  • 2× Battery
  • 2× ESP32-C6

How it's wired

  • Battery · posESP32-C6 · vin
  • Battery · negESP32-C6 · gnd
  • Battery · posESP32-C6 · vin
  • Battery · negESP32-C6 · gnd

The code

This MicroPython script runs on the emulated board every boot; edit it in the Code tab.

import bluetooth, time
b = bluetooth.BLE()
b.active(1)
b.config(bond=True, mitm=False, le_secure=False)
S = {}
server = b.config('mac')[1][-1] % 2 == 0
tag = 'SRV' if server else 'CLI'
tgt = []
cn = []
dis = []
enc = []
def cb(e, d):
    if e == 29:
        t, i, k = d
        if k is None:
            j = 0
            for q in S:
                if q[0] == t:
                    if j == i:
                        return S[q]
                    j += 1
            return None
        return S.get((t, bytes(k)), None)
    if e == 30:
        t, k, v = d
        k = (t, bytes(k))
        if v is None:
            S.pop(k, None)
        else:
            S[k] = bytes(v)
        return True
    if e == 28:
        enc.append(1)
        if len(enc) > 1:
            print(tag + ' re-encrypted from the stored bond')
        else:
            print(tag + ' encrypted, bond stored')
    elif e == 5 and not tgt:
        tgt.append((d[0], bytes(d[1])))
        print('CLI found advertiser')
    elif e == 7:
        print('CLI connected' + (' again' if cn else ''))
        cn.append(d[0])
    elif e == 8:
        dis.append(1)
        print('CLI link closed')
    elif e == 1:
        print('SRV central connected')
    elif e == 2:
        dis.append(1)
        print('SRV central left')
b.irq(cb)
if server:
    b.gap_advertise(50000, b'\x02\x01\x06\x05\xffCAFE', connectable=True)
    print('SRV ' + 'advertising, bondable')
    while not dis:
        time.sleep_ms(50)
    time.sleep_ms(300)
    b.gap_advertise(50000, b'\x02\x01\x06\x05\xffCAFE', connectable=True)
    print('SRV ' + 're-advertising for the bonded central')
else:
    b.gap_scan(0, 30000, 30000)
    while not tgt:
        time.sleep_ms(50)
    b.gap_scan(None)
    time.sleep_ms(50)
    b.gap_connect(tgt[0][0], tgt[0][1])
    while not cn:
        time.sleep_ms(50)
    time.sleep_ms(2000)
    b.gap_pair(cn[0])
    while not enc:
        time.sleep_ms(50)
    time.sleep_ms(1500)
    print('CLI ' + 'bonded; disconnecting')
    b.gap_disconnect(cn[0])
    while not dis:
        time.sleep_ms(50)
    time.sleep_ms(500)
    print('CLI ' + 'reconnecting to the stored address')
    b.gap_connect(tgt[0][0], tgt[0][1])
    while len(cn) < 2:
        time.sleep_ms(50)
    time.sleep_ms(2000)
    b.gap_pair(cn[1])
    while len(enc) < 2:
        time.sleep_ms(50)
    print('CLI ' + 'rebonded with NO pairing exchange - the bond held')

Related projects