Skip to content

ble not staying paired upon reset/reconnects. #10739

Closed

Description

@alpoman

CircuitPython version and board name

cp 10.0.3
waveshare esp32-s3 touch lcd-2

Code/REPL

# code.py -- ESP32-S3 Reverse TFT Feather
# Minimal BLE HID keyboard: D0, D1, D2 send A, B, C

import time
import board
import digitalio

import adafruit_ble
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.standard.hid import HIDService

from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode

# -----------------------------
# Buttons on ESP32-S3 Reverse TFT
# -----------------------------
# Per guide:
#  - D0 uses Pull.UP -> active LOW when pressed
#  - D1, D2 use Pull.DOWN -> active HIGH when pressed  [oai_citation:2‡Adafruit Learning System](https://learn.adafruit.com/esp32-s3-reverse-tft-feather?view=all&utm_source=chatgpt.com)

button0 = digitalio.DigitalInOut(board.D0)
button0.switch_to_input(pull=digitalio.Pull.UP)

button1 = digitalio.DigitalInOut(board.D1)
button1.switch_to_input(pull=digitalio.Pull.DOWN)

button2 = digitalio.DigitalInOut(board.D2)
button2.switch_to_input(pull=digitalio.Pull.DOWN)

prev0 = button0.value
prev1 = button1.value
prev2 = button2.value

# -----------------------------
# BLE HID setup (minimal)
# -----------------------------
ble = adafruit_ble.BLERadio()
ble.name = "RevTFT HID"

hid = HIDService()
advertisement = ProvideServicesAdvertisement(hid)
advertisement.short_name = "RevTFT"  # keep adv payload small
# You can add: advertisement.appearance = 961  # keyboard icon
# but skip it at first if you're still hitting memory issues

kbd = Keyboard(hid.devices)

print("BLE HID starting; device name:", ble.name)

while True:
    # Start advertising and wait for a connection
    print("Advertising...")
    ble.start_advertising(advertisement)

    while not ble.connected:
        # Just idle while waiting for a connection
        time.sleep(0.05)

    print("Connected!")

    # Now we're connected: scan buttons and send keys
    while ble.connected:
        v0 = button0.value
        v1 = button1.value
        v2 = button2.value

        # Helper: send a key once on the press edge
        def send_key(keycode, label):
            try:
                kbd.send(keycode)
                print(f"{label} key sent")
            except OSError as e:
                # connection might have dropped mid-send
                print("Send error:", e)

        # D0: active LOW -> press edge = high -> low
        if prev0 and not v0:
            send_key(Keycode.A, "D0 (A)")

        # D1: active HIGH -> press edge = low -> high
        if not prev1 and v1:
            send_key(Keycode.B, "D1 (B)")

        # D2: active HIGH -> press edge = low -> high
        if not prev2 and v2:
            send_key(Keycode.C, "D2 (C)")

        prev0 = v0
        prev1 = v1
        prev2 = v2

        time.sleep(0.01)  # debounce

    print("Disconnected; will re-advertise")
    # Loop repeats, re-calling start_advertising()

Behavior

after a few days working with esp32-s3 and ble hid(and midi). i noticed that if i forget settings in windows and clear binding on esp, i can get a stable ble connection, but, if i turn off ble in windows( or drop the connection in anyway). it may reconnect, but no longer works. also, once power cycle of device happens, any pairing still shows, but device never reconnects and works again, unitil i forget ble settings again, unbind device, reboot, and re-pairing then works again. obviously this isnt correct, as it shoukd survive reboots and signal drops/reconnects. I noticed as soon as i pkug in device, windows shows esp device as available bluetooth even before any code runs... is there a ble stack in the tinybootloader? that may be causing the conflict, and causing problems with ble resources/ mismatches while trying to reconnect. i dont know much how the bootloader, u2f file is configured for this board, and how its ble may or may not be in some kind ofauto mode on boot. this behavior is exactly same on ipad and mac as well, just curious if any headway on this or info is available, i just wanted to add my observations. btw, i was testing using Mikey's stripped down ble hid test code from this post, which avoids the nimbe memory bug. one more note, after once working, and powercycle, the code tryies to run but hangs...doesnt crash...and if i turn off blutooth in windows, code resumes. and if i turn on blutooth again, it hangs again. its like the esp is blocking on trying to connect, very odd. thanks for any help,
andy

Description

No response

Additional information

No response

Activity

  1. added this to the 10.x.x milestone on Dec 7, 2025
  2. ThomasAtBBTF commented on Jan 1, 2026

    @ThomasAtBBTF

    Same problem here !
    I do this:

    advertise()
    
    while True:
        # Wait for an iOS connection
        while not ble.connected:
            print("not connected")
            time.sleep(1)
    
        # There can be more than one connection; for a keyboard you usually want one.
        for connection in ble.connections:
            if connection.connected:
                print("Connected. paired =", connection.paired, connection)
    
                # Force iOS to actually pair/bond
                if not connection.paired:
                    print("Requesting pairing (bonding)...")
                    try:
                        connection.pair(bond=True)  # :contentReference[oaicite:3]{index=3}
                        print("returned from connection.pair(bond=True) call!")
                    except Exception as e:
                        print("Failed to pair connection with error:", e)
                    if connection.paired:
                        print("Pairing complete.")
                    else:
                        print("Pairing not complete!")
    
    

    And it works as long as I do not reset / power off my ESP32S3.
    (It works to stop the code with ctrl-c makes changes to the code and restart. After that the BLE connection will come back successfully.)

  3. modified the milestones: 10.x.x, 10.3.0 on May 19, 2026
  4. modified the milestones: 10.3.0, 10.3.x on Aug 27, 2026
  5. dhalbert commented on Sep 4, 2026

    @dhalbert
    Collaborator

    I believe this is fixed by #11237, which is in 10.3.0 and later. I'm closing this, but if you are still having trouble, we can reopen.

  6. jlubin2001 commented on Sep 26, 2026

    @jlubin2001

    Still seeing this on 10.3.1 (which should include #11237) with an Android host. It reproduces every time, and I'm happy to test a fix.

    Setup

    • Adafruit CircuitPython 10.3.1 on 2026-09-14; Adafruit Feather ESP32-S3 Reverse TFT with ESP32S3 (board ID adafruit_feather_esp32s3_reverse_tft)
    • Bootloader: TinyUF2 0.35.0 (fresh combined.bin, full flash erase first)
    • Libraries from bundle 10.x-mpy-20260910: adafruit_ble 10.1.3, adafruit_hid 6.1.10
    • Host: Samsung Galaxy A15, Android 16
    • Run from the REPL (import test_ble) with nothing else running. _bleio.adapter.erase_bonding() at start; the phone's old pairing was removed before the test.

    Minimal code

    import time, _bleio, adafruit_ble
    from adafruit_ble.advertising import Advertisement
    from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
    from adafruit_ble.services.standard.hid import HIDService
    from adafruit_ble.services.standard.device_info import DeviceInfoService
    from adafruit_hid.keyboard import Keyboard
    from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
    
    _bleio.adapter.erase_bonding()
    hid = HIDService()
    device_info = DeviceInfoService(software_revision=adafruit_ble.__version__, manufacturer="Test")
    adv = ProvideServicesAdvertisement(hid)
    adv.appearance = 961
    scan = Advertisement()
    scan.complete_name = "BLE HID test"
    ble = adafruit_ble.BLERadio()
    layout = KeyboardLayoutUS(Keyboard(hid.devices))
    
    n = 0
    while True:
        ble.start_advertising(adv, scan)
        while not ble.connected:
            time.sleep(0.2)
        n += 1
        t = time.monotonic()
        conn = ble.connections[0]
        print("CONNECTED #%d paired=%s" % (n, conn.paired))
        if not conn.paired:
            conn.pair()
            print("pair() finished paired=%s" % conn.paired)
        time.sleep(3)
        while ble.connected:
            layout.write("test line %d\n" % n)
            print("typed")
            time.sleep(3)
        print("DISCONNECTED after %.1f s" % (time.monotonic() - t))

    What happens

    1. The phone pairs from Settings > Bluetooth. connection.pair() returns in ~0.3 s with paired=True.
    2. Keystrokes are sent with no exception on the CircuitPython side, but nothing ever appears on the phone. No keystrokes arrived in any connection, including the ones where paired=True, and also on the first attempt before I added the explicit pair() call.
    3. The link drops ~6.5 s after every connection (every time, not intermittent).
    4. The phone reconnects immediately, and every reconnect starts with paired=False: the bond from the previous connection isn't kept. So it loops (connect → pair → drop) every few seconds until advertising stops.

    Log from my full test script, which has the same logic plus a 5-line limit (test_ble.py). Connections 6–8 are shown; #6 through #11 look identical.

    CONNECTED (connection #6)  paired=False
    pair() finished  paired=True
    before typing: connected=True  paired=True
    typed: test 1/5 (connection 6)
    DISCONNECTED after 6.5 s
    CONNECTED (connection #7)  paired=False
    pair() finished  paired=True
    before typing: connected=True  paired=True
    typed: test 1/5 (connection 7)
    DISCONNECTED after 6.5 s
    CONNECTED (connection #8)  paired=False
    pair() finished  paired=True
    before typing: connected=True  paired=True
    typed: test 1/5 (connection 8)
    DISCONNECTED after 6.6 s
    

    Expected: the bond persists, reconnects come back already paired, and the connection stays up.

    Context: this is for an assistive sip-and-puff Morse keyboard (AeroMorse) used as someone's only way to operate a computer, so a working BLE HID keyboard on ESP32-S3 would open up phones and tablets for them. Happy to try a test build.

  7. dhalbert commented on Sep 26, 2026

    @dhalbert
    Collaborator

    Could you test with 11.0.0-alpha.1? There were some further BLE fixes since 10.3.1. Thanks.

  8. jlubin2001 commented on Sep 27, 2026

    @jlubin2001

    Thanks — tested with 11.0.0-alpha.1 (2026-09-24 build) on the same setup: Adafruit Feather ESP32-S3 Reverse TFT (#5691), TinyUF2 0.35.0, adafruit_ble + adafruit_hid from the 10.x bundle, host = Samsung Galaxy A15 (Android 16). Same minimal HID keyboard test as before (HIDService + DeviceInfoService, appearance 961, conn.pair() if not paired, then layout.write() five lines, 3 s apart).

    Summary: pairing and the connection are now much better, but keystrokes still never arrive on the phone.

    Run 1 — bonds erased on the board at start, fresh pairing

     56.9  CONNECTED (#1)  paired=False
     56.9  PAIR FAILED: BluetoothError('Already in progress',)   <- phone had started pairing itself
     60.0  typed 3 lines ... DISCONNECTED after 12.3 s
     75.7  CONNECTED (#2)  paired=False -> pair() -> paired=True
     79.1  typed 1 line ... DISCONNECTED after 6.6 s
           (no further reconnects)
    

    Afterwards the phone keeps "AeroMorse Blue" under Paired devices (10.3.1 didn't), and the endless connect/drop/reconnect loop from 10.3.1 is gone.

    Run 2 — board keeps its bond, phone reconnects to the saved pairing: same pattern (paired=True after pair(), drops after 12.6 s and 6.6 s).

    Run 3 — phone's per-device "Text input" switch turned ON. In Android's settings for the paired device, Text input was OFF, and it can only be switched on while the board is connectable. With it ON:

     11.4  CONNECTED (#1)  paired=False -> pair() -> paired=True (0.3 s)
     14.8  typed 1/5 ... 27.5 typed 5/5
           stayed connected for the rest of the 3-minute test (no drop)
    

    So the drops seem to come from Android dropping the link while Text input is off. But even in run 3 — connected, paired, stable, notes app focused — no text appears on the phone, and layout.write() raises no error on the board. The status bar flickers BLE:Ok / BLE:Reconnecting while typing, if that's relevant.

    Run 4 — Windows 11 PC host (Intel Bluetooth), to rule out Android: paired and connected (paired=True), typed 5/5 lines, stayed connected — but nothing appeared in Notepad (focused) either. Device Manager shows a "Bluetooth Low Energy GATT compliant HID device" for it, so Windows does see a keyboard. Two things that may be clues:

    • Windows lists the device as CIRCUITPYb93e, not the name set with ble.name = "AeroMorse Blue" (the phone showed "AeroMorse Blue"). After the test, _bleio.adapter.name reads CIRCUITPYb93e again.
    • Alongside the HID service (0x1812) Windows also enumerated the BLE-workflow services (ADAF0001…, ADAF0004…, and 0xFEBB) on the same device. supervisor.disable_ble_workflow() isn't available on this build, so I couldn't test with the workflow off.
    • After this run the board dropped off USB entirely (drive and serial gone) and needed a replug.

    So it's not Android-specific: two different hosts pair, connect and enumerate a HID keyboard, but receive no keystrokes.

    Happy to test another build or add logging if that helps.

  9. jhonasn commented on Oct 17, 2026

    @jhonasn

    I'm having the same problem here, tested both on 10.3.1 and in 11.0.0-alpha.1

    board: VCC-GND YD-ESP32-S3 N16R8
    adafruit_ble and adafruit_hid bundle version 10.x and 11.x

    In my Linux (Arch) it works fine, but in my phone Android 10 (Xiaomi Redmi Note 8) and Fire TV fail.

    I will describe with my words what I think is the same problem on the devices that fail:

    • When cleared bonds and first pair it works fine but:
    • When reset the ESP, android auto connects to it and remain connected, android Input device toggle option keeps enabled, in serial debug says it is typing but nothing happens on android
    • If (on 10.3.1) I turn off and on android bluetooth then when auto reconnect it types/work but in alpha 11 don't (in v10 I was having the same problem but with kmk, so maybe this part is not 100% fair because I didn't test the minimal code bellow on v10)

    My minimal testing code was this:

    import time
    import adafruit_ble
    from adafruit_ble.advertising.standard import (
        ProvideServicesAdvertisement,
    )
    from adafruit_ble.services.standard.hid import HIDService
    from adafruit_ble.services.standard.device_info import DeviceInfoService
    from adafruit_hid.keyboard import Keyboard
    from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
    
    ble = adafruit_ble.BLERadio()
    ble.name = "StenoJoy-Test"
    hid = HIDService()
    device_info = DeviceInfoService(
        manufacturer="Juca",
        software_revision="BLE-HID-test-",
    )
    advertisement = ProvideServicesAdvertisement(hid)
    advertisement.appearance = 961
    keyboard = Keyboard(hid.devices)
    layout = KeyboardLayoutUS(keyboard)
    print("StenoJoy BLE HID test starting")
    while True:
        # Advertise whenever not connected
        if not ble.connected:
            print("Advertising...")
            ble.start_advertising(advertisement)
            while not ble.connected:
                time.sleep(0.1)
            print("CONNECTED")
    
        # Trying the @ThomasAtBBTF solution (don't solve the problem)
        isconn = False
        while not isconn:
            for connection in ble.connections:
                if connection.connected:
                    print("Connected. paired =", connection.paired, connection)
                    if not connection.paired:
                        print("Requesting pairing (bonding)...")
                        try:
                            connection.pair(bond=True)
                            print("returned from connection.pair(bond=True) call!")
                        except Exception as e:
                            print("Failed to pair connection with error:", e)
                        if connection.paired:
                            print("Pairing complete.")
                        else:
                            print("Pairing not complete!")
                    else:
                        isconn = True
                        print("PAIRED")
    
            # Give the host time to finish configuring HID
            time.sleep(2)
            try:
                layout.write("StenoJoy BLE test OK")
                keyboard.send(0x28)  # ENTER
                print("Initial test text sent")
            except Exception as e:
                print("Initial send error:", repr(e))
    
        # Send another test after each connection
        while ble.connected:
            time.sleep(3)
            if not ble.connected:
                break
            try:
                layout.write("BLE works")
                keyboard.send(0x28)  # ENTER
                print("Heartbeat text sent")
            except Exception as e:
                print("Heartbeat send error:", repr(e))
                break
        print("DISCONNECTED")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions